网站首页 > java教程 正文
何为泛型
正如字面意思,泛化的类型,指在编码时无法确定某一个具体的类型,需要先使用一个占位符(建议大写,全英),当运行时传入具体的类型来替换这一个泛型标记
为什么需要泛型
伪需求
假设我们需要一个列表去存 `String` 类型的数据,那这个结构的设计为
```c
class MyListForString{
String get();
void set(Sring int)
}
```
然后,发现需要里一个列表去存取 `Integer` 类型的数据,就需要重新定义一个结构
```c
class MyListForInteger{
Integer get();
void set(Integer int)
}
```
然后,我们需要一个列表存储 xx 类型的数据。。
用 Object 优化
这个需求抽出来,其实就是需要有一个列表结构,然后能够存储一个指定的类型(这个类型又可能会根据需要进行变化),并且需要能正确的取出对应的类型,现在,我们为了应对变化的需求,重复编写差不多的 `MyListForXX`
根据多态以及向上转型的知识点(不懂的 xdm 可以来看看我准备的前置知识点 [Java 三大基础特性の多态](https://juejin.cn/post/6998132551411843108 "https://juejin.cn/post/6998132551411843108")),我们可以用 `Object` 优化为成下面的代码块
```c
class MyList{
Object get();
void set(Object obj)
}
```
现在已经能做到通用性了,就是用起来,emm,得小心一点
```c
MyList list = new MyList();
list.set(1);
list.set("1");
Integer a = (Integer)list.get(); // 代码中取出来的类型实际是 Object, 我们需要手动强制为存入时的类型
String s = (String)list.get();
```
正如伪代码演示的这样,我们存入数据的时候,没有限制,但是,取出来的话,就需要小心翼翼了,毕竟谁也不知道 `list` 中存入了什么类型的数据,或许,我们可以修改实例创建的描述来实现一点点毫无约束力的限制?
```c
MyList listOnlyForInt = new MyList(); // 注意看实例名
list.set(1);
```
泛型优化
或许是受够了这种软弱无力的约束,java 在 `JDK5` 中终于引入了泛型
用泛型的思路来对上面的代码进行修改,好处是显而易见的,我们在创建 `MyList` 的实例时,就告诉了 JVM 这个实例操作的泛型 T 的实例类型是 `String`,JVM 会在我们存入数据的数据校验数据类型是否匹配,会在我们取出的时候,自动的强转为对应的类型
JVM 的内部并没有什么魔法,底层的使用的类型还是 Object,但是,JVM 会根据我们传递的具体泛型类型来做入参时的校验,出参时的强转
```c
class MyList<T>{
T get();
void set(T int)
}
MyList<String> strs = new MyList<String>();
String s = strs.get();
```
java.util.ArrayList
来阅读一下我们经常使用的集合类 `ArrayList` 的 `get(), add()` 的源码
1、集合里实际上使用了 `Object[]` 的数组
2、`add()` 将数据存入到了 `object` 的数组当中,这里涉及到一个向上转型的隐藏操作(变相的验证,所有的 `class` 都是继承至 `Object`)
3、`get()` 对获取到的数据,强转为泛型 `E` 的实际类型
```c
public E get(int index) { // line: 432
rangeCheck(index);
return elementData(index);
}
E elementData(int index) { // line: 421
return (E) elementData[index];
}
public boolean add(E e) { // line: 461
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
transient Object[] elementData; // non-private to simplify nested class access // line: 135
```
原来这就是泛型
从上面例子来看,细心的同学应该发现了,“咦,原来我写 `List, Set, Map` 那些集合的操作就是用上了泛型鸭?”
随手写几个:
```c
List<String> list = new ArrayList<>();
Set<Integer> set = new HashSet<>();
HashMap<String, User> hashMap = new HashMap<>();
```
没错,泛型的基础使用就那么回事
泛型的边界
在泛型的世界里,其实也有类似继承关系的说法,比如 `<T extends String>, <T super String>`
#### extends
```c
class MyList<T extends String>{
T get();
}
```
这段代码看起来没有多大的改变,唯一的区别就是泛型 T 是继承自 `String` 的某一个类型
**此外,因为是继承关系,我们可以获取到任意类型的泛型实例 T,并将他们向上转型并当成 `String` 处理,然后通过多态的思路去实际调用泛型 T 中对应的方法**
#### super
```c
class MyList<T super String>{
void set(T int)
}
```
这里可以理解为,我们运行时传入的泛型 `T`,是 `String` 的父类,具体是哪一级的父类就不得而知
extends 与 super 的 区别
`super` 和 `extends` 不同的一点就是, **使用了 `<T extends XXX>` 的泛型操作可以 取值 当成 XXX 处理,使用了 `<T super XXX>` 的泛型只能将 XXX 对应的实例 传入 给泛型类型处理**
### 类型擦除
```c
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
Class c3 = new ArrayList().getClass();
System.out.println(c1 == c2); // true
System.out.println(c1 == c3); // true
```
从上述的代码来看,`ArrayList` 虽然申明了具体的泛型,或者是不带泛型,他们对应的 `Class` 全是一样的?变相的说明,在 `class` 中,根本就不存在泛型的痕迹?
方法上的泛型
虽然整篇文章都在讲 `class` 上的泛型,实际上,泛型也是能应用与方法上的
**但是泛型是没有办法直接初始化的,需要我们传入一个具体的泛型实例,或者是对应的泛型 `class`**
泛型方法的定义格式为 `[作用域] [static] <T> T 方法名(参数列表)`
以我封装的一个 `JsonUtil.toObject()` 为例,方法的定义就是 `public static <T> T toObject()`
```c
public static <T> T toObject(String json, Class<T> clz) {
if (json == null) {
try {
return clz.newInstance();
} catch (Exception e) {
LogUtil.exception("JsonUtil.toObject: <T>", e);
}
}
return json_.toObject(json, clz);
}
public static <T> T toObject(String json, T t) { // 或者我们也可以传入泛型的一个具体的实例,不过,这种情况会非常的少见
toObject(json, t.getClass());
}
// 伪代码调用
User user = JsonUtil.toObject(jsonStr, User.class);
```
泛型的标记
在本篇文章中,大量使用了标记 T,来表示泛型,我们也可以换成其他的符号来标记,但都要求,先使用 `<标记>` 的方式声明这是一个泛型操作,比如演示代码
```c
class List<E>{}
<A> A get();
<A, B, C> Tuple<A,B,C> tupleInstance(){}
```
元组
忘记介绍一个应用了泛型还比较好玩的结构,**元组**,这个数据结构的意义是 **为方法返回多个参数**,当然,我们用 **map, list** 也能实现,但不如元组这么优雅
首先,我们定义一个返回 2 个泛型类型的 **元组** 结构
```c
class Tuple<A, B>{
final A a;
final B b;
public Tuple(A a1, B b1){
a = a1;
b = b1;
}
}
```
就和初始化一个普通的 class 一样,用法也没什么差异,变化的地方就是这个元组内部的2个字段,他们的 `class` 是在运行时确定下来了,我们可以在需要的时候,任意变化他们的类型
```c
Tuple<Integer, String> tuple = new Tuple<>(1, "1");
Integer a = tuple.a;
String b = tuple.b;
```
?最后
创作不易,如果这篇博文对各位有帮助,希望各位小伙伴可以一键三连哦!,感谢支持,我们下次再见~~~
猜你喜欢
- 2024-12-14 JAVA基础小知识(干货哟)
- 2024-12-14 Java 21新特性-虚拟线程
- 2024-12-14 Java8新特性之 lambda 表达式
- 2024-12-14 java基础之java8新特性:Lambda表达式
- 2024-12-14 Java 9特性抢鲜看
- 2024-12-14 渣翻:从 Java 8 到 Java 18 的新语言特性
- 2024-12-14 Java面向对象的特性
- 2024-12-14 Java17 vs Java8: 新旧对决,这些Java 17新特性你不容错过
- 2024-12-14 Spring Boot 2.6新特性:使用Java 17的Record作为配置属性
- 2024-12-14 Java 18 新特性:简单Web服务器 jwebserver
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)