头图

前言
不久前,被人问到Java 泛型中的通配符 T,E,K,V,? 是什么?有什么用?这不经让我有些回忆起该开始学习Java那段日子,那是对泛型什么的其实有些迷迷糊糊的,学的不这么样,是在做项目的过程中,渐渐有又看到别人的代码、在看源码的时候老是遇见,之后就专门去了解学习,才对这几个通配符 T,E,K,V,?有所了解。
泛型有什么用?
在介绍这几个通配符之前,我们先介绍介绍泛型,看看泛型带给我们的好处。
Java泛型是JDK5中引入的一个新特性,泛型提供了编译是类型安全检测机制,这个机制允许开发者在编译是检测非法类型。泛型的本质就是参数化类型,就是在编译时对输入的参数指定一个数据类型。

类型安全:编译是检查类型是否匹配,避免了ClassCastexception的发生。

java 代码解读复制代码 // 非泛型写法(存在类型转换风险)
List list1 = new ArrayList();
list1.add("a");
Integer num = (Long) list1.get(0); // 运行时抛出 ClassCastException

// 泛型写法(编译时检查类型)
List<String> list2 = new ArrayList<>();
// list.add(1); // 编译报错
list2.add("a");
String str = list2.get(0); // 无需强制转换

消除代码强制类型转换:减少了一些类型转换操作。

java 代码解读复制代码// 非泛型写法
Map map1 = new HashMap();
map1.put("user", new User());
User user1 = (User) map1.get("user");

// 泛型写法
Map<String, User> map2 = new HashMap<>();
map2.put("user", new User());
// 自动转换
User user2 = map2.get("user");

3.代码复用:可以支持多种数据类型,不要重复编写代码,例如:我们常用的统一响应结果类。
java 代码解读复制代码@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result<T> {

/**
 * 响应状态码
 */
private int code;

/**
 * 响应信息
 */
private String message;

/**
 * 响应数据
 */
private T data;

/**
 * 时间戳
 */
private long timestamp;
其他代码省略...

增强可读性:通过类型参数就直接能看出要填入什么类型。

java 代码解读复制代码List<String> list = new ArrayList<>();

泛型里的通配符
我们在使用泛型的时候,经常会使用或者看见多种不同的通配符,常见的 T,E,K,V,?这几种,相信大家一定不陌生,但是真的问你他们有什么作用?有什么区别时,很多人应该是不能很好的介绍它们的,接下来我就来给大家介绍介绍。
T,E,K,V

T(Type)
T表示任意类型参数,我们举个例子

java 代码解读复制代码pubile class A<T>{

prvate T t;
//其他省略...

}

//创建一个不带泛型参数的A

A a = new A();
a.set(new B());
B b = (B) a.get();//需要进行强制类型转换

//创建一个带泛型参数的A

A<B> a = new A<B>();
a.set(new B());
B b = a.get();

E(Element)
E表示集合中的元素类型

java 代码解读复制代码List<E> list = new ArrayList<>();

K(Key)
K表示映射的键的数据类型

java 代码解读复制代码Map<K,V> map = new HashMap<>();

V(Value)
V表示映射的值的数据类型

java 代码解读复制代码Map<K,V> map = new HashMap<>();

通配符 ?

无界通配符 <?>
表示未知类型,接收任意类型

java 代码解读复制代码 // 使用无界通配符处理任意类型的查询结果
public void logQueryResult(List<?> resultList) {

   resultList.forEach(obj -> log.info("Result: {}", obj));

}

上界通配符 <? extends T>
表示类型是T或者是子类

java 代码解读复制代码 // 使用上界通配符读取缓存
public <T extends Serializable> T getCache(String key, Class<T> clazz) {

   Object value = redisTemplate.opsForValue().get(key);
   return clazz.cast(value);

}

下界通配符 <? super T>
表示类型是T或者是父类

java 代码解读复制代码 // 使用下界通配符写入缓存
public void setCache(String key, <? super Serializable> value) {

   redisTemplate.opsForValue().set(key, value);

}

综合示例:
java 代码解读复制代码import java.util.ArrayList;
import java.util.List;

public class Demo {

//实体类
class Animal {
    void eat() {
        System.out.println("Animal is eating");
    }
}

class Dog extends Animal {
    @Override
    void eat() {
        System.out.println("Dog is eating");
    }
}

class Husky extends Dog {
    @Override
    void eat() {
        System.out.println("Husky is eating");
    }
}

/**
 * 无界通配符 <?>
 */
// 只能读取元素,不能写入(除null外)
public static void printAllElements(List<?> list) {
    for (Object obj : list) {
        System.out.println(obj);
    }
    // list.add("test");  // 编译错误!无法写入具体类型
    list.add(null);       // 唯一允许的写入操作
}

/**
 * 上界通配符 <? extends T>
 */ 
// 安全读取为Animal,但不能写入(生产者场景)
public static void processAnimals(List<? extends Animal> animals) {
    for (Animal animal : animals) {
        animal.eat();
    }
    // animals.add(new Dog());  // 编译错误!无法确定具体子类型
}

/**
 * 下界通配符 <? super T>
 */ 
// 安全写入Dog,读取需要强制转换(消费者场景)
public static void addDogs(List<? super Dog> dogList) {
    dogList.add(new Dog());
    dogList.add(new Husky()); // Husky是Dog子类
    // dogList.add(new Animal()); // 编译错误!Animal不是Dog的超类
    
    Object obj = dogList.get(0); // 读取只能为Object
    if (obj instanceof Dog) {
        Dog dog = (Dog) obj;     // 需要显式类型转换
    }
}

public static void main(String[] args) {
    // 测试无界通配符
    List<String> strings = List.of("A", "B", "C");
    printAllElements(strings);

    List<Integer> integers = List.of(1, 2, 3);
    printAllElements(integers);

    // 测试上界通配符
    List<Dog> dogs = new ArrayList<>();
    dogs.add(new Dog());
    processAnimals(dogs);

    List<Husky> huskies = new ArrayList<>();
    huskies.add(new Husky());
    processAnimals(huskies);

    // 测试下界通配符
    List<Animal> animals = new ArrayList<>();
    addDogs(animals);
    System.out.println(animals);

    List<Object> objects = new ArrayList<>();
    addDogs(objects);
}

}

我们需要清楚,这些只是我们开发过程中约定,不是强制规定,但遵循它们可以提高代码的可读性。
总结
我们在很多时候只是单纯的会使用某些技术,但是对它们里面许许多多常见的都是一知半解的,只是会使用确实很重要,但是如果有时间,我们不妨好好的在对这些技术进行深入学习,不仅知其然,而且知其所以然,这样我们的技术才会不断提升进步。


运维社
12 声望4 粉丝