zoukankan      html  css  js  c++  java
  • 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:eclipseworkspaceNorthAPIincompinnet	est>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:eclipseworkspaceNorthAPIincompinnet	est>

      从反编译工具中可以看出,至少反编译工具是可以从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);
        }
  • 相关阅读:
    随机性的控制
    856. 括号的分数
    376. 摆动序列(贪心算法)
    XGBoost 安装方法
    1405. 最长快乐字符串(贪心算法)
    1296. 划分数组为连续数字的集合(贪心算法)
    1353. 最多可以参加的会议数目(贪心算法)
    435. 无重叠区间(贪心算法)
    Array-数组-数据结构
    认识Redis和安装
  • 原文地址:https://www.cnblogs.com/virgosnail/p/9489455.html
Copyright © 2011-2022 走看看