跳到主要内容

Java 泛型方法

什么是泛型方法?

在之前的学习中,我们已经了解了Java泛型的基本概念。泛型方法是Java泛型的一个重要应用,它允许我们创建可以处理不同数据类型的方法,而不必为每种数据类型单独编写一个方法。

泛型方法的语法特点是在返回类型前使用尖括号(<>)声明类型参数,这些类型参数可以在方法参数、返回类型和方法体中使用。

备注

泛型方法可以在普通类中定义,也可以在泛型类中定义。方法的泛型参数独立于类的泛型参数。

基本语法

泛型方法的基本语法如下:

java
public <T> 返回类型 方法名(参数列表) {
// 方法体
}

其中,<T> 表示类型参数,可以是任何标识符,通常使用大写字母如T, E, K, V等。

泛型方法的简单示例

让我们来看一个简单的泛型方法示例:

java
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,它可以接受任何类型的数组并打印其元素。通过使用泛型方法,我们只需编写一个方法就能处理不同类型的数组。

多个类型参数的泛型方法

泛型方法可以有多个类型参数。下面是一个使用两个类型参数的示例:

java
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方法有自己的类型参数TU,它们与类的类型参数KV是不同的。

有界类型参数

有时我们希望限制泛型方法接受的数据类型。这时可以使用有界类型参数(Bounded Type Parameters)。

上界通配符示例

java
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方法来比较元素。

多重边界

泛型也可以有多重边界,例如:

java
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接口。

通配符

泛型方法中经常使用通配符来增加灵活性。

? 通配符

java
public static void printList(List<?> list) {
for (Object elem : list) {
System.out.print(elem + " ");
}
System.out.println();
}

这个方法可以打印任何类型的列表。

上界通配符 <? extends T>

java
public static double sumOfList(List<? extends Number> list) {
double sum = 0.0;
for (Number n : list) {
sum += n.doubleValue();
}
return sum;
}

这个方法接受Number及其子类组成的列表,并计算元素和。

下界通配符 <? super T>

java
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 5; i++) {
list.add(i);
}
}

这个方法可以向Integer及其父类组成的列表添加整数。

泛型方法的实际应用场景

1. 集合操作

泛型方法在集合操作中非常有用,比如复制、过滤等:

java
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. 数据交换

泛型方法可以用于实现安全的数据交换:

java
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. 数据转换

泛型方法可以用于实现不同数据结构之间的转换:

java
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编译器能够根据上下文推断出泛型方法的类型参数,这称为"类型推断"。在大多数情况下,你不需要显式地指定类型参数。

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泛型方法是一种强大的工具,它可以:

  1. 提高代码重用性,避免为不同数据类型写重复的代码
  2. 提供编译时类型安全检查,避免运行时类型转换错误
  3. 使用有界类型参数限制接受的数据类型
  4. 通过通配符增加方法的灵活性

泛型方法的关键特点:

  • 在返回类型之前声明类型参数(如 <T>
  • 方法的类型参数与类的类型参数无关
  • 可以有多个类型参数
  • 可以使用有界类型参数和通配符
编码建议
  • 尽量使用有意义的类型参数名称,如用T代表Type,E代表Element,K代表Key,V代表Value
  • 当不需要读取泛型对象的内容时,使用无界通配符<?>
  • 当需要从泛型对象中读取时,使用上界通配符<? extends T>
  • 当需要向泛型对象中写入时,使用下界通配符<? super T>

练习

  1. 编写一个泛型方法,查找数组中的最小值。
  2. 编写一个泛型方法,将一个数组中的元素倒序排列。
  3. 编写一个泛型方法,合并两个已排序的列表,保持结果有序。
  4. 编写一个泛型方法,从Map中根据键获取值,如果键不存在则返回默认值。

学习泛型方法是掌握Java高级特性的重要一步。通过练习和实践,你会发现泛型方法在实际编程中的强大作用,尤其是在处理集合和实现通用算法时。