1. 泛型类的使用:
public class GenericTypeTest
{
public static void main(String args[])
{
/*1.泛型类实例化,t1实例和t2实例分别制定泛型类型为String和Integer*/
/*2.泛型目的就是,通过编译检查,保证t1实例的所有的泛型操作都满足T为String,t2实例的所有的泛型操作都满足T为Integer*/
/*3.Java7以后的版本,可以只在引用声明的时候指定泛型类型,实例化代码可以使用<>代替,编译器会自动补齐*/
GenericTypeT1<String> t1 = new GenericTypeT1<>();
GenericTypeT1<Integer> t2 = new GenericTypeT1<Integer>();
/*通过反射查看T类型的成员变量*/
try
{
System.out.println(t1.getClass().getField("t").toString());
System.out.println(GenericTypeT1.class.getField("t").toString());
}
catch (NoSuchFieldException e)
{
System.out.println("SecurityException");
}
/*通过getClass()反射方法,查看编译后的带有泛型的方法*/
Method[] method1 = t1.getClass().getMethods();
for (int i = 0; i < t1.getClass().getMethods().length; i++)
{
System.out.println(method1[i].toString());
}
/*通过class反射,查看编译后的带有泛型的方法*/
Method[] method2 = GenericTypeT1.class.getMethods();
for (int i = 0; i < GenericTypeT1.class.getMethods().length; i++)
{
System.out.println(method2[i].toString());
}
}
}
/*定义一个泛型类型*/
class GenericTypeT1<T>
{
/*定义一个泛型类型的引用*/
public T t;
/*入参类型为一个泛型类型的引用*/
public void add(T t)
{
this.t = t;
}
/*返回类型为一个泛型类型的引用*/
public T get()
{
return this.t;
}
/*入参和返回类型分别为一个泛型类型为T的List类的实例引用*/
public List<T> addlist(List<T> lt)
{
return lt;
}
}
- 泛型类的作用:
Java1.7版本引入泛型,泛型类的作用是,在泛型类实例化的时候指定具体泛型类型;通过编译期间的检查来保证,该实例的所有具有泛型操作的行为都满足这个具体泛型类型!如上面例子:通过编译检查,保证t1实例的所有的泛型操作都满足T为String,t2实例的所有的泛型操作都满足T为Integer!
- 编译后的泛型类:
Java的泛型仅仅在编译期,作为编译检验,并没有具体泛型类型的泛型类!所以上面例子中t1实例和t2实例在编译后都是一个类型GenericTypeT1类!
通过打印输出可以看到所有的T类型都被替换成了Object类型(即泛型类的原始类型,这个原则在后面泛型的擦除中有详细说明)!可见Java的泛型是伪泛型,不会像C++的模板那样带来代码膨胀!
- 编译后的泛型类引用:
使用通配符的泛型引用:GenericTypeT1<?>、GenericTypeT1<? extends A>、GenericTypeT1<? super A>、GenericTypeT1<? extends T>、GenericTypeT1<? super T>
泛型方法中的泛型引用:GenericTypeT1<E>
泛型类中的泛型引用:GenericTypeT1<T>
泛型引用:GenericTypeT1<A>
说明:A为一个普通类型,T为一个泛型类的泛型类型,E为一个泛型方法的泛型类型!
结论:上面所有场景,编译后都会成为GenericTypeT1,无任何泛型类型!
- 泛型使用的约束:
1. 泛型类中不能显示实例化泛型类型,会编译报错!原因:编译后泛型类型中的T,会被替换为可识别到的最高级基类(原始类型),即泛型擦除;如上面的例子T类型都会被替换为Object类型,在泛型类中new T(),原本想new一个具体的泛型类型(运行态类型)的实例,但是却创建了一个基类实例,与设计不符!但是可以通过反射来创建一个运行态的泛型类型实例!!!
/*泛型类中,不能显示实例化泛型类型,编译报错!*/
public T tt = new T();
2. 泛型类的任何static成员都不能具有泛型类型的操作,包括static方法、static内部类、static属性等!原因:因为泛型类的泛型类型指定,是在泛型类实例化的时候进行的,类加载的时候,不能确定T具体是什么类型!而且Java泛型的作用,是在编译期间保证实例行为的一致性!所以对static成员设置泛型类型没有意义!!!
//Cannot make a static reference to the non-static type T, 编译报错
public static T tt;
public static void fun(T t)
{
}
3. 运行态时使用的泛型类不能显示具体的泛型类型:
- 泛型类实例的instanceof的使用:
原因:instanceof的执行是在运行期间,所以t1的反射类型已经在编译期擦除了,不存在具体的反射类型,所以下面第二个示例会报错!
if (t1 instanceof GenericTypeT1)
{
}
//编译报错
if (t1 instanceof GenericTypeT1<Integer>)
{
}
- 泛型类的反射class实例的获取:
原因:跟上面instanceof原因一样,class实例的获取也是在运行期间,泛型类的class实例在运行时期已经不存在具体的反射类型了,所以对应的引用声明也必须是擦除泛型类型的声明!
Class<GenericTypeT1> c1 = GenericTypeT1.class;
/*编译报错*/
Class<GenericTypeT1<Integer>> c2 = GenericTypeT1.class;
Class<? extends GenericTypeT1> c3 = t1.getClass();
/*编译报错*/
Class<? extends GenericTypeT1<Integer>> c4 = t1.getClass();
4. 编译期间泛型类相互赋值,泛型类型必须一致,不存在父子类继承关系:
GenericTypeT1<String> t3 = new GenericTypeT1<>();
GenericTypeT1<Object> t4 = new GenericTypeT1<>();
/*编译报错 GenericTypeT1<Object>与GenericTypeT1<String>,不存在继承关系; t3/t4本质是一个类型,不是父子类*/
t4 = t3;
5. 泛型类和泛型方法中的泛型类型,都不能为基础类型(int、char、boolean、byte等),因为编译期间要进行泛型类型擦除,将泛型类型转换为原始类型,基本类型不属于任何类型的子类,转换会报错!解决这个问题可以使用包装类来代替原始类型!
/*编译报错!!!*/
GenericTypeT1<int> t1 = new GenericTypeT1<>();
- 泛型类型的限制(只可以设置上限,不可以设置下限):
在定义泛型类的时候,都需要设定一个泛型类型T,这个T可以设置最多一个父类和多个接口的上限(使用&符号进行分割);从而来保证,在实例化这个泛型类的时候,泛型类型必须满足这个要求(泛型类型必须满足是这个父类本身或者其子类,并实现这多个接口),否则编译报错!
/*给泛型类型设定一个父类*/
class GenericTypeT1<T extends Number>
{
}
/*给泛型类型设定一个父类,并实现一个接口*/
class GenericTypeT1<T extends Number & java.io.Serializable>
{
}
上面两个例子最终的输出如下:可见在编译期间,已经将泛型类的泛型类型限定为Number类的本身及子类;默认不指定父类的泛型类型会自动转为Oject类!需要注意的是,如果限定了泛型类型,在泛型擦除的时候,只能擦除到固定父类,而不能擦除到Object类!详细说明,见后面泛型擦除
2. 泛型类型通配符:
通配符的作用是用来,接收各个类型的泛型类,不进行泛型类型的编译校验!
GenericTypeT1<Integer> t1 = new GenericTypeT1<>();
GenericTypeT1<Byte> t2 = new GenericTypeT1<Byte>();
GenericTypeT1<?> tt1 = t1;
GenericTypeT1<?> tt2 = t2;
tt1.add(null);
/*编译报错*/
tt1.add(new Integer(100));
约束:但是泛型类型的通配符有一个重要的约束,就是对于所有不明确类型的入参方法调用都会编译报错,因为编译通过可能会造成类型不一致的问题,除非入参为null,因为null是所有引用类型的实例!所以上面实例中最后一行操作会编译报错!
- 通配符的上限:
使用extends关键词来限定通配符范围为Integer本身及其子类;如果通配符在泛型类中使用时,也可以用泛型类型进行通配符限制,例如:GenericTypeT1<? extends T>
GenericTypeT1<Integer> t1 = new GenericTypeT1<>();
GenericTypeT1<Byte> t2 = new GenericTypeT1<Byte>();
GenericTypeT1<? extends Integer> tt1 = t1;
/*编译报错*/
GenericTypeT1<? extends Integer> tt2 = t2;
- 通配符的下限:
使用super关键词来限定通配符范围为Integer本身及其父类;如果通配符在泛型类中使用时,也可以用泛型类型进行通配符限制,例如:GenericTypeT1<? super T>
GenericTypeT1<Integer> t1 = new GenericTypeT1<>();
GenericTypeT1<Byte> t2 = new GenericTypeT1<Byte>();
GenericTypeT1<? super Integer> tt1 = t1;
/*编译报错*/
GenericTypeT1<? super Integer> tt2 = t2;
- 泛型类中使用通配符(泛型方法中原理相同):
当泛型类中使用到其他泛型类的时候,很容易触发到泛型类型和通配符配合的场景
class GenericTypeT1<T extends Number>
{
/*定义一个泛型类型的引用*/
public T t;
/*入参类型为一个泛型类型的引用*/
public void add(T t)
{
this.t = t;
}
/*返回类型为一个泛型类型的引用*/
public T get()
{
return this.t;
}
public List<T> addlist(List<T> lt)
{
return lt;
}
public List<?> addlist1(List<?> lt)
{
return lt;
}
public List<?> addlist2(List<? extends T> lt, List<T> lt1)
{
return lt;
}
public List<?> addlist3(List<T> lt1 ,List<? super T> lt)
{
return lt;
}
}
Ps:addlist2和addlist3方法,可以实现相同的逻辑关系,具体选择使用哪种方法,需要考虑T的范围,来确认!
3. 泛型方法的使用:
- 泛型方法的格式:
修饰符 <T,E> 方法返回值类型 方法名(形参列表)
{
}
public class GenericTypeTest
{
public static void main(String args[])
{
String s = "hello";
/*通过编译器自动识别泛型类型来调用方法*/
GenericTypeT1.fun(s,s);
/*显示指定泛型方法的泛型类型来调用方法*/
GenericTypeT1.<String,String>fun(s,s);
}
}
class GenericTypeT1<T>
{
/*T、E为泛型方法的泛型类型*/
public static <T,E> void fun(T t, E e)
{
}
/*T为泛型类的泛型类型、E为泛型方法的泛型类型*/
public <E> void fun1(T t, E e)
{
}
}
ps:
- 泛型方法的调用不需要明确指定泛型类型,编译器会自动根据参数类型进行识别,当然也可以显著的明确指定!
- 注意在Java8中,增强了对泛型方法的泛型类型进行识别的能力(可以通过调用上下文进行自动推断),但是也并不完全准确,所以在可能存在歧义的场景下,还是建议显示指定泛型方法的泛型类型!
- 泛型方法可以在泛型类中定义,也可以在普通类中定义
- 泛型方法可以是static属性的方法
- 泛型方法定义在泛型类中,可以有独立的泛型类型,但是声明的泛型类型不能与泛型类的泛型类型冲突,冲突的时候泛型方法的泛型类型会覆盖泛型类的泛型类型!
- 泛型方法中泛型类型的限制(只能设置上限,不能设置下限):
public <E extends Object, Q extends Object> void fun2(T t, Q q, E e)
{
}
/*可以在泛型方法的多个泛型类型中,相互设置限定,但限定规则不能有歧义*/
public <E extends Q, Q> void fun2(E e, Q q)
{
}
- 泛型方法的作用:
泛型方法的真正引入原因,是用来补充通配符不能将未知泛型类型的泛型类进行修改的不足!所以通过泛型类中的泛型方法,既实现了,接收各个类型的泛型类的功能、又可以确定传入参数的泛型类型(满足通配符的不足)、同时没有丢弃泛型类型在编译期间的类型校验(满足泛型类型擦除的不足)!所以在解决这种通配泛型类型的引用接收时,泛型方法要比通配符和泛型类型擦除更有优势!
- 泛型方法中,当参数为泛型类时,使用通配符还是泛型类型:
1. 需要明确泛型类型的,必须使用泛型类型;例如上面说的,对具体泛型类进行修改的行为!
2. 如果目的就是为了接受各种泛型类型的泛型类参数,且操作仅仅获取泛型类中数据的行为,考虑使用通配符!
例子:
class GenericTypeT1<T>
{
/*使用通配符描述*/
public void fun1(List<?> l, T t)
{
}
/*使用泛型方法描述*/
public <E> void fun2(List<E> l, T t)
{
}
}
- 泛型方法编译后形态:
编译后,泛型方法中的泛型类型,会被统一替换成为,明确的最高类型即原始类型(泛型擦除原则);泛型类的引用与上面阐述原则一致,都会被去掉泛型类型!
class GenericTypeT1<T>
{
/*fun(java.util.List,java.lang.Object,java.lang.Number)*/
public <E extends Number> void fun(List<E> l, T t, E e)
{
}
/*fun(java.util.List,java.lang.Object,java.lang.Object)*/
public <E> void fun(List<E> l, T t, E e)
{
}
}
输出结果:
- 方法重载:
普通方法、参数带泛型类型的方法、参数带泛型类引用的方法、泛型方法;这四种方法,编译后的方法特征(具体编译后形态,上面已经阐述过原则)依然满足方法重载的规则!可见泛型相关的描述,并不是方法重载的判断条件!!!
下面泛型类中,三个方法会被认定为同一个方法!!!
class GenericTypeT1<T>
{
/*下面三个方法会被认定为同一个方法fun(java.util.List,java.lang.Object,java.lang.Number):*/
/*编译报错*/
public void fun(List<?> l, T t, Number n)
{
}
/*编译报错*/
public <E extends Number> void fun(List<E> l, T t, E e)
{
}
/*编译报错*/
public void fun(List l, Object t, Number e)
{
}
}
- 泛型构造器:
public class GenericTypeTest
{
public static void main(String args[])
{
/*<String>为泛型构造器的泛型类型,<Integer>为泛型类的泛型类型;注意当使用泛型构造器的时候,不能在使用菱形原则*/
GenericTypeT1<Integer> t1 = new <String>GenericTypeT1<Integer>("Hello");
/*通过自动推断泛型类型,来调用泛型构造器*/
GenericTypeT1<Integer> tt1 = new GenericTypeT1<Integer>("Hello");
/*<String>为泛型构造器的泛型类型*/
GenericTypeT2 t2 = new <String>GenericTypeT2("Hello");
/*通过自动推断泛型类型,来调用泛型构造器*/
GenericTypeT2 tt2 = new GenericTypeT2("Hello");
}
/*泛型类中定义泛型构造器*/
class GenericTypeT1<T>
{
public <E> GenericTypeT1(E e)
{
}
}
/*普通类中定义泛型构造器*/
class GenericTypeT2
{
public <E> GenericTypeT2(E e)
{
}
}
ps:
- 泛型构造器其实就是一个特殊的泛型方法
- 泛型构造器可以在泛型类中定义,也可以在普通类中定义
- 泛型构造器可以显示泛型类型调用,也可以自动推断泛型类型调用,原则与泛型方法调用一直!
- 如果通过泛型构造器实例化泛型类的时候,不再支持"菱形"原则,必须要明确指出泛型类的泛型类型!
4. 泛型嵌套:
泛型嵌套:泛型类型声明为一个泛型类或者泛型类的子类
代码示例:
public class GenericTypeTest
{
public static void main(String args[])
{
GenericTypeS<String> ss = new GenericTypeS<>();
GenericTypeS<Integer> si = new GenericTypeS<>();
/*由于嵌套泛型类型不一致,编译报错!*/
GenericTypeT1<GenericTypeS<Integer>> t1 = new GenericTypeT1<>();
/*泛型引用中的泛型嵌套*/
GenericTypeT1<GenericTypeS<String>> t2 = new GenericTypeT1<>();
/*泛型引用中使用通配符的泛型嵌套*/
GenericTypeT1<? extends GenericTypeS<String>> t3 = new GenericTypeT1<>();
/*由于嵌套泛型类型不一致,编译报错;约束的泛型类型为GenericTypeF<Integer>的子类,但ss为GenericTypeS<Integer>*/
t2.fun(ss);
/*泛型方法中的泛型嵌套*/
t2.fun(si);
}
}
/*泛型类的泛型类型进行泛型嵌套*/
/*GenericTypeT1的泛型类型T范围为GenericTypeS的子类,并约束GenericTypeS的泛型类型为String*/
class GenericTypeT1<T extends GenericTypeF<String>>
{
/*泛型方法的泛型类型进行泛型嵌套*/
public <E extends GenericTypeF<Integer>> void fun(E e)
{
}
}
/*当定义泛型嵌套的泛型类时,如果嵌套泛型类型为泛型类本身,不能明确嵌套泛型类的泛型类型,因为会出现循环依赖*/
//class GenericTypeT2<T extends GenericTypeT2<String>> 编译报错!!!
class GenericTypeT2<T extends GenericTypeT2>
{
}
/*嵌套泛型父类*/
class GenericTypeF<T>
{
}
/*嵌套泛型子类*/
class GenericTypeS<T> extends GenericTypeF<T>
{
}
5. 泛型的擦除:
- 擦除的原则:其实泛型编译后的结果就是泛型擦除的结果!泛型类和泛型方法中的泛型类型会被擦除为,泛型类可以获得的最高类型(extends后的父类类型,无extends为Object基类)即原始类型;泛型类引用(不管是否有限定或者是否存在通配符),都会被擦除掉具体的泛型类型!
- 泛型的显示擦除:同样也遵循泛型擦除的原则,只是在编译期间,就先丢弃了泛型类型,可以接收泛型类可以获得的最高类型及其子类的所有类型!
public class GenericTypeTest
{
public static void main(String args[])
{
/*t1、t2、t3显示擦除泛型类型,可以接收GenericTypeT1类的最高泛型类型Number及其所有 子类的泛型类型,不再进行泛型的编译检查*/
GenericTypeT1 t1 = new GenericTypeT1();
GenericTypeT1 t2 = new GenericTypeT1<>();
GenericTypeT1 t3 = new GenericTypeT1<Integer>();
GenericTypeT1<Integer> t4 = new GenericTypeT1();
t1.add(Integer.valueOf(100));
t1.add(Byte.valueOf("1"));
/*String不是Number的子类,编译报错*/
t1.add("Hello");
t3.add(Integer.valueOf(100));
t3.add(Byte.valueOf("1"));
/*String不是Number的子类,编译报错*/
t3.add("Hello");
/*未进行泛型类型的显示擦除*/
t4.add(Integer.valueOf(100));
/*编译报错*/
t4.add(Byte.valueOf("1"));
/*编译报错*/
t4.add("Hello");
}
}
class GenericTypeT1<T extends Number>
{
}
- 与通配符的区别:
- 使用通配符与泛型擦除一样都可以接收,泛型类原始类型及其子类;但是通配符没有丢弃泛型类的泛型类型,如果在操作的时候造成泛型类型不一样的行为,编译依然会报错!但是经过泛型擦除的泛型类,已经不再进行任何泛型编译检查!
- 使用通配符不能对泛型类进行修改,泛型擦除可以进行修改!
6. 泛型类的继承:
代码示例:
/*GenericTypeT1继承自GenericTypeF泛型类,GenericTypeT1的泛型类型为T,
* GenericTypeF进行了泛型擦除,所以泛型类型为GenericTypeF的原始类型,
* 子类中继承自父类的泛型相关操作,泛型类型都是GenericTypeF的原始类型*/
class GenericTypeT1<T> extends GenericTypeF
{
}
/*GenericTypeT1继承自GenericTypeF泛型类,GenericTypeT1和GenericTypeF的泛型类型都为T,
* 子类中继承自父类的泛型相关操作,泛型类型都是T,注意子类和父类的泛型类型T的限定范围不能存在冲突!!!*/
class GenericTypeT2<T> extends GenericTypeF<T>
{
}
/*GenericTypeT1继承自GenericTypeF泛型类,GenericTypeT1的泛型类型为T,GenericTypeF的泛型类型为String
* 子类中继承自父类的泛型相关操作,泛型类型都是String*/
class GenericTypeT3<T> extends GenericTypeF<String>
{
}
class GenericTypeF<T>
{
}
/***********************带有泛型嵌套的泛型类的继承***********************/
class GenericTypeT1<T> extends GenericTypeF
{
}
class GenericTypeT2<T extends List<String>> extends GenericTypeF<T>
{
}
class GenericTypeT3<T> extends GenericTypeF<List<String>>
{
}
class GenericTypeF<T extends List<String>>
{
}
7. 泛型与数组:
- 泛型与数组的几点类比:
- 具有父子类关系的泛型类型的相同泛型类的不能相互赋值,Type<T父> = Type<T子>不可以,虽然他们的原始类型相同,但是这种不同泛型类型的赋值行为,编译会报错!
- 具有相同的泛型类型的父子泛型类可以相互赋值,父<T> = 子<T>可以,他们的原始类型相同,泛型类型也相同,所以编译可以通过!
- 父类数组引用和子类数组引用虽然类型不同,但是Java语法默认他们存在父子继承关系,可以相互赋值,也可以通过instanceof关键词判断,但是具体原因并不清楚!
数组相关问题见:<JavaSE学习笔记(2.Java中的数组)>
public class GenericTypeTest
{
public static void main(String args[])
{
Father[] f = new Father[10];
Sun[] s = new Sun[10];
/*通过反射可见两者类型并不相同*/
System.out.println(f.getClass());
System.out.println(s.getClass());
System.out.println(Father[].class);
System.out.println(Sun[].class);
/*instanceof可以检测s为Father[]的子类实例*/
if (s instanceof Father[])
{
System.out.println("True");
}
/*具备父子类继承关系,可以相互赋值*/
f = s;
s = (Sun[]) f;
}
}
class Father
{
}
class Sun extends Father
{
}
输出结果:
- 泛型类的数组(Java语法中不支持泛型数组,除非使用通配符,特定泛型类型会导致编译告警):
泛型类的数组 : 数组中每个元素都是一个泛型类的引用
public class GenericTypeTest
{
public static void main(String args[])
{
/*此处GenericTypeT1<String>[] t1 = new GenericTypeT1<String>[10],会导致编译报错
* 目的就是让实例化泛型对象的时候,不明确泛型类型,提供一个告警,描述t1的泛型类型不可靠,可能存在异常*/
GenericTypeT1<String>[] t1 = new GenericTypeT1[10];
/*如果都通过t1引用来访问,是会进行泛型类型检测的,不会造成异常*/
GenericTypeT1<String> tt1 = new GenericTypeT1<>();
GenericTypeT1<Integer> tt2 = new GenericTypeT1<>();
t1[0] = tt1;
/*泛型类型检测,编译报错!!*/
t1[1] = tt2;
/*如果将t1子类数组引用赋值给Object父类数组引用的时候,会造成子类泛型类型和子类自身类型的擦除*/
Object[] o1 = t1;
/*t1的泛型类型被擦除*/
o1[2] = tt2;
/*t1的自身类型都被擦除了,String跟GenericTypeT1完全不是一个类型,但是编译没有任何报错或者告警*/
o1[2] = "Hello";
}
}
class GenericTypeT1<T>
{
}
Ps:使用数组的时候,要十分小心,因为父类数组引用和子类数组引用存在继承关系;当将子类数组引用赋值给父类数组引用的时候,不仅仅会将子类数组的泛型类型擦除,就连子类的自身类型都擦除了,这个很容易造成类型不一样的异常,是Java语法的一个小缺陷;如果仅仅是父类数组引用中的元素跟子类数组引用中的元素存在继承关系,就不会出现这种异常!
- 泛型类型为数组的泛型类:
/*GenericTypeT1的泛型类型为一个String的数组类型*/
GenericTypeT1<String[]> t1 = new GenericTypeT1<>();