一、泛型的概念
泛型实现了参数化类型的概念,使代码可以用于多种类型
二、泛型的目的
- 希望类和方法能够具备最广泛的表达能力
- 用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性
三、泛型的使用
-
普通泛型类
public class NormalGenericsClass
{ private T key; public Generics(T key){ this.key = key; } public T getKey() { return key; } public void setKey(T key) { this.key = key; }}// 泛型类实例NormalGenericsClass a = new NormalGenericsClass (0);// Java7 开始支持省略后面的参数类型NormalGenericsClass a = new NormalGenericsClass<>(0); -
普通泛型接口
// Java中的 Comparator接口public interface Comparator
{ int compare(T o1, T o2); boolean equals(Object obj);}// 接口实现例子 java.text.Collatorpublic abstract class Collator implements java.util.Comparator -
普通泛型方法
private
int normalGenericsMethod(Generics i){ // 表示声明 T为泛型,写在返回值类型之前// 也可以声明多个泛型,如: return 0;}// 这是错误的,会提示 Cannot resolve symbol 'T'/*private int normalGenericsMethod(Generics i){ return 0;}*/ 有上界的泛型: 上界:通过关键字extends来表示给定的上界,那么参数就必须是给定的上界或者其子类型。上界可以是某个具体的类或接口,也可以是其他参数
-
上界为具体的类
public class GenericsUpperBound
extends NormalGenericsClass { public GenericsUpperBound(T key){ super(key); }} -
上界为具体的接口
public
T compareWith(T[] arr){ T start = arr[0]; for(int i = 1; i < arr.length; i++){ System.out.print(start.equals(arr[i])); } return start;} -
上界为其他类型参数
public class OtherUpperBound
{ public void otherArgs(NormalGenericsClass a){ // E是OtherUpperBound的类型参数,T是otherArgs方法的类型参数 // T的上界限定为E }}//例子:OtherUpperBound a = new OtherUpperBound<>;OtherUpperBound b = new OtherUpperBound<>;a.otherArgs(b);
四、通配符
-
有限定通配符
//重写6中的方法public void otherArgs(NormalGenericsClass a){ }
取自《Java编程的逻辑》8.2 <T extends E> 和 <? extends E>的区别 ①<T extends E>:用于定义类型参数,它声明了一个类型参数T,可放在泛型类定义中类名后面、泛型方法返回值前面 ②<? extends E>:用于实例化类型参数,它用于实例化泛型变量中的类型参数,只是这个类型是未知的,只知道是E或E的某个子类型
-
无限定通配符
public int demo(OtherUpperBound a){ }// 这两个等效public
int demo(OtherUpperBound a){ } 通配符重要限制: 只能读,不能写
取自《Java编程的逻辑》8.2 总结: 1) 通配符形式都可以用类型参数的形式来替代,通配符能做的,用类型参数都能做 2) 通配符形式可以减少类型参数,形式上往往更为简单,可读性也更好,所以,能用通配符的就用通配符 3) 如果类型参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,则只能用类型参数 4) 通配符形式和类型参数往往配合使用,定义必要的类型参数,使用通配符表达依赖,并接受更广泛的数据类型
-
超类型通配符(无法用类型参数替代)
public int demo(OtherUpperBound a){ }
取自《Java编程的逻辑》8.2 总结: 1)通配符的目的是为了使方法接口更为灵活,可以接受更为广泛的类型 2)<? super E>用于灵活写入或比较,使得对象可以写入父类型的容器,使得父类型的比较方法可以应用于子类对象,它不能被类型参数形式替代 3)<?>和<? extends E>用于灵活读取,使得方法可以读取E或E的任意子类型的容器对象,它们可以用类型参数的形式替代,但通配符形式更为简洁
五、泛型的局限性
-
父类实现了一个泛型接口,子类希望自定义泛型接口中的方法,只能重写父类的实现
class Base implements Comparable
class Child extends Base// 希望重写Comparable的比较方法/* class Child extends Base implements Comparable // 错误,因为类型擦除的原因,实际实现的都是Comparable接口,接口不允许被实现两次*/// 正确重写Comparable的比较方法的方式class Child extends Base { @Override public int compareTo(Base o){ if(!(o instanceof Child){ throw new IllegalArgumentException(); } Child c = (Child)o; \\实现代码 return 0; } \\其他代码} -
类型参数不能作为静态变量和静态方法的类型
Class Normal
{ public static demo1(T param){ // 错误的方法 } public static void demo2(E param){ // 正确的方法 }} -
不能通过类型参数创建对象
T a = new T(); // error Type parameter 'T' cannot be instantiated directly
六、泛型的使用细节
Java中的泛型是通过类型擦除实现的,类型参数在编译时会被替换为Object
-
对于不同传入的类型实参,生成的相应对象实例一样
Generics
demo = new Generics<>(123);Generics demo2 = new Generics<>("test");System.out.println(demo.getClass() == demo2.getClass()); // true -
静态方法和泛型
静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法/** 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法) 即使静态方法要使用泛型类中已经声明过的泛型也不可以。 如:public static void show(T t){..},此时编译器会提示错误信息 "StaticGenerator cannot be refrenced from static context"*/public static
void show(T t){} -
泛型的上下边界添加,必须与泛型的声明一起
//在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的
上添加上下边界, //即在泛型声明的时候添加//public T showKeyName(Generic container)编译器会报错:"Unexpected bound"public T showKeyName(Generic container){ System.out.println("container key :" + container.getKey()); T test = container.getKey(); return test;} - 基本类型不能用于实例化类型参数 泛型要求能包容的是对象类型,基本类型在java中不属于对象
-
运行时类型查询只适用于原始类型
NormalGenericsClass
a = new NormalGenericsClass<>(0);a instanceof NormalGenericsClass ;// Error Illegal generic type for instanceofa instanceof NormalGenericsClass ;// Error Cannot resolve symbol 'T'a instanceof NormalGenericsClass; // Passa instanceof NormalGenericsClass ; // Passa.getClass(); // class com.example.demo.generics.NormalGenericsClass -
类型推断只对赋值操作有效
Eg:public class New{ public static
Map map(){ return new HashMap (); }}/** -- 方法中传参 这时编译器不会执行类型判断。在这种情况下,编译器认为:调用泛型方法后, 其返回值被赋给一个Object类型的变量*/public class Test{ public static void main(String args[]){ fun(New.map()); }}public class Test{ public static void main(String args[]){ fun(New.
参考资料:
[1]《Java编程的逻辑》[2]《Thinking in Java》[3] [4]