《JavaSE学习笔记 - 泛型进阶》学习笔记(一)

本文是本人学习《JavaSE学习笔记 - 泛型进阶》这篇文章的笔记,记录自己学习理解的过程,主要是作为备忘。建议大家直接看《JavaSE学习笔记 - 泛型进阶》这篇文章原文后再来看本文。

原生类型

使用泛型的时候不指定类型实参,创建出来的类型就是原生类型

ArrayList rawList = new ArrayList();

原生类型与Object参数化类型的区别

  • 前者是逃避了类型检查,而后者则明确告知编译器它持有的是任意类型。
  • 前者是所有参数化类型的父类型,而后者并不能作为所有参数化类型的父类型。
List list = new ArrayList<String>();
list = new ArrayList<Integer>();

List<Object> list2 = new ArrayList<Object>();
list2 = new ArrayList<String>(); // 报错,需明确指定为Object类型

 类型擦除

你会发现无法用new T()来创建一个对象

public class GenericHolder<T> {
    // Cannot create a generic array of T
    private T[] array = new T[10];
}

 由以下代码可得出结论使用多种类型实参参数化同一泛型创建出对应的参数化类型时,这些参数化类型实际上都是同一种类型,即是它们共享同一份字节码

public class ErasedTypeEquivalence {
    public static void main(String[] args) {
        Class<?> c1 = new ArrayList<String>().getClass();
        Class<?> c2 = new ArrayList<Integer>().getClass();

        System.out.println(c1 == c2);
        System.out.println(c1.getName());
        System.out.println(c2.getName());
    }
}

// 输出结果
true
java.util.ArrayList
java.util.ArrayList

Java泛型是伪泛型,类型实参的类型信息在编译的过程中会被擦除掉,而这个过程就是类型擦除,在编译后的字节码文件中,所有泛型类型都已经替换为对应的原生类型,并在相应的地方插入了强制转换。所以在运行时,所有泛型的类型信息对于JVM是不可见的。更通俗直白地讲,类型擦除就是将泛型代码转换为非泛型代码的过程。

如下一个普通泛型类

public class Holder<T> {
    private T object;

    public void set(T object) {
        this.object = object;
    }

    public T get() {
        return object;
    }

    public static void main(String[] args) {
        Holder<String> holder = new Holder<String>();
        holder.set("MakwaN");
        String value = holder.get();
        System.out.println("value = " + value);
    }
}

命令行中运行该命令javap -c -v -p Holder>bytecode.txt查看字节码,我是用Android Studio的ASM Bytecode Viewer插件查看的

  • private Ljava/lang/Object; object:变量声明类型擦除成Object
  • public set(Ljava/lang/Object;)V:set方法参数擦除成Object
  • public get()Ljava/lang/Object;:get方法返回值擦除成Object

main()方法字节码

  • INVOKEVIRTUAL com/himmy/java/test/Holder.set (Ljava/lang/Object;)V:调用set()方法,参数类型为Object
  • INVOKEVIRTUAL com/himmy/java/test/Holder.get ()Ljava/lang/Object;:调动get()犯法,返回值类型为Object
  • CHECKCAST java/lang/String:转换成String类型

字节码语法不懂没关系,只要看这些关键的地方大概就会明白,所有的泛型都被擦除成Object,只是在使用的地方转换成具体的类型

准确的说,应该说泛型的类型形参将擦除到它被限制的第一个边界所代表的类型,因为未设置边界,所以默认为Object

// class version 51.0 (51)
// access flags 0x21
// signature <T:Ljava/lang/Object;>Ljava/lang/Object;
// declaration: com/himmy/java/test/Holder<T>
public class com/himmy/java/test/Holder {

  // compiled from: Holder.java

  // access flags 0x2
  // signature TT;
  // declaration: object extends T
  private Ljava/lang/Object; object

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 3 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/himmy/java/test/Holder; L0 L1 0
    // signature Lcom/himmy/java/test/Holder<TT;>;
    // declaration: this extends com.himmy.java.test.Holder<T>
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x1
  // signature (TT;)V
  // declaration: void set(T)
  public set(Ljava/lang/Object;)V
   L0
    LINENUMBER 7 L0
    ALOAD 0
    ALOAD 1
    PUTFIELD com/himmy/java/test/Holder.object : Ljava/lang/Object;
   L1
    LINENUMBER 8 L1
    RETURN
   L2
    LOCALVARIABLE this Lcom/himmy/java/test/Holder; L0 L2 0
    // signature Lcom/himmy/java/test/Holder<TT;>;
    // declaration: this extends com.himmy.java.test.Holder<T>
    LOCALVARIABLE object Ljava/lang/Object; L0 L2 1
    // signature TT;
    // declaration: object extends T
    MAXSTACK = 2
    MAXLOCALS = 2

