Java 泛型方法
什么是泛型方法?
在之前的学习中,我们已经了解了Java泛型的基本概念。泛型方法是Java泛型的一个重要应用,它允许我们创建可以处理不同数据类型的方法,而不必为每种数据类型单独编写一个方法。
泛型方法的语法特点是在返回类型前使用尖括号(<>
)声明类型参数,这些类型参数可以在方法参数、返回类型和方法体中使用。
泛型方法可以在普通类中定义,也可以在泛型类中定义。方法的泛型参数独立于类的泛型参数。
基本语法
泛型方法的基本语法如下:
public <T> 返回类型 方法名(参数列表) {
// 方法体
}
其中,<T>
表示类型参数,可以是任何标识符,通常使用大写字母如T, E, K, V等。
泛型方法的简单示例
让我们来看一个简单的泛型方法示例:
public class GenericMethodDemo {
// 泛型方法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[] 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("双精度型数组元素:");
printArray(doubleArray); // 传递一个双精度型数组
System.out.println("字符型数组元素:");
printArray(charArray); // 传递一个字符型数组
}
}
输出结果:
整型数组元素:
1 2 3 4 5
双精度型数组元素:
1.1 2.2 3.3 4.4
字符型数组元素:
H E L L O
在上面的例子中,我们定义了一个泛型方法 printArray
,它可以接受任何类型的数组并打印其元素。通过使用泛型方法,我们只需编写一个方法就能处理不同类型的数组。
多个类型参数的泛型方法
泛型方法可以有多个类型参数。下面是一个使用两个类型参数的示例:
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
// 泛型方法,独立于类的类型参数
public <T, U> void printTypes(T t, U u) {
System.out.println("T的类型是: " + t.getClass().getName());
System.out.println("U的类型是: " + u.getClass().getName());
}
public static void main(String[] args) {
Pair<String, Integer> pair = new Pair<>("Hello", 100);
pair.printTypes(3.14, "World");
}
}
输出结果:
T的类型是: java.lang.Double
U的类型是: java.lang.String
在这个例子中,printTypes
方法有自己的类型参数T
和U
,它们与类的类型参数K
和V
是不同的。
有界类型参数
有时我们希望限制泛型方法接受的数据类型。这时可以使用有界类型参数(Bounded Type Parameters)。
上界通配符示例
public class BoundedTypeDemo {
// 使用extends关键字指定上界
public static <T extends Comparable<T>> T findMax(T[] array) {
if (array == null || array.length == 0) {
return null;
}
T max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
public static void main(String[] args) {
Integer[] intArray = { 3, 5, 1, 4, 2 };
String[] strArray = { "apple", "orange", "banana", "peach" };
Integer maxInt = findMax(intArray);
String maxStr = findMax(strArray);
System.out.println("最大整数:" + maxInt);
System.out.println("字典序最大的字符串:" + maxStr);
}
}
输出结果:
最大整数:5
字典序最大的字符串:peach
在这个例子中,我们使用了<T extends Comparable<T>>
来限制类型T
必须实现Comparable
接口。这样我们可以使用compareTo
方法来比较元素。
多重边界
泛型也可以有多重边界,例如:
public static <T extends Number & Comparable<T>> T findMin(T[] array) {
if (array == null || array.length == 0) {
return null;
}
T min = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(min) < 0) {
min = array[i];
}
}
return min;
}
在这个例子中,类型T
必须同时是Number
的子类并实现Comparable
接口。
通配符
泛型方法中经常使用通配符来增加灵活性。
?
通配符
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}
这个方法可以打印任何类型的列表。
上界通配符 <? extends T>
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}
这个方法接受Number
及其子类组成的列表,并计算元素和。
下界通配符 <? super T>
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}
这个方法可以向Integer
及其父类组成的列表添加整数。
泛型方法的实际应用场景
1. 集合操作
泛型方法在集合操作中非常有用,比如复制、过滤等:
public class CollectionUtils {
public static <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> result = new ArrayList<>();
for (T item : list) {
if (predicate.test(item)) {
result.add(item);
}
}
return result;
}
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 过滤出偶数
List<Integer> evenNumbers = filter(numbers, n -> n % 2 == 0);
System.out.println("偶数列表:" + evenNumbers);
// 过滤出大于5的数
List<Integer> greaterThanFive = filter(numbers, n -> n > 5);
System.out.println("大于5的数:" + greaterThanFive);
}
}
输出结果:
偶数列表:[2, 4, 6, 8, 10]
大于5的数:[6, 7, 8, 9, 10]
2. 数据交换
泛型方法可以用于实现安全的数据交换:
public class SwapUtils {
public static <T> void swap(T[] array, int i, int j) {
T temp = array[i];
array[i] = array[j];
array[j] = temp;
}
public static void main(String[] args) {
Integer[] numbers = {1, 2, 3, 4, 5};
System.out.println("交换前:" + Arrays.toString(numbers));
swap(numbers, 1, 3);
System.out.println("交换后:" + Arrays.toString(numbers));
}
}
输出结果:
交换前:[1, 2, 3, 4, 5]
交换后:[1, 4, 3, 2, 5]
3. 数据转换
泛型方法可以用于实现不同数据结构之间的转换:
public class ConversionUtils {
public static <T> Set<T> listToSet(List<T> list) {
return new HashSet<>(list);
}
public static <T> List<T> setToList(Set<T> set) {
return new ArrayList<>(set);
}
public static void main(String[] args) {
List<String> fruits = Arrays.asList("apple", "orange", "banana", "apple", "peach");
System.out.println("原始列表:" + fruits);
Set<String> uniqueFruits = listToSet(fruits);
System.out.println("转为Set(去重):" + uniqueFruits);
List<String> fruitsList = setToList(uniqueFruits);
System.out.println("再转回List:" + fruitsList);
}
}
输出结果:
原始列表:[apple, orange, banana, apple, peach]
转为Set(去重):[banana, orange, apple, peach]
再转回List:[banana, orange, apple, peach]
泛型方法的类型推断
Java编译器能够根据上下文推断出泛型方法的类型参数,这称为"类型推断"。在大多数情况下,你不需要显式地指定类型参数。
public class TypeInference {
public static <T> T[] createArray(T element, int size) {
@SuppressWarnings("unchecked")
T[] array = (T[]) Array.newInstance(element.getClass(), size);
for (int i = 0; i < size; i++) {
array[i] = element;
}
return array;
}
public static void main(String[] args) {
// 编译器能够推断出T的类型是String
String[] stringArray = createArray("Hello", 5);
System.out.println(Arrays.toString(stringArray));
// 编译器能够推断出T的类型是Integer
Integer[] intArray = createArray(42, 3);
System.out.println(Arrays.toString(intArray));
}
}
输出结果:
[Hello, Hello, Hello, Hello, Hello]
[42, 42, 42]
总结
Java泛型方法是一种强大的工具,它可以:
- 提高代码重用性,避免为不同数据类型写重复的代码
- 提供编译时类型安全检查,避免运行时类型转换错误
- 使用有界类型参数限制接受的数据类型
- 通过通配符增加方法的灵活性
泛型方法的关键特点:
- 在返回类型之前声明类型参数(如
<T>
) - 方法的类型参数与类的类型参数无关
- 可以有多个类型参数
- 可以使用有界类型参数和通配符
- 尽量使用有意义的类型参数名称,如用T代表Type,E代表Element,K代表Key,V代表Value
- 当不需要读取泛型对象的内容时,使用无界通配符
<?>
- 当需要从泛型对象中读取时,使用上界通配符
<? extends T>
- 当需要向泛型对象中写入时,使用下界通配符
<? super T>
练习
- 编写一个泛型方法,查找数组中的最小值。
- 编写一个泛型方法,将一个数组中的元素倒序排列。
- 编写一个泛型方法,合并两个已排序的列表,保持结果有序。
- 编写一个泛型方法,从Map中根据键获取值,如果键不存在则返回默认值。
学习泛型方法是掌握Java高级特性的重要一步。通过练习和实践,你会发现泛型方法在实际编程中的强大作用,尤其是在处理集合和实现通用算法时。