0、引言
在项目开发的过程中,常常需要将一些高频复用的方法封装成工具类,例如最近学到的Redis缓存中,解决缓存穿透、解决缓存击穿的方法(例如解决缓存穿透的问题的方法queryWithPassThrough),传入一个Long型id,返回你要查询的数据。
问题在于,queryWithPassThrough方法不能指定一种返回类型,比如店铺类型、人员类型、评论类型等等各种类型,作为工具类,我们希望它能够随着调用而返回对应的类型。
因此,需要使用泛型方法来完成这个需求。
1、泛型方法的区分与定义
定义:
在确定某个方法是否为泛型方法时,需要判断其是否声明了类型参数,类型参数的作用域是在方法内还是在整个泛型类中。
如果只涉及到特定方法操作的类型参数,则该方法是泛型方法;如果类型参数定义在泛型类中,则不论它们是否被方法使用,该方法都不是泛型方法。
举例:
例如, 按照上述原则,用以下例子method0、1、2进行说明:
class a<T,M>{
T t;
M m;
public void method0(T t,M m){
//不是泛型方法,仅仅是用了泛型类的标识符
System.out.println(t.getClass());
System.out.println(m.getClass());
System.out.println("====================");
}
public<S,U>void method1(S s,U u){
System.out.println(s.getClass());
System.out.println(u.getClass());
System.out.println("====================");
}//是泛型方法,根据传入的参数自己规划数据类型
public<K>void method2(K k,M m){
//k来自于泛型方法,而m是泛型类的标识符,但因为有K
//因此也是泛型方法
System.out.println(k.getClass());
System.out.println(m.getClass());
}
}
method0
方法的类型参数 T 和 M 都是定义在实例变量上的泛型类型,它们只在泛型类中使用,而不涉及方法的特定操作。因此,method0
不是泛型方法。
method1
方法声明了一个类型参数 S 和一个类型参数 U,这些参数只在该方法中使用。由于它们是方法级别的,所以method1
是泛型方法。
method2
方法声明了一个类型参数 K,它只在该方法中使用,所以method2
是泛型方法。但是,方法中的参数 M 是泛型类的类型参数,因此也可以用在泛型方法中。
2、使用泛型方法实现自适应返回类型
public <R,ID> R queryWithPassThrough(
String keyPrefix, ID id, Class<R> type,
Function<ID, R> dbFallback,
Long time, TimeUnit unit)
例如上述方法,是防止缓存穿透的方法定义,<R,ID>是定义的两个泛型,R类型是这个方法的返回类型(即实际要查询的数据类型),通过函数式编程Function函数,来实现查询ID类型的id所对应的数据类型R的数据。
这里将介绍一下:(1)Function函数的使用。(2)用反射实现返回指定类型数据
2.1 Function函数的使用
由于具体的查询语句可能是变化的,因此,“具体怎么查”,这个查询函数不是定死的,作为工具类也不能把它写死,而是采用函数式编程的方法:
即,要用什么函数查询,就传入什么函数。
Funtion的使用方法与底层原理:
从源码看,对于Function接口,其内部存在一个apply方法:
对于第二章开头的代码,内部调用:
R r = dbFallback.apply(id);
在本例中:
apply
是Function
接口中定义的抽象方法,用于接受一个参数并返回一个结果。在Function<ID, R>
中,apply
方法接受一个类型为ID
的参数,返回一个类型为R
的结果。因此,我们只需要重写这个apply方法,变成我们想要使用的方法即可!
在以上的基础上如果想要调用函数,可以使用lambda表达式,传入id->getById(id),则相当于:
Function<ID, R> dbFallback = id -> getById(id);
//或者:
Function<ID, R> dbFallback = this::getById;
//可能有的人对lambda语法不熟悉,其实就是使用了匿名内部类的方式实现apply方法:
//即:
Function<ID, R> dbFallback = new Function<>(){
@Override
public R apply(ID id){
return getById(id);
}
}
在调用时的语句就可以这么写:
Shop shop = cacheClient .queryWithPassThrough(CACHE_SHOP_KEY, id, Shop.class, id->getById(id), CACHE_SHOP_TTL, TimeUnit.MINUTES);
在实际上,我们想传入什么方法来进行查询都可以(想怎么实现apply都可以),从而实现了函数式编程。
2.2 用反射实现返回类型的变化
public <R,ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type,
Function<ID, R> dbFallback, Long time, TimeUnit unit)
在以上的方法定义下,编写具体查询代码的时候发现:从Redis里get得到的数据时json字符串格式的,但是我们想要的是任意的“R”型,比如商店类的Shop类型,等类型,该怎么办呢?
String key = keyPrefix + id;
// 1.从redis查询商铺缓存
String json = stringRedisTemplate.opsForValue().get(key);
// 2.判断是否存在
if (StrUtil.isNotBlank(json)) {
// 3.存在,直接返回
return JSONUtil.toBean(json, type);
}
这里可以采用反射的方法:使用JSONUtil类中的toBean方法:
传入:Shop.class即可,就会通过这个toBean进行转换。