  // access flags 0x1
  // signature ()TT;
  // declaration: T get()
  public get()Ljava/lang/Object;
   L0
    LINENUMBER 11 L0
    ALOAD 0
    GETFIELD com/himmy/java/test/Holder.object : Ljava/lang/Object;
    ARETURN
   L1
    LOCALVARIABLE this Lcom/himmy/java/test/Holder; L0 L1 0
    // signature Lcom/himmy/java/test/Holder<TT;>;
    // declaration: this extends com.himmy.java.test.Holder<T>
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
   L0
    LINENUMBER 15 L0
    NEW com/himmy/java/test/Holder
    DUP
    INVOKESPECIAL com/himmy/java/test/Holder.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 16 L1
    ALOAD 1
    LDC "MakwaN"
    INVOKEVIRTUAL com/himmy/java/test/Holder.set (Ljava/lang/Object;)V
   L2
    LINENUMBER 17 L2
    ALOAD 1
    INVOKEVIRTUAL com/himmy/java/test/Holder.get ()Ljava/lang/Object;
    CHECKCAST java/lang/String
    ASTORE 2
   L3
    LINENUMBER 18 L3
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    NEW java/lang/StringBuilder
    DUP
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V
    LDC "value = "
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 2
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L4
    LINENUMBER 19 L4
    RETURN
   L5
    LOCALVARIABLE args [Ljava/lang/String; L0 L5 0
    LOCALVARIABLE holder Lcom/himmy/java/test/Holder; L1 L5 1
    // signature Lcom/himmy/java/test/Holder<Ljava/lang/String;>;
    // declaration: holder extends com.himmy.java.test.Holder<java.lang.String>
    LOCALVARIABLE value Ljava/lang/String; L3 L5 2
    MAXSTACK = 3
    MAXLOCALS = 3
}

如果非要在泛型中创建实例呢?可以通过Class<T>实现,而且比较麻烦,还要传入Class类型,且仅适用含无参构造函数的类

public class Holder<T> {
    private T object;
    private Class<T> clz;

    public Holder(Class<T> clz) {
        this.clz = clz;
    }

    public T get() {
        try {
            object = clz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return object;
    }

    public static void main(String[] args) {
        Holder<String> holder = new Holder<>(String.class);
        String s = holder.get();
        System.out.println(s.getClass().getSimpleName());
    }
}

创建泛型数组

下面这种写法是行不通的

T[] array = new T[10];

还是需要Class<T>来实现,配合Array.newInstance()方法

T[] array = (T[]) Array.newInstance(class, length);

instanceof判断的问题

与常见泛型对象或者泛型数组一样,还是要依靠Class

这样是不行哒

obj instanceof T;

这样子是ok哒

clz.isInstance(obj);

边界

设置边界的语法为<T extends TypeA &  TypeB & TypeC>,可以设置多个边界,第一个位置可以为类或者接口,之后的必须为接口,类似于java继承规则的单继承多接口

对于单边界,泛型实参的范围为边界类型或者其子类型

对于多边界,泛型实参分为所有边界类型及所有边界类型的共同子集。比如泛型类所限制的边界是T extends A & B & C,那么类型实参必须同时为类型ABC的子类型才行。

泛型的边界可以限制泛型形参为某个类型的子集,并且在泛型类内可以使用边界类的方法,如下代码可以直接使用length()方法,因为泛型形参的范围是String或者String的子类

public class Holder<T extends String> {
   private T t;
   public int length() {
       return t.length();
   }
}

默认边界,对于<T>未设置边界的情况,其默认边界为Object,相当于<T extends Object>

关于多边界情况下的类型擦除,有如下结论,可结合字节码验证结论

  • 类型形参将擦除到它被限制的第一个边界所代表的类型,即编译后只用第一个边界所代表的类型替换掉类型形参。
  • 当代码涉及到除第一个边界之外其他边界所代表的类型时,编译器会为其对应的边界所代表的类型进行强制类型转换,并检查转型是否成功。

还需要注意的是泛型边界中只能用extends,super关键字是在通配符上下限中使用的,后面会介绍

泛型边界是用在泛型形参上,而泛型统配符?的上下限extends、super是用在泛型实参上,这点需要注意下

当然,泛型边界也可以应用再泛型方法上

static <T extends String & Runnable> void fun(T t) {
    System.out.println(t.length());
}

 

发布了290 篇原创文章 · 获赞 51 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/mqdxiaoxiao/article/details/104691652
今日推荐