tip:作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。
推荐:体系化学习Java(Java面试专题)
Java 引入泛型的主要目的是为了提高代码的类型安全性和可读性。在 Java 5 之前,集合框架中的容器可以存储任意类型的对象,这就使得程序员需要在运行时进行类型转换,容易引发类型转换异常。而引入泛型后,集合框架中的容器可以限定存储的元素类型,使得程序员可以在编译时进行类型检查,避免了类型转换异常的发生。此外,泛型还可以提高代码的可读性和可维护性,使得代码更易于理解和修改。泛型的引入使得 Java 语言更加类型安全,更加适合大规模软件开发。
接下来我们举个例子说明为什么:
假设有一个需求,需要编写一个方法,用于比较两个对象是否相等。最初的实现可能是这样的。
public static boolean compareObject(String s1, String s2) {
return s1.equals(s2);
}
但是入参不一定是 String,也有可能是 int、long 等,那么该怎么写呢?难道每个都写一遍?
public static boolean compareObject(String s1, String s2) {
return s1.equals(s2);
}
public static boolean compareObject(Integer s1, Integer s2) {
return s1.equals(s2);
}
这样的代码可读性,可维护性都很差,于是我们用泛型进行改进, 一个方法就搞定了。
public static <T> boolean compareObjects(T o1, T o2) {
return o1.equals(o2);
}
泛型是 Java 中的一个重要特性,它可以让我们编写更加通用、可复用的代码。
package com.pany.camp.base;
public class Genericity<T, U> {
private T first;
private U second;
public Genericity(T first, U second) {
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public U getSecond() {
return second;
}
public void setFirst(T first) {
this.first = first;
}
public void setSecond(U second) {
this.second = second;
}
@Override
public String toString() {
return "(" + first + ", " + second + ")";
}
}
这个示例中定义了一个泛型类 Genericity,它有两个类型参数 T 和 U,分别表示 Genericity 中的两个元素的类型。Genericity类有一个构造函数,用于初始化 Genericity对象的两个元素。Genericity 类还有一些方法,用于获取和设置 Genericity 对象的元素,以及将 Genericity 对象转换成字符串表示。
package com.pany.camp.base;
public class GenericityMain {
public static void main(String[] args) {
Genericity<String, Integer> pair = new Genericity<>("Hello", 123);
// 输出:(Hello, 123)
System.out.println(pair);
}
}
它的第一个元素是一个字符串,第二个元素是一个整数。使用泛型可以让我们编写更加通用、可复用的代码,可以在不同的场景中使用相同的类来处理不同类型的数据。
package com.pany.camp.base;
public interface Pair<T, U> {
T getFirst();
U getSecond();
}
我们定义了一个泛型接口 Pair,它有两个类型参数 T 和 U,分别表示 Pair 中的两个元素的类型。Pair 接口有两个方法,getFirst() 和 getSecond(),用于获取 Pair 对象的两个元素。
package com.pany.camp.base;
public class StringIntegerPair implements Pair<String, Integer> {
private String first;
private Integer second;
public StringIntegerPair(String first, Integer second) {
this.first = first;
this.second = second;
}
@Override
public String getFirst() {
return first;
}
@Override
public Integer getSecond() {
return second;
}
}
然后我们创建了一个 StringIntegerPair 类,它实现了 Pair 接口,并指定了 T 和 U 的具体类型为 String 和 Integer。StringIntegerPair 类有一个构造函数,用于初始化 StringIntegerPair 对象的两个元素。StringIntegerPair 类还实现了 Pair 接口的两个方法,用于获取 StringIntegerPair 对象的两个元素。
泛型方法可以让我们编写更加通用、可复用的代码,可以在不同的场景中使用相同的方法来处理不同类型的数据。
例子如下:
public static <T> int countOccurrences(T[] array, T element) {
int count = 0;
for (T value : array) {
if (value.equals(element)) {
count++;
}
}
return count;
}
我们定义了一个泛型方法 countOccurrences,它有两个参数,一个是泛型类型数组 array,另一个是泛型类型元素 element。countOccurrences 方法的作用是统计数组中与 element 相等的元素个数。
String[] strings = {"foo", "bar", "baz", "foo"};
int count1 = countOccurrences(strings, "foo"); // 返回 2
int count2 = countOccurrences(strings, "qux"); // 返回 0
Integer[] integers = {1, 2, 3, 2, 1};
int count3 = countOccurrences(integers, 2); // 返回 2
int count4 = countOccurrences(integers, 4); // 返回 0
我们分别使用了字符串类型数组和整数类型数组来调用 countOccurrences 方法,传入不同的元素来统计数组中相等的元素个数。
泛型的上下限是指在使用泛型时,可以限制泛型参数的类型范围,从而提高代码的类型安全性。上限限制了泛型参数的类型必须是某个类或其子类,下限限制了泛型参数的类型必须是某个类或其父类。
使用泛型的上下限可以避免类型转换错误,提高代码的类型安全性。
在 Java 中,可以使用 extends 关键字来指定泛型的上限,使用 super 关键字来指定泛型的下限。例如:
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 使用 extends 指定泛型的上限
public class NumberBox<T extends Number> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 使用 super 指定泛型的下限
public class IntegerBox<T super Integer> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
NumberBox 类使用 extends 指定泛型参数的上限为 Number 类型及其子类,因此只能使用 Number 类型及其子类作为泛型参数。而 IntegerBox 类使用 super 指定泛型参数的下限为 Integer 类型及其父类,因此只能使用 Integer 类型及其父类作为泛型参数。