当前位置: 首页 > article >正文

JAVA学习笔记第二阶段开始 Day11 五种机制---机制1:泛型机制

JAVA基础进阶版链接

https://pdai.tech/md/java/basic/java-basic-x-generic.html

五种机制

泛型机制

用处,提高类型安全性和代码重用

  • 泛型在编写代码中使用【类型占位符】,而不是具体的类型,泛型是通过“类型擦除”来实现的
  • 类型安全性,在运行过程中设置错误检查。
  • 代码重用,编写通用的代码,在多个类型中使用。
  • 方法的通用性,允许在同一方法中更换使用不同的类型。

三种泛型

泛型类、接口、方法三者的关系比喻:对象:汽车,类:汽车工厂,接口:汽车的操作规范和设计图,方法:汽车的具体操作。方法是类或接口的一部分。类实现接口,接口规定哪些方法的实现,接口定义方法,但没有实现。类里面有方法,提供具体的方法实现。泛型类可以继承泛型接口。

  • 泛型方法:方法的签名中包含一个或多个类型参数,允许方法在调用时使用不同的类型。

    • public <U> void printInfo(U data)
    • public <T> T getObject(Class<T> c){
      T t =c.newInstance();}//创建对象
      //调用指定泛型类
      Generic generic=new Generic();//类名实例化对象
      Object obj=generic.getObject(Class.forName("com.cnblogs.test.User"));//对象调用方法
      //此时obj是User类的实例
      
      
  • 泛型类:该类在创建实例时接受不同类型的参数。

    • class GenericClass <T>
  • 泛型接口,只定义了方法的接口和常量

    • interface Info <T>{ // 在接口上定义泛型
      public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
      }
  • 泛型类和泛型方法

    • 
      class GenericClass<T> {
          private T value;
          //构造方法,创建类的实例并初始化对象的属性,无返回类型
          public GenericClass(T value) {
              this.value = value;
          }
          //返回值是类的泛型参数T
          //Getter方法,获取对象属性值的方法,公共的非静态方法,不接受任何参数。
          //public 返回值类型 getXxx()
          public T getValue() {
              return value;
          }
          //方法没有返回值
          public void setValue(T value) {
              this.value = value;
          }
      
          //泛型方法,方法级别的泛型参数
          public <U> void printInfo(U data) {
              System.out.println("Data: " + data);
          }
      }
      
      public class ex1_class {
          public static void main(String[] args) {
              // 1. 创建 GenericClass 的实例,并指定泛型参数为 String
              //右边的<>菱形语法,类型参数与左侧声明的泛型参数类型相同,在实例化时参数为("Hello, Generics!")
              GenericClass<String> genericString = new GenericClass<>("Hello, Generics!");
      
              // 2. 使用 getValue 方法获取属性值
              System.out.println("Value: " + genericString.getValue()); // 输出: Value: Hello, Generics!
      
              // 3. 使用 setValue 方法修改属性值
              genericString.setValue("Updated Value");
              System.out.println("Updated Value: " + genericString.getValue()); // 输出: Updated Value: Updated Value
      
              // 4. 调用泛型方法 printInfo,传入不同类型的数据
              genericString.printInfo(123);       // 输出: Data: 123 (Integer 类型)
              genericString.printInfo(45.67);    // 输出: Data: 45.67 (Double 类型)
              genericString.printInfo("Hello!"); // 输出: Data: Hello! (String 类型)
      
              // 5. 创建另一个 GenericClass 实例,泛型参数为 Integer
              GenericClass<Integer> genericInteger = new GenericClass<>(100);
      
              // 6. 获取和修改 Integer 类型的属性值
              System.out.println("Value: " + genericInteger.getValue()); // 输出: Value: 100
              genericInteger.setValue(200);
              System.out.println("Updated Value: " + genericInteger.getValue()); // 输出: Updated Value: 200
      
              // 7. 使用泛型方法 printInfo,传入不同类型的数据
              genericInteger.printInfo("Generics are powerful!"); // 输出: Data: Generics are powerful!
          }
      }
      
      
  • 泛型接口

    • 
      、interface Info<T>{        // 在接口上定义泛型  
          public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
      }  
      class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
          private T var ;             // 定义属性  
          public InfoImpl(T var){     // 通过构造方法设置属性内容  
              this.setVar(var) ;  
          }  
          public void setVar(T var){  
              this.var = var ;  
          }  
          public T getVar(){  
              return this.var ;  
          }  
      } 
      public class GenericsDemo24{  
          public static void main(String arsg[]){  
              Info<String> i = null;        // 声明接口对象  
              i = new InfoImpl<String>("汤姆") ;  // 通过子类实例化对象  
              System.out.println("内容:" + i.getVar()) ;  
          }  
      }  
      

