我今天聊聊Java中的泛型, 它是一个广泛使用但讨论较少的主题。我们经常会使用它,但大多数开发人员并没有真正了解它。
Java开发中你们肯定都用过List或者ArrayList。那你们应该记得如何定义他们吧?
List list = new ArrayList(); // 这里的Integer 就是使用了泛型
这就是我们声明的方式。所以,我们使用了泛型。这里,是我们传递的指定类型。那是一个类型。在我们创建这样的列表后,您只能将整数添加到列表中。
那如果我们不指定类型呢?
List numList = new ArrayList(); //不指定类型
如果我们像上面这样定义列表,我们就可以将从 Object 超类扩展的任何类型的数据添加到列表中。
所以添加泛型后,我们可以实现此列表的类型安全。
泛型意味着参数化类型。Java 让我们创建一个类、接口和方法,可以在泛型域中与不同类型的数据(对象)一起使用。
泛型的优点是:
- 代码可重用性——我们可以使用具有多种对象类型的通用代码
- 编译时类型检查——Java 将在编译时检查泛型代码是否有错误
- 类型安全——我们可以限制添加不必要的数据
- 集合中的用法——集合需要对象类型来处理数据
让我们举个例子来解释为什么我们需要泛型。
想象一下,您必须使用打印机类打印数字和文本。打印机有一种在创建数据时接受数据的方法。
在传统方式中,我们必须创建 2 个类,因为我们有 2 种数据类型:数字(整数)和文本(字符串)
public class TextPrinter {
private final String data;
public TextPrinter(String data) {
this.data = data;
}
public void print() {
System.out.println("print::: " + data);
}
}
public class NumberPrinter {
private final Integer data;
public NumberPrinter(Integer data) {
this.data = data;
}
public void print() {
System.out.println("print::: " + data);
}
}
使用:
public class GenericsMain {
public static void main(String[] args) {
NumberPrinter numberPrinter = new NumberPrinter(5);
numberPrinter.print(); // 输出 print::: 5
TextPrinter textPrinter = new TextPrinter("Hello");
textPrinter.print(); // 输出 print::: Hello
}
}
有没有觉得代码重复了?唯一的区别就是数据类型不同!
下面我们利用泛型来改造一下,使它成为一个通用的类型。
public class Printer {
private final T data;
public Printer(T data) {
this.data = data;
}
public void print() {
System.out.println("print::: " + data);
}
}
使用:
Printer integerPrinter = new Printer(5);
integerPrinter.print(); // 输出 print::: 5
Printer stringPrinter = new Printer("Hello");
stringPrinter.print(); // 输出 print::: Hello
Printer doublePrinter = new Printer(45.34);
doublePrinter.print(); // 输出 print::: 45.34
Printer longPrinter = new Printer(5L);
longPrinter.print();z //输出 print::: 5
现在我们就只写了一个类,T用来表示作为通用标准的类型。我们甚至可以为其他数据类型(例如 Double/Long)创建打印对象。代码可重用性是通过风格实现的。
我们还可以创建多个类型的通用类。如下:
public class MultiPrinter {
private final T data1;
private final V data2;
public MultiPrinter(T data1, V data2) {
this.data1 = data1;
this.data2 = data2;
}
public void print() {
System.out.println("print::: " + data1 + " : " + data2);
}
}
MultiPrinter multiPrinter = new MultiPrinter(5, "Hello");
multiPrinter.print(); // 输出 print::: 5 : Hello
Java 类型命名约定:
- E - 元素(用于集合)
- K — 键(在地图中使用)
- N——数字
- T——类型
- V - 值(在地图中使用)
- S、U、V 等 — 第二、第三、第四类型
有界泛型
这是泛型的高级版本。我们可以通过有界泛型来限制更多并实现更多类型安全。
假设我们有一个AnimalPrinter类,它只能打印动物详细信息。不允许与其他物体一起使用。如何实现这一目标?
public class Animal {
private final String name;
private final String color;
private final Integer age;
public Animal(String name, String color, Integer age) {
this.name = name;
this.color = color;
this.age = age;
}
public String getName() {
return name;
}
public String getColor() {
return color;
}
public Integer getAge() {
return age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Animal animal = (Animal) o;
return Objects.equals(name, animal.name) && Objects.equals(color, animal.color) && Objects.equals(age, animal.age);
}
@Override
public int hashCode() {
return Objects.hash(name, color, age);
}
}
public class Cat extends Animal {
public Cat(String name, String color, Integer age) {
super(name, color, age);
}
}
public class Dog extends Animal {
public Dog(String name, String color, Integer age) {
super(name, color, age);
}
}
public class AnimalPrinter {
private final T animalData;
public AnimalPrinter(T animalData) {
this.animalData = animalData;
}
public void print() {
System.out.println("Name::: " + animalData.getName());
System.out.println("Color::: " + animalData.getColor());
System.out.println("Age::: " + animalData.getAge());
}
}
在这个类中,T 扩展 Animal 部分完成了工作!我们限制了狗和猫的通用性!
AnimalPrinter animalPrinter1 = new AnimalPrinter(new Cat("Jim", "brown", 2));
animalPrinter1.print();
AnimalPrinter animalPrinter2 = new AnimalPrinter(new Dog("Rocky", "black", 5));
animalPrinter2.print();
多重界限
假设我们想向打印机通用功能添加更多功能。我们可以这样实现。
public class AnimalPrinter {
..................
}
我使用 Serialized 接口提供了 Serialized 功能。这里有一些重要的事情需要记住。
- 我们必须在子类(Cat 和 Dog)中实现接口。
- 类应该放在第一位,然后是 & 和接口。
- 由于 Java 不支持多重继承,因此只能扩展 1 个类。
泛型通配符
通配符由问号?表示 在 Java 中,我们用它们来指代未知类型。这可以用作泛型的参数类型。然后它将接受任何类型。在下面的代码中,我使用通配符将任何对象的列表用作方法参数。
public static void printList(List list) {
System.out.println(list);
}
printList(
Arrays.asList(
new Cat("Jim", "brown", 2),
new Dog("Rocky", "black", 5)
)
);
printList(Arrays.asList(50, 60));
printList(Arrays.asList(50.45, 60.78));
// output:
// [generics.Cat@b1fa3959, generics.Dog@62294cd9]
// [50, 60]
// [50.45, 60.78]
列表现在可以是任何类型!
上限通配符
考虑这个例子:
public static void printAnimals(List animals) {
animals.forEach(Animal::eat);
}
如果我们想象Animal的子类型,例如Dog ,我们就不能将此方法与Dog列表一起使用,即使Dog是Animal的子类型。我们可以使用通配符来做到这一点。
public static void printAnimals(List