我想要一个可以通过索引创建其他类的实例的类。我想要一个包含所有可以实例化的类型(所有类型都扩展Object
)的列表,这样我就可以有一个像public Object newObject(int i)
这样的函数,它将返回该类型的新实例(它们每个都有一个默认构造函数)。
有没有办法存储类型列表?
您在这里有2个选项。
然而,第二种选择很常见。我将首先解释它,以便您知道应该避免什么,并将工厂解决方案放在上下文中。
类有一个对象表示:java. lang.Class
类的一个实例。您可以轻松获取这些实例:Class
给定这样一个对象,您可以获取no-args构造函数并调用它:
Class<?> c = Class.forName("java.lang.String");
Constructor<?> noArgsConstructor = c.getConstructor();
Object fresh = noArgsConstructor.newInstance();
System.out.println("".equals(fresh)); // prints 'true'
然而,这样做有很多问题:
Class
(和Constructor
)的泛型被破坏了。泛型允许泛型-你可以有一个列表
interface Namable {
void setName(String name);
}
public static <T extends Namable> T create(Class<T> type) {
T namable = type.newConstructor().newInstance();
namable.setName("Jane");
return namable;
}
// to use:
@lombok.Data
class Person implements Namable {
String name;
}
Person p = create(Person.class);
System.out.println(p.name); // prints 'Jane'
到目前为止,一切顺利。但现在让我们做同样的事情,但使用List
而不是Namable
:
public <L extends List<String>> L create(Class<L> listType) {
List<String> list = listType.getConstructor().newInstance();
list.add("Hello, World!");
return list;
}
这…编译很好,但您不能在没有很多问题和编译器警告的情况下使用它:
create(ArrayList<String>.class); // does not compile
create(ArrayList.class); // does, but warnings.
>
这种反射式访问会引发大量异常。当然,您可以处理它们,但这很烦人。
编译器和类型系统在这里对您没有帮助。作为“合同”的一部分,类
因此,这不是一个好主意。
这一切如此不稳定的原因是静态成员没有“做”类型层次结构。构造函数只是一个不稳定的静态方法——想想吧:你不能在接口中声明需要某个构造函数——这与静态方法相同。它们也不做动态调度——就像静态方法一样。它们不需要接收者(x.foo()
中的x
称为接收者)——就像静态方法一样。
您根本无法创建一个接口,该接口规定实现
的任何类型都必须具有某些静态属性。一点也不。java作为一种语言根本不是这样工作的。只有实例才玩动态调度游戏,因此只有实例才有意义拥有一个允许您做出promise的类型层次结构(例如:“所有类型为List
的东西都有一个. size()
方法,而不管它是哪个特定的列表),因此,让工具(编译器、编辑器)提供帮助。
所以,我们需要以某种方式将静态事物变成实例事物。
输入:工厂模式。
工厂只是第二个元层,与其说是描述“一棵树”,不如说是描述“了解某一属树并能制造新树的东西”:
public interface Tree {
void sprout();
}
class Birch implements Tree {
public void sprout() { ... }
}
我们在此添加:
public interface TreeFactory {
Tree create();
}
public class BirchFactory implements TreeFactory {
Birch create() { return new Birch(); }
}
我们现在已经“实例化”了曾经是静态方面的东西——根据“树”或“桦树”的概念,create()
是“静态”——作为一个概念,它适用于所有这些方面。但是从“树制造商”的概念来看,create()
是一个实例——每个树制造商都有自己的创建概念。
使用这种模式,反射式访问的所有问题都消失了:
>
您可以使用该接口来判定您有例如没有参数。
没有奇怪的检查异常要处理。
适用于泛型。
工厂的一种变体是隐式工厂——你也可以只传递一个函数来动态创建它们,而不是显式地创建一个TreeFactory
对象。事实上,java核心库自己的Collectors. toMap
和Collection.toArray
使用了这种技术:由于泛型,集合实际上不能创建一个正确类型的新数组,所以它需要你告诉它如何做到这一点。你可以用IntFunction来做到这一点
List<String> list = new ArrayList<String>();
list.toArray(size -> new String[size]);
// or simpler:
list.toArray(String[]::new);
您可以将相同的技术应用于您的代码:
public <T extends Tree> List<T> plantManyTrees(int size, Supplier<? extends T> treeFactory) {
var planted = new ArrayList<T>();
for (int i = 0; i < size; i++) {
planted.add(treeFactory.get());
}
return planted;
}
// to use:
List<Birch> copseOfBirches = plantManyTrees(100, () -> new Birch());
或者,如果您想要,例如,基于字符串的动态查找树类型:
Map<String, Supplier<? extends Tree>> makers = new HashMap<>();
makers.put("Birch", () -> new Birch());
makers.put("Willow", () -> new Willow());
等等
您可以使用列表或类数组。祝你好运!