组成

  • 泛型类 class ClassName <T> {}
    • 泛型类型参数 <T>
    • 类体 {…} 成员变量、方法等
  • 泛型方法
    • public <U> void methodName(U data){}
      • (U data)方法参数
      • {…}方法体
  • 泛型接口
    • interface InterfaceName <T> {}}
      • {…}接口体

泛型的上下限

  • 上下限
    • extends表示类型的上界,代表参数的类型可以是此类型或者此类型的【子类】
    • super表示类型的下界,代表参数的类型可以是此类型或者此类型的【父类】super父
  • 泛型不具有协变性(Covariance),泛型类型参数之间没有继承关系
  • List <? extend A> 编译擦除到类型A
  • class A{}
    class B extends A {} //B是A的子类
    如下funD方法会报错
    public static void funC(List<A> listA) {
        // ...  
    }
    public static void funD(List<B> listB) {
        funC(listB); // Unresolved compilation problem: The method doPrint(List<A>) in the type test is not applicable for the arguments (List<B>)
        // ...   
    }
    
  • 修改方法
    public static void funC(List<? extend A> listA) {
        // ...  
    }
    public static void funD(List<B> listB) {
        funC(listB); // Unresolved compilation problem: The method doPrint(List<A>) in the type test is not applicable for the arguments (List<B>)
        // ...   
    }
    

泛型擦除

  • 在编译的时候,删除<>及其包围的部分
  • 向后兼容
  • 根据参数类型的上下界,推断并替换类型为原生态类型。
    • 当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或下界。
    • 没有限制的,替换为Object类型
  • 请添加图片描述

如何证明泛型擦除

  • 判断类型是否相等?
    • publicclassTest {
          publicstaticvoidmain(String[] args) {
              ArrayList < String > list1 = newArrayList < String > ();
              list1.add("abc");
              ArrayList < Integer > list2 = newArrayList < Integer > ();
              list2.add(123);
              System.out.println(list1.getClass() == list2.getClass()); // true,则泛型都被擦除了
          }
      }
      
  • 通过反射添加元素
    • 在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。
    • public class Test {
      
          public static void main(String[] args) throws Exception {
              ArrayList < Integer > list = new ArrayList < Integer > ();
              list.add(1); //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
              list.getClass().getMethod("add", Object.class).invoke(list, "asd");
              for (int i = 0; i < list.size(); i++) {
                  System.out.println(list.get(i));
              }
          }
      
      }
      

如何理解类型擦除后保留的原始类型?

原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。

如何理解泛型的编译期检查?(类型检查)

  • Java编译器是通过先检查代码中泛型的类型和实际使用的类型是否一致→类型擦除→编译。
  • 类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
    • 举个例子:
      class Box<T> {
          private T value;
      
          public void set(T value) {
              this.value = value;
          }
      
          public T get() {
              return value;
          }
      }
      Box<String> stringBox = new Box<>();
      stringBox.set("Hello");  // 正确
      String str = stringBox.get();  // 正确
      
      Box<Integer> intBox = new Box<>();
      intBox.set(123);  // 正确
      Integer num = intBox.get();  // 正确
      
      // 错误用法:类型不匹配,编译时会报错
      stringBox.set(123);  // 错误:不能把 Integer 类型放到 Box<String> 中
      
      

