1.定义:泛型就是允许定义类、接口时指定类型形参,这个类型形参将在声明变量、创建对象时确定(传入实际的类型参数,也可以称为类型实参)。
public interface List<E>{
void add(E x);
}
public class Fruit<T,S>{
private T a;
private S b;
public T getSth(){
return a;
}
public S getSth2(){
return b;
}
}
注意:1》包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态生成无数多个逻辑上的子类,但这种子类在物理上不存在。
2》当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。例如,为Fruit<T>定义构造器时,其构造器名依然是Fruit,而不是Fruit<T>,但是调用该构造器时却可以使用Fruit<T>形式。
3.设定类型形参上限
1》单个上限:public class Apple<T extends Number>{}
2》多个上限: public class Apple<T extends Number&java.io.Serializable>{},注意类一定要放在第一个
4.从泛型类派生子类
1》public class A extends Fruit<String>{}
2》public class A extends Fruit{}
只有在定义类、接口时才能使用类型形参,当使用类、接口时应该为类型形参传入实际的类型,也可以不传入特定类型,但是一定不能使用类型形参。例如以下代码就是错误的,public class A extends Fruit<T>{}。
5.并不存在泛型类
举例,系统并不会为ArrayList<String>生成新的class文件,也不会把ArrayList<String>当成新的类来处理。
List<String> l1=new ArrayList<String>();
List<Integer> l2=new ArrayList<Integer>();
System.out.println(l1.getClass()==l2.getClass());//此处输出true,因为不管泛型类型的实际参数是什么,运行时总有同样的类
注意:1》在静态变量、静态代码块、静态方法的声明和初始化中不允许使用类型形参。
2》instanceof运算符后不能使用泛型类。
例如以下代码就是错误的,
List<?> c=new ArrayList<String>();
c.add(new Object());//因为我们不知道c里面的元素类型,所以不能向其中添加对象。
7.设定类型通配符的上限和下限
上限:List<? extends Shape> shape;
下限: List<? super Long> list;
同样地,我们也无法向设定上限或下限的泛型类中添加对象。例如:
shape.add(0,new Rectangel());
这一句会报错,其中Rectangel是Shape的子类,new Rectangel()的类型是? extends Shape,我们无法确定这个类型,所以不能添加对象。
8.定义泛型方法
所谓泛型方法,就是在声明方法时定义一个或多个类型形参。
修饰符 <T,S> 返回值类型 方法名(参数列表){方法体}
9.泛型方法和类型通配符的区别
1》泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的依赖关系,就不应该使用泛型方法。
2》通配符就是被设计用来支持灵活的子类化的。如果泛型方法中的类型形参只是用来在不同的调用点传入不同的实际类型,应该使用通配符。如果有需要,我们可以同时使用泛型方法和类型通配符。
3》类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型。但泛型方法中类型形参必须在方法中显示声明。
10.泛型和方法重载
使用泛型进行方法重载时一定要严谨,因为泛型既允许通配符设定上限,也允许通配符设定下限。例如:
public class Test{
public static <T> void copy(Collection<T> dest,Collection<? extends T> src){};
public static <T> T copy(Collection<? super T> dest,Collection<T> src){};
List<Integer> l2=new ArrayList<Integer>();
Test.copy(l1,l2);//报错,编译器无法确定调用哪个copy方法
11.泛型和数组
java在泛型设计时有一个很重要的原则:如果系统在编译时没有产生“未经检查的转换”警告,则程序在运行时不会产生ClassCastException异常。
1》数组元素的类型不能包含类型变量或类型形参,除非是无上限的类型通配符。
例如,List<String>[] lsa=new ArrayList<String>[10];//编译报错
List<String>[] lsa=new ArrayList[10];//不报错,有警告,运行时可能异常
List<?>[] lsa=new ArrayList<?>[10];//不报错,无警告,运行时可能异常
2》但是可以声明这样的数组,但不能创建这样的对象。
例如,可以声明List<String>[]形式的数组,但是不能创建new ArrayList<String>[10]这样的对象。
3》类似的,创建元素类型是类型变量的数组对象也将导致编译错误。
例如,
<T> T[] makeArray(Collection<T> coll){
return new T[coll.size()];//编译报错
}
1》将所有的泛型参数用最左边界类型(最顶级的父类型)替换。
2》移除所有的类型参数。
interface Comparable <A> {
public int compareTo( A that);
}
final class NumericValue implements Comparable <NumericValue> {
priva te byte value;
public NumericValue (byte value) { this.value = value; }
public byte getValue() { return value; }
public int compareTo( NumericValue t hat) { return this.value - that.value; }
}
class Collections {
public static <A extends Comparable<A>>A max(Collection <A> xs) {
Iterator <A> xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList <NumericValue> numberList = new LinkedList <NumericValue> ();
numberList .add(new NumericValue((byte)0));
numberList .add(new NumericValue((byte)1));
NumericValue y = Collections.max( numberList );
}
}
经过类型擦除后的类型为
interface Comparable {
public int compareTo( Object that);
}
final class NumericValue implements Comparable {
priva te byte value;
public NumericValue (byte value) { this.value = value; }
public byte getValue() { return value; }
public int compareTo( NumericValue t hat) { return this.value - that.value; }
public int compareTo(Object that) { return this.compareTo((NumericValue)that); }
}
class Collections {
public static Comparable max(Collection xs) {
Iterator xi = xs.iterator();
Comparable w = (Comparable) xi.next();
while (xi.hasNext()) {
Comparable x = (Comparable) xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList numberList = new LinkedList();
numberList .add(new NumericValue((byte)0));
numberList .add(new NumericValue((byte)1));
NumericValue y = (NumericValue) Collections.max( numberList );
}
}
第一个泛型类Comparable <A>擦除后 A被替换为最左边界Object。Comparable<NumericValue>的类型参数NumericValue被擦除掉,但是这直接导致NumericValue没有实现接口Comparable的compareTo(Object that)方法,于是编译器充当好人,添加了一个桥接方法。
第二个示例中限定了类型参数的边界<A extends Comparable<A>>A,A必须为Comparable<A>的子类,按照类型擦除的过程,先讲所有的类型参数 ti换为最左边界Comparable<A>,然后去掉参数类型A,得到最终的擦除后结果。
另外,当把一个具有泛型信息的对象赋给另外一个没有泛型信息的变量时,则所有在尖括号之间的类型信息都被扔掉了。
例如,List<Integer> ls=new ArrayList<Integer>();
ls.add(6);
List list=ls;//转换
List<String> ls2=list;//警告,未经检查的转换
System.out.println(ls2.get(0));//运行时异常
13.类型擦除需要注意的问题
1》用同一泛型类的实例区分方法签名?-----NO
public class Erasure{
public void test(List<String> ls){}
public void test(List<Integer ls>){}
}
编译这个类报错,因为调用test()方法时无法区分参数。
2》同时catch同一个泛型异常类的多个实例?-----NO
如果定义了一个泛型异常类GenericException<T>,千万别同时catch GenericException<Integer>和GenericException<String>,因为它们在运行时是一样的。
3》泛型类的静态变量是共享的?------YES
由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
14.总结
1》虚拟机中没有泛型,只有普通类和普通方法
2》所有泛型类的类型参数在编译时都会被擦除
3》创建泛型对象时请指明类型参数,让编译器尽早做检查
4》不要忽略编译器的警告信息,因为那意味着潜藏的ClassCastException
5》注意概念:桥接方法。
public interface List<E>{
void add(E x);
}
public class Fruit<T,S>{
private T a;
private S b;
public T getSth(){
return a;
}
public S getSth2(){
return b;
}
}
注意:1》包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态生成无数多个逻辑上的子类,但这种子类在物理上不存在。
2》当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明。例如,为Fruit<T>定义构造器时,其构造器名依然是Fruit,而不是Fruit<T>,但是调用该构造器时却可以使用Fruit<T>形式。
3.设定类型形参上限
1》单个上限:public class Apple<T extends Number>{}
2》多个上限: public class Apple<T extends Number&java.io.Serializable>{},注意类一定要放在第一个
4.从泛型类派生子类
1》public class A extends Fruit<String>{}
2》public class A extends Fruit{}
只有在定义类、接口时才能使用类型形参,当使用类、接口时应该为类型形参传入实际的类型,也可以不传入特定类型,但是一定不能使用类型形参。例如以下代码就是错误的,public class A extends Fruit<T>{}。
5.并不存在泛型类
举例,系统并不会为ArrayList<String>生成新的class文件,也不会把ArrayList<String>当成新的类来处理。
List<String> l1=new ArrayList<String>();
List<Integer> l2=new ArrayList<Integer>();
System.out.println(l1.getClass()==l2.getClass());//此处输出true,因为不管泛型类型的实际参数是什么,运行时总有同样的类
注意:1》在静态变量、静态代码块、静态方法的声明和初始化中不允许使用类型形参。
2》instanceof运算符后不能使用泛型类。
6.使用类型通配符
注意:数组和泛型不同,假设Foo是Bar的子类型,那么 Foo[]也是Bar[]的子类型,但是G<Foo>不是G<Bar>的子类型。
定义泛型类时不能使用类型通配符,因为使用带类型通配符的泛型类表示所有确定泛型类的父类。一般用于取泛型类中的数据,不能用于向其中添加数据。例如以下代码就是错误的,
List<?> c=new ArrayList<String>();
c.add(new Object());//因为我们不知道c里面的元素类型,所以不能向其中添加对象。
7.设定类型通配符的上限和下限
上限:List<? extends Shape> shape;
下限: List<? super Long> list;
同样地,我们也无法向设定上限或下限的泛型类中添加对象。例如:
shape.add(0,new Rectangel());
这一句会报错,其中Rectangel是Shape的子类,new Rectangel()的类型是? extends Shape,我们无法确定这个类型,所以不能添加对象。
8.定义泛型方法
所谓泛型方法,就是在声明方法时定义一个或多个类型形参。
修饰符 <T,S> 返回值类型 方法名(参数列表){方法体}
9.泛型方法和类型通配符的区别
1》泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的依赖关系,就不应该使用泛型方法。
2》通配符就是被设计用来支持灵活的子类化的。如果泛型方法中的类型形参只是用来在不同的调用点传入不同的实际类型,应该使用通配符。如果有需要,我们可以同时使用泛型方法和类型通配符。
3》类型通配符既可以在方法签名中定义形参的类型,也可以用于定义变量的类型。但泛型方法中类型形参必须在方法中显示声明。
10.泛型和方法重载
使用泛型进行方法重载时一定要严谨,因为泛型既允许通配符设定上限,也允许通配符设定下限。例如:
public class Test{
public static <T> void copy(Collection<T> dest,Collection<? extends T> src){};
public static <T> T copy(Collection<? super T> dest,Collection<T> src){};
}
这么定义不会有任何错误,但是调用时会报错:
List<Number> l1=new ArrayList<Number>();List<Integer> l2=new ArrayList<Integer>();
Test.copy(l1,l2);//报错,编译器无法确定调用哪个copy方法
11.泛型和数组
java在泛型设计时有一个很重要的原则:如果系统在编译时没有产生“未经检查的转换”警告,则程序在运行时不会产生ClassCastException异常。
1》数组元素的类型不能包含类型变量或类型形参,除非是无上限的类型通配符。
例如,List<String>[] lsa=new ArrayList<String>[10];//编译报错
List<String>[] lsa=new ArrayList[10];//不报错,有警告,运行时可能异常
List<?>[] lsa=new ArrayList<?>[10];//不报错,无警告,运行时可能异常
2》但是可以声明这样的数组,但不能创建这样的对象。
例如,可以声明List<String>[]形式的数组,但是不能创建new ArrayList<String>[10]这样的对象。
3》类似的,创建元素类型是类型变量的数组对象也将导致编译错误。
例如,
<T> T[] makeArray(Collection<T> coll){
return new T[coll.size()];//编译报错
}
12.擦除和转换
java中泛型的处理几乎都在编译器处理,编译器生成的字节码是不包含泛型信息的,泛型类型信息将在编译时被擦除,这称之为类型擦除.
类型擦除的主要过程:1》将所有的泛型参数用最左边界类型(最顶级的父类型)替换。
2》移除所有的类型参数。
interface Comparable <A> {
public int compareTo( A that);
}
final class NumericValue implements Comparable <NumericValue> {
priva te byte value;
public NumericValue (byte value) { this.value = value; }
public byte getValue() { return value; }
public int compareTo( NumericValue t hat) { return this.value - that.value; }
}
class Collections {
public static <A extends Comparable<A>>A max(Collection <A> xs) {
Iterator <A> xi = xs.iterator();
A w = xi.next();
while (xi.hasNext()) {
A x = xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList <NumericValue> numberList = new LinkedList <NumericValue> ();
numberList .add(new NumericValue((byte)0));
numberList .add(new NumericValue((byte)1));
NumericValue y = Collections.max( numberList );
}
}
经过类型擦除后的类型为
interface Comparable {
public int compareTo( Object that);
}
final class NumericValue implements Comparable {
priva te byte value;
public NumericValue (byte value) { this.value = value; }
public byte getValue() { return value; }
public int compareTo( NumericValue t hat) { return this.value - that.value; }
public int compareTo(Object that) { return this.compareTo((NumericValue)that); }
}
class Collections {
public static Comparable max(Collection xs) {
Iterator xi = xs.iterator();
Comparable w = (Comparable) xi.next();
while (xi.hasNext()) {
Comparable x = (Comparable) xi.next();
if (w.compareTo(x) < 0) w = x;
}
return w;
}
}
final class Test {
public static void main (String[ ] args) {
LinkedList numberList = new LinkedList();
numberList .add(new NumericValue((byte)0));
numberList .add(new NumericValue((byte)1));
NumericValue y = (NumericValue) Collections.max( numberList );
}
}
第一个泛型类Comparable <A>擦除后 A被替换为最左边界Object。Comparable<NumericValue>的类型参数NumericValue被擦除掉,但是这直接导致NumericValue没有实现接口Comparable的compareTo(Object that)方法,于是编译器充当好人,添加了一个桥接方法。
第二个示例中限定了类型参数的边界<A extends Comparable<A>>A,A必须为Comparable<A>的子类,按照类型擦除的过程,先讲所有的类型参数 ti换为最左边界Comparable<A>,然后去掉参数类型A,得到最终的擦除后结果。
另外,当把一个具有泛型信息的对象赋给另外一个没有泛型信息的变量时,则所有在尖括号之间的类型信息都被扔掉了。
例如,List<Integer> ls=new ArrayList<Integer>();
ls.add(6);
List list=ls;//转换
List<String> ls2=list;//警告,未经检查的转换
System.out.println(ls2.get(0));//运行时异常
13.类型擦除需要注意的问题
1》用同一泛型类的实例区分方法签名?-----NO
public class Erasure{
public void test(List<String> ls){}
public void test(List<Integer ls>){}
}
编译这个类报错,因为调用test()方法时无法区分参数。
2》同时catch同一个泛型异常类的多个实例?-----NO
如果定义了一个泛型异常类GenericException<T>,千万别同时catch GenericException<Integer>和GenericException<String>,因为它们在运行时是一样的。
3》泛型类的静态变量是共享的?------YES
由于经过类型擦除,所有的泛型类实例都关联到同一份字节码上,泛型类的所有静态变量是共享的。
14.总结
1》虚拟机中没有泛型,只有普通类和普通方法
2》所有泛型类的类型参数在编译时都会被擦除
3》创建泛型对象时请指明类型参数,让编译器尽早做检查
4》不要忽略编译器的警告信息,因为那意味着潜藏的ClassCastException
5》注意概念:桥接方法。