前言
这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战
最近一段时间经常研究 Java 源码,在源码中经常可以看到泛型的影子,看到了泛型就想到了我以前那张懵逼的脸😮 什么是泛型?泛什么型?什么泛型? 一提到泛型,可能很多小伙伴都知道它,但是并不了解它,正好今天我就和大家分享一下我对泛型的理解,听小弟娓娓道来~ ✍
什么是泛型
百度上有很多关于泛型的解释,但是描述的内容都不是很好理解,俗话说“繁琐问题必有猥琐解法”,那么泛型的“猥琐”解法就来了👇
“泛型”,通过字面可以理解为广泛的类型(这个类型可以应用在类、接口、方法,所以说他广泛),既然它是广泛的类型,那么就保证了代码与它们能够操作的数据类型是拆分开的,并不会绑定在一起,这也就可以让同一套代码用于多种数据类型,使用泛型的好处就是保证了代码的复用性,降低了耦合度,当然也就提升了代码的可读性。
下面咱们结合一段代码来看看:
import java.util.ArrayList;
import java.util.List;
/**
* 深入浅出聊 Java 泛型
* @description: GenericDemo
* @author: 庄霸.liziye
* @create: 2021-12-20 10:47
**/
public class GenericDemo {
public static void main(String[] args) {
List arrayList = new ArrayList();
arrayList.add("我在人民广场吃着炸鸡");
arrayList.add("卖炸鸡花了");
arrayList.add(20);
for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.println("item = " + item);
}
}
}
不出意外,这段代码抛出异常了~
咱们这时候还没有指定 List 集合的类型,如果将 List 集合指定成 String、或者 Integer 类型的话,那么 IDEA 就直接提示代码存在问题了👇
所以在这种场景下,就需要用上泛型了~ 接下来就分别简单说说泛型类、泛型接口和泛型方法🍺
泛型类
咱们先写一个简单的泛型类 + 测试方法👇
/**
* 简单泛型类
* @description: Generic
* @author: 庄霸.liziye
* @create: 2021-12-20 11:33
**/
public class Generic<T>{
private T value;
public Generic(T value) {
this.value = value;
}
public T getValue(){
return value;
}
}
/**
- 深入浅出聊 Java 泛型
- @description: GenericDemo
- @author: 庄霸.liziye
- @create: 2021-12-20 10:47
**/
public class GenericDemo {
public static void main(String[] args) {
Generic<Integer> genericInteger = new Generic<Integer>(20);
Generic<String> genericString = new Generic<String>("我在人民广场吃着炸鸡");
System.out.println("genericInteger = " + genericInteger.getValue());
System.out.println("genericString = " + genericString.getValue());
}
}
咱们执行一下测试方法,看看执行结果是什么样子的~
这里有几点需要注意的是:
- 泛型类中,T 可以随便写为任意标识,常见的标识有 T(Type)、E(Element)、K(Key)、V(Value) 等。
- 泛型的类型参数只能是类类型(类类型说白了就是 Class 类型),不能是简单类型(如 int 等)。
- 使用泛型类的时候,不一定要传入泛型类型实参,当传入非泛型类型实参时,泛型类中使用泛型的方法或成员变量定义的类型可以为任何类型。举个小例子👇
泛型接口
直接上代码✌
/**
* 深入浅出聊 Java 泛型
* @description: GenericDemo
* @author: 庄霸.liziye
* @create: 2021-12-20 10:47
**/
public class GenericDemo {
public static void main(String[] args) {
IMessage<String> message = new MessageImpl<String>();
message.printMessage("我在人民广场吃着炸鸡");
}
}
/**
* 定义泛型接口
**/
interface IMessage<T>{
public void printMessage(T t);
}
/**
* 实现泛型接口
**/
class MessageImpl<T> implements IMessage<T>{
@Override
public void printMessage(T t) {
System.out.println(t);
}
}
上面的代码是在实现泛型接口的类时未传入泛型实参的情况,在这种情况下有一点是需要注意的:在声明类的时候,需将泛型的声明也加在接口实现类中(class MessageImpl < T > implements IMessage< T >),如果在接口实现类中未声明泛型的话,编辑器则会直接报错,如下图所示:
咱们改造一下代码,再来看看第二种情况~
/**
* 深入浅出聊 Java 泛型
* @description: GenericDemo
* @author: 庄霸.liziye
* @create: 2021-12-20 10:47
**/
public class GenericDemo {
public static void main(String[] args) {
IMessage<String> message =new MessageImpl();
message.printMessage("我在人民广场吃着炸鸡");
}
}
/**
* 定义泛型接口
**/
interface IMessage<T>{
public void printMessage(T t);
}
/**
* 实现泛型接口
**/
class MessageImpl implements IMessage<String>{
@Override
public void printMessage(String t) {
System.out.println(t);
}
}
这次我们传入了泛型实参,虽然我们只创建了一个泛型接口,但是我们可以为T传入无数个实参,形成无数种类型的泛型接口。此时有一点是需要特别注意:当泛型类型传入了实参类型时,则所有使用泛型的地方都要替换成传入的实参类型。
泛型方法
/**
* 深入浅出聊 Java 泛型
* @description: GenericDemo
* @author: 庄霸.liziye
* @create: 2021-12-20 10:47
**/
public class GenericDemo {
public static void main(String[] args) {
//显式赋值
String p1 = GenericDemo.<String>printfArray("我在人民广场吃着炸鸡");
//隐式赋值,常用此方式,可以不指定 <String>
String p2 = GenericDemo.printfArray("我在人民广场吃着炸鸡");
System.out.println("p1 = " + p1);
System.out.println("p2 = " + p2);
}
/**
* 泛型方法
* @param message
* @param <T>
* @return
*/
public static <T> T printfArray(T message) {
return message;
}
}
关于泛型方法,这里就需要多说几句了👇
① 泛型方法在调用方法的时候指明泛型的具体类型,和泛型类刚好相反(泛型类在实例化类的时候指明泛型的具体类型)。
②在上面写的泛型方法中,static 与 T(即返回值类型)中间 < T > 非常重要,可以理解为这就是泛型方法的一个标记,只有声明了< T > 的方法才是泛型方法。但是,在泛型类中的使用了泛型的成员方法并不是泛型方法。
用上面定义的泛型类举个例子👀
/**
* 简单泛型类
* @description: Generic
* @author: 庄霸.liziye
* @create: 2021-12-20 11:33
**/
public class Generic<T>{
private T value;
public Generic(T value) {
this.value = value;
}
public T getValue(){
return value;
}
}
在泛型类中定义了一个 getValue() 方法,有些小伙伴可能会觉得 getValue() 方法是一个泛型方法,其实不然。虽然在方法中使用了泛型,但 getValue() 方法只是一个普通的成员方法,只不过他的返回值类型用的是声明泛型类时声明的泛型。
③ 与泛型类的定义一样,此处的 T 可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
泛型的边界
在使用泛型的时候,我们还可以为传入的泛型类型实参进行边界的限制。
/**
* 简单泛型类
* @description: Generic
* @author: 庄霸.liziye
* @create: 2021-12-20 11:33
**/
public class Generic<T extends Number>{
private T value;
public Generic(T value) {
this.value = value;
}
public T getValue(){
return value;
}
}
先改造一下泛型类,为泛型添加边界,限制传入的类型实参必须是 Number (或其他)类型的子类。如果此时我们传入的参数类型是 String ,那么编辑器则会提示错误(String 类型并不是 Number 类型的子类)👇
泛型的擦除机制
通过上面的例子,相信大家也对泛型有了一个更深的理解,最后再简单说一下泛型的擦除机制。
import java.util.ArrayList;
import java.util.List;
/**
* 深入浅出聊 Java 泛型
* @description: GenericDemo
* @author: 庄霸.liziye
* @create: 2021-12-20 10:47
**/
public class GenericDemo {
public static void main(String[] args) {
List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();
Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();
System.out.println(classStringArrayList == classIntegerArrayList);
}
}
我们明明声明的是两个类型不同的 List 集合,为什么打印出来的结果是 true 呢?
这就是泛型的擦除机制。在运行过程中任何和类型有关的信息都会被擦除,在运行过程中中,ArrayList< String > 和 ArrayList< Integer > 的具体信息都被擦除成它们的原生类型也就是 ArrayList 类型。也就是说泛型的类型在逻辑上可以看成是多个不同的类型,实际上都是一样的。
再说的具体点:玩英雄联盟的时候,我们选择了“剑圣”这个英雄,无论我们使用了“暗影·易”、“天人合一·易”还是“源计划·易”,他只是看起来不一样了,其实它依然是“剑圣”这个英雄。
最后再说一句:在研究源码的时候会发现,泛型使用频率最高的场景就是在定义集合的时候,所以在我们日常的编码过程中,合理的使用泛型去定义集合可以大大的简化编码,同时还可以保证我们的代码有一个良好的可读性和可用性👍。
小结
本人经验有限,有些地方可能讲的没有特别到位,如果您在阅读的时候想到了什么问题,欢迎在评论区留言,我们后续再一一探讨🙇
希望各位小伙伴动动自己可爱的小手,来一波点赞+关注 (✿◡‿◡) 让更多小伙伴看到这篇文章~ 蟹蟹呦(●'◡'●)
如果文章中有错误,欢迎大家留言指正;若您有更好、更独到的理解,欢迎您在留言区留下您的宝贵想法。
爱你所爱 行你所行 听从你心 无问东西