使用MyBatis-Plus时给SFunction接口创建属性数组以简化代码
文章目录
通过一个简单的实例介绍如何在使用MyBatis-Plus时给SFunction接口创建属性数组以简化代码,同时巩固下自己的Java
基础知识。
背景
实体类User
源码如下
import lombok.Data;
@Data
public class User {
private Long id;
private String name;
private int age;
private String email;
private String department;
private String phone;
}
接口类UserMapper
源码如下,其集成了MyBatis-Plus
的基础接口,以便后续可使用MyBatis-Plus
进行快速开发。
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.lucumt.entity.User;
public interface UserMapper extends BaseMapper<User> {
}
对应的测试代码如下,需要查询User
类中的相关属性,可以看出第8行的代码长度很长,出现了滚动条导致阅读起来不太方便
public class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
public void testQueryUsers() {
List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery().select(User::getId, User::getName, User::getAge, User::getEmail, User::getPhone, User::getDepartment));
Assertions.assertNotNull(userList);
}
}
虽然可以通过换行来消除滚动条,但本质上还是一行代码,当该类的属性很多时,若需要实现类似SELECT *
的效果则会导致多个换行,还是不太便于阅读理解。
public class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
public void testQueryUsers() {
List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery().select(User::getId, User::getName,
User::getAge, User::getEmail, User::getPhone, User::getDepartment));
Assertions.assertNotNull(userList);
}
}
自己期望将这些属性都放到一个数组中,在实现简化的同时也能便于阅读理解。
尝试
最开始自己很直接的想把上述属性提取到一个数组中,将代码修改如下
public class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
public void testQueryUsers() {
SFunction<User, ?>[] properties = new SFunction[]{User::getId, User::getName,
User::getAge, User::getEmail, User::getPhone, User::getDepartment};
List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery().select(properties));
Assertions.assertNotNull(userList);
}
}
修改完毕后编译器立即显示编译错误,直接放置到数据中的方式不通。
将其修改为如下后,编译错误消失,此时虽然实现了将它们都放到数组中,但是每个属性都需要进行变量声明,代码篇幅较多,不是自己期望的最优解。
public class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
public void testQueryUsers() {
SFunction<User, ?> getId = User::getId;
SFunction<User, ?> getName = User::getName;
SFunction<User, ?> getAge = User::getAge;
SFunction<User, ?> getEmail = User::getEmail;
SFunction<User, ?> getPhone = User::getPhone;
SFunction<User, ?> getDepartment = User::getDepartment;
SFunction<User, ?>[] properties = new SFunction[]{getId, getName, getAge, getEmail, getPhone, getDepartment};
List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery().select(properties));
Assertions.assertNotNull(userList);
}
}
分析
为啥第一种方式方式会报错,而第二种方式却能正常工作呢?
自己虽然知道这和Java
和泛型相关的类型擦除,但限于自己孱弱的Java
基础知识,并不能给出一个很好的解释。
我决定寻求更专业的协助,在Stackoverflow
上提出了一个问题,有2个大佬都从Java语言规范的角度给出了让人信服的回答。
首先前面图中出现的错误不是根本原因,基于我最开始的写法会导致多个错误,而IDE只会展示最顶层的错误,由此让人很迷惑,实际的原因是类型擦除
。
在Array Initializers中有如下说明
Each variable initializer must be assignment-compatible (§5.2) with the array’s component type, or a compile-time error occurs.
翻译成人话就是数组中的每一个元素的类型都必须与数组类型兼容,否则会出现编译错误。
前述的数组创建方式
SFunction<User, ?>[] properties = new SFunction[]{User::getId, User::getName,
User::getAge, User::getEmail, User::getPhone, User::getDepartment};
实际上等价于下述代码
SFunction getId = User::getId;
SFunction getName = User::getName;
SFunction getAge = User::getAge;
SFunction getEmail = User::getEmail;
SFunction getPhone = User::getPhone;
SFunction getDepartment = User::getDepartment;
SFunction<User, ?>[] properties = new SFunction[]{getId, getName, getName, getEmail, getPhone, getDepartment};
而上述6行代码是无法编译通过的,其错误信息和前面截图一样,这是代码层面的问题溯源。
为什么那6行代码无法编译呢?需要在Java语言规范
的Function Types中去寻找答案
The function type of the raw type of a generic functional interface I<…> is the erasure of the function type of the generic functional interface I<…>.
在这个回答中回答者结合上述说明给出了详细的解释:
由于存在继承关系,通用的SFunction<T, R>
函数类型与Function<T, R>相同,其利用T
作为输入,R
作为输出,可以简单记做T->R
。由于T
和R
都没有进行类型约束(即通过extends
或super
关键字进行约束),故它们都将被类型擦除为默认的Object
类型,故SFunction<T, R>
的函数类型实际上为Object->Object
。
回到我们的问题中来User::getId
是否兼容Object->Object
呢?显然不是,其输入为 User
,输出为Integer
,实际的函数类型为User->Integer
,故而不兼容。
基于上述分析以及对应回答者的建议,有如下几种方式:
-
修改源码以兼容函数类型
Function<? super Object, ? extends Object> properties = User::getId;
-
采用
List
代替数组List<SFunction<User, ?>> propertyList = Lists.newArrayList(User::getId, User::getName);
-
显示的指定为实际的类型
// 通过变量实现 SFunction<User, ?>[] properties = new SFunction[] {val -> ((User) val).getId(), val -> ((User) val).getName() }; // 方法引用实现 SFunction<User, ?>[] propertiesss = new SFunction[]{(SFunction<User, ?>) User::getId, (SFunction<User, ?>) User::getName};
-
编写一个特定的方法用于进行类型转化
<T, R> SFunction<T, R> makeFunction(SFunction<T, R> x) { return x; }
改进
前面的4种方式,第1种由于需要修改源码不现实,故不考虑,第2种是修改数据类型与MyBatis-Plus
的方法签名不匹配,也不考虑。
采用第3种方式可将代码修改如下,此种方式与之前的差别不大,还是无法避免强制转换
public class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
public void testQueryUsers() {
SFunction<User, ?>[] properties = new SFunction[]{
(SFunction<User, ?>) User::getId,
(SFunction<User, ?>) User::getName,
(SFunction<User, ?>) User::getAge,
(SFunction<User, ?>) User::getEmail,
(SFunction<User, ?>) User::getPhone,
(SFunction<User, ?>) User::getDepartment
};
List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery().select(properties));
Assertions.assertNotNull(userList);
}
}
第4种方式相较于第3种只是提供了一个额外的方法进行统一的类型转化,但本质上和之前的一样
public class UserMapperTest {
@Resource
private UserMapper userMapper;
@Test
public void testQueryUsers() {
SFunction<User, ?>[] properties = new SFunction[]{
getProperty(User::getId),
getProperty(User::getName),
getProperty(User::getAge),
getProperty(User::getEmail),
getProperty(User::getPhone),
getProperty(User::getDepartment)
};
List<User> userList = userMapper.selectList(Wrappers.<User>lambdaQuery().select(properties));
Assertions.assertNotNull(userList);
}
private <T, R> SFunction<T, R> getProperty(SFunction<T, R> x) {
return x;
}
}
后记
前述问题的解决主要是依赖于Java语言规范
,而自己却一直不怎么关注,我想起了另外一件事情。
虽然自己的在Stackoverflow
上的积分也很多,但实话实说其中的大部分都是我在ChatGPT
没有出来之前通过回答大量的JavaScript
水出来的
如上图所示,截止到当前个人在Stackoverflow
上的总积分为13496,而自己回答了729个问题,排除掉自己提问带来的积分,平均每个回答带来的积分只有18.4
,完全是数量压倒质量(quantity over quality)。
具体到相关问题和回答时,自己的表现也一般,如下图所示,得分最高的回答也才10个赞,只贡献了50分。
而自己关注的另一位大佬的回答概况如下,其平均得分是我的两倍
在具体问题和回答上的积分更是碾压我!
深入查看后可发现该大佬很多问题都是基于Java语言规范
回答的,此种回答言简意赅,直击问题并且很容易获得很多赞,从而快速提高在Stackoverflow
中的积分。
要想获得类似上面大佬一样的成就,必须要熟练掌握Java语言规范
,而我们日常学习与开发中,更多关注的是Java
的基本语法与使用场景,对Java语言规范
则很少有人深入学习与研究。
结合当前Java
就业市场的内卷以及大的环境,如果不能深入钻研并精通某一方面,很容易遭到淘汰,我们不仅要提高知识面的广度,更要熟练掌握某一个领域才能在残酷的竞争中立足。
Quality over Quantity!