1. 概述
本文将深入探讨 Java 中的菱形运算符(<>
),并分析 泛型(Generics)和集合框架(Collections API)如何推动其演进。
2. 原始类型
在 Java 1.5 之前,集合框架只支持原始类型——构造集合时无法参数化类型参数:
List cars = new ArrayList();
cars.add(new Object());
cars.add("car");
cars.add(new Integer(1));
这种写法允许添加任意类型,极易在运行时引发类型转换异常,简直是踩坑重灾区。
3. 泛型
Java 1.5 引入了泛型——允许我们在声明和构造对象时参数化类型参数,包括集合框架中的类:
List<String> cars = new ArrayList<String>();
但此时仍需在构造函数中显式指定参数化类型,遇到复杂泛型时代码可读性极差:
Map<String, List<Map<String, Map<String, Integer>>>> cars
= new HashMap<String, List<Map<String, Map<String, Integer>>>>();
这种设计的原因是:为保持向后兼容,原始类型依然存在,编译器需要区分原始类型和泛型:
List<String> generics = new ArrayList<String>(); // ✅ 推荐
List<String> raws = new ArrayList(); // ❌ 原始类型
即使编译器允许在构造函数中使用原始类型,也会给出警告:
ArrayList is a raw type. References to generic type ArrayList<E> should be parameterized
4. 菱形运算符
Java 1.7 引入的菱形运算符——通过类型推断机制大幅简化泛型赋值的冗余写法:
List<String> cars = new ArrayList<>(); // 类型自动推断
编译器的类型推断功能会自动匹配最合适的构造函数声明。
看一个车辆引擎相关的接口和类层次结构示例:
public interface Engine { }
public class Diesel implements Engine { }
public interface Vehicle<T extends Engine> { }
public class Car<T extends Engine> implements Vehicle<T> { }
使用菱形运算符创建 Car
实例:
Car<Diesel> myCar = new Car<>(); // 编译器自动推断 T=Diesel
编译器内部通过以下逻辑工作:
- 识别
Diesel
实现了Engine
接口 - 根据
Car<T extends Engine>
的约束推断类型参数 - 自动匹配
Car<Diesel>
构造函数
5. 总结
简单粗暴地说,菱形运算符通过编译器类型推断解决了泛型赋值的冗余问题,让代码更简洁安全。记住这个核心价值:
- ✅ 减少重复代码
- ✅ 保持类型安全
- ✅ 提升可读性
实际开发中,所有泛型类初始化都应优先使用菱形运算符,避免原始类型带来的潜在风险。