2、泛型的实现
2.1 泛型的使用
1、类或接口后面→泛型类/泛型接口(参数化的类型)
- 当类中的某个成员变量不确定时,可以考虑在类的上面声明泛型;当类/接口中某个非静态方法的形参或返回值不确定时。
- 当类或接口中的某个/某些非静态方法的形参返回值类型不确定时,可以考虑在类/接口上声明泛型来表示这个未知的类型。如果多个方法都使用了类/接口上面声明的泛型,它们的类型是一致的。
//java.lang.comparable接口:
public interface Compareble<T> {public int compare(T o);//不是泛型方法
}
//java.util.Comparator:int compare(T t1,T t2)
2、方法的返回值类型前面→泛型方法
- 当某个方法在设计时,它的形参、返回值不确定时,需要调用才能确定时,可以使用泛型方法。eg:复制任意类型的数组的方法。
//java.util.Arrays类
public static <T> List<T> asList(T... a){....
}
- asList方法返回的是一个只读的集合,不能删除、添加元素(报错)
List<String> list = Arrays.asList("hello","world","zhang","san");
list.add("wangwu");//UnsupportedOprationException
list.removw("san");//UnsupportedOprationException
- list不是ArrayList类型,而是ArrayList里面的一个私有内部类( java.util.Array&ArrayList),并没有add()\remove()方法
//复制一个数组
public static <T> T[] copyOf(T[] original,int length);
- 每一个泛型方法声明的
<T>
都是独立的,与其他方法无关。方法在调用时,会根据实参类型自动推断<T>
的具体类型。 - 在类/接口上声明的泛型类型
<T>
不能用于静态方法不能用于静态成员/静态方法,因为T类型和对象有关。在static
修饰符后面加一个<T>
。
class MyGenrics<T>{public void method(T t){...}public void fun(T t){...}//上面两个方法的T是一致的public static void test1(T t){...}//报错,T类型与MyGenrics对象有关public static <T> void test2(T t){...}//test2方法的T类型与MyGrics对象无关,与上面的T类型是相互独立的,相当于:// public static <K> void test(K k){...}
}
2.2 类型变量的上限
- <T extends 上限>:T的类型必须是<=上限,要么是上限本身,要么继承上限类型。
- <T extends 类 & 接口1 &接口2& …>:一个类型变量的上限可以是一个也可以是多个,但只能有一个类上限,且类在左边,其他类型上限在右边
Class Student<T extends Number & Comparable & ...>{...}
- JVM在运行是不支持泛型类型的识别
2.3 泛型的擦除(eraser)
- 如果用户在使用泛型类或泛型接口时,没有使用指定泛型的类型,就会发生泛型的擦除,将泛型转为非泛型。
- 泛型擦除后,自动按照类型变量声明时的第一个上限进行处理,如果没有指定参数类型的上限,上限为Object。eg:
List<Integer>和ListString<String>
在运行时都会转为List
类型。
泛型并不是由虚拟机执行的,而是在编译时实行的。JVM并不能识别泛型,如果没有指定<T>
的类型,编译器会将<T>
视为Object类型进行处理,并可以根据<T>
的类型自动实现安全的强制转换。
<T>
不能是基本数据类型(Object类型无法拥有基本类型)- 不能取得带泛型的
Class
,也不能判断泛型的类型 - 不能实例化T类型→借助
Class<T>
参数并通过反射来实例化T
类型
Student<String> stu1 = new Student<>(String.class);
3、泛型的继承关系
- 泛型类、泛型接口和泛型类型之间的关系
1、泛型类、泛型接口的继承关系
在泛型类和泛型接口继承和实现的过程中,子类可以获取父类的泛型类型<T>
。
子类继承泛型父类时,子接口继承泛型父接口、或实现类实现泛型父接口时:
- 指定类型变量对应的实际类型参数,此时子类或实现类不再是泛型类
class Student<T>{private String name;private T score;...
}
//Primary不是泛型类
class Primary extends Student<String>{...
}
- 指定类型变量,此时子类、子接口、实现类仍然是泛型类或泛型接口
class Senior extends<E> extends Student<E>{...
}
2、泛型类型之间的继承关系
在Java中,泛型是不可变的,即泛型类型之间没有继承关系,即使它们的类型参数之间存在父子关系。所以Student<Integer>
和Student<Number>
之间不是继承关系,而是不同类型的泛型类型。
Student<Integer> stu1 = new Student<>(100);
Student<Number> stu2 = inStu;//发生类型转换,但是编译报错classCastException
虽然Integer是Number的子类,但是Studeny<Integer>
无法赋值给Student<Number>
,stu1和stu2都指向同一个对象,即stu1所指向的对象。Java中的泛型是不可变的,stu2只能访问该对象的Number类型的方法和属性,而不能访问Integer类型的方法和属性。
并且,泛型通过类型擦除实现的,在编译后,Student<Integer>
和Student<Number>
都被擦除为Student类型,编译器不能保证类型的一致性。
4、通配符
什么情况下使用?
当声明了一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,但还是不能确定这个泛型类/泛型接口的类型变量<T>
的具体类型→ 类型通配符 ?
4.1 三种形式
1、<?>
:?代表任意类型
2、<? extends 上限>
:?代表<=上限类型
- 该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。
3、<? supur 下限>
:?代表>=下限类型
- 该泛型类中所有参数是T类型的方法或成员都可以使用,但是有要求。参数类型不是T类型的方法照常使用。
<? extends Number>
Collection<?> coll = new ArrayList<>();
coll.add("hello");//报错
coll.add(1);//报错
coll.add(1.0);//报错
<?>:泛型类型未知,不能确定集合的元素`void add(E t)`方法中E由?表示,直到add方法被调用时,也不能确定E的类型。
Collection<? extends Number> coll = new ArrayList<Double>();
coll.add(1.0);//报错
2、<? extends 上限>
:?代表<=上限类型
- 该泛型类中所有参数是T类型的方法或成员都无法正常使用。参数类型不是T类型的方法照常使用。
3、<? supur 下限>
:?代表>=下限类型
- 该泛型类中所有参数是T类型的方法或成员都可以使用,但是有要求。参数类型不是T类型的方法照常使用。
<? extends Number>
Collection<?> coll = new ArrayList<>();
coll.add("hello");//报错
coll.add(1);//报错
coll.add(1.0);//报错
1、<?>:泛型类型未知,不能确定集合的元素void add(E t)
方法中E由?表示,直到add方法被调用时,也不能确定E的类型。
Collection<? extends Number> coll = new ArrayList<Double>();
coll.add(1.0);//报错
2、<? extends Number>:泛型类型限定为Number及其子类。void add(E t)
方法中<E>
由<? extends Number>
表示,但是直到add方法被调用时,也不能确定E的类型,可以是<=Number的任意一种类型。
Collection<? super Number> coll = new ArrayList<>();
coll.add(1);
coll.add(1.0);
coll.add("hello");//报错
3、<? super Number>
:可以添加Number对象或Number子类对象。
PECS原则
PECS原则:Producer Extends Consumer Super。如果需要返回T,它是生产者(Producer),使用extends通配符;如果需要写入T,它是消费者(Consumer),使用super通配符。
public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i=0; i<src.size(); i++) {T t = src.get(i); // src是producerdest.add(t); // dest是consumer}
}
4.2 注意的问题
1、<?>
如果某个泛型类/接口<T>
在使用时,泛型指定为<?>
,里面所有使用T类型声明的方法都不能正常使用(不能读也不能写,可以判断null类型)
2、<? extends 上限>
如果某个泛型类/接口<T>
在使用时,泛型指定为<? extends 上限>
,里面所有使用T类型声明的方法都不能正常使用(只能读也不能写,传入null除外[异常])
2、<? supur 下限>
如果某个泛型类/接口<T>
在使用时,泛型指定为<? supur 上限>
,里面所有使用T类型声明的方法都可以正常使用,但不能超过下限的类型(只能写也不能读,获取Object除外)
public class Collections {// 把src的每个元素复制到dest中:public static <T> void copy(List<? super T> dest, List<? extends T> src) {for (int i=0; i<src.size(); i++) {T t = src.get(i);dest.add(t);}}
}
- copy()方法内部不会读取dest,因为不能调用dest.get()来获取T的引用;
- copy()方法内部也不会修改src,因为不能调用src.add(T)。