超详细Java泛型解析,由浅入深带你认识和使用泛型

一、前言

泛型是JDK5中引入的新特性。在 Java 编程中,泛型(Generics)是一个非常强大的特性,它能够让代码更加通用、安全和灵活

泛型的主要作用是在类、接口和方法中使用参数化类型,从而使代码可以处理不同类型的数据,而不需要重复编写相同的逻辑。


二、泛型引入

为了更好地理解泛型,我们先抛出一个问题:没有泛型的时候,我们的类、接口和方法是怎样存储数据的呢?我们以常用的集合为例。

2.1 没有泛型的时候,集合是如何储存数据的呢?

  • 如果我们没有给集合指定类型,默认所有数据类型都是Object类型,此时可以往集合添加任意的数据类型。(实际上这是一种多态)

2.2 这样就会带来一个坏处

  • 我们在获取数据的时候无法使用它的特有行为。例如下图,我们没有定义集合的泛型,往集合里面传递一个字符串对象,我们想遍历元素,再用字符串特有的length方法计算字符串长度,但是发生报错。
  • 原因是:我们遍历出来的对象实际上是一个Object对象,使用到了多态,在调用方法时编译看左边,运行看右边。但是在编译的时候没有在Object类中发现有length方法,所以无法调用
  • (这也就是多态的弊端:不能访问子类特有的功能,只能访问其重写的父类功能)

2.3 那么我们进行类型转换不就行了吗?

我们可以看到,经过强制类型转换后确实可以调用子类中的特有功能。

但是,这个问题远远没有解决,如果我们添加的是不同类型的元素呢?

运行之后发现报了类型转换异常的错误,因为第二次遍历得到的是一个整数类型的数据,第三次遍历得到的是自定义类型的数据,无法强制转换为String类型。

由此我们发现,当存入不同类型数据的时候,我们的问题仍然得不到解决,不同类型的数据仍然无法一一调用其特有的功能。

2.4 那么怎样能够在添加数据的时候让类型统一?

这个时候我们就要站在java源代码开发者的角度去思考:

用户使用这个对象时候都会想要传入什么类型的数据?我应该怎样满足?

  • 在源码中定义一个StringArrayList、IntegerArraylist类...,在其add方法指定添加的类型?

那肯定不行呀,这也太麻烦了!每一种类型都要写一种集合,这代码量也太大了,开发者的学习成本也会大大增加!

这个时候,我们就要使用到泛型了。


三、泛型详解

3.1 泛型的好处?

  • 统一了数据类型
  • 把运行时期的问题提前到了编译时期
    • 理解:如运行时对象想调用特有的方法无法调用问题,当我们使用了泛型后,在类和方法中指定了传入的数据类型,这个问题在编译时期就已经解决了。
  • 避免了类型的强制转换

我们再回到上面的问题,我们给ArrayList集合定义一个泛型后,传入String类型,即可统一集合的数据。而且,如果想要创建一个存储Integer类型元素的集合,也可以通过该传入相应的泛型类型去创建。

3.2 泛型的定义格式

  • <类型>: 指定一种类型的格式.尖括号里面可以任意书写,一般只写一个字母.例如: <E> <T>

  • <类型1,类型2…>: 指定多种类型的格式,多种类型之间用逗号隔开.例如: <E,T> <K,V>

3.3 泛型的创建与使用

3.3.1 泛型类

使用场景:

当一个类中,某个变量的数据类型不确定时,就可以定义带有泛型的类

我们举个例子:

javabea类:

测试类:

在这个例子中我们可以发现,在类定义的泛型,类内的变量都可以用其来指定变量的类型

在这里有一个细节需要注意:

当我们在类的类名后定义一个泛型,其也可以被接口的泛型类型接收

3.3.2 泛型方法

使用场景:

当一个方法中,某个变量的数据类型不确定时,就可以定义带有泛型的方法

注意:泛型类型<类型>要写在方法修饰符的后面

举个例子:

拓展:如果我们需要传入多个值怎么办(3个以上),一个一个定义岂不是很麻烦,所以我们使用可变参数来完成

3.3.3 泛型接口

使用场景:

当一个接口中,某个变量的数据类型不确定时,就可以定义带有泛型的接口

那我们如何使用一个带有泛型的接口呢?

方法一:实现类给出具体类型

方法二:实现类延续泛型,创建对象时再确定

方法二示例图

3.3.4 泛型定义的一些细节

  • 泛型中不能写基本数据类型,如果需要用到如int等基本数据类型,使用其包装类Integer等
  • 指定泛型具体类型后,传递数据时,可以传入该类类型或者其子类类型
    • 通过前面的Object类型集合的问题剖析也可以很好理解(字符串、整数、Student类都是其子类类型)
  • 如果不写泛型,类型默认是Object

这里我们有一个值得注意的点是:泛型不具备继承性,但是数据具备继承性

这句话应该怎么理解呢?

在下图中我们发现我们定义了一个method方法,传入一个泛型类型为Ye的集合参数,当我们调用method方法,传入Ye的子类Fu和Zi的时候产生报错!这说明泛型不具备继承性

但是我们为同一个集合传入数据时,是可以传入其子类数据的,前面的Object的讲述大家已经很熟悉。这说明数据具备继承性

拓展:Java中的泛型实际上是一种伪泛型

3.4 泛型通配符

3.4.1 引入

在下图方法中,根据上述的学习可知:泛型里写什么类型,那么它就只能传递什么类型的数据

比如我想传递Ye类型的数据则泛型写的是Ye

但是这又引发了一个问题:此时它可以接收任意类型的数据,如果要传递一个Student类型也是可以的。

但是如果我虽然不确定类型,而且上述说了,泛型不具备继承性,定义了ArrayList<Ye>就只能传入Ye类型的泛型值。

但是希望传递Ye继承体系的Ye、Fu、Zi类型该怎么处理呢?有没有一种折中的可能?

这里我们就需要引入泛型通配符了。

3.4.2 通配符

? extends E:表示可以传递E或者E所有的子类类型

?super E:表示可以传递E或者E所有的父类类型

应用场景
  • 如果我们在定义类、方法、接口的时候,如果类型不确定,就可以定义泛型类、泛型方法、泛型接口。
  • 如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以泛型的通配符

泛型的通配符关键点:可以限定类型的范围。


四、泛型的局限性

尽管泛型很强大,但它们也有一些限制:

  • 类型擦除:Java 泛型在编译后会被类型擦除,这意味着在运行时无法获取泛型的具体类型信息。比如 List<String>List<Integer> 在运行时实际上是相同的类型。

  • 不能使用基本类型:泛型只能使用引用类型,不能直接使用基本数据类型(如 intchar 等)。不过,你可以使用它们的包装类(如 IntegerCharacter)来解决这个问题。

  • 静态上下文中不能使用泛型类型参数:因为泛型在静态上下文中不可用,不能在静态方法或静态字段中使用泛型类型参数。

    class MyClass<T> {
        // 成员方法可以使用泛型
        public void instanceMethod(T param) {
            System.out.println(param);
        }
    
        // 静态方法无法使用泛型T
        public static void staticMethod(T param) { // 错误:静态上下文不能引用实例的泛型类型
            System.out.println(param);
        }
    }
    

 五、总结

泛型是 Java 中非常有用的特性,它能够让你的代码更加灵活和安全。通过泛型,程序员可以编写出更通用的类、接口和方法,减少代码重复,并且避免不必要的类型转换错误。在编写代码时,理解和正确使用泛型将极大地提升代码的质量和可维护性。

希望这篇博文能够让你对 Java 泛型有一个清晰的理解,帮助你在项目中更好地使用泛型!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值