首页>>后端>>java->《回炉重造》——泛型

《回炉重造》——泛型

时间:2023-12-06 本站 点击:0

泛型

前言

以前学习到「泛型」的时候,只是浅浅的知道可以限制类型,并没有更深入理解,可以说基础的也没理解到位,只是浮于表面,所以,现在回炉重造,重学泛型!打好基础!

什么是泛型?

泛型(Generic) ,Generic 的意思有「一般化的,通用的」。

是 JDK 5 中引入的新特性,它提供编译时的类型安全检测,允许我们在编译时检测到非法的数据类型,本质是 参数化类型。

这里还涉及到一个词「参数化类型」。什么意思呢?

意思就是:把类型参数化(只能感慨中国文化博大精深),即我们可以把类型作为参数,换句话说,就是所操作的数据类型被指定为一个参数。

说到参数,我们也熟悉,你看,方法上的形参、调用方法时的实参,这些都是参数,对吧。

同理,类型,即 Java 中的各种基本的引用类型,当然包含你自己定义的类型,说白了就是各种类(Class),类可以作为参数,就是上面讲的把类型作为参数(好吧,好像讲了一堆废话)。这又涉及到一个词,即「类型参数」。

我们可以看看 ArrayList 的源码,如下:

public class ArrayList<E> extends AbstractList<E>        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{    ...}

ArrayList<E> 中的 <E>,这里的 E 可以说是一个「类型形参」。

而我们写 ArrayList<String> list = new ArrayList<>() 的时候,给 ArrayList 这个集合指定了一个具体的类型 String ,形参 E 传入的实参就是 String,也就是说 String 是一个「类型实参」。

简而言之:

ArrayList<E> 中的 E 称为 类型形参;ArrayList<String> 中的 String 称为 类型实参。这两个合起来,就是上面提到的「类型参数」。

为什么会有泛型的出现?

泛型和集合有千丝万缕的关系,我们现在用集合,也是使用「泛型集合」。

List<Integer> list = new ArrayList<>(); // 泛型集合

当然,我们一开始学习的时候,并没有用到泛型,即非泛型集合。

List list = new ArrayList();    // 非泛型集合

在以前没有泛型的情况下,我们看看会出现什么问题。默认 ArrayList 集合中存储的元素类型是 Object ,这样很好,Java 中任何类型的终极父类就是 Object,什么类型的数据都能存储到这个集合中。

比如我可以这样操作(经典案例):

List list = new ArrayList();    // 非泛型集合list.add("Hello World!");       // 存储 String 类型list.add(23);                   // 存储 int 类型,这里会自动装箱为 Integer 类型list.add(true);                 // 存储 Boolean 类型for (Object o : list) {         // 用 Object 接收,合情合理    System.out.println(o);}

我们存储数据之后,后续肯定需要使用它,就需要从集合中取出来,而取出来进一步操作是需要明确具体的数据类型,那么就需要进行强制类型转换。

