Java基础-泛型

慈云数据 2024-03-15 技术支持 66 0

泛型

    • 基本概念
    • 为什么我们需要泛型
    • 泛型类型
      • 泛型类
        • 简单泛型类
        • 多元泛型类
        • 泛型接口
        • 泛型方法
          • 为什么要使用泛型方法呢?
          • 使用方法
          • 泛型的上下限
            • 上限
            • 下限
            • 加点难度的例子
              • 例子一
              • 例子二
              • 例子三
              • 深入理解泛型
                • 什么是泛型擦除后保留的原始类型
                • 泛型类型擦除原则
                • 如何进行擦除的?
                • 怎么证明存在类型擦除呢
                • 如何理解泛型的编译期检查
                • 泛型中参数化类型为什么不考虑继承关系
                • 如何理解泛型的多态和桥接方法
                • 如何理解基本类型不能作为泛型类型
                • 如何理解泛型类型不能实例化
                • 如何理解泛型类中的静态方法和静态变量

                  基本概念

                  Java泛型是JDK5引入的一个新特性,它提供编译时类型安全检测机制.

                  该机制可以在编译时检测到非法的类型.

                  泛型的本质是参数化类型(所操作的数据类型被指定为一个参数)

                  换句话说:由原来具体的类型,转换为类似方法中的变量参数(形参

                   
                  

                  为什么我们需要泛型

                  我们举个例子来看一下:

                  List arrayList = new ArrayList();
                       arrayList.add("string");
                       arrayList.add(1);
                       for(int i = 0; i 
                  

                  这段代码在编译时没问题,但一运行就会报错,报错日志如下:

                  java.lang.Integer cannot be cast to java.lang.String
                  

                  在JDK1.5之前,没有范型的情况下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型的转换,这需要开发者对实际参数类型可以预知才能进行.

                  对于强转类型的转换作物,编译器在编译期可能不会提示错误,在运行时才出现异常,所以这是一个安全隐患.

                  1. 提高代码的利用率

                    范型使得数据的类别可以像参数一样由外部传递进来.它提供了一种扩展能力.更符合面向抽象开发的软件编程宗旨

                  2. 把一些运行时错误提前到了编译时,提高安全性.

                    只有相匹配的数据才可以正常赋值,否则编译起不通过,它属于类型安全检测机制,提高了软件的安全性,防止出现低级失误

                  我们再来一个例子来体验一下泛型的好处:

                  private static int add(int a, int b) {
                      System.out.println(a + "+" + b + "=" + (a + b));
                      return a + b;
                  }
                  private static float add(float a, float b) {
                      System.out.println(a + "+" + b + "=" + (a + b));
                      return a + b;
                  }
                  private static double add(double a, double b) {
                      System.out.println(a + "+" + b + "=" + (a + b));
                      return a + b;
                  }
                  

                  如果没有泛型的话,要实现这种不同类型的加法,每种类型都需要重载一个add方法;

                  通过泛型,我们可以复用为一个方法:

                  private static  double add(T a, T b) {
                      System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
                      return a.doubleValue() + b.doubleValue();
                  }
                  

                  泛型类型

                  泛型有三种存在方式:

                  1. 泛型类
                  2. 泛型接口
                  3. 泛型方法

                  泛型类

                  泛型用于类的定义中,通过泛型可以完成对一组类向外提供相同的接口

                  我们先从一个简单泛型类看起

                  简单泛型类
                  class Point{         // 此处可以随便写标识符号,T是type的简称  
                      private T var ;     // var的类型由T指定,即:由外部指定  
                      public T getVar(){  // 返回值的类型由外部决定  
                          return var ;  
                      }  
                      public void setVar(T var){  // 设置的类型也由外部决定  
                          this.var = var ;  
                      }  
                  }  
                  public class GenericsDemo06{  
                      public static void main(String args[]){  
                          Point p = new Point() ;     // 里面的var类型为String类型  
                          p.setVar("it") ;                            // 设置字符串  
                          System.out.println(p.getVar().length()) ;   // 取得字符串的长度  
                      }  
                  }
                  
                  多元泛型类
                  class Notepad{       // 此处指定了两个泛型类型  
                      private K key ;     // 此变量的类型由外部决定  
                      private V value ;   // 此变量的类型由外部决定  
                      public K getKey(){  
                          return this.key ;  
                      }  
                      public V getValue(){  
                          return this.value ;  
                      }  
                      public void setKey(K key){  
                          this.key = key ;  
                      }  
                      public void setValue(V value){  
                          this.value = value ;  
                      }  
                  } 
                  public class GenericsDemo09{  
                      public static void main(String args[]){  
                          Notepad t = null ;        // 定义两个泛型类型的对象  
                          t = new Notepad() ;       // 里面的key为String,value为Integer  
                          t.setKey("汤姆") ;        // 设置第一个内容  
                          t.setValue(20) ;            // 设置第二个内容  
                          System.out.print("姓名;" + t.getKey()) ;      // 取得信息  
                          System.out.print(",年龄;" + t.getValue()) ;       // 取得信息  
                    
                      }  
                  }
                  

                  泛型接口

                  与泛型类的定义及使用基本相同,允许在接口定义中使用类型参数.

                  实现该接口的类可以为这些类型参数提供具体的类型.

                  增加了代码的灵活性和重用性

                  常被用在各种类的生产器中.

                  举个例子来说:

                  interface Info{        // 在接口上定义泛型  
                      public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
                  }  
                  class InfoImpl implements Info{   // 定义泛型接口的子类  
                      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 i = null;        // 声明接口对象  
                          i = new InfoImpl("汤姆") ;  // 通过子类实例化对象  
                          System.out.println("内容:" + i.getVar()) ;  
                      }  
                  }  
                  

                  泛型方法

                  为什么要使用泛型方法呢?

                  因为泛型类需要在实例化的时候就指明类型,一旦实例化完成,这个对象的泛型类型就固定下来了。如果你想使用另一种类型的泛型对象,你需要重新实例化这个类,并指定新的类型。不太灵活;

                  而泛型方法允许在调用方法的时候才指定泛型的具体类型。这意味着可以在不重新实例化类的情况下,多次调用同一个泛型方法,并每次指定不同的类型。

                  举个例子来说:

                  • 泛型类
                    public class Box {  
                        private T t;  
                          
                        public void set(T t) {  
                            this.t = t;  
                        }  
                          
                        public T get() {  
                            return t;  
                        }  
                    }  
                      
                    // 使用时需要指定具体类型,并实例化  
                    Box integerBox = new Box();  
                    integerBox.set(10);  
                    Integer value1 = integerBox.get(); // 获取时类型已经确定为Integer  
                      
                    Box stringBox = new Box(); // 需要重新实例化来存储字符串类型  
                    stringBox.set("Hello");  
                    String value2 = stringBox.get(); // 获取时类型已经确定为String
                    
                    • 泛型方法
                      public class Utility {  
                          // 泛型方法,可以在调用时指定类型  
                          public static  T echo(T input) {  
                              return input;  
                          }  
                      }  
                        
                      // 使用时直接调用方法,并指定类型(类型推断)  
                      Integer intValue = Utility.echo(10); // 这里T被推断为Integer类型  
                      String strValue = Utility.echo("Hello"); // 这里显式指定了T为String类型,但实际上可以省略,因为编译器可以推断出来  
                      
                      使用方法

                      在调用方法的时候指明泛型的具体类型.泛型方法的定义比泛型类和接口要复杂.

                      泛型类是指实例化类的时候指明泛型的具体类型;

                      泛型方法是在调用方法的时候指明泛型的具体类型.

                      只有声明了 的方法才是泛型方法,在泛型类中使用了泛型的成员方法并不是泛型方法.

                      举个例子来说明基本语法:

                      /**
                       * 泛型方法介绍
                       * @param url
                       * @param result 
                       * @param  只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
                       *           与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
                       * @return T 返回值为T类型
                       */
                      public static  T doGetRequst(String url,Result result){
                      }
                      

                      或者拿一张网图来解释:

                      在这里插入图片描述

                      调用泛型方法语法格式

                      在这里插入图片描述

                      Class的作用是指明泛型的具体类型,而Class类型的变量c,可以用来创建泛型类的对象.

                      为什么要用变量c来创建对象呢?

                      因为既然是泛型方法,就代表我们不知道具体的类型是什么,也不知道构造方法是什么,因此没有办法去new一个对象

                      但是我们可以利用变量c的newInstance方法区创建对象,也就是利用反射创建对象.

                      看一个简单的实例帮助理解:

                      public class GenericMethodExample {  
                        
                          public static  void printArray(T[] array) {  
                              for (T element : array) {  
                                  System.out.print(element + " ");  
                              }  
                              System.out.println();  
                          }  
                        
                          public static void main(String[] args) {  
                              Integer[] intArray = {1, 2, 3, 4, 5};  
                              String[] strArray = {"Hello", "World"};  
                        
                              // 调用泛型方法并传入整型数组  
                              printArray(intArray);  
                        
                              // 调用泛型方法并传入字符串数组  
                              printArray(strArray);  
                          }  
                      }
                      

                      泛型的上下限

                      泛型上下限是用来约束泛型类型参数的范围/

                      上限

                      泛型的上限使用extends来定义,表示参数化的类型必须是制定类型或其子类.

                      eg: 表示T可以是Number类或其任何子类(如Integer,Double等)

                      所以它也称为上界通配符,它定义了泛型类型的上界,在泛型方法或泛型类中使用上限时,可以确保类型安全,并且只允许在泛型容器中添加符合约束条件的对象.

                      上限例子

                      class Info{    // 此处泛型只能是数字类型
                          private T var ;        // 定义泛型变量
                          public void setVar(T var){
                              this.var = var ;
                          }
                          public T getVar(){
                              return this.var ;
                          }
                          public String toString(){    // 直接打印
                              return this.var.toString() ;
                          }
                      }
                      public class demo1{
                          public static void main(String args[]){
                              Info i1 = new Info() ;        // 声明Integer的泛型对象
                          }
                      }
                      
                      下限

                      泛型的下限使用super关键字来定义,表示参数化的类型必须是制定类型或其超类(父类).

                      泛型系统并不支持下界通配符(? super T) 作为类型参数来声明泛型类或接口.

                      但是在使用泛型方法的时候,可以使用下界通配符(? super T)来放宽对类型的约束,允许方法接受更广泛的类型参数.

                      实际上在泛型方法的参数中使用下界通配符的情况相对较少见,因为Java的类型推断通常足够智能,可以推断出正确的类型参数而无需显式指定下界。

                      我们在使用通配符(如 ? , ? extends T , ? super T)来表示未知类型或类型范围,但是要遵循一定的规则和最佳实践,确保代码的类型安全和可读性.

                      例如,在PECS原则(Product Extends, Consumer Super)中建议,当泛型容器用于提供(produce) 元素时,应使用上限通配符;

                      当用于消费(consume)元素时,应使用无界通配符或下限通配符(尽管Java不支持直接作为类型参数约束的下限通配符)

                      这样可以确保类型兼容性并避免不必要的类型转换错误。

                      下限例子

                      class Info{
                          private T var ;        // 定义泛型变量
                          public void setVar(T var){
                              this.var = var ;
                          }
                          public T getVar(){
                              return this.var ;
                          }
                          public String toString(){    // 直接打印
                              return this.var.toString() ;
                          }
                      }
                      public class GenericsDemo21{
                          public static void main(String args[]){
                              Info i1 = new Info() ;        // 声明String的泛型对象
                              Info i2 = new Info() ;        // 声明Object的泛型对象
                              i1.setVar("hello") ;
                              i2.setVar(new Object()) ;
                              fun(i1) ;
                              fun(i2) ;
                          }
                          public static void fun(Info
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon