携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
前言:
阅读本章内容,你可以学到如下知识:
- 泛型是什么
- 为什么要使用泛型
- 泛型中的E,K,N,T分别代表什么?
- 泛型的使用
一丶泛型是什么?
泛型就是具体的类型参数化,在具体传入或者调用时才去传入具体的类型。(指定容器要持有什么类型的对象,由编译器保证类型的正确性)
二丶为什么要使用泛型
先看个例子,以我们最常用的list集合举例,如果我们没有泛型,集合里面放的都是Object对象
public static void main(String[] args) {
List<Object> listObject = new ArrayList<>();
listObject.add(1);
listObject.add(new Album());
System.out.println(listObject);
for (Object object : listObject) {
String str = (String) object;
System.out.println(str);
}
List<String> listStr = new ArrayList<>();
listStr.add("str1");
listStr.add("str12");
for (String s : listStr) {
System.out.println(s);
}
}
复制代码
从上面代码我们可以发现两个问题
- 我们
listObject
其实放的是Integer
和Number
类型,我们在编译时可以随意强制转换然后进行方法调用,但是我们将Integer
和Number
类型都转换为String
其实在运行期是会报错的,我们这里的错误就推迟到运行期才暴露出来,而使用泛型我们在编译器就可以提前做类型检查,这样错误就在编译期暴露出来了 - 我们看到第二个
listStr
集合,因为我们指定了泛型是String
,所以当我们使用list
中的元素就不用做强制转换。
我们再看一段代码
public class Main {
@Test
public void test() {
String messageContent = "[message]:泛型真好用";
String commentContent = "[comment]:泛型真好用";
Message message = new Message();
Comment comment = new Comment();
execute(messageContent, message::setContent);
execute(commentContent, comment::setContent);
System.out.println(message.getContent()); // [message]:泛型真好用
System.out.println(comment.getContent()); // [comment]:泛型真好用
}
private <T> void execute(T t, Consumer<T> consumer) {
consumer.accept(t);
}
}
class Message {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
class Comment {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
复制代码
- 我们在上面那个例子中可以发现,我们定义了两个类,
Message
和Comment
,然后我们定义了一个execute
方法,传入了一个泛型和一个消费型函数,我们在里面执行accept
方法,这样我们这个方法相当于是一个通用的算法,我们可以传入任意对象然后实现不同的consumer
函数就行
所以可以总结一下我们为什么要使用泛型
在编译时会有更强的类型检查,使我们在编译期就可以发现一些类型转换的问题(修复编译期错误比修复运行期错误更容易,因为运行期的错误很难找到)
我们使用泛型,在重写时定义好了泛型类型,就不需要在代码中进行强制转换了
我们可以通过泛型实现更加通用的算法
三丶泛型中的E,K,N,T分别代表什么?
E
:元素(在java集合框架中广泛使用)K
:键(Map<K, V>)N
:数字T
:类型V
:值 (作为返回值或者映射值)S
,U
,V
等:第2,第3,第4类型
它们其实是可以完全互换的,只是我们这样定义是一个约定
四丶泛型的使用
老规矩,我们还是先看代码
class Node<K, V> {
private K k;
private V v;
public Node(K k, V v) {
this.k = k;
this.v = v;
}
public K getK() {
return k;
}
public void setK(K k) {
this.k = k;
}
public V getV() {
return v;
}
public void setV(V v) {
this.v = v;
}
public <T extends BaseInteface> void sengMessage(T t){
System.out.println(t.num);
}
}
复制代码
-
类或者接口上面使用泛型我们需要在类名后面写一个<>,然后再写上我们的泛型(定义几个泛型,<>放几个泛型),我们在方法上面就可以使用这些泛型了
-
我们在方法上使用泛型有两种方式
- 使用我们类已经定义好了的泛型直接使用即可
- 使用我们没有在类中定义的泛型我们在方法名前使用尖括号定义泛型即可
我们接下来再看看我们经常说的上界通配符和下界通配符
public class Wildcar <T extends Number, V extends BaseIntefaceImpl>{
private T t;
private V v;
public Wildcar(T t, V v) {
this.t = t;
this.v = v;
}
public void show(List<? super Number> lists){
}
public static void main(String[] args) {
Wildcar<Integer, BaseIntefaceImpl> intefaceWildcar = new Wildcar<>(1, new BaseInfoParent());
System.out.println(intefaceWildcar.t.intValue());
System.out.println(intefaceWildcar.v);
}
}
复制代码
T extends Number
:这里是使用的上界通配符,我们在后面代码中所定义的T只能是extend的子类
List<? super Number> lists
:这里使用的是下界通配符,表示填充为任意Number的父类
总结
如果我们有阅读代码的习惯,我们会发现源码中大量应用了泛型,我们如果想编写出更具有通用性的代码,泛型一定是我们需要熟练掌握的,下一章我会分享一下泛型在工作中的简单应用。