Java泛型类型获取方式全面解析

小豆丁 3小时前 ⋅ 3 阅读
聊天记录
头像

必须要继承才能获得泛型类型吗?

头像

这是一个非常好的问题!不,不一定非要通过继承才能获取泛型类型。 有多种方式可以在运行时获取泛型信息。

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);
    }
}

总结

不一定非要通过继承才能获取泛型类型,但有多种方式:

  1. 继承方式(MyBatis-Plus采用):简洁但耦合度高
  2. 构造函数传入:灵活但代码冗余
  3. 方法参数传入:最灵活但使用麻烦
  4. 匿名内部类:类型安全但语法复杂

MyBatis-Plus 选择继承方式主要是为了:

  • 简化开发:一行代码获得完整功能
  • 框架集成:与Spring无缝集成
  • 使用习惯:符合Java开发者的习惯
  • 历史延续:延续MyBatis的设计理念

选择哪种方式取决于具体的需求和场景:

  • 框架开发:通常选择继承或反射
  • 业务开发:可以选择更灵活的方式
  • 工具库:通常提供多种选择
头像

反射方式(不使用继承) public class GenericService<T> { private Class<T> entityClass;

@SuppressWarnings(&quot;unchecked&quot;)
public GenericService() {
    // 通过获取当前类的泛型参数
    ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
    if (type.getActualTypeArguments().length &gt; 0) {
        this.entityClass = (Class&lt;T&gt;) type.getActualTypeArguments()[0];
    } else {
        throw new IllegalArgumentException(&quot;必须指定泛型参数&quot;);
    }
}

// 必须被子类继承才能工作

}

// 仍然需要继承 public class UserService extends GenericService<User> { // ... } ?????

头像

您观察得非常准确!这段代码确实是矛盾的。您指出了一个关键点:

1. 矛盾所在

JAVA
public class GenericService<T> {
    public GenericService() {
        // 通过获取当前类的泛型参数
        ParameterizedType type = (ParameterizedType) getClass().getGenericSuperclass();
        // ...
    }
    // 必须被子类继承才能工作
}

这段代码的问题在于:

  1. 它声称"不使用继承"
  2. 但实际上仍然需要继承才能工作

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. 总结

您指出的矛盾是正确的。那段代码声称"不使用继承",但实际上:

  1. 错误的说法getClass().getGenericSuperclass() 需要继承关系
  2. 真正不依赖继承:应该通过构造函数、方法参数或工厂方法传入Class对象
  3. 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泛型类型擦除带来的限制,也是各种框架选择不同解决方案的原因。

全部评论: 0

    我有话说: