美文网首页
Java泛型

Java泛型

作者: crush_d872 | 来源:发表于2022-03-14 22:23 被阅读0次

    泛型的定义与作用

    泛型即参数化类型,而参数概念,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。而参数化类型就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式,然后在调用时传入具体的类型(类型实参)
    泛型的本质是为了参数化类型

    泛型的优点

    1.编译时的强类型检查
    泛型要求在声明时指定数据类型,Java编译器在编译时会对泛型代码做强类型检查,并在代码违反类型安全时发出告警。
    2.避免了类型转换
    未使用泛型:

    List list = new ArrayList();
    list.add("hello");
    String s = (String) list.get(0);
    

    使用泛型:

    List<String> list = new ArrayList<String>();
    list.add("hello");
    String s = list.get(0);  
    

    泛型编程可以实现通用算法
    通过使用泛型,程序员可以实现通用算法,这些算法可以处理不同类型的集合,可以自定义,并且类型安全且易于阅读

    PCES法则:泛型上下边界

    泛型擦除原理,泛型桥方法

    泛型擦除做了如下工作:
    1.把泛型中的所有类型参数替换为Object,如果指定类型边界,则使用类型边界来替换。因此,生成的字节码仅包含普通的类,接口和方法。

    2.擦除出现的类型声明,即去掉<>的内容,比如T get()方法,声明就变成了Object get();List<String>就变成了List。如有必要,插入类型转换以保持类型安全。

    3.生成桥接方法以保留扩展泛型类型中的多态性。类型擦除确保不为参数化类型创建新类;因此,反省不会产生运行时开销

    而对于泛型中的桥方法,举例如下

    public class B extends A<String> {
        @Override
        public void setValue(String value) {
            System.out.println("---B.setValue()---");
        }
    }
    

    通过上述的泛型的擦除机制,实际A类中的方法应该是这样的

    // A 类中的 setValue 方法
    public void setValue(Object value){
        this.value = value;
    }
    

    这个时候就出现了B类中的setValue方法参数与A类中的setValue方法参数不一样。安装Java重写方法的规则,B类中单setValue方法实际上并没有重写父类中的方法,而是重载。
    所以实际上B有两个setValue方法,一个自己的,一个继承来的。
    所以在某些场景,比如反射调用B类中的方法的时候,就有可能会调到父类的setValue方法,但是Java编译器处理了这种情况 如下:

    public final class com/example/myapplication/test/A extends com/example/myapplication/test/B {
    
    
      // access flags 0x1
      public setValue(Ljava/lang/String;)V
        // annotable parameter count: 1 (visible)
        // annotable parameter count: 1 (invisible)
        @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
       L0
        ALOAD 1
        LDC "t"
        INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
       L1
        LINENUMBER 5 L1
        LDC "Not yet implemented"
        ASTORE 2
       L2
        ICONST_0
        ISTORE 3
       L3
        NEW kotlin/NotImplementedError
        DUP
        NEW java/lang/StringBuilder
        DUP
        INVOKESPECIAL java/lang/StringBuilder.<init> ()V
        LDC "An operation is not implemented: "
        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;
        INVOKESPECIAL kotlin/NotImplementedError.<init> (Ljava/lang/String;)V
        CHECKCAST java/lang/Throwable
        ATHROW
       L4
        LOCALVARIABLE this Lcom/example/myapplication/test/A; L0 L4 0
        LOCALVARIABLE t Ljava/lang/String; L0 L4 1
        MAXSTACK = 4
        MAXLOCALS = 4
    
      // access flags 0x1041
      public synthetic bridge setValue(Ljava/lang/Object;)V
       L0
        LINENUMBER 3 L0
        ALOAD 0
        ALOAD 1
        CHECKCAST java/lang/String
        INVOKEVIRTUAL com/example/myapplication/test/A.setValue (Ljava/lang/String;)V
        RETURN
        MAXSTACK = 2
        MAXLOCALS = 2
    
      // access flags 0x1
      public <init>()V
       L0
        LINENUMBER 3 L0
        ALOAD 0
       L1
        LINENUMBER 3 L1
        INVOKESPECIAL com/example/myapplication/test/B.<init> ()V
        RETURN
       L2
        LOCALVARIABLE this Lcom/example/myapplication/test/A; L0 L2 0
        MAXSTACK = 1
        MAXLOCALS = 1
    
      @Lkotlin/Metadata;(mv={1, 4, 3}, bv={1, 0, 3}, k=1, d1={"\u0000\u0018\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\u0010\u000e\n\u0002\u0008\u0002\n\u0002\u0010\u0002\n\u0002\u0008\u0002\u0018\u00002\u0008\u0012\u0004\u0012\u00020\u00020\u0001B\u0005\u00a2\u0006\u0002\u0010\u0003J\u0010\u0010\u0004\u001a\u00020\u00052\u0006\u0010\u0006\u001a\u00020\u0002H\u0016\u00a8\u0006\u0007"}, d2={"Lcom/example/myapplication/test/A;", "Lcom/example/myapplication/test/B;", "", "()V", "setValue", "", "t", "app_debug"})
      // compiled from: A.kt
    }
    

    会发现其中存在两个setValue,其参数值不一样,其中Object类型的就是Java编译器帮我们生成的桥方法

    类型边界

    类型边界可以对泛型的类型参数设置限制条件
    类型边界的语法形式如下:

    <T extends XXX>
    

    示例:

    public class GenericsExtendsDemo01 {
        static <T extends Comparable<T>> T max(T x, T y, T z) {
            T max = x; // 假设x是初始最大值
            if (y.compareTo(max) > 0) {
                max = y; //y 更大
            }
            if (z.compareTo(max) > 0) {
                max = z; // 现在 z 更大
            }
            return max; // 返回最大对象
        }
    
        public static void main(String[] args) {
            System.out.println(max(3, 4, 5));
            System.out.println(max(6.6, 8.8, 7.7));
            System.out.println(max("pear", "apple", "orange"));
        }
    }
    // Output:
    // 5
    // 8.8
    // pear
    

    类型边界可以设置多个,语法形式如下:

    <T extends B1&B2&B3>
    

    类型通配符

    类型通配符一般是使用?代替具体的类型参数。例如List<?>在逻辑上是List<String>等所有List<具体类型实参>的父类

    上界通配符语法形式为<? extends Number>
    下界通配符语法形式为<? super Number>
    上界和下界通配符不能同时使用

    无界通配符语法形式为<?>
    其有两种应用场景:
    1.可以使用Object类中提供的功能来实现的方法
    2.使用不依赖于类型参数的泛型类中的方法

    相关文章

      网友评论

          本文标题:Java泛型

          本文链接:https://www.haomeiwen.com/subject/vybrdrtx.html