泛型总结
概述
对于泛型,最基本的了解就是:泛型提供了编译时类型平安检测机制,该机制容许程序在编译时检测到非法的类型,以保障类型平安。泛型的实质是参数化类型,在面向对象编程的语言中,容许程序员在强类型校验下定义某些可变局部,以达到代码复用的目标
- 强调的是编译期间的查看,如果在运行期间进行操作,比方应用反射是能够绕过泛型的编译查看的,当然最初可能就会引入一些系列的类型不兼容的谬误
泛型劣势
- 解耦类型,比方一个类、接口或者办法要兼容多种数据类型的场景,晋升代码可重用性
保障类型平安,防止类型转换谬误
- 比方泛型与汇合的联结应用,防止了汇合充斥各种数据类型的数据最终不可避免的导致数据转换异样的状况产生
- 晋升可读性,从编码阶段就明确某个类或者办法要解决的对象类型是什么
泛型原理
Java 的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦掉(Java编译器生成的字节码是不包涵泛型信息的),这也就是通常所说
类型擦除
- 所谓的类型擦除就是将普泛型T改为Object,设置了限定通配符extends的,擦除为类型下限,设定了限定通配符super的被擦除为Object,应用
?
作为通配符的同样被擦除为Object - 伪泛型之伪在于泛型不能代表任意类型,比方说泛型不能够是根本数据类型(留神,泛型能够是数组类型),C++、C#都实现了真正的泛型,而Java只在编译层面应用泛型信息,查看完就擦除了,对于JVM来说泛型实质上就是Object类型
Java的伪泛型实现了最大的兼容性,相比与其余真泛型的语言来说,最直观的体验就是即使源码中定义了泛型,然而实际上泛型可用可不必,与Java5的晚期版本也进行了兼容
- 实际上如果应用IDEA编程时,如果提供了泛型然而不应用泛型的话,会有
Raw use of parameterized class 'Class名'
提醒,用来提醒应用泛型以取得应用泛型的劣势
- 实际上如果应用IDEA编程时,如果提供了泛型然而不应用泛型的话,会有
对于泛型类型擦除的误会:
既然编译器将泛型擦除了,那么为什么在运行时还能通过反射取得泛型信息呢,这是因为,上边说的泛型擦除实际上是将代码中用到的泛型替换为Object,然而在特定的状况下,泛型作为函数或者类的申明的一部分,会被当做是元信息存储,并能够在反射时应用,留神此时只是取得了编译时即确定的泛型信息,然而泛型运行时的具体类型依然无奈取得(运行时并不存在泛型,曾经被擦除),能够参考Java类型零碎和Java反射的了解
- 这种反射对于获取泛型信息的反对能够看做是JVM对于Java泛型擦除失落类型信息的一种补救
- 这么看Java的泛型就像是
语法糖
。这种实现形式的益处在于不用批改JVM
,缩小了潜在改变带来的危险
- 所谓的类型擦除就是将普泛型T改为Object,设置了限定通配符extends的,擦除为类型下限,设定了限定通配符super的被擦除为Object,应用
因为Java引入了泛型,所以,只用
Class
来标识类型曾经不够了。实际上,Java的类型系统结构如下- 对于Java泛型零碎可参考Java类型零碎
对于泛型擦除的了解还能够参考下边的案例进行了解
// 这是咱们本人编写的泛型public class Pair<T> { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { return first; } public T getLast() { return last; }}// 应用泛型的时候的代码Pair<String> p = new Pair<>("Hello", "world");String first = p.getFirst();String last = p.getLast();// 编译之后的代码Pair p = new Pair("Hello", "world");String first = (String) p.getFirst();String last = (String) p.getLast();// 对应的字节码 0 new #2 <com/diego/genericType/Pair> 3 dup 4 ldc #3 <Hello> 6 ldc #4 <world> 8 invokespecial #5 <com/diego/genericType/Pair.<init> : (Ljava/lang/Object;Ljava/lang/Object;)V>11 astore_112 aload_113 invokevirtual #6 <com/diego/genericType/Pair.getFirst : ()Ljava/lang/Object;>16 checkcast #7 <java/lang/String>19 astore_220 aload_121 invokevirtual #8 <com/diego/genericType/Pair.getLast : ()Ljava/lang/Object;>24 checkcast #7 <java/lang/String>27 astore_328 returnPair<String> pair1 = new Pair<>("li","jia");Pair<String> pair2 = new Pair<>("li","jia");// JVM只晓得有Pair类型的对象,而不意识泛型System.out.println(pair1.getClass() == pair2.getClass()); // true
- 从字节码中一窥泛型到底时,能够看到泛型T全副擦除为Object类型,同时在返回类型为泛型的办法中,还有一个非凡的指令
checkcast
即执行类型强转,因为泛型的应用保障了返回值必然是指定类型,所以类型强制转化(先向上晋升为Object,再还原回来)肯定胜利,如果不匹配则抛出ClassCastException
运行时异样(正确应用泛型则不会呈现该异样) - 能够总结道:泛型起作用的过程除了编译时的平安类型查看外还包含,在泛型变量与确定类型的变量做赋值的地位会做平安的强制类型转换(得益于编译过程中的类型一致性查看)
- 从字节码中一窥泛型到底时,能够看到泛型T全副擦除为Object类型,同时在返回类型为泛型的办法中,还有一个非凡的指令
把泛型间接看做是
Object
类也是不对的,参考下边的例子了解// 实例化泛型的需要public class Pair<T> { private T first; private T last; public Pair() { // Compile error: first = new T(); last = new T(); } // 应用泛型重写equals办法时的难堪 public boolean equals(T t) { // .... }}
当咱们有在类中实例化泛型的需要的时候,是不能间接应用new进行实例化的,因为如果间接这样做可行的话,编译器会将其转换为创立一个Object对象,那么在应用泛型的时候(毕竟泛型是由用户指定的任意援用类型),不能进行强转,否则会呈现类型强转谬误,正确的做法是依赖内部的参数
public class Pair<T> { private T first; private T last; public Pair(Class<T> clazz) { first = clazz.newInstance(); last = clazz.newInstance(); }}// 这是模块中很常见的伎俩Pair<String> pair = new Pair<>(String.class);
- equals办法实际上在擦拭后与父类Object的equals办法形成重写,造成了事实上的歧义,因而实际上定义这样的办法时会间接报错,应将泛型T改为Object以实现重写,或者更改办法名不进行重写的尝试
Java泛型的局限
<T>
不能是根本类型,例如int
,因为理论类型是Object
,Object
类型无奈持有根本类型无奈获得带泛型的
Class
Pair<String> p1 = new Pair<>("Hello", "world");Pair<Integer> p2 = new Pair<>(123, 456);Class c1 = p1.getClass();Class c2 = p2.getClass();System.out.println(c1==c2); // trueSystem.out.println(c1==Pair.class); // true
无奈判断带泛型的类型
Pair<Integer> p = new Pair<>(123, 456);// Compile error:if (p instanceof Pair<String>) {}
- 并不存在
Pair<String>.class
,而是只有惟一的Pair.class
- 并不存在
- 不能应用
new
实例化T
类型以及T
类型数组
泛型的应用
罕用的泛型符号
罕用的通配符为:
T
,E
,K
,V
,?
,仅仅是约定俗成的符号而已,就跟参数的名字一样T
(type) 示意具体的一个Java类型K
V
(key value) 别离代表java键值中的Key Value------罕用于双泛型E
(element) 代表Element,用于示意汇合中的元素?
示意不确定的 Java 类型,或者说是任意的Java类型
再次强调,泛型自身是Java数据类型的代号,而不是数据类型自身,比方下边的例子
public class GenericTypeTest<T> { <String, T, Alibaba> String get(String string, Alibaba alibaba) { return string; } public static void main(String[] args) { GenericTypeTest<Integer> test = new GenericTypeTest<>(); Integer s = test.get(123, 123); }}
- String与Alibaba都是具体类型的一个代表符号,而这个具体类型是什么由函数调用方传入的参数的理论类型定义
泛型通配
所谓通配即是指定泛型能够代表一系列的类型,以晋升泛型的应用效率,然而如果放开接管任意类型的话,则失去了泛型的类型限度作用,因而引入了以下两种可接管多种受泛型束缚的类型的泛型模式:
< ? extends T >
确保泛型必须是 T 的子类来设定泛型的上界,实用于生产汇合元素的场景,进一步讲就是适宜只读数据场景,比方计算汇合中所有元素的和int sumOfList(List<? extends Integer> list) { int sum = 0; for (int i=0; i<list.size(); i++) { Integer n = list.get(i); sum = sum + n; } return sum;}
< ? super T >
确保类型必须是 T 的父类来设定泛型的下界,实用于增加汇合元素的场景,进一步讲就是适宜写数据场景,比方一个简略的set办法void set(Pair<? super Integer> p, Integer first, Integer last) { p.setFirst(first); p.setLast(last);}
最完满的展现extends通配符和super通配符应用的例子就是Java规范库的
Collections
类定义的copy()
办法: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); } }}
- 泛型通配的利用实际上能够分为代表泛型类型匹配与代表泛型汇合的个性两局部
extends
能够向
<? extends T>
束缚的汇合援用赋值任何 T 及 T 子类的汇合- 留神反过来是不容许的
容许从汇合中读数据,然而数据都会被强制转为类型T。限度写数据,只容许写入null。以下边的代码为例:
Pair<? extends Number, ? extends Number> pair = new Pair<>(1,2);// Integer key = pair.getKey(); // 编译时类型转换报错,实际上是Integer key = (Number)pair.getKey();
- 类型强转的限度是有情理的,如果汇合中存储有Double类型的数据话,能够应用Integer去承接吗,显然不适合,应用Number类型的援用承接更正当,也能够了解为汇合中的子类数据取出时类型被擦除了
- 同理,
Pair<? extends Number>
持有的类型都是Number或者Number的子类,然而又不能确定到底是哪个类型因而只能传入人畜有害的null
super
能够向
<? super T>
束缚的汇合援用赋值任何 T 及 T 的父类汇合- 留神反过来是不容许的
容许向汇合中写数据,然而数据类型只能是T或者是T类型的子类类型,能够读数据,然而类型全副失落,只能返回Object类型的数据。以下边的代码为例:
Pair<? super Cat> pair = new Pair<>();pair.setFirst(new Cat()); // 编译通过pair.setFirst(new Garfield()); // 编译通过// pair.setFirst(1); // 编译谬误Object first = pair.getFirst(); // 编译通过
- 写数据时,只能写入类型T或者其子类,这样能够放弃汇合内的类型兼容,如果能够写T的某个父类类型或者其余任意类型,就很难放弃类型兼容
- 因为能够向受约束的援用赋值T以及T的任意父类的汇合,所以取数据时间接丢掉类型以放弃类型兼容,当然也能够了解为类型信息间接被擦除掉了
PECS准则
对于什么时候用extends,什么时候用super的问题,《Effective Java》书中给出了一个论断即PECS准则
producer-extends, consumer-super
- 要从泛型类取数据时,用extends
- 要往泛型类写数据时,用super
- 既要取又要写,就不必通配符(即extends与super都不必)
多重限定
类型参数能够应用多重限定,即应用
&
来示意,指定泛型T必须是A和B这两个接口的独特实现类public class Multilimit implements MultiInterfaceA ,MultiInterfaceB { public static <T extends MultiInterfaceA & MultiInterfaceB> void test (T t) { }}
- 与汇合中的泛型利用做辨别
应用案例
一般泛型的应用
以下边的代码为例,了解个别的泛型的应用
public class ListNoGeneric { public static void GenericTest() { // 1. 汇合类中不应用泛型 List a1 = new ArrayList(); a1.add(new Object()); a1.add(new Integer(111)); a1.add(new String("hello a1a1")); // 2. 把a1援用赋值给a2,留神a2的类型中引入了泛型<Object> List<Object> a2 = a1; a2.add(new Object()); a2.add(new Integer(222)); a2.add(new String("hello a2a2")); // 3. 把a1援用赋值给a3,留神a3的类型中引入了泛型<Integer> List<Integer> a3 = a1; a3.add(new Integer(333)); // 下边两行代码编译谬误 //a3.add(new Object()); //a3.add(new String("hello a3a3")); // 4. 把a1援用赋值给a4,留神区别在于a4的类型引入了通配符 List<?> a4 = a1; a4 = a3; a4.remove(0); a4.clear(); // 编译出错,不容许增加任何元素 //a4.add(new Object()); // 容许增加null a4.add(null); // 读数据时,失落类型信息,间接擦除为Object Object o = a4.get(0); }}
- 第一段代码演示了泛型推出之前的汇合的应用,各种类型能够一股脑的放到汇合中,然而取出应用时则要小心的校验类型,否则会呈现类型转换谬误
第二段引入了Object作为泛型类型,此时仍能够增加各种类型的数据,因为都是兼容Object的类型
同时也展现了Java中泛型的向前兼容,即非泛型汇合能够赋值给任何泛型限度的汇合,只管Java泛型放弃了对历史代码的兼容,然而这种兼容很容易引入BUG,参考下边的代码,将来还是要尽量应用泛型定义
/** * 演示泛型的类型兼容引入的BUG */public class TypeCompatibility { public static void main(String[] args) { JSONObject jsonObject = JSONObject.parseObject("{\"level\":[\"3\"]}"); List<Integer> intList = new ArrayList<>(10); if (jsonObject != null) { intList.addAll(jsonObject.getJSONArray("level")); int amount = 0; for (Integer i : intList) { // ClassCastException: String cannot be cast to Integer amount = amount + i; } } }}
adAll办法的办法申明是:
boolean addAll(Collection<? extends E> c);
,而JSONArray类型的定义是public final class JSONArray extends AbstractJSON implements JSON, List
,因为泛型兼容,所以增加胜利,然而理论增加的不是Integer类型的数据而是String类型,最终导致呈现强转异样- 实际上在fastjson(1.2.79)中JSONArray的类型定义曾经更改为
public class JSONArray extends JSON implements List<Object>, Cloneable, RandomAccess, Serializable
,此时类型查看起作用,addAll处间接提醒类型不兼容,这里体现了List
与List<Object>
的不同
- 实际上在fastjson(1.2.79)中JSONArray的类型定义曾经更改为
第三段代码也展现了Java泛型的兼容性,如果将不带泛型的a1改为
List<Object>
,则第三段代码的赋值就会编译出错,反过来同样会编译谬误;与之比照的是,如果数组这样赋值则不会出错,因为数组是协变的(所谓协变的概念可参考Java类型零碎)List<Object> b = new ArrayList<>();// List<Integer> c = b; // java.util.List<java.lang.Object>无奈转换为java.util.List<java.lang.Integer>List<Integer> c = new ArrayList<>();// List<Object> d = c; // java.util.List<java.lang.Integer>无奈转换为java.util.List<java.lang.Object>Integer[] integers = new Integer[]{1,2,3};Object[] objects = integers; // 可转换Object[] objs = new Object[3];// Integer[] ins = objs; // java.lang.Object[]无奈转换为java.lang.Integer[]
- 确定的泛型类型(即未应用通配符的泛型类型)是没有协变性的
第四段代码阐明,当应用
?
作为泛型通配符时,示意该泛型类型的援用能够指向任何泛型类型的汇合,然而不能增加任何元素,读出的数据也会失落类型信息,然而能够执行remove
或者clear
,比方如果在上述代码中应用a4 = a3
也会失常编译通过。然而不要误以为应用?
做通配符时能够容许任意类型的数据增加到汇合中,如果这样的话,泛型实际上失去了类型爱护的意义,应用?
做泛型的汇合个别作为参数接管内部的汇合或者返回一个不明确元素类型的汇合。这种性质在自定义的应用泛型的类中是一样的:public class Pair<T> { private T first; private T last; public Pair() {} public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { return first; } public T getLast() { return last; } public void setFirst(T first) { this.first = first; } public void setLast(T last) { this.last = last; } public static void main(String[] args) { Pair<?> pair = new Pair<>(); //pair.setFirst(1); // 增加失败 // pair.setLast(2); // 增加失败 pair = new Pair<Integer>(); // 编译通过 Pair<?> pair1 = new Pair<>(1,2); // 编译通过 }}
- 大多数状况下,能够引入泛型参数
<T>
打消<?>
通配符 - 实际上
<?>
等价于<? extends Object>
- 大多数状况下,能够引入泛型参数
泛型限定符的应用
以下边的代码为例,了解泛型限定符的应用
public static void GenericWithLimitTest() { // 首先申明三个顺次继承的类 List<Animal> animals = new ArrayList<>(); List<Cat> cats = new ArrayList<>(); List<Garfield> garfields = new ArrayList<>(); animals.add(new Animal()); cats.add(new Cat()); garfields.add(new Garfield()); // 测试赋值操作 // 上行编译出错,List<? extends Cat>类型的汇合只承受Cat或者其子类的汇合 List<? extends Cat> extendsCat = animals; List<? super Cat> superCat = animals; extendsCat = cats; superCat = cats; extendsCat = garfields; // 上行编译出错,List<? super Cat>类型的汇合只承受Cat或者其父类的汇合 superCat = garfields; // add办法测试 // 泛型中应用extends关键字的汇合,只容许写null extendsCat.add(new Animal()); extendsCat.add(new Cat()); extendsCat.add(new Garfield()); extendsCat.add(null); // 泛型中应用super关键字的汇合,只容许写Cat类型数据或者其子类 superCat.add(new Animal()); superCat.add(new Cat()); superCat.add(new Garfield()); // get办法测试,应用extends关键字时,读出的数据会被转型为Cat类型 Cat cat = extendsCat.get(0); Object cat1 = extendsCat.get(0); // 上行编译出错,Cat类型的值向其子类援用赋值会呈现类型强转失败 Garfield cat2 = extendsCat.get(0); // 应用super关键字时,能够读数据,然而数据类型失落 Object object = superCat.get(0); // Object类型的对象不能被赋值到其子类的援用 Cat cat3 = superCat.get(0); }
反射API对泛型的反对
Java的局部反射API反对泛型
// compile warning:Class clazz = String.class;// 必须强转,否则会编译失败String str = (String) clazz.newInstance();// no warning:Class<String> clazz = String.class;String str = clazz.newInstance();
调用
Class
的getSuperclass()
办法返回的Class
类型是Class<? super T>
Class<? super String> sup = String.class.getSuperclass();
构造方法
Constructor
也反对泛型:Class<Integer> clazz = Integer.class;Constructor<Integer> cons = clazz.getConstructor(int.class);Integer i = cons.newInstance(123);
带泛型数组
能够申明带泛型的数组,但不能用
new
操作符创立带泛型的数组:Pair<String>[] ps = null; // okPair<String>[] ps = new Pair<String>[2]; // compile error!
必须通过强制转型实现带泛型的数组:
Pair<String>[] ps = (Pair<String>[]) new Pair[2];
因为须要强制转换,所以咱们不得不首先创立一个一般的数组,如果不是间接应用new来创立这个一般的数组,而是应用了一个变量来援用这个一般数组的话,可能会导致谬误,这是因为编译器会查看带泛型的数组,然而不会查看一般的数组,然而通过强制转型之后,两个数组援用实际上指向同一个数组,不被查看的一般数组可能会引入不合乎泛型的成员,从而导致谬误
Pair[] arr = new Pair[2];Pair<String>[] ps = (Pair<String>[]) arr;ps[0] = new Pair<String>("a", "b");arr[1] = new Pair<Integer>(1, 2);// ClassCastException:Pair<String> p = ps[1];String s = p.getFirst();
带泛型的数组在泛型擦除后实际上就是一般的数组类型
Pair[] arr = new Pair[2];Pair<String>[] ps = (Pair<String>[]) arr;System.out.println(ps.getClass() == Pair[].class); // true
在反对泛型的办法中创立泛型数组时不能间接
new T[]
,因为这样实际上创立的是Object[]
类型的数组,正确的创立办法与创立泛型实例的办法统一:引入Class
实例public T[] create(Class<T> tClass) { return (T[])Array.newInstance(tClass, 5);}
泛型可变参数
除了应用
newInstance
办法之外还能够应用泛型可变参数创立泛型数组public class ArrayHelper { @SafeVarargs static <T> T[] asArray(T... objs) { return objs; }}String[] ss = ArrayHelper.asArray("a", "b", "c");Integer[] ns = ArrayHelper.asArray(1, 2, 3);
应用泛型可变参数存在肯定的危险
public class ArrayHelper { static <K> K[] pickTwo(K k1, K k2, K k3) { return asArray(k1, k2); } static <T> T[] asArray(T... objs) { return objs; } public static void main(String[] args) { String[] arr = asArray("one", "two", "three"); System.out.println(Arrays.toString(arr)); // ClassCastException: String[] firstTwo = pickTwo("one", "two", "three"); System.out.println(Arrays.toString(firstTwo)); }}
- 间接调用
asArray(T...)
仿佛没有问题,然而在另一个办法中,咱们返回一个泛型数组就会产生ClassCastException
,起因还是因为类型擦除,在pickTwo()
办法外部,编译器无奈检测K[]
的正确类型,因而返回了Object[]
实际上IDEA会有正告,除非确认齐全没有问题,才能够用
@SafeVarargs
打消正告
- 间接调用
- 思考到上述呈现的对于泛型数组的问题,如果在办法外部创立了泛型数组,最好不要将它返回给内部应用
类型转换
要明确一点:泛型只是提供一种编译时的类型查看机制,它并不是类、接口、办法的一部分,除此之外,确定的泛型类型(即未应用通配符的泛型类型)是没有协变性的
// 创立ArrayList<Integer>类型:ArrayList<Integer> integerList = new ArrayList<Integer>();// 增加一个Integer:integerList.add(new Integer(123));// “向上转型”为ArrayList<Number>:赋值失败ArrayList<Number> numberList = integerList;
泛型的定义
Class
以ArrayList
为例
// 类名前面要加一个<T>,其作用就是先申明这个泛型的符号(实质上就是个类型参数),尔后类中就能够应用此泛型符号作为个别的类型标记public class ArrayList<T> { private T[] array; private int size; public void add(T e) {...} public void remove(int index) {...} public T get(int index) {...}}
应用Java类中的泛型的时候留神
在静态方法中不能应用类上定义的泛型,类上定义的泛型仿佛与实例对象this有关系,所以不能在动态语境中应用,如果非要应用,能够应用定义在办法上的泛型(与类或者办法自身无关,只与泛型定义的地位无关),下边看实例代码
public class Pair<T> { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { ... } public T getLast() { ... } // 对静态方法应用<T>: 编译不通过 this cannot be referenced from a static contex public static Pair<T> create(T first, T last) { return new Pair<T>(first, last); }}class Pair<T> { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { return (T)first; } public T getLast() { return (T)last; } // 对静态方法应用<K>:对于一般的函数也是实用的 public static<K> Pair<K> create(K first, K last) { return new Pair<K>(first, last); }}// 批改为应用办法上申明的泛型public class Demo { public static void main(String[] args) { Pair<String> pair = Pair.create("li","jia"); Pair<Integer> pair1 = pair.create(1,2); System.out.println(pair.getFirst()); }}
一个不带泛型的类能够继承自带泛型的类
class Pair<T> { private T first; private T last; public Pair(T first, T last) { this.first = first; this.last = last; } public T getFirst() { return (T)first; } public T getLast() { return (T)last; } public <K> Pair<K> create(K first, K last) { return new Pair<K>(first, last); }}class IntPair extends Pair<Integer> { public IntPair(Integer first, Integer last) { super(first, last); }}
对于个别的泛型类型的实例,无奈通过其Class实例获取泛型,然而对于子类继承带泛型父类的场景或者是办法中应用了泛型等场景,均能够通过反射获取对应的泛型信息,以上边的
IntPair
类为例Class<IntPair> clazz = IntPair.class;Type t = clazz.getGenericSuperclass();if (t instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) t; Type[] types = pt.getActualTypeArguments(); // 可能有多个泛型类型 Type firstType = types[0]; // 取第一个泛型类型 Class<?> typeClass = (Class<?>) firstType; System.out.println(typeClass); // Integer}
Interface
一个典型的案例就是
Comparable
接口interface Comparable<T> { int compareTo(T o);}
对于带泛型的类、接口的继承与实现能够有两种模式,父类或者是接口能够是带通配符泛型的,也能够是具体类型的
class IntPair<T> extends Pair<T>{ // ...}class IntPair extends Pair<Integer>{ // ...}class Person implements Comparable<Person> { String name; int score; Person(String name, int score) { this.name = name; this.score = score; } public int compareTo(Person other) { return this.name.compareTo(other.name); } public String toString() { return this.name + "," + this.score; }}
- 实现带泛型接口的类必须实现正确的泛型类型
Function
定义在函数上的泛型优先被被所在函数应用
public static <E> void printArray( E[] inputArray ){ for ( E element : inputArray ){ System.out.printf( "%s ", element ); } System.out.println();}
- 泛型申明的地位在函数修饰符后,返回类型前
多泛型的应用
以上三种地位的泛型都能够应用多泛型,典型的例子就是Map的定义
public class Pair<T, K> { private T first; private K last; public Pair(T first, K last) { this.first = first; this.last = last; } public T getFirst() { ... } public K getLast() { ... }}Pair<String, Integer> p = new Pair<>("test", 123);
嵌套泛型的应用
所谓嵌套泛型的含意就是泛型代表的类型也应用了泛型
public class Subset { public List<List<Integer>> subsets(int[] nums) { // 对于蕴含泛型的类型,保障内层泛型统一即可 LinkedList<List<Integer>> res = new LinkedList<>(); LinkedList<Integer> track = new LinkedList<>(); backtrack(0, nums, res, track); return res; } private void backtrack(int i, int[] nums, LinkedList<List<Integer>> res, LinkedList<Integer> track) { res.push(new ArrayList<>(track)); for (int j = i; j < nums.length; j++) { track.push(nums[j]); backtrack(j + 1, nums, res, track); track.pop(); } }}
- 留神
res
的类型,只有内部的理论类型能够思考转型,即LinkedList
转到List
,外部的泛型放弃齐全的统一(必须是齐全的统一,泛型不存在什么转型)即可 对于
LinkedList<List<Integer>>
外部的泛型在res.push
起作用,ArrayList
能够降级为List
res.push(new ArrayList<>(track))
这一句中,会查看track
的泛型类型,如果应用的是ArrayList
的空参数构造函数也是容许的,如果track
泛型的类型不统一,IDE会提醒无奈猜想ArrayList
的泛型类型
- 留神
参考
- 《码出高效:Java开发手册》
- 廖雪峰Java教程
- Java中的逆变与协变