1、定义
泛型(Generics)是JDK5引入的一种参数化类型特性。它提供了编译时类型安全检测机制。其本质是参数类型,所操控的数据类型被指定为一个参数。泛型不存在于JVM虚拟机。
2、泛型的使用以及常见名称
在类名之后,类型参数部分由尖括号(<>)分隔。它指定的类型参数(也称为类型变量)T1,T2,T3...Tn;
泛型的定义格式如下:
class name<T1,T2,....Tn>{/* ... * /}
泛型接口,单个类型参数
public interface Generics<T> {
}
泛型接口,多个个类型参数
public interface GenericsN<T,S,U,V> {
}
泛型类/接口的继承/实现
public class Generics2<T> implements Generics<T> {
}
public class Generics4 implements Generics<String> {
}
public abstract class ExGenerics<T> {
}
public class Generics4 implements Generics<String> {
}
泛型方法
public class GenricsMethod {
public <T>T test01(T t){
return t;
}
}
最常见的参数名称
- E-Element(Java Collections Framework)
- K-key
- N-Number
- T-type
- V-value
- S,U,V etc.-2end,3rd,4th types
3、泛型的优点
- 代码更加健壮(只要编译期不会有警告,那么运行期就不会出现ClassCastException)
- 代码更加简洁,不用强制转换
- 代码更灵活,复用性比较强
//以下是不带泛型的代码需要强制转换
List list=new ArrayList();
list.add("添加String类型");
String s= (String) list.get(0);
//当使用泛型重写时,代码不需要强制
List<String> list=new ArrayList<>();
list.add("添加String类型");
String s=list.get(0);
//代码更灵活,复用性比较强,如:
public static <K,V> boolean compare(Pair<K,V> p1,Pair<K,V>p2){
return p1.getKey().equals(p2.getKey())&&p1.getValue().equals(p2.getValue());
}
public static void main(String[] args) {
Pair<Integer,String> p1=new OrderPair<>(1,"pear");
Pair<Integer,String> p2=new OrderPair<>(2,"orange");
compare(p1,p2);
}
在此方法中,我们可以传入不同类型进行比较。
4、原始类
原始类是没有任何类型参数的泛型类或接口的名称。例如给定通用Box类:
public class Box<T> {
public void set(T t){/* ... */}
}
要创建Box的参数化类型,为形式类型参数T提供一个实际的类型参数:
Box<Integer> intBox=new Box<>();
如果省略实际的类型参数,则创建Box的原始类型
Box rawBox=new Box();
因此,Box是通用类型Box的原始类型。
为了向后兼容,允许将参数化类型分配给其原始类型。
Box<String> stringBox=new Box<>();
Box rawBox=stringBox;
但是,如果将原始类型分配给参数化类型会收到警告
Box box=new Box();//box is a raw type of Box<T>
Box<Integer> intBox=box;//warning:unchecked conversion
如果使用原始类型来调用相应的泛型中定义的泛型方法,也会收到警告。
Box<String> stringBox=new Box<>();
Box rawBox=stringBox;
rawBox.set(8);//warning:unchecked invocation to set(T)
该警告表明原始类型会绕过通用类型检查,从而将不安全代码的捕获推迟到运行时。因此,应避免使用原始类型。
5、限定类型参数
要声明一个限定类型参数,则需列出类型参数名称,然后列出extends关键字,然后列出其上限,(在本例中为Number)
public class Box<T> {
private T t;
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public <U extends Number> void inspect(U u){
System.out.println("T: "+t.getClass().getName());
System.out.println("U: "+u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox=new Box<>();
integerBox.setT(new Integer(10));
integerBox.inspect(new Double(3));
// integerBox.inspect("3"); //限定类型是Number,Stringbu不符合
}
}
多重限定
<T extends B1&B2&B3>
具有多个限定的类型变量是范围中列出的所有类型的子类。如果范围之一是类,则必须先使用它。
Class A{}
interface B{}
interface C{}
Class D <T extends A&B&C>{}
如过未先指定A,则会出现编译时错误。
6、泛型类型的本质
public class RawTest {
public static void main(String[] args) {
List<String> stringList=new ArrayList<>();
List<Integer> intList=new ArrayList<>();
System.out.println("stringList的类型:"+stringList.getClass());
System.out.println("intList的类型:"+intList.getClass());
System.out.println(stringList.getClass()==stringList.getClass());
}
}
运行结果:
stringList的类型:class java.util.ArrayList
intList的类型:class java.util.ArrayList
true
由此可见,通过运行时获取的类的信息是完全一致的,泛型类型被擦除了。擦除后只留下原始类型,这里也就是ArrayList。
使用ASM ByteCode Viewer查看
public interface Plate<T> {
public void set(T t);
public T get();
}
// class version 51.0 (51)
// access flags 0x601
// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: com/lich/generices_demo/generics_raw/Plate<T>
public abstract interface com/lich/generices_demo/generics_raw/Plate {
// compiled from: Plate.java
// access flags 0x401
// signature (TT;)V
// declaration: void set(T)
public abstract set(Ljava/lang/Object;)V
// access flags 0x401
// signature ()TT;
// declaration: T get()
public abstract get()Ljava/lang/Object;
}
public class AIPlate<T extends Comparable<T>> implements Plate<T> {
List<T> items=new ArrayList<>(10);
public AIPlate() {
}
@Override
public void set(T t) {
items.add(t);
Collections.sort(items);
}
@Override
public T get() {
int index =items.size()-1;
if (index>=0){
return items.get(index);
}else {
return null;
}
}
}
// class version 51.0 (51)
// access flags 0x21
// signature <T::Ljava/lang/Comparable<TT;>;>Ljava/lang/Object;Lcom/lich/generices_demo/generics_raw/Plate<TT;>;
// declaration: com/lich/generices_demo/generics_raw/AIPlate<T extends java.lang.Comparable<T>> implements com.lich.generices_demo.generics_raw.Plate<T>
public class com/lich/generices_demo/generics_raw/AIPlate implements com/lich/generices_demo/generics_raw/Plate {
// compiled from: AIPlate.java
// access flags 0x0
// signature Ljava/util/List<TT;>;
// declaration: java.util.List<T>
Ljava/util/List; items
// access flags 0x1
public <init>()V
L0
LINENUMBER 20 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 18 L1
ALOAD 0
NEW java/util/ArrayList
DUP
BIPUSH 10
INVOKESPECIAL java/util/ArrayList.<init> (I)V
PUTFIELD com/lich/generices_demo/generics_raw/AIPlate.items : Ljava/util/List;
L2
LINENUMBER 21 L2
RETURN
L3
LOCALVARIABLE this Lcom/lich/generices_demo/generics_raw/AIPlate; L0 L3 0
// signature Lcom/lich/generices_demo/generics_raw/AIPlate<TT;>;
// declaration: com.lich.generices_demo.generics_raw.AIPlate<T>
MAXSTACK = 4
MAXLOCALS = 1
// access flags 0x1
// signature (TT;)V
// declaration: void set(T)
public set(Ljava/lang/Comparable;)V
L0
LINENUMBER 26 L0
ALOAD 0
GETFIELD com/lich/generices_demo/generics_raw/AIPlate.items : Ljava/util/List;
ALOAD 1
INVOKEINTERFACE java/util/List.add (Ljava/lang/Object;)Z
POP
L1
LINENUMBER 27 L1
ALOAD 0
GETFIELD com/lich/generices_demo/generics_raw/AIPlate.items : Ljava/util/List;
INVOKESTATIC java/util/Collections.sort (Ljava/util/List;)V
L2
LINENUMBER 29 L2
RETURN
L3
LOCALVARIABLE this Lcom/lich/generices_demo/generics_raw/AIPlate; L0 L3 0
// signature Lcom/lich/generices_demo/generics_raw/AIPlate<TT;>;
// declaration: com.lich.generices_demo.generics_raw.AIPlate<T>
LOCALVARIABLE t Ljava/lang/Comparable; L0 L3 1
// signature TT;
// declaration: T
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1
// signature ()TT;
// declaration: T get()
public get()Ljava/lang/Comparable;
L0
LINENUMBER 33 L0
ALOAD 0
GETFIELD com/lich/generices_demo/generics_raw/AIPlate.items : Ljava/util/List;
INVOKEINTERFACE java/util/List.size ()I
ICONST_1
ISUB
ISTORE 1
L1
LINENUMBER 34 L1
ILOAD 1
IFLT L2
L3
LINENUMBER 35 L3
ALOAD 0
GETFIELD com/lich/generices_demo/generics_raw/AIPlate.items : Ljava/util/List;
ILOAD 1
INVOKEINTERFACE java/util/List.get (I)Ljava/lang/Object;
CHECKCAST java/lang/Comparable
ARETURN
L2
LINENUMBER 37 L2
FRAME APPEND [I]
ACONST_NULL
ARETURN
L4
LOCALVARIABLE this Lcom/lich/generices_demo/generics_raw/AIPlate; L0 L4 0
// signature Lcom/lich/generices_demo/generics_raw/AIPlate<TT;>;
// declaration: com.lich.generices_demo.generics_raw.AIPlate<T>
LOCALVARIABLE index I L1 L4 1
MAXSTACK = 2
MAXLOCALS = 2
// access flags 0x1041
public synthetic bridge get()Ljava/lang/Object;
L0
LINENUMBER 16 L0
ALOAD 0
INVOKEVIRTUAL com/lich/generices_demo/generics_raw/AIPlate.get ()Ljava/lang/Comparable;
ARETURN
L1
LOCALVARIABLE this Lcom/lich/generices_demo/generics_raw/AIPlate; L0 L1 0
// signature Lcom/lich/generices_demo/generics_raw/AIPlate<TT;>;
// declaration: com.lich.generices_demo.generics_raw.AIPlate<T>
MAXSTACK = 1
MAXLOCALS = 1
// access flags 0x1041
public synthetic bridge set(Ljava/lang/Object;)V
L0
LINENUMBER 16 L0
ALOAD 0
ALOAD 1
CHECKCAST java/lang/Comparable
INVOKEVIRTUAL com/lich/generices_demo/generics_raw/AIPlate.set (Ljava/lang/Comparable;)V
RETURN
L1
LOCALVARIABLE this Lcom/lich/generices_demo/generics_raw/AIPlate; L0 L1 0
// signature Lcom/lich/generices_demo/generics_raw/AIPlate<TT;>;
// declaration: com.lich.generices_demo.generics_raw.AIPlate<T>
MAXSTACK = 2
MAXLOCALS = 2
}
以上代码以及字节码可看到:
1.类型强转
CHECKCAST java/lang/Comparable
2.桥方法
// access flags 0x1041
public synthetic bridge get()Ljava/lang/Object;
那么,Java泛型的原理是?什么是泛型擦除机制?
Java的泛型是JDK5新引入的特性,为了向下兼容,虚拟机其实是不支持泛型的,所以Java实现的是一种伪泛型机制,也就是说Java在编译期间擦除了所有泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类,在Java运行时根本就不存在的泛型信息。
Java编译器具体是如何擦除泛型类的呢?
1.检查泛型类型,获取目标类型
2.擦除类型变量,并替换为限定类型。如果泛型类型没有限定(<T>),则用Object作为原始类型;如果有限定类型(T extends XClass),则用XClass作为原始类型;如果有多个限定类型(T extends XClass1&XClass2),则使用第一个边界作为原始类
3.必要时插入类型转换以保持类型安全。
4.生成桥方法以在扩展时保持多态。
3.泛型类型变量不能使用基本类型
当类型擦除后,ArrayList的原始类中的变量(T)替换成Object,但是Object类型不能存放int值
4.不能使用instanceof运算符
因为擦除后,ArrayList<String> 只剩下原始类型,泛型信息String不存在了,所以没法使用instanceof.
5.泛型在静态方法和静态类中的问题
因为泛型类中的泛型参数的实例化是在定义泛型类型对象(比如ArrayList<Integer>)的时候指定的,而静态成员是不需要使用对象来调用的,所有对象都没有创建,如何确定这个泛型参数是什么。
6.泛型类型中的方法冲突
因为擦除后,两个equals方法变成一样的了。
7.没法创建泛型实例
因为类型不确定。
8.没有泛型数组
因为数组是协变,擦除后没法满足数组协变的原则。
7、泛型,继承和子类型。
容器里面装的东西直接有继承关系,但容器之间没有继承关系。
苹果 is a 水果,装苹果的盘子 not-is-a 装水果的盘子。
给定两个具体的类型A和B(例如Fruit和Apple),无论A和B是否相关,MyClass<A>与MyClass<B>都没有半毛钱关系,他们的公共父对象是Object。
那么泛型的继承应该是?
public interface Plate<T> {
public void set(T t);
public T get();
}
public class AIPlate<T extends Comparable<T>> implements Plate<T> {
private List<T> items = new ArrayList<T>(10);
public AIPlate(){
}
@Override
public void set(T t) {
items.add(t);
Collections.sort(items);
}
@Override
public T get(){
int index = items.size() -1;
if(index>= 0){
return items.get(index);
}else{
return null;
}
}
// @Override
// public boolean equals(T obj) {
// return super.equals(obj);
// }
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
@Override
public String toString() {
return "Plate{" +
"items=" + items +
'}';
}
}
public class BigPlate<T> extends AIPlate<T> {
private List<T> items = new ArrayList<>(20);
public BigPlate(){
}
@Override
public void set(T t) {
items.add(t);
}
@Override
public T get(){
int index = items.size() -1;
if(index>= 0){
return items.get(index);
}else{
return null;
}
}
@Override
public String toString() {
return "Plate{" +
"items=" + items +
'}';
}
}
public class ColorPlate<K,T> extends BigPlate<T> {
private List<T> items = new ArrayList<>(20);
public ColorPlate(){
}
@Override
public void set(T t) {
items.add(t);
}
@Override
public T get(){
int index = items.size() -1;
if(index>= 0){
return items.get(index);
}else{
return null;
}
}
@Override
public String toString() {
return "Plate{" +
"items=" + items +
'}';
}
}
只要对应的T是一样的,那么都是继承关系。
8、通配符
泛型类(接口)间并无关联。仅仅因为它们和它们类型相关。然而你可以通过使用通配符在泛型类(接口)间建立联系间建立联系
上界通配符
转成<? extends Fruit> 后的后遗症
但是这种不是严格的限制,我们可以用反射
虽然反射能解决这个问题,但是安全性又是个问题。
下界通配符
非限定通配符
<?>
9、泛型与反射
泛型不是说被擦除了吗?那为何还与反射有关?
10、泛型的PECS原则
如果你只需要从集合中获得类型T,使用<? extends T> 通配符,如果你只需要将类型T放到集合中,使用<? super T>通配符。
如果你既要获取又要放置元素,则不使用任何通配符,例如List<Apple>。PESC即Producer exends Consumer super。之所以需要PECS原则,主要是为了提高API的灵活性。
public static void scen07() {
List<Apple> src = new ArrayList<>();
src.add(new Apple(1));
List<Apple> dest = new ArrayList<>(10);
dest.add(new Apple(2));
System.out.println(dest);
copy(dest,src);
System.out.println(dest);
List<Banana> src1 = new ArrayList<>();
src1.add(new Banana(1));
List<Banana> dest1 = new ArrayList<>(10);
dest1.add(new Banana(2));
copy1(dest1,src1);
List<Fruit> dest2 = new ArrayList<>(10);
dest2.add(new Banana());
Test1.<Fruit>copy3(dest2,src1);
}
public static void scen07() {
List<Apple> src = new ArrayList<>();
src.add(new Apple(1));
List<Apple> dest = new ArrayList<>(10);
dest.add(new Apple(2));
System.out.println(dest);
copy(dest,src);
System.out.println(dest);
List<Banana> src1 = new ArrayList<>();
src1.add(new Banana(1));
List<Banana> dest1 = new ArrayList<>(10);
dest1.add(new Banana(2));
copy1(dest1,src1);
List<Fruit> dest2 = new ArrayList<>(10);
dest2.add(new Banana());
Test1.<Fruit>copy3(dest2,src1);
}