想象一下:您是一名 Java 开发人员,从事涉及处理大量不同数据类型的复杂项目。你有你的列表、你的地图、你的集合,它们都充满了不同种类的对象。试图跟踪所有这些是一场噩梦,而且您一直担心不小心将错误类型的对象传递给方法或函数。但不要害怕,我的开发伙伴,因为有一个解决你的困境的方法:泛型。
泛型是 Java 中的一项功能,它允许您编写可以处理多种数据类型的代码。将它们视为占位符,在您使用它们时可以用实际类型替换。它们是在 Java 5 中引入的,从此成为该语言的重要组成部分。但是历史课已经讲完了,让我们深入研究一些代码示例。
假设您正在从事一个涉及对对象列表进行排序的项目。如果没有泛型,你可能会这样写:
List unsortedList = new ArrayList();
unsortedList.add("apple");
unsortedList.add("banana");
unsortedList.add(42); //我们向字符串列表中添加了一个整数
Collections.sort(unsortedList);
这段代码可能会编译而不会出现任何错误,但是当您运行它时,您将得到一个 ClassCastException,因为您正试图对同时包含字符串和整数的列表进行排序。但不要害怕,因为使用泛型,我们可以完全避免这个问题:
List<String> unsortedList = new ArrayList<>();
unsortedList.add("apple");
unsortedList.add("banana");
unsortedList.add(42); // 编译器错误:类型不兼容
Collections.sort(unsortedList);
通过指定我们的列表应该只包含字符串,我们已经消除了向它添加整数的可能性。当我们尝试添加一个整数时,编译器会捕获它并给我们一个错误。这似乎是一个很小的改进,但它可以为您省去很多麻烦。
但泛型的力量并不止于此。假设您正在处理一个需要返回特定类型对象的方法。如果没有泛型,你可能会这样写:
public Object getObject() {
//这里是一些代码
return someObject;
}
这个方法可以返回任何类型的对象,这在某些情况下可能没问题,但不是很具体。使用泛型,我们可以让我们的代码更精确:
public <T> T getObject(Class<T> clazz) {
//这里是一些代码
return clazz.cast(someObject);
}
现在我们的方法可以返回任何类型的对象,只要我们在调用该方法时指定我们想要的类型即可。例如:
String myString = getObject(String.class);
Integer myInteger = getObject(Integer.class);
通过使用泛型,我们使代码更灵活,同时也更具体。这是双赢的。
现在让我们来看看我在日常工作中遇到的这种情况:
我在这个庞大的遗留代码库中工作,数据库中有超过一千个类和表。基本代码最初是用 Java 7 编写的,后来升级到 Java 8。你明白了!在这种特定情况下,我们无法重构所有基本代码以在我们拥有的所有类中实现 Builder Factories。这是一项艰巨而痛苦的任务。在这种情况下,仿制药救了我的命!我已经实现了一个通用类,可以在我想要的任何类中使用 Builder 模式!看一下类:
public class Builder<T> {
// Supplier 是一个函数式接口,它不接受任何参数并返回一个值。
// 在这种情况下,Supplier 用于创建泛型类型 T 的新实例。
private final Supplier<T> instantiator;
// 一个 Consumer 函数列表,其中每个 Consumer 以某种方式修改一个 T 实例。
private final List<Consumer<T>> instanceModifiers = new ArrayList<>();
// 采用 T 实例的构造函数
public Builder(Supplier<T> instantiator) {
this.instantiator = instantiator;
}
//静态工厂方法,该方法使用给定的实例器创建新的生成器
public static <T> Builder<T> of(Supplier<T> instantiator) {
return new Builder<>(instantiator);
}
// 该方法接受一个bicconsumer和一个U类型的值,并向列表中添加一个新的Consumer
// 给定的值。
public <U> Builder<T> with(BiConsumer<T, U> consumer, U value) {
Consumer<T> c = instance -> consumer.accept(instance, value);
instanceModifiers.add(c);
return this;
}
// 方法使用实例化器创建T的新实例,应用所有的instancemodifier
// 应用于它,并返回结果。
public T build() {
T value = instantiator.get();
instanceModifiers.forEach(modifier -> modifier.accept(value));
instanceModifiers.clear();
return value;
}
}
这个类允许我们实现构建器模式,而不必修改代码库中的每个类。我们可以简单地创建一个Builder
类的新实例,并用它来构建任何类型的对象。让我们看一些代码示例:
public class Person {
private String name;
private int age;
private String address;
// getters and setters
}
// 使用构建器创建Person对象
Person person = Builder.of(Person::new)
.with(Person::setName, "John Doe")
.with(Person::setAge, 30)
.build();
在这个例子中,我们使用Builder
类来创建一个Person
对象。我们首先创建该类的一个新实例Builder
并通过引用传入一个新Person
对象。然后我们使用该with
方法来设置对象的name
和字段。最后,我们调用创建最终对象的方法。
另一个例子:
public class Car {
private String make;
private String model;
private int year;
public Car(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
// getters and setters
}
// 使用构建器创建Car对象
Car car = Builder.of(() -> new Car(null, null, 0))
.with(Car::setMake, "Toyota")
.with(Car::setModel, "Camry")
.with(Car::setYear, 2021)
.build();
在这个例子中,我们使用Builder
类来创建一个Car
对象。我们遵循与之前相同的模式,创建该类的一个新实例Builder
并通过引用传入一个新Car
对象。然后,我们使用该with
方法设置对象的make
、model
和year
字段Car
。最后,我们调用build
创建最终Car
对象的方法。
总之,Java 中的泛型可以成为处理复杂数据类型的开发人员的救星。它们允许我们编写更灵活和更具体的代码,这可以让我们免于很多麻烦。因此,下次您处理 Java 项目时,请记住使用泛型。你未来的自己会感谢你。