Java 泛型

Posted virgosnail

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 泛型相关的知识,希望对你有一定的参考价值。

1. 概述

  泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。

  那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

  泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。

  也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

2. 目的

  类型安全性:一旦使用类型参数后,在该方法或框架中就不存在其他的数据类型,同时也避免了类型转化的需求;

3. 泛型擦除

  泛型是提供给javac编译器看的,编译器阻止源程序的非法输入,限定集合中的输入对象的类型

  编译器在编译期间去掉带参数类型说明的集合,字节码文件中不存在泛型的类型信息. 跳过编译期后皆可往某个泛型集合加入其它类型的对象

3.1 .java文件

package com.pinnet.test;

import java.util.ArrayList;

public class Generic<T> {
    
    T name;

    public static void say() {
        ArrayList<Integer> ric = new ArrayList<>();
        ric.add(12);
    }

    public T hi() {
        return name;
    }
}

3.2 .class文件反汇编(看不太懂啊)

D:\\eclipse\\workspace\\NorthAPI\\bin\\com\\pinnet\\test>javap -verbose Generic.class
Classfile /D:/eclipse/workspace/NorthAPI/bin/com/pinnet/test/Generic.class
  Last modified 2018-8-15; size 920 bytes
  MD5 checksum 0c37f13b82e7c11498e5a1193ad70010
  Compiled from "Generic.java"
public class com.pinnet.test.Generic<T extends java.lang.Object> extends java.lang.Object
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/pinnet/test/Generic
   #2 = Utf8               com/pinnet/test/Generic
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               name
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               Signature
   #8 = Utf8               TT;
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Methodref          #3.#13         // java/lang/Object."<init>":()V
  #13 = NameAndType        #9:#10         // "<init>":()V
  #14 = Utf8               LineNumberTable
  #15 = Utf8               LocalVariableTable
  #16 = Utf8               this
  #17 = Utf8               Lcom/pinnet/test/Generic;
  #18 = Utf8               LocalVariableTypeTable
  #19 = Utf8               Lcom/pinnet/test/Generic<TT;>;
  #20 = Utf8               say
  #21 = Class              #22            // java/util/ArrayList
  #22 = Utf8               java/util/ArrayList
  #23 = Methodref          #21.#13        // java/util/ArrayList."<init>":()V
  #24 = Methodref          #25.#27        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
  #25 = Class              #26            // java/lang/Integer
  #26 = Utf8               java/lang/Integer
  #27 = NameAndType        #28:#29        // valueOf:(I)Ljava/lang/Integer;
  #28 = Utf8               valueOf
  #29 = Utf8               (I)Ljava/lang/Integer;
  #30 = Methodref          #21.#31        // java/util/ArrayList.add:(Ljava/lang/Object;)Z
  #31 = NameAndType        #32:#33        // add:(Ljava/lang/Object;)Z
  #32 = Utf8               add
  #33 = Utf8               (Ljava/lang/Object;)Z
  #34 = Utf8               ric
  #35 = Utf8               Ljava/util/ArrayList;
  #36 = Utf8               Ljava/util/ArrayList<Ljava/lang/Integer;>;
  #37 = Utf8               hi
  #38 = Utf8               ()Ljava/lang/Object;
  #39 = Utf8               ()TT;
  #40 = Fieldref           #1.#41         // com/pinnet/test/Generic.name:Ljava/lang/Object;
  #41 = NameAndType        #5:#6          // name:Ljava/lang/Object;
  #42 = Utf8               SourceFile
  #43 = Utf8               Generic.java
  #44 = Utf8               <T:Ljava/lang/Object;>Ljava/lang/Object;
{
  T name;
    descriptor: Ljava/lang/Object;
    flags:
    Signature: #8                           // TT;

  public com.pinnet.test.Generic();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #12                 // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/pinnet/test/Generic;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/pinnet/test/Generic<TT;>;

  public static void say();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=0
         0: new           #21                 // class java/util/ArrayList
         3: dup
         4: invokespecial #23                 // Method java/util/ArrayList."<init>":()V
         7: astore_0
         8: aload_0
         9: bipush        12
        11: invokestatic  #24                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        14: invokevirtual #30                 // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
        17: pop
        18: return
      LineNumberTable:
        line 10: 0
        line 11: 8
        line 12: 18
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            8      11     0   ric   Ljava/util/ArrayList;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            8      11     0   ric   Ljava/util/ArrayList<Ljava/lang/Integer;>;

  public T hi();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC
    Signature: #39                          // ()TT;
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #40                 // Field name:Ljava/lang/Object;划重点
         4: areturn
      LineNumberTable:
        line 15: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/pinnet/test/Generic;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/pinnet/test/Generic<TT;>;
}
SourceFile: "Generic.java"
Signature: #44                          // <T:Ljava/lang/Object;>Ljava/lang/Object;