如何理解泛型的多态?泛型的桥接方法

  • 桥接方法
    • 在父类、子类的继承场景中出现的。父类是泛型类、且在该类中存在泛型方法。子类继承父类,并实现泛型方法。如果在子类实现中不包含父类经过类型擦除后生成的原始类型方法,则编译器会自动将该原始类型方法添加到子类中。这个被添加的原始类型方法叫做桥接方法。
    • 假设你有一个泛型类或接口,子类要重写父类的泛型方法。但是,由于泛型的擦除机制,子类重写的泛型方法和父类的泛型方法签名就会发生不一致,这时 Java 编译器就会创建一个“桥接方法”(Bridge Method)来解决这个问题,确保方法签名一致
    • 问题:在编译时,泛型类型会被擦除,变成他们的原始类型或边界类型。泛型的这种设计保证了向后兼容,但是有一些继承和重写方法会出现问题。
    • 例子:https://blog.csdn.net/claram/article/details/105383998
    • // 泛型接口
      interface GenericInterface<T> {
          T getValue();
      }
      
      // 实现类
      class MyClass implements GenericInterface<String> {
          @Override
          public String getValue() {
              return "Hello, World!";
          }
      }
      
      
    • 父类getValue是泛型方法,签名是T getValue()。子类重写方法,签名变成了String getValue(),因为在Myclass中T被替换成了String。
    • 由于泛型的擦除机制,父类的getValue在编译时变成了Object getValue(),这样产生问题,子类的getValue方法签名和父类不一致了。因此需要桥接方法,在子类中生成一个额外的签名object。
  • 好处
    • 父类和子类的方法签名一致,满足 Java 的方法重写原则。
    • 在运行时,真正的调用会指向子类的实现,保证功能的正确性。

如何理解泛型类中的静态方法和静态变量

  • 静态方法和静态变量不可以使用泛型类参数。
  • 因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。

如何理解异常中使用泛型?

  • Java 不允许在异常类型中使用泛型。
    • 不使用泛型异常类
    • 捕捉父类异常
  • 在异常处理过程中,Java 的泛型类型已经被擦除。所以,Java 编译器在 运行时 无法知道泛型类型的具体信息。
  • 异常的类型系统:Java 中的异常是 类层次结构 (Inheritance Hierarchy)的一部分。所有异常类都继承自 Throwable 类,其中常用的异常类有 ExceptionRuntimeException。当捕捉异常时,Java 会根据异常类型进行匹配。
  • 在编译时,BoxException<T> 被擦除成 BoxException,而 Box<T> 也被擦除成 Box。因此,当我们在异常处理中捕获这个异常时,编译器无法准确地知道异常的具体类型,无法进行 精确的类型检查

如何获取泛型的参数类型?

在 Java 中,由于泛型 类型擦除 的机制,不能直接获取泛型类型的真实类型。但是可以通过 反射匿名类 的方式来间接获取泛型类型。具体方法是:

  • 使用 ParameterizedType 接口获取 方法 的泛型类型信息。
  • 使用匿名类来保留泛型类型,允许通过反射访问泛型的类型参数。

【!等学完反射机制再回来看一遍】


http://www.kler.cn/a/456506.html

相关文章:

  • 揭秘文件上传漏洞之操作原理(Thoughts on File Upload Vulnerabilities)
  • [创业之路-229]:《华为闭环战略管理》-5-平衡记分卡与战略地图
  • vue 嵌套el-dialo,当内层的弹窗弹出时,整个页面被遮罩
  • 【漫话机器学习系列】028.CP
  • plsql :用户system通过sysdba连接数据库--报错ora-01031
  • RocketMQ(二)RocketMQ实战
  • Java和Go语言的优劣势对比
  • DVWA靶场搭建及错误解决教程
  • SQL 基础教程
  • 音视频学习(二十八):websocket-flv
  • 攻防世界web第二题unseping
  • leetcode刷题——动态规划(2)
  • Vue使用Tinymce 编辑器
  • 《机器学习》数据预处理简介
  • 2024第一届Solar杯应急响应挑战赛wp
  • Blazor开发中注册功能设计研究
  • 阿里云 安全组设置 仍失效问题 解决方案
  • 欢迪迈手机商城设计与实现基于(代码+数据库+LW)
  • CCF-GESP 等级考试 2023年12月认证C++三级真题解析
  • UAVCAN/DroneCAN链路开发
  • 单例模式懒汉式、饿汉式(线程安全)
  • Live555、FFmpeg、GStreamer介绍
  • acitvemq AMQP:因为消息映射策略配置导致的MQTT接收JMS消息乱码问题 x-opt-jms-dest x-opt-jms-msg-type
  • 机器学习基本概念,基本步骤,分类,简单理解,线性模型
  • 【期末复习】JavaEE(下)
  • Arduino中借助LU-ASR01实现语音识别