专业的JAVA编程教程与资源

网站首页 > java教程 正文

Java 高级特性之泛型

temp10 2024-12-14 15:15:33 java教程 12 ℃ 0 评论

何为泛型

正如字面意思,泛化的类型,指在编码时无法确定某一个具体的类型,需要先使用一个占位符(建议大写,全英),当运行时传入具体的类型来替换这一个泛型标记

为什么需要泛型

伪需求

假设我们需要一个列表去存 `String` 类型的数据,那这个结构的设计为

Java 高级特性之泛型

```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;

```

?最后

创作不易,如果这篇博文对各位有帮助,希望各位小伙伴可以一键三连哦!,感谢支持,我们下次再见~~~

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表