D:\\eclipse\\workspace\\NorthAPI\\bin\\com\\pinnet\\test>

  从反编译工具中可以看出,至少反编译工具是可以从class文件中找到你之前使用的泛型的,证明文件中是存在标记来记录这个泛型的,然后从javap中我们可以看出,虽然泛型不在代码中了,但是他还是记录在了注释中,这样就可以解释通为什么有的反编译工具可以反编译出泛型了。

  泛型在编译期会被擦除的结论是没有问题的,在jvm中不存在泛型的概念。但是反编译工具通过注释中的记录找到了之前使用过的泛型类型,并在反编译时将其添加回来,所以我们所看到的反编译的文件中泛型存在,好像与泛型擦除这一概念冲突了。然而事实证明并没有,只是反编译工具太智能了而已。

  上述结论节选自 : 关于java泛型擦除反编译后泛型会出现问题

4. 对于参数化泛型类型,其 getClass() 方法的返回值和原始类型的完全一样

        ArrayList<String> a = new ArrayList<>();
        ArrayList<Integer> b = new ArrayList<>();
        
        System.out.println(a.getClass()==b.getClass());
        System.out.println(a.getClass()==ArrayList.class);
        System.out.println(b.getClass()==ArrayList.class);

  执行结果

5. 通配符

5.1 限定通配符?的上边界

ArrayList<? extends Integer> list;

  集合中只能存放 Integer 及其子类实例

5.2 限定通配符?的下边界

ArrayList<? super Integer> list;

 

   集合中只能存放 Integer 及其父类实例

6. 兼容性

  原始类型与参数化类型互相引用

  类型检查就是编译时完成的。new ArrayList()只是在内存中开辟一个存储空间,可以存储任何的类型对象。

  而真正涉及类型检查的是它的引用,因为我们是使用它引用 list1 来调用它的方法,比如说调用add()方法。所以 list1 引用能完成泛型类型的检查。

  为了保证代码的兼容性,下面的代码编译器(javac)允许,类型安全有你自己保证

        // 原始类型引用指向参数化类型对象  可以添加任意类型的对象
     // 静态类型为Object 实际类型为String List list = new ArrayList<String>(); // 编译通过 list.add(123); // 编译通过 list.add("123"); // 结果为 true 说明将元素自动转为 Object 类型 System.out.println(list.get(0) instanceof Object); // 参数化类型引用指向原始类型对象  只能添加指定类型对象
     // 静态类型为String 实际类型为Object List <String>list2 = new ArrayList<>(); // 编译不通过 // list2.add(123); // 编译通过 list2.add("123"); // 结果为 true 说明将元素自动转为 String 类型 System.out.println(list2.get(0) instanceof String);

 

7. 泛型接口

  • 不能用于全局常量
  • 只能用于公共的抽象方法
  • 实现类实现泛型接口时必须指定泛型方法
interface Human<T> {
    
    void create(T t);

}

class Man implements Human<Integer> {

    @Override
    public void create(Integer t) {

    }

}

 

 

8. 泛型类

  • 声明时使用泛型
  • 运行时确定类型
  • 泛型声明时不能使用在静态变量和静态方法上(编译期间就要确定)
class Info<T>{
    T obj;

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
    
    public static void main(String[] args) {
        //   静态类型                 实际类型   
        Info<String> info = new Info<String>();
        info.getObj();
    }
}

 

9. 泛型方法

  • 在访问权限修饰符和返回值中间加 <T> 声明方法为泛型方法
  • 运行时确定参数类型
    public <T> void doSomething(T obj) {
        System.out.println(obj);
    }

 

以上是关于Java 泛型的主要内容,如果未能解决你的问题,请参考以下文章

201621123062《java程序设计》第九周作业总结

什么意思 在HashMap之前 ? Java中的泛型[重复]

这个嵌套类构造函数片段可以应用于泛型类吗?

201621123037 《Java程序设计》第9周学习总结

Java——泛型

作业09-集合与泛型