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

              目录
              相关文章
              |
              3月前
              |
              安全 Java 编译器
              揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
              【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
              75 2
              |
              16天前
              |
              Java API
              [Java]泛型
              本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
              14 0
              [Java]泛型
              |
              25天前
              |
              存储 安全 Java
              🌱Java零基础 - 泛型详解
              【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
              10 1
              |
              1月前
              |
              Java 语音技术 容器
              java数据结构泛型
              java数据结构泛型
              26 5
              |
              30天前
              |
              存储 Java 编译器
              Java集合定义其泛型
              Java集合定义其泛型
              17 1
              |
              2月前
              |
              Java 编译器 容器
              Java——包装类和泛型
              包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
              35 9
              Java——包装类和泛型
              |
              1月前
              |
              存储 Java 编译器
              【用Java学习数据结构系列】初识泛型
              【用Java学习数据结构系列】初识泛型
              18 2
              |
              2月前
              |
              安全 Java API
              【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
              String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
              【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
              |
              30天前
              |
              安全 Java 编译器
              Java基础-泛型机制
              Java基础-泛型机制
              13 0
              |
              2月前
              |
              存储 安全 搜索推荐
              Java中的泛型
              【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。