泛型是Java语言中的一个重要特性,它提供了一种在编译时期检查和强制类型安全性的机制。泛型编程使得代码更加灵活、可读性更好,并且能够避免类型转换异常。
为什么需要泛型
在Java早期版本中,集合类(如List、Map等)存储的元素类型都是Object,因此在使用时需要进行强制类型转换。这种做法存在以下问题:
- 安全性:由于强制类型转换是在运行时进行的,编译器无法在编译时进行类型检查,因此容易产生类型转换异常。
- 代码冗余:每次从集合中获取元素时都需要进行强制类型转换,增加了代码复杂性和编写代码的工作量。
泛型的引入解决了以上问题,它允许我们在定义类或方法时指定参数类型,这样在使用时就不需要进行类型转换。
泛型的基本概念
在Java中,泛型是通过参数化类型(Parameterized Type)来实现的。参数化类型就是将类型参数放在尖括号(<>)中,可以是任意标识符,常见的有E、T、K、V等。
List<String> strList = new ArrayList<>();
上述代码中的List
泛型类和泛型方法
泛型不仅可以应用于整个类,也可以应用于方法。通过定义泛型类和泛型方法,可以更灵活地处理不同类型的数据。
泛型类
泛型类是具有类型参数的类。在定义泛型类时,需要在类名后面加上尖括号,并在尖括号中声明类型参数。
public class Box<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
上述代码中的Box类可以存储任意类型的数据。在创建Box对象时,需要指定具体的类型。
Box<Integer> intBox = new Box<Integer>();
intBox.setValue(10);
int value = intBox.getValue();
泛型方法
泛型方法是具有类型参数的方法。在定义泛型方法时,需要在方法返回类型前面加上尖括号,并在尖括号中声明类型参数。
public <T> void printList(List<T> list) {
for (T item : list) {
System.out.println(item);
}
}
上述代码中的printList方法可以接收任意类型的List,并打印出其中的元素。调用该方法时,传入的List的类型将决定泛型的具体类型。
List<Integer> intList = Arrays.asList(1, 2, 3);
printList(intList);
通配符
通配符(Wildcard)是用来表示未知类型的,它是一种泛型的特殊形式。通配符使用问号(?)表示,可以表示任意类型。
上界通配符
上界通配符(Upper Bounded Wildcard)用来限制通配符只能表示某个类或其子类。上界通配符使用extends关键字。
public static double sumOfList(List<? extends Number> list) {
double sum = 0;
for (Number number : list) {
sum += number.doubleValue();
}
return sum;
}
上述代码中的sumOfList方法接收一个List,而这个List的元素必须是Number类或其子类。这样可以保证sumOfList方法可以对List中的元素进行数值计算。
下界通配符
下界通配符(Lower Bounded Wildcard)用来限制通配符只能表示某个类或其父类。下界通配符使用super关键字。
public static void addToList(List<? super Integer> list) {
list.add(10);
}
上述代码中的addToList方法接收一个List,而这个List的元素必须是Integer类或其父类。这样可以保证addToList方法可以向List中添加Integer类型的元素。
泛型和继承关系
在泛型中,类之间的继承关系与泛型类型参数之间的继承关系是相互独立的。即泛型类A<T>和泛型类B<T>之间没有继承关系,即使A和B之间的类在继承关系中。
总结
泛型是Java中的一个重要特性,它提供了一种在编译时期检查和强制类型安全性的机制。通过使用泛型,我们可以编写更加灵活、可读性更好的代码,并且能够避免类型转换异常。泛型类和泛型方法可以处理不同类型的数据,通配符可以限制通配符表示的类型范围。
本文来自极简博客,作者:冬日暖阳,转载请注明原文链接:Java中的泛型编程详解