for (Object o : list) {    String s = (String) o;      // 强制类型转换    // 后续操作...}

此时代码并不会报错,编译也不会有问题,直到我们运行时,就会出现异常——ClassCastException

这也是必然的,毕竟我们的集合中还有其他类型的数据,其他类型的数据,再怎样强制类型转换也不可能转成 String 类型,也就会出现异常了。

看到这里,估计有小伙伴要问了,我一个一个强制转换不行吗?我知道存储的是什么数据,到时直接获取相对应的数据进行强转就行了啊。 是,没错,你可以一个一个强转,数量少的情况下是可以,但是你数量很多的情况下呢?你怎么办?

所以,泛型出现了,它可以限制下我们在编译期的类型,保证类型是安全的,即运行时不会发生异常的。

List<String> list = new ArrayList<>();  // 泛型集合list.add("Hello World!");               // 存储 String 类型list.add("23");list.add("Coding Coding");for (String s : list) {                 // 用 String 接收    System.out.println(s);    // 后续操作...}

看到这里的小伙伴,可能有这么一个疑惑:那这样为什么不直接使用一个 String 数组呢? 这个问题问得好。

数组确实能够存储同一个数据类型的数据,但是对于想无限制存储元素时,数组就有它的缺点,数组长度是固定不可变。总而言之,数组使用起来不方便,所以才有集合的出现,而集合又因为有这种问题,进而出现泛型集合。

这里使用了泛型,那么我们在 add() 的时候,编译期间就会对添加的元素进行类型检查,而且在获取集合元素的时候,也不需要强制类型转换了,直接用指定的类型接收就行了。

使用泛型有什么好处?

无需强制类型转换(集合、反射)

增加代码可读性,我们可以通过泛型,知道现在操作的是什么数据类型。一句话,给人看的。

代码复用,可以根据不同情况传入不同的数据类型,进行不同的操作。

泛型类

定义语法:

class 类名<通配符,通配符,通配符...> {    private 通配符 变量名;    ...}

通配符:T、E、K、V,也就是上面说的类型形参。(这里的通配符,也有人称为泛型标识)

使用语法:

类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();类名<具体的数据类型> 对象名 = new 类名<>();       // JDK 7 开始可以省略,人们称为 菱形语法

举个栗子:

/**    定义泛型类*/public class Generic<T> {    private T variable;            public void setVariable(T variable) {        return this.variable = variable;    }        public T getVariable() {        return variable;    }}

测试

Generic<String> g = new Generic<>();    // 指定泛型为 Stringg.setVariable("god23bin");              // 正常g.setVariable(23);                      // 提示错误,因为这里是 int 型String var = g.getVariable();

需要注意的点:

你使用泛型类时,没有指定数据类型,那么将默认为 Object 类型

泛型的类型参数,只能是引用数据类型,不支持基本数据类型。

泛型在逻辑上,你操作的是不同的数据类型,但是实际上,还是同样的类型(比如上面例子中的 Generic 类,泛型指定不同的数据类型,逻辑上是不同的,但是实际上还是 Generic 类型,这里就涉及到「类型擦除」)

如果有继承:

List<Integer> list = new ArrayList<>(); // 泛型集合0

泛型接口

定义语法:

List<Integer> list = new ArrayList<>(); // 泛型集合1

使用语法:

List<Integer> list = new ArrayList<>(); // 泛型集合2

泛型方法

之前是在类和接口上定义了泛型,然而有时候,我们并不需要整个类都定义类型,只需要其中某一个方法定义泛型,只关心这一个方法,这时就可以使用把泛型定义在方法上,这样调用泛型方法的时候,才指定具体的类型参数。

定义语法:

List<Integer> list = new ArrayList<>(); // 泛型集合3

举个栗子:

List<Integer> list = new ArrayList<>(); // 泛型集合4

这里需要注意的是,泛型方法和泛型类中使用了泛型的普通的方法是不一样的。

List<Integer> list = new ArrayList<>(); // 泛型集合5

而且,如果你在泛型类中定义了泛型方法,那么泛型方法中的 <T,...> 类型形参和泛型类上的类型形参是不一样的,是相互独立的。还有,泛型方法可以定义成静态的,还没完,泛型方法还可以结合可变参数。

举个栗子:

List<Integer> list = new ArrayList<>(); // 泛型集合6

通配符之问号

之前出现的 T,E,K,V 这些,也都是通配符,不过,这些通配符是属于类型形参的通配符。那么类型实参的通配符呢?这就来啦!类型实参通配符:?。没错,你没看错,就是一个问号。

类型实参的通配符是使用 ? 来代表具体的类型实参的,代表任意类型。

举个例子:

List<Integer> list = new ArrayList<>(); // 泛型集合7

上面要求 Games 指定的类型为 String。那么我们这样操作:

List<Integer> list = new ArrayList<>(); // 泛型集合8

所以使用 ? 通配符

List<Integer> list = new ArrayList<>(); // 泛型集合9

通配符上下限

类型通配符的上下限,有的地方也称为上下界,还有称限定通配符的,意思都一样。

上限语法:

List list = new ArrayList();    // 非泛型集合0

这里的 extends 可以这样理解,<? extends A> ,使用的时候,我们传入的实参类型需要小于等于A类,即需要是A的子类或A本身,这样就限制了通配符的上限了,你最高只能是A类。

下限语法:

List list = new ArrayList();    // 非泛型集合1

这里的 super 可以这样理解,<? super A> ,使用的时候,我们传入的实参类型需要大于等于A类,即需要是A的父类或A本身,这样就限制了通配符的下限了,你最低只能是A类。

举个例子,这里有 A、B、C 三个类,A 是 B 的父类,B 是 C 的父类。

List list = new ArrayList();    // 非泛型集合2

调用这个方法

List list = new ArrayList();    // 非泛型集合3

需要注意的是,你搞了通配符的上限,在集合中,那么是只能用来读取数据,而不能用来存储数据,这该怎么理解呢?

List list = new ArrayList();    // 非泛型集合4

那么下限呢?放心,下限没有这个问题,可以存储数据。

List list = new ArrayList();    // 非泛型集合5

类型擦除

泛型的限制,只在编译期存在,一旦在运行了,那么便消失了,即类型被擦除了。

有两种情况:

无限制类型擦除

有限制类型擦除

无限制:

有限制:

泛型方法上的类型擦除也是同理。还有一个知识点就是,在泛型接口的类型擦除中,会出现一个「桥接方法」,主要是保持接口和类的实现关系。

以上,就是泛型的基本内容了。

面试题

开始回顾八股文!!!

Java 泛型是什么?常用的通配符有哪些?

泛型(Generics)是 JDK5 中引入的一个新特性,它提供了编译时类型安全检测的机制。这个机制可以在编译时就检测到非法的数据类型。本质是一个参数化类型,就是所操作的数据类型可以被指定为一个特定的参数类型。

常用的通配符有 T(Type)、K(Key)、V(Value)、E(Element)、?(未知类型)

Java 的泛型是如何工作的 ? 什么是类型擦除?(泛型擦除是什么?)

Java 的泛型是伪泛型,因为在 Java 运行期间,这些泛型信息都会被擦掉,就是所谓的类型擦除(泛型擦除)。

什么是泛型中的限定通配符和非限定通配符?

限定通配符,顾名思义,就是对类型进行限定,Java 中有两种限定通配符。

一种是 < ? extends T >,它通过确保类型必须是T的子类来限定上界,即类型必须为T类型或者T子类

另一种是< ? super T >,它通过确保类型必须是T的父类来限定下届,即类型必须为T类型或者T的父类

< ? > 表示了非限定通配符,因为 < ? > 可以用任意类型来替代。

你的项目中哪里用到了泛型?

可用于定义通用返回结果类 CommonResult 通过参数 T 可根据具体的返回类型动态指定结果的数据类型

用于构建集合工具类。参考 Collections 中的 sort, binarySearch 方法

最后的最后

由本人水平所限,难免有错误以及不足之处, 屏幕前的靓仔靓女们 如有发现,恳请指出!

最后,谢谢你看到这里,谢谢你认真对待我的努力,希望这篇博客对你有所帮助!

你轻轻地点了个赞,那将在我的心里世界增添一颗明亮而耀眼的星!

原文:https://juejin.cn/post/7100916739302653965


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/java/15915.html