深入理解Java中的泛型机制
文章目录
- 深入理解Java中的泛型机制
- 简单了解泛型
- Java中泛型的存在有以下几个主要意义:
- 类型安全:泛型提供了编译时的类型检查机制,可以在编译阶段捕获类型错误,减少运行时出现的类型相关的异常。通过使用泛型,可以确保程序在类型上是安全的,避免了类型转换错误和潜在的ClassCastException。
- 代码复用:泛型使得代码更加通用和可重用。可以编写泛型类和方法,能够适用于多种数据类型,而不需要针对每种类型编写重复的代码。通过泛型,可以实现更高效和简洁的代码。
- 简化API设计:通过使用泛型,可以设计更加通用和灵活的API接口。例如,Java集合框架中的泛型容器(如List、Set、Map)可以在不同场景下适应各种类型的数据,提供一致的接口和操作。
- 提高代码可读性和可维护性:泛型可以增加代码的可读性和可维护性。通过使用泛型,可以在代码中清晰地表达和约束数据类型的关系,使得代码更易理解和维护。
- 避免类型强制转换:使用泛型可以避免手动进行类型强制转换的繁琐和容易出错。编译器会自动进行类型推断和转换,减少了手动转换的代码和潜在的类型转换错误。
- "奇门邪术"--绕过类型检查
简单了解泛型
以前对于泛型的认知就停留在编译后会变成Object类型,对于它的优点没有进行过多的思考,以至于在前几个月的面试中被虐的是体无完肤.今天就来系统的总结一下泛型存在的意义到底是什么.
Java中泛型的存在有以下几个主要意义:
类型安全:泛型提供了编译时的类型检查机制,可以在编译阶段捕获类型错误,减少运行时出现的类型相关的异常。通过使用泛型,可以确保程序在类型上是安全的,避免了类型转换错误和潜在的ClassCastException。
List<String> names = new ArrayList<>(); // 使用泛型声明一个只能存储字符串的列表
names.add("Alice");
names.add(123); // 编译时错误,类型不匹配
在上述示例中,通过使用泛型限定列表只能存储字符串类型的元素,编译器会在添加元素时进行类型检查,如果尝试添加非字符串类型的元素,编译时就会发现错误,提供了类型安全性。
代码复用:泛型使得代码更加通用和可重用。可以编写泛型类和方法,能够适用于多种数据类型,而不需要针对每种类型编写重复的代码。通过泛型,可以实现更高效和简洁的代码。
public class Pair<T, U> {
private T first;
private U second;
public Pair(T first, U second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public U getSecond() {
return second;
}
}
// 使用泛型的Pair类可以适用于不同类型的数据
Pair<String, Integer> pair1 = new Pair<>("Alice", 25);
Pair<String, String> pair2 = new Pair<>("John", "Doe");
在上述示例中,通过使用泛型类
Pair
,我们可以创建可以存储不同类型数据的对象。这样可以避免为每个不同类型的数据编写单独的类,实现了代码的复用和通用性。
简化API设计:通过使用泛型,可以设计更加通用和灵活的API接口。例如,Java集合框架中的泛型容器(如List、Set、Map)可以在不同场景下适应各种类型的数据,提供一致的接口和操作。
List<Integer> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
numbers.add(30);
int sum = numbers.stream().mapToInt(Integer::intValue).sum();
在上述示例中,通过使用泛型的集合类
List<Integer>
,我们可以直接向列表中添加整数类型的元素。在使用流操作计算列表元素的和时,不需要手动进行类型转换,而是直接使用整数类型的方法,简化了API的设计和使用。
提高代码可读性和可维护性:泛型可以增加代码的可读性和可维护性。通过使用泛型,可以在代码中清晰地表达和约束数据类型的关系,使得代码更易理解和维护。
public <T extends Comparable<T>> T getMax(T a, T b) {
return a.compareTo(b) > 0 ? a : b;
}
// 使用泛型方法获取最大值
int maxInt = getMax(10, 20);
double maxDouble = getMax(3.14, 2.5);
在上述示例中,通过使用泛型方法
getMax()
,我们可以在不同的数据类型上获取最大值。通过泛型方法的参数类型和返回类型的一致性,代码可读性更高,更易于理解和维护。
避免类型强制转换:使用泛型可以避免手动进行类型强制转换的繁琐和容易出错。编译器会自动进行类型推断和转换,减少了手动转换的代码和潜在的类型转换错误。
List rawList = new ArrayList(); // 声明一个未指定泛型类型的原始列表
rawList.add("Alice");
String name = (String) rawList.get(0); // 需要进行强制类型转换
List<String> 此回答由于中途截断,以下是接上的完整回答。
```java
List<String> typedList = new ArrayList<>(); // 使用泛型声明一个指定类型的列表
typedList.add("Alice");
String name = typedList.get(0); // 无需进行强制类型转换
在上述示例中,使用原始列表时,需要进行强制类型转换以将元素转换为所需的类型。而使用带有泛型的列表时,无需进行强制类型转换,编译器自动推断并进行类型检查,避免了类型转换错误的风险。
“奇门邪术”–绕过类型检查
既然泛型存在的意义有一个可以类型检查,使得错误的程序被扼杀在摇篮中,那么就有一起奇奇怪怪的方法来绕开它,就需要依赖一种强大的机制–反射.
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 定义一个集合,存储Integer的类型
final ArrayList<Integer> list = new ArrayList<Integer>();
//通过反射机制获取list的Class对象
final Class<? extends ArrayList> aClass = list.getClass();
//通过Class对象获取Method(实例方法类)
final Method add = aClass.getDeclaredMethod("add", Object.class);
//设置权限检查为不检查
add.setAccessible(true);
//调用该实例方法
add.invoke(list,"hello");
add.invoke(list,"world");
add.invoke(list,"Java");
System.out.println(list);
//输出:[hello, world, Java]
}
在上面的代码中,因为反射是一种运行时动态获取类,方法的一直机制,所以可以看到final Method add = aClass.getDeclaredMethod(“add”, Object.class);这一行代码中的字节码文件中的类型参数是Object.class,而彼时Integer.class,这也正好说明了泛型的类型擦除机制.