必须要继承才能获得泛型类型吗?
这是一个非常好的问题!不,不一定非要通过继承才能获取泛型类型。 有多种方式可以在运行时获取泛型信息。
1. 获取泛型类型的几种方式
1.1 方式一:通过继承获取(MyBatis-Plus的方式)
JAVA
// 子类继承时指定具体类型
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
// 父类可以通过反射获取子类的泛型信息
class ServiceImpl<M, T> {
protected Class<T> getEntityClass() {
// 通过 this.getClass() 获取子类的泛型信息
ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
Type[] types = type.getActualTypeArguments();
return (Class<T>) types[1]; // 获取第二个泛型参数
}
}
1.2 方式二:构造时传入 Class
JAVA
// 1. 构造函数传入
public class GenericService<T> {
private final Class<T> entityClass;
public GenericService(Class<T> entityClass) {
this.entityClass = entityClass;
}
}
// 使用
GenericService<User> service = new GenericService<>(User.class);
// 2. 工厂方法传入
public class GenericService<T> {
public static <T> GenericService<T> create(Class<T> entityClass) {
return new GenericService<>(entityClass);
}
}
1.3 方式三:使用方法参数传递
JAVA
public class GenericService<T> {
public void doSomething(Class<T> entityClass, T entity) {
// 已经有了 Class 对象
}
public <E> List<E> queryByType(Class<E> type) {
// 通过方法参数获取类型
return jdbcTemplate.query("SELECT * FROM " + getTableName(type),
new BeanPropertyRowMapper<>(type));
}
}
1.4 方式四:通过匿名内部类
JAVA
// 创建匿名内部类实例
GenericService<User> service = new GenericService<User>() {};
// 可以解析匿名类的泛型信息
2. 为什么 MyBatis-Plus 选择继承方式?
2.1 设计理念:约定优于配置
JAVA
// 简单:只需继承,无需额外配置
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
// 自动获得所有功能
}
2.2 减少重复代码
JAVA
// 如果不通过继承,每个Service都要:
@Service
public class UserService {
private final MyBatisTemplate<User> template;
public UserService(MyBatisTemplate<User> template) {
this.template = template;
}
// 需要实现每个CRUD方法...
}
2.3 Spring 集成友好
JAVA
// Spring 自动扫描时能识别继承关系
@Repository
public interface UserMapper extends BaseMapper<User> {
// 自动注册为Bean
}
// Service也能自动注册
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
// 自动注入Mapper
}
3. 其他框架的泛型获取方式
3.1 Gson(Google JSON库)
JAVA
// 通过TypeToken在运行时获取泛型
Type type = new TypeToken<List<User>>() {}.getType();
List<User> users = gson.fromJson(json, type);
// 通过匿名内部类创建TypeToken
3.2 Spring Data JPA
JAVA
// 方式1:继承方式(类似MyBatis-Plus)
public interface UserRepository extends JpaRepository<User, Long> {
// 泛型信息在接口定义中
}
// 方式2:RepositoryFactory
@Repository
public class CustomUserRepository {
// 通过RepositoryFactory动态创建
}
3.3 Jackson JSON库
JAVA
// JavaType 可以表示泛型类型
JavaType type = mapper.getTypeFactory()
.constructParametricType(List.class, User.class);
List<User> users = mapper.readValue(json, type);
4. 不依赖继承的完整示例
4.1 构造函数传递方式
JAVA
public class GenericService<T> {
private final Class<T> entityClass;
private final String tableName;
public GenericService(Class<T> entityClass) {
this.entityClass = entityClass;
this.tableName = resolveTableName(entityClass);
}
public T findById(Long id) {
// 使用entityClass进行查询
return queryById(entityClass, id);
}
private String resolveTableName(Class<?> clazz) {
TableName anno = clazz.getAnnotation(TableName.class);
return anno != null ? anno.value() : clazz.getSimpleName();
}
}
// 使用
GenericService<User> userService = new GenericService<>(User.class);
4.2 反射方式(不使用继承)
JAVA
public class GenericService<T> {
private Class<T> entityClass;
@SuppressWarnings("unchecked")
public GenericService() {
// 通过获取当前类的泛型参数
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
if (type.getActualTypeArguments().length > 0) {
this.entityClass = (Class<T>) type.getActualTypeArguments()[0];
} else {
throw new IllegalArgumentException("必须指定泛型参数");
}
}
// 必须被子类继承才能工作
}
// 仍然需要继承
public class UserService extends GenericService<User> {
// ...
}
5. 各种方式的对比
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 继承 | 简洁、自动、符合框架设计 | 强耦合、单继承限制 | ORM框架、工具类库 |
| 构造函数传入 | 灵活、解耦、可测试 | 冗余代码、需要显式传入 | 通用工具、配置灵活的场景 |
| 方法参数传入 | 最灵活、类型安全 | 每个方法都要传参数 | 工具方法、通用查询 |
| 匿名内部类 | 类型安全、可获取完整泛型 | 语法复杂、创建多个实例 | JSON库、类型转换 |
6. 为什么 MyBatis-Plus 选择继承?
6.1 历史原因
MyBatis-Plus 延续了 MyBatis 的"通用 Mapper"设计理念:
JAVA
// MyBatis 传统方式
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User findById(Long id);
// 每个方法都要写SQL
}
// MyBatis-Plus 方式
public interface UserMapper extends BaseMapper<User> {
// 自动拥有CRUD方法
}
6.2 开发效率
继承方式让开发者用最少的代码获得最多的功能:
JAVA
// 只需一行继承
@Service
public class UserService extends ServiceImpl<UserMapper, User> {
// 自动拥有:
// 1. CRUD操作
// 2. 分页支持
// 3. 条件构造器
// 4. 自动填充
// 5. 逻辑删除
// ...
}
6.3 框架集成
继承方式更容易与 Spring 等框架集成:
JAVA
// Spring 可以自动扫描和注入
@Autowired
private UserService userService; // 自动识别为Service Bean
7. 替代方案示例
如果你不想使用继承,可以这样设计:
JAVA
// 1. 定义泛型服务接口
public interface IGenericService<T> {
T findById(Long id);
List<T> findAll();
boolean save(T entity);
}
// 2. 实现类(不依赖继承)
public class GenericService<T> implements IGenericService<T> {
private final Class<T> entityClass;
private final BaseMapper<T> mapper;
public GenericService(Class<T> entityClass, BaseMapper<T> mapper) {
this.entityClass = entityClass;
this.mapper = mapper;
}
@Override
public T findById(Long id) {
return mapper.selectById(id);
}
// 其他方法实现...
}
// 3. 配置Bean
@Configuration
public class ServiceConfig {
@Bean
public GenericService<User> userService(UserMapper mapper) {
return new GenericService<>(User.class, mapper);
}
}
总结
不一定非要通过继承才能获取泛型类型,但有多种方式:
- 继承方式(MyBatis-Plus采用):简洁但耦合度高
- 构造函数传入:灵活但代码冗余
- 方法参数传入:最灵活但使用麻烦
- 匿名内部类:类型安全但语法复杂
MyBatis-Plus 选择继承方式主要是为了:
- 简化开发:一行代码获得完整功能
- 框架集成:与Spring无缝集成
- 使用习惯:符合Java开发者的习惯
- 历史延续:延续MyBatis的设计理念
选择哪种方式取决于具体的需求和场景:
- 框架开发:通常选择继承或反射
- 业务开发:可以选择更灵活的方式
- 工具库:通常提供多种选择
反射方式(不使用继承) public class GenericService<T> { private Class<T> entityClass;
@SuppressWarnings("unchecked")
public GenericService() {
// 通过获取当前类的泛型参数
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
if (type.getActualTypeArguments().length > 0) {
this.entityClass = (Class<T>) type.getActualTypeArguments()[0];
} else {
throw new IllegalArgumentException("必须指定泛型参数");
}
}
// 必须被子类继承才能工作
}
// 仍然需要继承 public class UserService extends GenericService<User> { // ... } ?????
您观察得非常准确!这段代码确实是矛盾的。您指出了一个关键点:
1. 矛盾所在
JAVA
public class GenericService<T> {
public GenericService() {
// 通过获取当前类的泛型参数
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
// ...
}
// 必须被子类继承才能工作
}
这段代码的问题在于:
- 它声称"不使用继承"
- 但实际上仍然需要继承才能工作
2. 为什么会这样?
这是因为 getClass().getGenericSuperclass() 的工作原理:
JAVA
// 1. 直接实例化(失败)
GenericService<User> service = new GenericService<>(); // ❌ 获取不到泛型
// 2. 通过匿名内部类(实际上是继承)
GenericService<User> service = new GenericService<User>() {}; // ✅ 可以获取泛型
// 这里创建了一个 GenericService<User> 的匿名子类
// 3. 通过显式子类(需要继承)
public class UserService extends GenericService<User> { } // ✅ 可以获取泛型
3. 真正"不使用继承"的方式
3.1 构造函数传入(真正不依赖继承)
JAVA
public class GenericService<T> {
private final Class<T> entityClass;
// 通过构造函数传入,完全不依赖继承
public GenericService(Class<T> entityClass) {
this.entityClass = entityClass;
}
}
// 使用
GenericService<User> service = new GenericService<>(User.class);
3.2 工厂方法
JAVA
public class GenericService<T> {
private final Class<T> entityClass;
private GenericService(Class<T> entityClass) {
this.entityClass = entityClass;
}
// 静态工厂方法
public static <T> GenericService<T> of(Class<T> entityClass) {
return new GenericService<>(entityClass);
}
}
// 使用
GenericService<User> service = GenericService.of(User.class);
4. 为什么 getGenericSuperclass() 需要继承?
4.1 泛型信息存储位置
JAVA
// GenericService<User> 的泛型信息存储在:
// 1. 类定义的Signature属性中
// 2. 只有在有具体实现/子类时才能获取
// 直接实例化时:
GenericService<User> service = new GenericService<User>();
// 编译后:GenericService service = new GenericService(); // 泛型被擦除
// 通过子类时:
class UserService extends GenericService<User> { }
// 编译后,UserService的父类信息中保留了泛型
4.2 实际内存中的类型信息
对象实例 → 类对象 → 泛型签名
↑
getClass() 返回运行时类
5. 修正后的"真正不依赖继承"示例
5.1 方式一:类型令牌(Type Token)
JAVA
public class GenericService<T> {
private final TypeReference<T> typeReference;
public GenericService(TypeReference<T> typeReference) {
this.typeReference = typeReference;
}
public void process() {
// 获取泛型类型
Type type = typeReference.getType();
// ...
}
}
// TypeReference 类似 Jackson/Gson 的实现
public abstract class TypeReference<T> {
private final Type type;
protected TypeReference() {
Type superClass = getClass().getGenericSuperclass();
this.type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
}
// 使用
GenericService<List<User>> service =
new GenericService<>(new TypeReference<List<User>>() {}); // 匿名内部类
5.2 方式二:通过方法参数传递
JAVA
public class GenericService<T> {
// 每个需要类型的方法都接受Class参数
public T findById(Serializable id, Class<T> entityClass) {
return queryById(id, entityClass);
}
public List<T> findAll(Class<T> entityClass) {
return queryAll(entityClass);
}
}
6. MyBatis-Plus 的设计选择
MyBatis-Plus 选择继承方式,因为:
6.1 继承链已经存在
JAVA
// 用户代码
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
// MyBatis-Plus
public class ServiceImpl<M, T> {
protected Class<T> getEntityClass() {
// 利用已有的继承链
return (Class<T>) GenericTypeUtils.resolveTypeArguments(
this.getMapperClass(), BaseMapper.class)[0];
}
}
6.2 简化API设计
JAVA
// 继承方式:简洁
userService.getById(1L);
// 非继承方式:冗长
userService.getById(1L, User.class);
7. 总结
您指出的矛盾是正确的。那段代码声称"不使用继承",但实际上:
- 错误的说法:
getClass().getGenericSuperclass()需要继承关系 - 真正不依赖继承:应该通过构造函数、方法参数或工厂方法传入Class对象
- MyBatis-Plus的实际情况:选择了继承方式,因为这样API更简洁
8. 正确的实现对比
JAVA
// ❌ 伪"不依赖继承"(实际仍需继承)
public class GenericService<T> {
public GenericService() {
// 仍然需要继承才能工作
ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
}
}
// ✅ 真正不依赖继承
public class GenericService<T> {
private final Class<T> entityClass;
public GenericService(Class<T> entityClass) {
this.entityClass = entityClass; // 直接传入,不依赖继承
}
}
关键点
您的质疑是正确的:如果代码中使用 getGenericSuperclass(),那就一定需要继承关系才能获取泛型信息。真正不依赖继承的方式是通过参数显式传递Class对象。
这是Java泛型类型擦除带来的限制,也是各种框架选择不同解决方案的原因。