JavaSE-06-泛型(4个实战)
JavaSE-06-泛型(4个实战)
泛型的作用,将类型参数化,更高一级的抽象,扩展能力边界,同时可以限定上下限。在编译期,进行类型擦除,将在运行期强转cast报错提前检验出来,类型更安全。
Java 泛型(Generics)是 Java 5 引入的重要特性,它为集合类提供了类型安全机制,并提升了代码的可重用性。
画板
一、使用强转
集合是可以存放任意对象的,只要把对象存储集合后,对象会被提升成Object类型。取出每一个对象,在进行相应的操作前,这时必须采用类型转换。
代码示例:
public class Test { public static void main (String[] args) { Collection coll = new ArrayList (); coll.add( "abc11" ); coll.add( "water" ); coll.add( 18 ); //由于集合没有做任何限定,任何类型都可以给其中存放,就是Object类型 Iterator it = coll.iterator(); while (it.hasNext()){ //需要打印每个字符串的长度,就要把迭代出来的对象转成String类型 String str = (String) it.next(); System.out.println(str.length()); } } }
程序在运行时发生了问题java.lang.ClassCastException。为什么会发生类型转换异常呢? 分析下:由于集合中什么类型的元素都可以存储,导致取出时强转引发运行时 ClassCastException。怎么来解决这个问题?
Collection虽然可以存储各种对象,但实际上通常Collection只存储同一类型对象。例如都是存储字符串对象。因此在JDK5之后,新增了泛型(Generic)语法,在设计API时可以指定类或方法支持泛型,这样使用API的时候也变得更为简洁,并得到了编译时期的语法检查。
泛型:可以在类或方法中预支地使用未知的类型。泛型是一种参数化类型机制,允许在定义类、接口或方法时使用一个或多个类型参数。这些类型参数在使用时被具体类型替代。
List<String> list = new ArrayList <>(); list.add( "Hello" ); // 编译错误:list.add(123);
一般在创建对象时,将未知的类型确定具体的类型。当没有指定泛型时,默认类型为Object类型。
二、泛型好处
1、类型安全:编译期检查类型,将运行时期的ClassCastException,转移到了编译时期变成了编译失败。避免运行时 ClassCastException。
2、代码复用:同一套逻辑可以适用于多种数据类型。
3、减少强制类型转换:无需手动 cast。避免了类型强转的麻烦。
代码示例:
public class GenericDemo2 { public static void main (String[] args) { Collection<String> list = new ArrayList <String>(); list.add( "abc11" ); list.add( "water" ); // list.add(18);//当集合明确类型后,存放类型不一致就会编译报错,报错提前,利用编译器检查功能。 // 集合已经明确具体存放的元素类型, // 那么在使用迭代器的时候,迭代器也同样会知道具体遍历元素类型,类型推断 Iterator<String> it = list.iterator(); while (it.hasNext()){ String str = it.next(); //当使用Iterator<String>控制元素类型后, // 就不需要强转了。获取到的元素直接就是String类型 System.out.println(str.length()); } } }
泛型是数据类型的一部分,我们将类名与泛型合并一起看做数据类型。
三、定义与使用
泛型,用来灵活地将数据类型应用到不同的类、方法、接口当中。将数据类型作为参数进行传递。
类中使用泛型
class Box <T> { private T value; public void set (T value) { this .value = value; } public T get () { return value; } }
定义格式:
修饰符 class 类名<代表泛型的变量> { }
使用泛型
例如,API中的ArrayList集合。泛型在定义的时候不具体,使用的时候才变得具体。在使用的时候确定泛型的具体数据类型。
class ArrayList <E>{ public boolean add (E e) { } public E get ( int index) { } .... }
使用泛型: 即什么时候确定泛型。在创建对象的时候确定泛型。例如,ArrayList<String> list = new ArrayList<String>();此时,变量E的值就是String类型,那么我们的类型就可以理解为:
class ArrayList <String>{ public boolean add (String e) { } public String get ( int index) { } ... }
再例如,ArrayList<Integer> list = new ArrayList<Integer>();此时,变量E的值就是Integer类型,那么我们的类型就可以理解为:
class ArrayList <Integer> { public boolean add (Integer e) { } public Integer get ( int index) { } ... }
方法中使用泛型
工具静态方法(常用)
public static <T> void printList (List<T> list) { for (T item : list) { System.out.println(item); } } // 使用 printList(List.of( 1 , 2 , 3 )); printList(List.of( "a" , "b" ));
定义格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
调用方法时,确定泛型的类型,实例方法
public class TestMethod { public <M> void show (M m) { // 类型名字任意 System.out.println(m.getClass()); } public <M> M show2 (M m) { return m; } } public class MethodDemo { public static void main (String[] args) { // 创建对象 TestMethod tm = new TestMethod (); // 入参各种类型,有点像等价多个重载方法,入参的类型可变 tm.show( "ccc" ); // String.class mm.show( 123 ); // Integer.class mm.show( 12.45 ); // Double.class } }
接口使用泛型
// 定义泛型接口,可以多个泛型参数 interface Repository <T, ID> { T findById (ID id) ; void save (T entity) ; } // ORM框架中常见的mapper接口,实体类和主键泛型 class UserRepository implements Repository <User, Long> { @Override public User findById (Long id) { ... } @Override public void save (User user) { ... } }
定义格式:
修饰符 interface接口名<代表泛型的变量> { }
定义泛型接口
public interface GenericInterface <E>{ public abstract void add (E e) ; public abstract E getE () ; }
1、定义实现类时确定泛型的类型
public class MyImp1 implements GenericInterface <String> { // 定义实现类时确定泛型 @Override public void add (String e) { // 省略... } @Override public String getE () { return null ; } }
此时,泛型E的值就是String类型。
2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型
public class MyImp2 <E> implements GenericInterface <E> { // 直接复用 @Override public void add (E e) { // 省略... } @Override public E getE () { return null ; } } public class GenericInterfaceTest { public static void main (String[] args) { MyImp2<String> my = new MyImp2 <String>(); // 创建对象时确定泛型 my.add( "aa" ); } }
四、泛型通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
通配符基本使用
public static void printUnknownList (List<?> list) { for (Object o : list) { System.out.println(o); } } List<?> list = List.of( "hello" , 123 );
泛型的通配符:不知道使用什么类型来接收的时候,此时可以使用,表示未知通配符。
此时只能接受数据,不能往该集合中存储数据。如下:
public static void main (String[] args) { Collection<Intger> list1 = new ArrayList <Integer>(); getElement(list1); Collection<String> list2 = new ArrayList <String>(); getElement(list2); } public static void getElement (Collection<?> coll) { // ?代表可以接收任意类型 // ... } // 泛型不存在继承关系 Collection<Object> list = new ArrayList<String>();这种是错误的
通配符高级使用
之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是为了增加具体类型的约束,设计者可以在JAVA的泛型中指定一个泛型的上限和下限,以此来框定具体类型的范围,保证最终运行时期的类型可预期。
泛型的上限:
- 格式:
类型名称 <? extends 类 > 对象名称 - 意义:
只能接收该类型及其子类 - 类比:头顶,天空,天花板,
cell
泛型的下限:
- 格式:
类型名称 <? super 类 > 对象名称 - 意义:
只能接收该类型及其父类型 - 类比:大地,地板,
floor
总结:本身类一定可以,extends 继承,所以为子类;super 超类,所以为父类
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类
public static void main (String[] args) { Collection<Integer> list1 = new ArrayList <Integer>(); Collection<String> list2 = new ArrayList <String>(); Collection<Number> list3 = new ArrayList <Number>(); Collection<Object> list4 = new ArrayList <Object>(); getElement(list1); getElement(list2); //报错 getElement(list3); getElement(list4); //报错 getElement2(list1); //报错 getElement2(list2); //报错 getElement2(list3); getElement2(list4); } // 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类 public static void getElement1 (Collection<? extends Number> coll) {} // 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类 public static void getElement2 (Collection<? super Number> coll) {}
五、泛型命名建议
| 类型变量 | 含义 |
|---|---|
| T | Type(通用类型) |
| E | Element(集合元素) |
| K / V | Key / Value(键值对) |
| R | Return type(返回类型) |
| A , B | 多个泛型参数 |
六、实战1(订单服务)
定义通用接口
interface OrderService <O extends Order > { // 泛型接口 O getOrderById (Long id) ; void saveOrder (O order) ; }
具体实现
class DiningOrderService implements OrderService <DiningOrder> { // 确定具体泛型类型 @Override public DiningOrder getOrderById (Long id) { // 查询数据库 return new DiningOrder (); } @Override public void saveOrder (DiningOrder order) { // 保存逻辑 } }
抽象基类支持扩展(变与不变)
abstract class BaseOrderService <O extends Order > implements OrderService <O> { // 泛型抽象基类 protected abstract O loadFromDB (Long id) ; @Override public O getOrderById (Long id) { return loadFromDB(id); } }
子类继承并定制(自己的实现)
class TakeawayOrderService extends BaseOrderService <TakeawayOrder> { @Override protected TakeawayOrder loadFromDB (Long id) { // 实现取餐订单加载逻辑 return new TakeawayOrder (); } }
七、实战2(声明多个泛型)
在错误中学习,印象更深刻,更容易掌握泛型这个知识点。
class ConsumeService implements MyService <ConsumeMapper<ConsumeOrder>> { private String name; private ConsumeMapper<ConsumeOrder> mapper; @Override public String getName () { return name; } @Override public void setName (String name) { this .name = name; } @Override public void setMapper (ConsumeMapper<ConsumeOrder> mapper) { this .mapper = mapper; } } class MysqlConsumeMapper extends ConsumeMapper <ConsumeOrder>{ } class ConsumeOrder {} class ConsumeMapper <T> implements Mapper <T> {} interface Mapper <T> {} interface MyService <M extends Mapper <T>> { String getName () ; void setName (String name) ; void setMapper (M mapper) ; }
结果:
类型参数ConsumeMapper<com.demo.ConsumeOrder>不在类型变量M的范围内
这个错误的根本原因是:泛型边界不匹配,导致类型擦除后无法确定类型范围。
错误位置分析
class ConsumeService implements MyService <ConsumeMapper<ConsumeOrder>> { ... }
接口定义是这样的:
interface MyService <M extends Mapper <T>> { String getName () ; void setName (String name) ; void setMapper (M mapper) ; }
问题在于:
- MyService 接口中的泛型定义是
<M extends Mapper<T>>,但 没有声明 T 是什么类型。 - 编译器不知道
T是从哪来的,所以当你说ConsumeMapper<ConsumeOrder>的时候,它不知道这个 ConsumeOrder 是否满足T的条件。
正确做法:明确泛型边界
将接口改为明确绑定两个泛型参数:
interface MyService <M extends Mapper <T>, T> { String getName () ; void setName (String name) ; void setMapper (M mapper) ; }
实现类就可以正确使用了:
class ConsumeService implements MyService <ConsumeMapper<ConsumeOrder>, ConsumeOrder> { private String name; private ConsumeMapper<ConsumeOrder> mapper; @Override public String getName () { return name; } @Override public void setName (String name) { this .name = name; } @Override public void setMapper (ConsumeMapper<ConsumeOrder> mapper) { this .mapper = mapper; } }
- 避免在类中声明未绑定的泛型字段(如
Mapper<T>) - 如果某个类已经明确了泛型类型,就不要再使用通配符或未绑定的泛型参数
- 推荐使用“泛型上界 + 接口组合”的方式提高扩展性
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 类型参数不在范围内 | 接口泛型 MyService> 中 T 没有被声明 | 改为 MyService, T> 明确绑定 |
| 实现类泛型不完整 | ConsumeService 实现时只传了一个泛型参数 | 改为 MyService, ConsumeOrder> |
八、实战3(经典使用)
在实际项目开发中,泛型不仅用于集合和接口抽象,还可以结合工具类设计、工厂模式、注解驱动等机制,实现更加灵活、可扩展的代码结构。
泛型工具类设计(Generic Utility Class)
场景:希望封装一个通用的数据处理类,支持不同类型的转换、过滤、映射等操作。
示例:GenericUtils<T>
public class GenericUtils <T> { private final List<T> dataList; public GenericUtils (List<T> dataList) { this .dataList = dataList; } // 过滤器:根据条件筛选数据 public List<T> filter (Predicate<T> predicate) { return dataList.stream().filter(predicate).toList(); } // 映射器:将 T 转换为 R 类型 public <R> List<R> map (Function<T, R> mapper) { return dataList.stream().map(mapper).toList(); } // 打印数据 public void print () { dataList.forEach(System.out::println); } }
使用示例:
List<String> names = List.of( "Alice" , "Bob" , "Charlie" ); GenericUtils<String> utils = new GenericUtils <>(names); // 过滤长度 > 3 的名字 List<String> filtered = utils.filter(name -> name.length() > 3 ); // 映射为大写形式 List<String> upperCaseNames = utils.map(String::toUpperCase);
泛型工厂模式(Generic Factory Pattern)
场景:需要一个统一入口来创建不同类型的对象,例如数据库访问层(DAO)、服务层(Service)等。
示例:BeanFactory<T>
@FunctionalInterface interface BeanSupplier <T> { // Bean提供者,哪些bean呢,不知道,所以泛型 T get () ; } class BeanFactory <T> { // Bean工厂,哪个bean呢,不知道,所以泛型 private final Map<String, BeanSupplier<T>> registry = new HashMap <>(); public void register (String key, BeanSupplier<T> supplier) { // 注册单个bean提供者 registry.put(key, supplier); } public T create (String key) { // 根据key获取bean提供者,进行生产具体bean BeanSupplier<T> supplier = registry.get(key); if (supplier == null ) { throw new IllegalArgumentException ( "No bean registered for key: " + key); } return supplier.get(); } }
使用示例:
BeanFactory<Mapper<?>> factory = new BeanFactory <>(); factory.register( "user" , UserMapper:: new ); // 具体-用户 factory.register( "order" , OrderMapper:: new ); // 具体-订单 Mapper<?> userMapper = factory.create( "user" ); Mapper<?> orderMapper = factory.create( "order" );
扩展:结合 Spring 工厂方法
如果你使用 Spring 框架,可以结合 @Component 和 @Qualifier 实现更强大的泛型工厂。
@Component public class MapperFactory <T> { private final Map<String, Mapper<?>> registry; // 该工厂由spring的依赖注入机制注入 public MapperFactory (Map<String, Mapper<?>> registry) { this .registry = registry; } @SuppressWarnings("unchecked") public <T> Mapper<T> getMapper (Class<T> type) { String name = type.getSimpleName().toLowerCase(); return (Mapper<T>) registry.get(name); } }
泛型 + 注解组合使用(Generic + Annotation)
场景:想为不同类型定义不同的行为,并通过注解自动绑定执行逻辑,如日志记录、权限校验、字段映射等。
示例:泛型字段处理器 + 注解
定义注解
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @interface FieldMapping { // 字段映射 String value () ; // 字段名称 }
定义泛型处理器
public class FieldMapper <T> { public void mapFields (T target, Map<String, Object> data) throws IllegalAccessException { for (Field field : target.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(FieldMapping.class)) { FieldMapping annotation = field.getDeclaredAnnotation(FieldMapping.class); String key = annotation.value(); field.setAccessible( true ); field.set(target, data.get(key)); // 根据注解获取字段名称,通过反射设置值 } } } }
使用示例
class User { @FieldMapping("name") // 可以和username不一样,并且还可以下划线、驼峰之间转换 private String username; @FieldMapping("age") private int age; } Map<String, Object> userData = Map.of( "name" , "Alice" , "age" , 25 ); User user = new User (); new FieldMapper <User>().mapFields(user, userData); // 给user这个对象各个字段赋值
数据解析框架
目标:通过注解+工厂+泛型,构建一个通用的数据解析框架,支持多种数据源(JSON、CSV、XML)并自动映射到实体类。
步骤:
-
- 定义数据解析器接口
-
- 实现 JSON / CSV 解析器
-
- 使用注解配置字段映射
-
- 使用工厂创建对应解析器
接口定义
interface DataParser <T> { // 数据解析器,解析哪种数据类型?不知道,所以泛型设置 T parse (String content, Class<T> clazz) ; // 设置泛型类型相关的处理方法 }
JSON 解析器
class JsonDataParser <T> implements DataParser <T> { // 细分数据源,Json,这个的T在创建对象时确定 @Override public T parse (String content, Class<T> clazz) { // 使用 Jackson 或 Gson 实现解析 return null ; } }
CSV 解析器 + 注解
class CsvDataParser <T> implements DataParser <T> { private final FieldMapper<T> fieldMapper = new FieldMapper <>(); @Override public T parse (String content, Class<T> clazz) throws Exception { T instance = clazz.getDeclaredConstructor().newInstance(); // 模拟从 CSV 中读取一行数据 Map<String, Object> row = parseRow(content); fieldMapper.mapFields(instance, row); return instance; } private Map<String, Object> parseRow (String csvLine) { // 简化版 CSV 行解析 return Map.of( "name" , "Alice" , "age" , 30 ); } }
工厂类
class ParserFactory { // 统一各个细分解析器,解析器工厂,不是map,就是switch public static <T> DataParser<T> getParser (String format) { return switch (format.toLowerCase()) { case "json" -> (DataParser<T>) new JsonDataParser <>(); case "csv" -> (DataParser<T>) new CsvDataParser <>(); default -> throw new IllegalArgumentException ( "Unsupported format: " + format); }; } }
使用示例
String jsonInput = "{\"name\":\"Alice\",\"age\":30}" ; String csvInput = "name,Alice;age,30" ; // 根据key选择细分解析器Json,创建对象时,确定了T为User类型 DataParser<User> jsonParser = ParserFactory.getParser( "json" ); User user1 = jsonParser.parse(jsonInput, User.class); // 根据key选择细分解析器csv,创建对象时,确定了T为User类型 DataParser<User> csvParser = ParserFactory.getParser( "csv" ); User user2 = csvParser.parse(csvInput, User.class);
小结
| 技术 | 应用场景 | 优势 |
|---|---|---|
| 泛型工具类 | 数据处理、转换、过滤 | 提高复用性,减少重复代码 |
| 泛型工厂 | 统一创建不同类型的实例 | 避免硬编码,提高扩展性 |
| 泛型+注解 | 字段映射、行为绑定 | 自动化配置,提升开发效率 |
| 泛型+反射 | 动态解析、适配不同格式 | 支持多数据源、增强灵活性 |
九、实战4(注解+泛型驱动=解耦)
Spring Boot 泛型高级实战:自动装配、策略模式、泛型+枚举组合设计。在实际企业级项目中,泛型 + 自动装配 + 策略模式 + 枚举 是构建高扩展性系统的重要手段。
场景说明
希望实现一个 消费规则处理器框架,支持多种消费规则(如餐段规则、会员规则、时段规则等),并通过泛型机制统一处理这些规则的加载、应用和计算逻辑。
输入对象
public class DinnerConsumeRuleDTO { /* 已有字段 */ }
目标
- 支持多种消费规则类型(枚举驱动)
- 使用泛型抽象统一接口
- 使用 Spring 自动装配不同实现
- 使用策略模式动态选择处理类
模块结构设计
com.demo.canteen.rule ├── ConsumeRuleType.java // 枚举定义规则类型 ├── ConsumeRuleContext.java // 上下文参数 ├── ConsumeRuleHandler.java // 泛型接口 ├── ConsumeRuleHandlerRegistry.java // 注册中心 ├── impl │ ├── DinnerConsumeRuleHandler.java │ └── MemberConsumeRuleHandler.java └── config └── RuleAutoConfiguration.java
代码实现
定义规则类型枚举
public enum ConsumeRuleType { // 分类规则 DINNER, MEMBER, TIME_BASED }
定义上下文对象(可复用)
@Data public class ConsumeRuleContext <T> { // 相关数据集中引用,整体传递,上下文 private T ruleData; // 规则数据(如 DinnerConsumeRuleDTO) private BigDecimal amount; // 原始金额 }
定义泛型处理器接口
public interface ConsumeRuleHandler <T> { // 规则处理器,泛型T,各种具体规则实体 ConsumeRuleType getRuleType () ; BigDecimal apply (ConsumeRuleContext<T> context) ; }
实现餐段规则处理器
@Component public class DinnerConsumeRuleHandler implements ConsumeRuleHandler <DinnerConsumeRuleDTO> { @Override public ConsumeRuleType getRuleType () { return ConsumeRuleType.DINNER; } @Override public BigDecimal apply (ConsumeRuleContext<DinnerConsumeRuleDTO> context) { DinnerConsumeRuleDTO rule = context.getRuleData(); BigDecimal amount = context.getAmount(); // 应用折扣 if (rule.getDiscount() != null && rule.getDiscount() < 100 ) { amount = amount.multiply( new BigDecimal (rule.getDiscount()).divide( new BigDecimal ( 100 ))); } // 最低消费限制 if (rule.getMinCost() != null && amount.compareTo(rule.getMinCost()) < 0 ) { amount = rule.getMinCost(); } return amount; } }
枚举驱动的注册中心
@Service public class ConsumeRuleHandlerRegistry { // spring依赖注入,泛型工厂 private final Map<ConsumeRuleType, ConsumeRuleHandler<?>> handlers = new HashMap <>(); public ConsumeRuleHandlerRegistry (List<ConsumeRuleHandler<?>> handlerList) { for (ConsumeRuleHandler<?> handler : handlerList) { handlers.put(handler.getRuleType(), handler); // 规则分类枚举当做key } } @SuppressWarnings("unchecked") public <T> ConsumeRuleHandler<T> getHandler (ConsumeRuleType type) { return (ConsumeRuleHandler<T>) handlers.get(type); } }
Spring 自动配置类(非必须,用于演示)
@Configuration public class RuleAutoConfiguration { // 可以在这里做初始化或条件装配 }
使用示例
@RestController @RequestMapping("/api/rules") public class RuleController { private final ConsumeRuleHandlerRegistry registry; // 规则注册器(工厂) public RuleController (ConsumeRuleHandlerRegistry registry) { this .registry = registry; } @PostMapping("/apply/dinner") public BigDecimal applyDinnerRule ( @RequestBody DinnerConsumeRuleDTO ruleDTO) { ConsumeRuleContext<DinnerConsumeRuleDTO> context = new ConsumeRuleContext <>(); context.setRuleData(ruleDTO); context.setAmount( new BigDecimal ( "100" )); ConsumeRuleHandler<DinnerConsumeRuleDTO> handler = registry.getHandler(ConsumeRuleType.DINNER); return handler.apply(context); } }
优势与扩展性
| 技术点 | 优势 |
|---|---|
| 泛型接口 | 统一处理多种规则类型,避免冗余代码 |
| 枚举驱动 | 明确业务规则分类,便于扩展 |
| 自动装配 | Spring 托管所有 Handler,无需手动注册 |
| 策略模式 | 动态选择执行逻辑,提高可维护性 |
后续扩展建议
- 添加缓存机制(如
Redis缓存已加载的规则) - 增加规则优先级排序(按顺序执行多个规则),编排
- 配合事件监听器实现异步规则应用,
@EventListener - 结合
Spring Expression Language(SpEL)实现灵活表达式规
十、总结
合理使用泛型,可以增强代码安全性,避免在运行期强转出错。类型的参数化,为抽象设计的接口、类、方法,提供更多动态选择,减少代码量,扩展更优雅。结合注解+泛型+工厂+策略等组合使用,可以更好地解耦代码,应对业务的多变性,实现需求。
更多推荐
所有评论(0)