JAVA中的泛型

简介: 泛型,泛型的语法,泛型类 ,基本数据类型和对应的包装类 ,泛型方法, 语法,小结

 如下是定义了一个类,这个类中可以存放一组 int 类型的数据。

classNumber{
int[] arr;
publicNumber(inta) {
this.arr=newint[10];
    }
publicvoidsetArr(intn, intdata) {
this.arr[n] =data;
    }
publicintgetArr(intn) {
returnarr[n];
    }
}

image.gif

可是虽然这样写没有问题可是它的应用范围实在是太小了,它只能存储 int 类型的数据。如果我们现在想让这个类中可以存放任何类型的数据应该怎么做呢?

在 JAVA 中利用泛型就可以实现这点。

泛型

泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。

泛型的语法:


class 泛型类名称<类型形参列表> {

}

class ClassName<T1, T2, ..., Tn> {

}


类名后的 <T> 代表占位符,表示当前类是一个泛型类。

类型形参一般使用一个大写字母表示,常用的名称有:

    • E 表示 Element
    • K 表示 Key
    • V 表示 Value
    • N 表示 Number
    • T 表示 Type
    • S, U, V 等等 - 第二、第三、第四个类型
      1. 泛型是将数据类型参数化,进行传递
      2. 使用 <T> 表示当前类是一个泛型类。
      3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换

      泛型类

      所谓的泛型类其实就是类中的属性可以根据业务场景的不同而改变(这种改变只能发生在定义类的时候,当类一旦被定义完成就无法改变


      语法:

        • 泛型类<类型实参> 变量名; // 定义一个泛型类引用
        • new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象

        我们将开始的代码进行改进:

        classNumber<T>{
        Object[] arr;//这里可以暂时忽略Object,下文会说publicNumber(inta) {
        this.arr=newObject[10];
            }
        //设置数组下标为n的元素值publicvoidsetArr(intn, Tdata) {
        this.arr[n] =data;
            }
        //获取数组下标为n的元素publicTgetArr(intn) {
        return (T)arr[n];
            }
        }

        image.gif

        此时这个类就是一个泛型类我们可以根据不同的业务场景来让它存储不同类型的值:

        //存储整型Number<Integer>num1=newNumber<>(2);
        num1.setArr(0,1);
        num1.setArr(1,2);
        System.out.println(num1.getArr(0));
        System.out.println(num1.getArr(1));
        //存储字符串类型Number<String>num2=newNumber<>(2);
        num2.setArr(0,"zhangsan");
        num2.setArr(1,"lisi");
        System.out.println(num2.getArr(0));
        System.out.println(num2.getArr(1));

        image.gif

        image.png

        我们都有泛型了为啥在泛型类中还要使用Object类来定义数组?

        原因有两个:

          • Object类是所有类的父类所以它可以存储所有类型的值;
          • 在 JAVA 中由于数组比较特殊在创建数组时必须要指明数组的类型,而泛型是一种不确定类型的写法所以无法定义成泛型数组  。

          image.png

          还有一种写法:

          image.png

          这种写法虽然不会报错,而且也可以运行但是其实写成这样也不太好,因为它只是骗过了编译器。

          这里还是推荐将数组定义成这样:

          image.png

          调用的时候需要注意

          如果是简单类型(如:int , char……)必须要传对应的包装类。

          image.png

          image.png

          基本数据类型和对应的包装类

          基本数据类型 包装类
          byte Byte
          short Short
          int Integer
          long Long
          float Float
          double Double
          char Character
          boolean Boolean

          这个表看起来多其实除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写

          泛型除了可以应用于类之外也可以应用于方法。

          泛型方法

          语法:

          方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

          publicstatic<T>voidswap(T[] array, inti, intj) {
          Tt=array[i];
          array[i] =array[j];
          array[j] =t;
              }

          image.gif

          泛型方法也和类一样不能使用基本类型,需要使用对应的包装类

          image.png

          我们可以测试一下:

          publicstaticvoidmain(String[] args) {
          Integer[] b= {1,2,3,4};
          swap(b, 0, 1);
          System.out.println(Arrays.toString(b));
              }

          image.gif

          image.png

          擦除机制

          泛型如此灵活那么它到底是如何进行编译的呢?

          其实泛型的编译是通过擦除机制来进行完成的。

          以这个泛型类为例:

          classNumber<T>{
          T[] arr;
          publicNumber(inta) {
          this.arr= (T[])newObject[10];
              }
          publicvoidsetArr(intn, Tdata) {
          this.arr[n] =data;
              }
          publicTgetArr(intn) {
          return (T)arr[n];
              }
          }

          image.gif

          我们可以在cmd中利用 javap -c 来打开一个字节码文件

          image.gif

          我们可以看出在编译的时候已经将所有的 T 都替换为了 Object 类型。

          Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。

          泛型的上界

          在定义泛型类时,有时需要对传入的类型变量做一定的约束,就可以通过类型边界来约束。

          语法:
          class 泛型类名称<类型形参 extends 类型边界> {}

          classNumber<TextendsString>{
           ...
          }

          image.gif

          当我们此时 Number 就只能接受 String和String类的子类 ,否则就会报错。

          Number<String>num1=newNumber<>(1);
          Number<Integer>num2=newNumber<>(1);

          image.gif

          image.png

          通配符

          通配符:?

          泛型虽然很好用但是此时又出现了一个新的问题,如果想写一个方法形参是泛型类那么就会很麻烦,因为我们根本不知道当前泛型的具体类型:

          publicstaticvoidprintf(Number<String>a) {
          System.out.println(a.getArr(0));
          }

          image.gif

          如上我们简单定义了一个输出方法,方法形参是 Number 这个泛型类,当我们调用时:

          Number<String>num1=newNumber<>(1);
          num1.setArr(0, "zhangsan");
          Number<Integer>num2=newNumber<>(1);
          num2.setArr(0, 4);
          printf(num1);
          printf(num2);

          image.gif

          当用这段代码测试后就会报错原因是类型不匹配,因为我们定义方法时使用的是 String 类型,可是这可是泛型,有可能还会是 Integer

          image.png

          此时就需要用到 通配符:?

          当我们换成通配符之后再使用上述代码进行测试就可以正常运行了

          publicstaticvoidprintf(Number<?>a) {
          System.out.println(a.getArr(0));
          }

          image.gif

          image.png

          通配符也和泛型一样有时也需要对其范围进行限制:

          通配符的上界

            • 语法:
            • <? extends 上界>
            • 表示 ?代表的类只能是 上界 这个类本身 或 其子类

            此时我们简单定义三个类:

            classA {}
            classBextendsA{}
            classCextendsB{}

            image.gif

            对之前的打印方法进行修改:

            publicstaticvoidprintf(Number<?extendsB>a) {
            }

            image.gif

            用以下代码进行测试:

            Number<A>num1=newNumber<>(1);
            Number<B>num2=newNumber<>(1);
            Number<C>num3=newNumber<>(1);
            printf(num1);
            printf(num2);
            printf(num3);

            image.gif

            因为 A 类并不是 B 的子类所以就会发生异常

            image.png

            通配符的下界

              • 语法:
              • <? super 下界>
              • 表示 ?代表的类只能是 下界 这个类本身 或 其父类

              对之前的打印方法进行修改:

              publicstaticvoidprintf(Number<?superB>a) {
              }

              image.gif

              还是用以下代码进行测试:

              Number<A>num1=newNumber<>(1);
              Number<B>num2=newNumber<>(1);
              Number<C>num3=newNumber<>(1);
              printf(num1);
              printf(num2);
              printf(num3);

              image.gif

              因为 C 类并不是 B 的父类所以发生异常。

              image.png

              目录
              相关文章
              |
              7天前
              |
              存储 Java 编译器
              深入理解 Java 泛型和类型擦除
              【4月更文挑战第19天】Java泛型是参数化类型,增强安全性与可读性,但存在类型擦除机制。类型擦除保证与旧版本兼容,优化性能,但也导致运行时无法访问泛型信息、类型匹配问题及数组创建限制。为应对这些问题,可使用Object类、instanceof运算符,或借助Guava库的TypeToken获取运行时类型信息。
              |
              7天前
              |
              JavaScript Java 编译器
              Java包装类和泛型的知识点详解
              Java包装类和泛型的知识点的深度理解
              |
              7天前
              |
              安全 Java 程序员
              Java 泛型
              Java 泛型
              16 0
              |
              2天前
              |
              安全 Java 编译器
              Java一分钟之——泛型方法与泛型接口
              【5月更文挑战第20天】Java泛型提供编译时类型安全检查,提升代码重用和灵活性。本文探讨泛型方法和接口的核心概念、常见问题和避免策略。泛型方法允许处理多种数据类型,而泛型接口需在实现时指定具体类型。注意类型擦除、误用原始类型和泛型边界的理解。通过明确指定类型参数、利用通配符和理解类型擦除来避免问题。泛型接口要精确指定类型参数,适度约束,利用默认方法。示例代码展示了泛型方法和接口的使用。
              26 1
              Java一分钟之——泛型方法与泛型接口
              |
              2天前
              |
              存储 安全 Java
              Java一分钟之-泛型擦除与类型安全
              【5月更文挑战第20天】Java泛型采用类型擦除机制,在编译期间移除泛型信息,但在编译阶段提供类型安全检查。尽管需要类型转换且可能产生警告,但可以通过特定语法避免。使用泛型时应注意自动装箱拆箱影响性能,无界通配符仅允许读取。理解这些特性有助于编写更安全的代码。
              27 4
              |
              3天前
              |
              安全 Java API
              Java一分钟之-泛型通配符:上限与下限野蛮类型
              【5月更文挑战第19天】Java中的泛型通配符用于增强方法参数和变量的灵活性。通配符上限`? extends T`允许读取`T`或其子类型的列表,而通配符下限`? super T`允许向`T`或其父类型的列表写入。野蛮类型不指定泛型,可能引发运行时异常。注意,不能创建泛型通配符实例,也无法同时指定上下限。理解和适度使用这些概念能提升代码的通用性和安全性,但也需兼顾可读性。
              25 3
              |
              7天前
              |
              安全 Java 编译器
              java泛型浅谈
              java泛型浅谈
              7 1
              |
              7天前
              |
              存储 安全 Java
              掌握8条泛型规则,打造优雅通用的Java代码
              掌握8条泛型规则,打造优雅通用的Java代码
              掌握8条泛型规则,打造优雅通用的Java代码
              |
              7天前
              |
              Java
              JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识
              【5月更文挑战第2天】JAVA难点包括异常处理、多线程、泛型和反射,以及复杂的分布式系统知识。入坑JAVA因它的面向对象特性、平台无关性、强大的标准库和活跃的社区支持。
              42 2
              |
              7天前
              |
              安全 Java 编译器
              【JAVA】泛型和Object的区别
              【JAVA】泛型和Object的区别