泛型

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、泛型的优点

  1. 代码更加健壮(只要编译期不会有警告,那么运行期就不会出现ClassCastException)
  2. 代码更加简洁,不用强制转换
  3. 代码更灵活,复用性比较强

//以下是不带泛型的代码需要强制转换   


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);



    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值