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