关键词:泛型类(参数化类型),泛型方法(即参数化方法),类型参数(T),
泛型
为什么需要泛型?
- 一般的类和方法,只能使用具体的类型,要么使用基本类型,要么使用自定义类型,如果我们需要编写适用于多种类型的代码,通过继承和接口的方式,局限性都太大,(JAVA单继承特性,实现接口则是需要实现方法),故要通过泛型,
- 泛型的意义在于它有很强的通用性
泛型类
生成器与工厂方法的区别:生成器不需要参数(只需要定义一个next方法),而工厂方法一般需要参数
interface Generator<T> {
T next() throws Exception;
}
class Coffee {
private static int count=0;
private final int id=count++;
@Override
public String toString() {
return this.getClass().getSimpleName() +
"id=" + id;
}
}
//拿铁
class Latte extends Coffee {
}
//摩卡
class Mocha extends Coffee {
}
//热奶
class Cuppuccino extends Coffee {
}
public class CoffeeGenerator implements Generator<Coffee>,Iterable<Coffee>{
Class[] classes = {Coffee.class, Latte.class, Mocha.class, Cuppuccino.class};
Random random=new Random(47);
public int size;// for iteratioin
public CoffeeGenerator(int size) {
this.size = size;
}
@Override
public Coffee next() {
try {
return (Coffee) (classes[random.nextInt(classes.length)].newInstance());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Iterator iterator() {
return new CoffeeIterator();
}
public class CoffeeIterator implements Iterator<Coffee>{
int count=size;
@Override
public boolean hasNext() {
return count>0;
}
@Override
public Coffee next() {
count--;
//其实这个迭代器也能生成元素,虽然迭代器的本意不是这个,哈哈
return CoffeeGenerator.this.next();
}
}
public static void main(String[] args) {
CoffeeGenerator coffees = new CoffeeGenerator(5);
for (int i = 0; i <5 ; i++) {
System.out.println(coffees.next());
}
for (Coffee coffee : new CoffeeGenerator(5)) {
System.out.println(coffee);
}
}
}
//output
Mochaid=0
Latteid=1
Mochaid=2
Coffeeid=3
Coffeeid=4
Mochaid=5
Latteid=6
Mochaid=7
Coffeeid=8
Coffeeid=9
泛型方法
定义:泛型参数放置在返回值之前的方法,(其他的只能算普通方法)
规则
- 尽量使用泛型方法,而不是泛型类
- 是否拥有泛型方法,与这个类是不是泛型类没有关系
- 静态方法无法访问泛型类的类型参数
创建一个通用的Generator,
class CountObject {
private static int count=0;
private int id=count++;
public int getId() {
return id;
}
@Override
public String toString() {
return "CountObject{" +
"id=" + id +
'}';
}
}
class BasicGenerator<T> implements Generator{
public Class<T> type;
public BasicGenerator(Class<T> type) {
this.type = type;
}
//返回一个生成器对象
public static <T> Generator<T> create(Class<T> c) {
return new BasicGenerator<T>(c);
}
@Override
public T next() throws Exception {
return type.newInstance();
}
}
public class BasicGeneratorTest {
public static void main(String[] args) throws Exception {
Generator generator = BasicGenerator.create(CountObject.class);
for (int i = 0; i <5 ; i++) {
System.out.println(generator.next());
}
}
}
擦除
为什么要有擦除?
- 擦除是实现泛型的重要部分,不然将不兼容之前的非泛型代码
谁来执行擦除?
- 编译器会擦除类型参数,故ArrayList与ArrayList的Class对象是一样的,且在泛型代码内部无法获取类型参数的具体类型,得到的只是类型参数的占位符,如E,K,V
class A {
}
class B {
}
class Q<Q> {
}
//坐标
class Coordinates<X, Y> {
}
public class LostInformation {
public static void main(String[] args) {
LinkedList<A> linkedList = new LinkedList<>();
HashMap<A, B> hashMap = new HashMap<>();
Q<A> q = new Q<>();
Coordinates<A, B> coordinates = new Coordinates<>();
System.out.println(Arrays.toString(linkedList.getClass().getTypeParameters()));
System.out.println(Arrays.toString(hashMap.getClass().getTypeParameters()));
System.out.println(Arrays.toString(q.getClass().getTypeParameters()));
System.out.println(Arrays.toString(coordinates.getClass().getTypeParameters()));
}
}
//output
[E]
[K, V]
[Q]
[X, Y]
擦除的后果
- 因为擦除让我们失去了在泛型代码中进行某些操作的能力,任何运行时需要知道确切类型的操作将报错
public class Erased<T> {
private final int SIZE=10;
public void f(Object o) {
if (o instanceof T){}// error
T t=new T();// error
T[] array=new T[SIZE];// error
T[] array1 = (T[]) new Object[SIZE];
}
}
边界
因为擦除在方法体(以方法为单位)中移除了类型信息,所以在运行时的问题就是边界,即对象进入和离开方法的地点,这些也是编译器进行检查和插入转型代码的地点,
擦除的补偿
为什么需要补偿?
- 因为擦除让我们失去了在泛型代码中进行某些操作的能力,任何在运行时需要知道明确类型(而你给的是Object)的操作都将被禁止
- 如创建对象 new T(),instanceof的使用,创建数组 new T[]
类型参数保存在哪里?那个类的Class对象中,
如何补偿?
- 通过传入具体类型的类型标签:如具体类的Class对象
class Building {
}
class House extends Building {
}
public class ClassTypeCapture<T> {
Class<T> type;
public ClassTypeCapture(Class<T> type) {
this.type = type;
}
public Boolean capture(Object arg) {
return this.type.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctp = new ClassTypeCapture<Building>(Building.class);
System.out.println(ctp.capture(new Building()));
System.out.println(ctp.capture(new House()));
ClassTypeCapture<House> ctp1 = new ClassTypeCapture<House>(House.class);
System.out.println(ctp1.capture(new Building()));
System.out.println(ctp1.capture(new House()));
}
}
//output
true
true
false
true
通配符
通配符?
需要引用某一确切的类型,如
LinkedList<? extends Fruit> fruits = new LinkedList<? extends Fruit>();//error
LinkedList<? extends Fruit> fruits = new LinkedList<Fruit>();//right
数组编译期检查缺陷,有些异常在运行时才抛出,泛型可以在编译期进行错误检测
JAVA泛型不是协变的,数组才是,举个例子
- Number[]是Integer[]基类
- LinkedList类型则不是LinkedList的基类
边界
上边界
T与Myclass(指定确切类型)可以互换,通配符?
需要引用某一确切的类型
? extend T:指明了上界T
LinkedList<? extends Fruit> fruits
表明这个fruits将引用持有Fruit类型或Fruit某一派生类类型的LinkedList对象
?
引用的是T或者是T的某一派生类类型,添加T类类型的元素是不安全的- get方法返回的用T来接收
下边界
? super T:指明了下界T,
LinkedList<? super Fruit> fruits
表明这个fruits将引用持有Fruit类型或Fruit某一基类类型的LinkedList对象
- 此时
?
引用的是T或者是T的某一基类类型,故添加T类型的元素是安全的 - get方法返回的是Object来接收,因为Object是所有类的基类
class Food {
}
class Fruit extends Food{
}
class Banana extends Fruit{
}
class WildCards {
public static void main(String[] args) {
LinkedList<Fruit> container = new LinkedList<>();
container.add(new Food());
container.add(new Fruit());
container.add(new Banana());
LinkedList<? extends Fruit> fruits = new LinkedList<Banana>(Arrays.asList(new Banana(),new Banana()));
fruits.add(new Food());// compile error
fruits.add(new Fruit());// compile error
fruits.add(new Banana());// compile error
fruits = new LinkedList<Food>();// compile error
fruits = new LinkedList<Fruit>();
fruits = new LinkedList<Banana>();
Fruit fruit = fruits.get(0);
LinkedList<? super Fruit> anotherFruits = new LinkedList<Food>(Arrays.asList(new Fruit(),new Banana()));
anotherFruits.add(new Food());// compile error
anotherFruits.add(new Fruit());
anotherFruits.add(new Banana());
anotherFruits = new LinkedList<Food>();
anotherFruits = new LinkedList<Fruit>();
anotherFruits = new LinkedList<Banana>();// compile error
Object object = anotherFruits.get(0);
}
}
? extend T,?
引用的是T或者是T某一的派生类类型,添加T类类型的元素是不安全的
? super T,此时?
引用的是T或者是T的某一基类类型,故添加T类型的元素是安全的
PECS原则
- 如果需要从一个集合中读取T类型的元素,且不能写入,用 ? extends,即Producer extends
- 如果需要从一个集合中写入T类型的元素,且不需要读,则用 ? super,即Consumer super
- 如果需要一个集合又读又写,则是不要用通配符
无界通配符 ?
无界通配符类型与原生类型不同,
为什么需要无界通配符?
- 处理多个泛型参数是,有时其中一个参数是任意类型,其他参数是确切类型时候
捕获转换
- 未指定确切类型的通配符类型被捕获,并转换成确切的类型
public class CaptureCoversion {
static <T> void f1(Holder<T> holder) {
T t = holder.get();
System.out.println(t.getClass().getSimpleName());
}
static void f2(Holder<?> holder) {
f1(holder);
}
public static void main(String[] args) {
Holder<Double> holder = new Holder<Double>(1.0);
f2(holder);
}
}
问题
基本类型不能作为类型参数
-
如不能创建ArrayList之类的东西
-
解决的方法是:使用包装器类,自动包装机制帮你双向转换
public class ListOfInt {
public static void main(String[] args) {
List<Integer> list = new LinkedList<>();
for (int i = 0; i <5 ; i++) {
list.add(i);
}
for (int i = 0; i <5 ; i++) {
System.out.print(list.get(i)+"\t");
}
}
}
当然,自动包装机制不能解决所有问题,它不能应用到数组(单个会自动转换,数组则不会自动转换)
int arr[] = new Integer[]{1, 2, 3};//error
实现参数化接口
- 一个类不能实现同一泛型接口的两种变体,如Hourly
interface Payable<T> {
}
class Employee implements Payable<Employee> {
}
//钟点工
public class Hourly extends Employee implements Payable<Hourly>{ //error
}
自限定类型(待续)
动态类型安全
异常
混型
潜在类型机制
对潜在类型机制的补偿
注意
- 泛型涉及到编译器行为,日后要补充一下