深入理解Jdk5引入的Java泛型:类型安全与灵活性并存
在Java的中,有一个强大的工具,它可以让你在编写代码时既保持类型安全,又享受灵活性。**这个工具就是——泛型(Generics)。**本文将引导你深入了解Java泛型的奥秘,让你从此编写更强大、更安全的代码。
基本概念:
泛型是什么?
泛型是Java中的一项强大功能,它允许你编写能够处理多种数据类型的类、接口和方法。通过使用泛型,你可以将数据类型作为参数传递给类或方法,从而实现更高层次的代码重用和类型安全。
Java 泛型(generics)本质是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
假定我们有这样一个需求:写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?
答案是可以使用 Java 泛型。
使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。
泛型类和泛型方法
泛型在Java中有两种主要形式:泛型类和泛型方法。泛型类是具有一个或多个类型参数的类,而泛型方法是在调用时可以接受不同类型参数的方法。
泛型类的基本概念
泛型类是一种允许在创建类的实例时指定数据类型的类。通过使用泛型,你可以将类的数据类型参数化,从而使得类可以适用于多种数据类型。让我们来看一个简单的例子:一个通用的容器类Box
。
public class Box<T> {
private T content;
public Box(T content) {
this.content = content;
}
public T getContent() {
return content;
}
public void setContent(T content) {
this.content = content;
}
}
在上面的示例中,我们定义了一个泛型类Box
,它可以存储任意类型的数据。T
是一个类型参数,它在实例化Box
对象时确定。
使用泛型类
现在,让我们看看如何使用泛型类Box
来存储不同类型的数据。
public class GenericClassExample {
public static void main(String[] args) {
Box<Integer> intBox = new Box<>(42);//存储整数
int intValue = intBox.getContent();
System.out.println("Integer value: " + intValue);
Box<String> stringBox = new Box<>("Hello, Stevedash!");//存储字符串
String stringValue = stringBox.getContent();
System.out.println("String value: " + stringValue);
}
}
在上述示例中,我们创建了两个Box
对象,一个存储整数,另一个存储字符串。通过使用泛型类,我们可以在编译时就确保数据类型的正确性,避免了在运行时出现类型错误。
类型安全与重用性
泛型类不仅提供了类型安全性,还增加了代码的重用性。你可以轻松地创建适用于不同类型的通用类,减少了代码的冗余。这使得你的程序更加健壮、可维护。
泛型类的限制
虽然泛型类非常强大,但也有一些限制。例如,无法创建泛型数组,也不能使用基本数据类型作为泛型参数(需要使用对应的包装类)。
自定义泛型类
除了使用Java提供的泛型类,你还可以自定义泛型类来满足特定需求。通过实现自己的泛型类,你可以更好地理解泛型的原理,以及如何灵活地使用它。
泛型方法
你可以写一个泛型方法,该方法在调用时可以接收不同类型的参数。根据传递给泛型方法的参数类型,编译器适当地处理每一个方法调用。
下面是定义泛型方法的规则:
-
所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前(在下面例子中的 )。
-
每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符。
-
类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符。
-
泛型方法体的声明和其他方法一样。注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)。
java 中泛型标记符:
-
E - Element (在集合中使用,因为集合中存放的是元素)
-
T - Type(Java 类)
-
K - Key(键)
-
V - Value(值)
-
N - Number(数值类型)
-
? - 表示不确定的 java 类型
下面的例子演示了如何使用泛型方法打印不同类型的数组元素:
实例
public class GenericMethodTest
{
// 泛型方法 printArray
public static < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
public static void main( String args[] )
{
// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3, 4, 5 };
Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };
System.out.println( "整型数组元素为:" );
printArray( intArray ); // 传递一个整型数组
System.out.println( "\n双精度型数组元素为:" );
printArray( doubleArray ); // 传递一个双精度型数组
System.out.println( "\n字符型数组元素为:" );
printArray( charArray ); // 传递一个字符型数组
}
}
编译以上代码,运行结果如下所示:
整型数组元素为:
1 2 3 4 5
双精度型数组元素为:
1.1 2.2 3.3 4.4
字符型数组元素为:
H E L L O
在这段代码中,虽然我们使用了泛型方法printArray
来输出不同类型的数组元素,但实际上并没有创建一个泛型数组。我们只是在使用已有的数组来进行迭代遍历并输出元素,而不是通过泛型参数创建新的数组。
在Java中,直接创建一个泛型数组是不允许的。例如,以下代码是不合法的:
// 这是不合法的,无法直接创建泛型数组
T[] array = new T[10];
泛型数组的限制是为了确保类型安全性。虽然在代码中使用了泛型方法,但我们并没有在代码中直接创建泛型数组,而是使用了已有的数组。因此,虽然代码中有泛型方法,但没有涉及创建泛型数组的情况。
通配符和边界
泛型还支持通配符和边界的概念,让你能够更精细地控制泛型类型的范围。通配符?
表示未知类型,而边界则限制了允许传递的类型范围。
import java.util.List;
public class GenericWildcard {
/**
* 打印通配符类型的列表元素
*
* @param list 要打印的列表
*/
public static void printList(List<?> list) {
for (Object element : list) {
System.out.print(element + " ");
}
System.out.println();
}
/**
* 计算数字类型列表的总和
*
* @param list 要计算总和的列表
* @return 列表元素的总和
*/
public static <T extends Number> double sumOfList(List<T> list) {
double sum = 0;
for (T element : list) {
sum += element.doubleValue();
}
return sum;
}//<T extends Number>: 这是泛型类型参数的声明。在这里,<T> 表示泛型类型参数的名称,你可以在方法体内使用它来代表实际的数据类型。extends Number 是一个泛型类型的限定符。它的意思是,类型参数 T 必须是 Number 类或其子类。这样做的目的是为了确保列表中的元素都是数字类型,以便在方法中进行数字运算。
public static void main(String[] args) {
// 创建一个包含不同类型元素的列表
List<?> wildcardList = List.of("Hello", 42, 3.14, 'A');
// 打印通配符类型的列表元素
System.out.print("通配符列表元素:: ");
printList(wildcardList);
// 创建一个数字类型的列表
List<Integer> integerList = List.of(1, 2, 3, 4, 5);
// 计算数字类型列表的总和
double sum = sumOfList(integerList);
// 输出计算结果
System.out.println("整数列表总和: " + sum);
}
}
输出结果如下:
通配符列表元素: Hello 42 3.14 A
整数列表总和: 15.0
上面的示例演示了一个使用通配符的方法printList
和一个使用边界的方法sumOfList
。通配符使方法能够接受不同类型的列表,而边界则限制了方法只能接受继承自Number
的类型列表。
泛型与集合框架
Java的集合框架(如List、Set、Map等)广泛使用了泛型,使得集合中的元素类型可以更加明确和安全。使用泛型可以在编译时捕获类型错误,避免在运行时出现类型转换异常。
最后总结:
Java泛型是一项强大的功能,它既保持了类型安全,又提高了代码的灵活性和重用性。通过学习泛型的使用,能够编写更强大、更安全的代码,让我们的程序更加健壮和可维护。
总结一下泛型类的优缺点:
泛型类的优点:
- 类型安全性: 泛型类在编译时可以检查数据类型,从而提前发现类型错误,避免了在运行时出现类型转换异常。
- 代码重用性: 泛型类允许你创建通用的数据结构,适用于多种数据类型,减少了代码的冗余,提高了代码的重用性。
- 灵活性: 泛型类使得你可以创建适用于不同数据类型的通用类,从而在不同场景下实现灵活的数据存储和处理。
- 编译时类型检查: 泛型类使得编译器能够检查和强制确保类型的正确性,从而提高了代码的可靠性和稳定性。
- 更好的可读性: 使用泛型类可以提供更清晰的代码结构和命名,使得代码更易于理解和维护。
泛型类的缺点:
- 类型参数限制: 泛型类无法直接使用基本数据类型作为类型参数,需要使用对应的包装类。同时,无法创建泛型数组。
- 复杂性: 对于初学者来说,泛型的概念可能会有一定的复杂性,需要一些时间来理解和掌握。
- 编译时错误信息: 有时,编译器产生的泛型相关的错误信息可能不够直观,需要花时间来解读和修复。
- 擦除和类型擦除: 泛型类在编译时会进行类型擦除,这可能会导致在某些情况下失去一些类型信息。
- 部分场景不适用: 在某些特定的场景下,泛型类可能不适用,需要根据实际需求来选择是否使用泛型。
尽管泛型类存在一些缺点,但它的优点远远超过了缺点,可以帮助我们写出更安全、更灵活、更高效的代码。
作者:Stevedash
发表于:2023年8月8日15点19分
来源:Java 泛型 | 菜鸟教程 (runoob.com)
注:本文内容基于个人学习理解,如有错误或疏漏,欢迎指正。感谢阅读!如果觉得有帮助,请点赞和分享。