1.泛型(Generics)产生的背景
在Java语言早期版本中,集合框架(比如List)中的元素是Object类型的,这就意味着可以向集合中存储任何对象。但是,使用Object类型会因为运行时类型错误和难以维护的代码而带来许多问题。因此,为了提高代码的类型安全性和可维护性,Java引入了泛型。
Java泛型的设计目标之一是提高代码的可读性。泛型使用类型参数来替代原来集合框架中使用的Object类型。例如,在Java 5之前,我们需要使用以下方式创建一个字符串类型的ArrayList:
List names = new ArrayList();
names.add("John");
names.add("Tom");
在这个例子中,names列表中的元素类型是Object。这意味着我们需要在运行时对列表中的元素进行类型转换。对于大型的应用程序,这种类型转换的逻辑可能会在代码中的多个位置出现,增加了代码的复杂性和维护难度。而在泛型应用之后,我们可以使用下面的代码:
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Tom");
在这个例子中,中括号中的表示元素类型为String,这意味着我们可以放心地在代码中使用String类型的变量,而不用担心类型错误。这种方式更直观、更可读、更可维护。
因此,Java泛型背后的设计目标
- 是提高代码的可读性和可维护性,避免类型错误,降低代码的错误风险。
- 另外,泛型还能够提高代码的可复用性,避免重复的代码,增强了Java语言的灵活性和通用性。
2.泛型可以使用的位置
在Java中,泛型可以应用于以下几个位置:
- 类型参数用于类声明中,表名类中代码凡是出现T,类型都是相同的,例如:
public class MyClass<T> {
// generic class
}
- 类型参数用于接口声明中,例如:
public interface MyInterface<T> {
// generic interface
}
- 类型参数用于方法声明中,例如:
public <T> void printList(List<T> list) {
// generic method
}
这个使用场景比较多,常见使用为边界限制,比如
public <T extends Number> T someFunction(List<T> list) {
// 函数体
}
在这个例子中,类型参数T被限制为Number的子类型,因此可以在函数中使用Number的任何实现类。
再举一个实际例子,该例中定义了R,ID两个泛型,在方法体中就可以直接使用这两种泛型了,提高了代码的通用性,读起来也清晰很多。
/**
* 防止缓存穿透的查询方法
*
* @param keyPrefix
* @param id
* @param type json字符串反序列化的对象类型
* @param dbFallback 回调函数,用于查询数据库
* @param time
* @param unit
* @param
* @param
* @return
*/
public <R,ID> R queryWithPassThrough(
String keyPrefix, ID id, Class type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
String key = keyPrefix + id;
// 1.从redis查询商铺缓存
String json = stringRedisTemplate.opsForValue().get(key);
// 2.判断是否存在
if (StrUtil.isNotBlank(json)) {
// 3.存在,直接返回
return JSONUtil.toBean(json, type);
}
// 判断命中的是否是空值
if (json != null) {
// 返回一个错误信息
return null;
}
// 4.不存在,根据id查询数据库
R r = dbFallback.apply(id);
// 5.不存在,返回错误
if (r == null) {
// 将空值写入redis
stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
// 返回错误信息
return null;
}
// 6.存在,写入redis
this.set(key, r, time, unit);
return r;
}
- 类型参数用于构造函数中,例如:
public class MyClass<T> {
public MyClass(T t) {
// generic constructor
}
}
注意:泛型不能直接应用于基本类型,而只能是引用类型。此外,在Java的语言规范中,泛型的类型擦除指的是所有泛型类型参数都被替换为它们的边界或者Object。因此,在编译时,泛型类或者方法中的类型实参会被擦除,而运行时只能获得到它们的原始类型。