目录
Complete Reference
传递过来的类型参数必须是引用类型,不能使用基本类型。
Exp: Gen<int> i = new Gen<int>; //error
对特定版本的泛型类型的引用和同一泛型类型的其他版本不是类型兼容的。
Exp: intOb = strOb;//error
带两个类型参数:
Exp: class TwoGen<T, V>{}
上界:
class Stats<T extends Number>
限制T的范围也会阻止创建非数值类型的Stats对象;同时,所有T类型的对象都可以调用Number声明的方法。
除了使用类作为边界之外,也可以使用接口。边界可以包含一个类和一个或多个接口,类和接口之间用&符号连接。如果用一个类作为限定,它必须是限定列表中的第一个。为了提高效率,应该将标签(tagging)接口(即没有方法的接口)放在边界列表的末尾。
Exp: class Gen<T extends MyClass & MyInterface>{}
Exp: public class Gen <T extends GenClass & Comparable<T> & Serializable> {
上界:
<? extends superclass>
只有superclass的子类(包括本身)是可接受参数。
下界:
<? super subclass>
只有subclass的超类(包括本身)是可接受参数
有界的通配符为类型参数指定上界或下界,从而可以限制方法能够操作的对象类型。
通配符?
Exp: Boolean sameAvg(Stats<?> ob){}
通配符不会影响能够创建什么类型的Stats对象,只是简单地匹配所有有效的Stats对象。
可以声明本身使用一个或多个类型参数的泛型方法(不一定在泛型类中)。
Exp: static <T extends Comparable<T>, V extends T> Boolean isIn(T x, V[] y){}
Exp: public static void print(Pair<?> a, Pair<?> b){}
Exp: public static <T extends Comparable<T>> boolean compare(Pair<T> a, Pair<T> b) {}
Exp: public static <T, V> boolean sameType(T x, V y){}
泛型构造函数
可以将构造函数泛型化,即使它们的类不是泛型类。
Exp: private double val;
<T extends Number> GenCons(T arg)
{
val = arg.doubleValue();
}
泛型接口
Exp: interface MinMax<T extends Comparable<T>>
Exp: class MyClass<T extends Comparable<T>> implements MinMax<T>
Exp: class MyClass1<T extends Comparable<T>, V> implements MinMax<T>
Exp: class MyClass2 implements MinMax<Integer>
Exp: class MyClass<T> implements MinMax<T> //error
Exp: class MyClass<T> implements MinMax<T extends Comparable<T>> //error
Exp: class MyClass2 implements MinMax<> //error
因为MinMax要实现Comparable的类型,所以实现类MyClass必须指定相同的界限。此外,一旦建立这个界限,就不需要再在Implements子句中指定。一般而言,如果类实现了泛型接口,那么类也必须是泛型化的,至少需要带有将被传递给接口的类型参数。
原始类型
将原始引用赋给泛型引用绕过了类型安全机制,javac会给出未检查警告,错误只有在运行时才会出现(如果有错)。
只有在兼容遗留代码时才使用原始类型,否则不使用。
泛型类层次
泛型类可以作为超类或子类。
在泛型类层次中,类层次中的所有子类必须向上传递喜剧类所需的所有类型参数。
Gen10_Sub1指定的类型参数T也被传递给extends子句中的Gen10,这意味着传递给Gen10_Sub1的任何类型也会被传递给Gen10。除了将T传递给Gen10超类外,Gen10_Sub1没有再使用类型参数T,即使泛型超类的子类不必泛型化,也仍然必须指定泛型超类所需要的类型参数。
或如下,不传递(因为没有用到)
子类多个类型参数
Exp: class Gen10_Sub2<T, V> extends Gen10<T>
泛型子类
非泛型类作为泛型子类的超类是完全可以的。
Exp: class NonGen{}
Exp: class Gen<T> extends NonGen()
Gen以常规方式继承NonGen。
instanceof
在运行时不能使用泛型类型擦除信息,所以instanceof无法知道sOb1是否是Gen10_1_Sub<String>类型的实例。
强转
只有当两个泛型类实例的类型相互兼容并且它们的类型参数也相同时,才能将其中的一个实例转换为另一个实例。
Exp: (Gen<Integer>) int_Ob; //OK
Exp: (Gen<Long>) int_Ob; //Error
方法重写
像重写其它任何方法一样重写泛型类方法。
可以使用菱形运算符简写
Exp: Gen10_1<Integer> iOb = new Gen10_1<Integer>(88);
Exp: Gen10_1<Integer> iOb = new Gen10_1<>(88);
对java语言的语法或JVM所做的任何修改必须避免破坏以前的代码。为了满足这个条件,java使用擦除实现泛型。编译时,所有泛型信息被移除(擦除),意味着在运行时没有类型参数,它们只是一种源代码机制。
桥接方法子类中重写方法的类型擦除不能产生与超类中方法相同的擦除。对于这促情况,会生成使用超类类型擦除的方法,并且这个方法调用具有由子类指定的类型探险的方法。当然,桥接只会在字节码级别发生,你不会看到,也不能使用。
//重写情况
模糊性错误
//重载情况
void set(T o){}
void set(V o){}
擦除后两个版本的set方法都变为同一形式:void set(Object o)
解决:使用两个独立的方法名会更好些。通常,模糊性错误的解决方案涉及调整代码结构,因为模糊性通常意味着在设计中存在概念性错误。
使用泛型的一些限制
不能实例化参数的实例:
静态成员不能使用在类中声明的类型参数。
不能实例化元素类型为类型参数的数组,不能创建特定类型的泛型引用数组。
泛型类不能扩展Throwable,这意味着不能创建泛型异常类。
Core Java
类型参数,指示元素的类型。
ArrayList<String> list = new ArrayList<String>();
泛型类,具有一个或多个类型变量的类。
public class Pair<T, U>{}
注:类型变量使用大写形式,且比较短,E表示集合的元素类型,K和V 分别表示表的关键字与值的类型。T(或U、S)表示“任意类型”。
泛型类可以看作普通类的工厂。
泛型方法
public static <T> T getMiddle(T…a)
注:类型变量放在修饰符后面,返回类型前面。
泛型方法可以定义在普通类中,也可以定义在泛型类中。
调用:
String middle = ArrayAlg.<String>getMiddle(“John”, “Q.”, “Public”); //<String>可省略
虚拟机泛型类型对象——所有对象都属于普通类。
无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定的变量用Object)。
在虚拟机中,用参数类型和返回类型确定一个方法,因此,编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确处理这种情况。
虚拟机中没有泛型,只有普通的类和方法;
所有的类型参数都用它们的限定类型替换;
桥方法被合成来保持多态;
为保持类型的安全性,必要时插入强制类型转换。
参数化类型的数组
Pair<String>[] table = new Pair<String>[10]; //error
如果需要收集参数化类型对象,可以:
ArrayList<Pair<String>>
不能构造一个泛型数组,如果数组仅仅作为一个类的私有实例域,就可以将这个数组声明为Object[],并且在获取元素时进行类型转换。
泛型类继承规则
Manager extends Employee
Pair<Manager>和Pair<Employee>无任何关系
Pair<Manager> m = …;
Pair<Employee> e = m; //error
无论S与T有任何关系,Pair<S>与Paif<T>没有什么关系。
永远可以将参数化类型转换为一个原始类型。
Pair<Manager> managerBuddies = new Pair<>(ceo, cfo);
Pair rawBuddies = managerBuddies; //OK
rawBuddies.setFirst<new File(“…”)); //only a compile-time warning
当使用getFirst获得外来对象 并赋给Manager变量时,与通常一样,会抛出ClassCastException异常。这里失去的只是泛型 程序 设计提供的附加安全性。
不建议这样做。
带有超类型限定的通配符可以向泛型对象写入,带有子类型限定 的通配符可以从泛型对象读取。
? extends Employee getFirst(); //OK
将getFirst的返回值赋给一个Employee的引用完全合法。
void setFirst(? extends Emplyee); //error
编译器只知道需要某个Employee的子类型,但不知道具体是什么类型。
void setFirst(? super manager); // OK
编译器不知道setFirst()方法的确切类型,但是可以用任意Manager对象调用它。
? super Manager getFrist(); //error
如果调用getFirst(),返回的对象就不会得到保证。
varargs
Java1.5提供了一个叫varargs的新功能,就是可变长度的参数。
"Varargs"是"variable number of arguments"的意思。有时候也被简单的称为"variable arguments"
定义实参个数可变的方法:只要在一个形参的"类型"与"参数名"之间加上三个连续的"."(即"...",英文里的句中省略号),就可以让它和不确定个实参相匹配。
Generics: Non-Reifiable Types
不可具体化类型。
可具体化类型和不可具体化类型的定义:
可具体化类型:就是一个可在整个运行时可知其类型信息的类型。
包括:基本类型、非泛型类型、原始类型和调用的非受限通配符。
不可具体化类型:无法整个运行时可知其类型信息的类型,其类型信息已在编译时被擦除:
例如:List<String>和List<Number>,JVM无法在运行时分辨这两者
堆污染:
发生时机:当一个参数化类型变量引用了一个对象,而这个对象并非此变量的参数化类型时,堆污染就会发生。
分模块对代码进行分别编译,那就很难检测出潜在的堆污染,应该同时编译
带泛型的可变参数问题:
T...将会被翻译为T[],根据类型擦除,进一步会被处理为Object[],这样就可能造成潜在的堆污染
避免堆污染警告
@SafeVarargs:当你确定操作不会带来堆污染时,使用此注释关闭警告
@SuppressWarnings({"unchecked", "varargs"}):强制关闭警告弹出(不建议这么做)
所谓的可具体化类型,就是一个可在整个运行时可知其类型信息的类型。包括了基本类型、非泛型类型、原始类型和调用的非受限通配符。
不可具体化类型是指其类型信息在编译时被类型擦除机制所移除的类型。这里的类型移除是指在调用没有定义为无限制通配符的泛型时所进行的类型擦除。我们无法在运行时可知一个不可具体化类型的所有信息。一个此类型的例子就是List<String>和List<Number>。JVM在运行时无法分辨两者。正如《泛型的约束》中讲到的那样,有几种特定情景下是不能使用泛型的:例如在instanceof表达式中时,或者作为一个数组元素时。
当一个参数化类型变量引用了一个对象,而这个对象并非此变量的参数化类型时,堆污染就会发生。如果一个程序执行了一些引起编译时未检查类型警告的操作时,这种情况就会发生。一个这样的未检查警告既可在编译时发出也可在运行时发出,前者是根据编译时类型检查规则,而后者是在一个包含参数化类型操作(如类型转换、方法调用)的正确性无法保证时发出警告的。例如,在将原始类型和参数化类型混合时,或者执行了一个类型未检查的转换时,堆污染就会发生。
在正常情况下,当所有代码同时被编译后,编译器就会对潜在堆污染发出一个警告以引起你的关注。如果你分模块对代码进行分别编译,那就很难检测出潜在的堆污染。如果你确定代码编译后没有产生警告,那么堆污染就不会发生了。