我有一个父界面。
public interface Parent{...}
以及两个实现该接口(interface)的类。
public class ChildA implements Parent {...}
public class ChildB implements Parent {...}
现在我想根据传递的参数创建子对象的对象。像这样的事情,
Parent instance1 = Parent.getChild("Key1");
目前,我通过使用 HashMap 来实现这一点。
static Map<String,Class<?>> map = new HashMap<String,Class<?>>();
static void init() throws ClassNotFoundException {
map.put("Key1",Class.forName("ChildA"));
map.put("Key2",Class.forName("ChildB"));
}
static Parent getChild(String key) throws InstantiationException, IllegalAccessException {
return (Parent) map.get(key).newInstance();
}
但这里的问题是每次我实现一个新的 child 时,我都必须将其添加到 Parent init 方法中。那么有没有更干净的方法来做到这一点?比如将 key 的信息添加到 child 本身,
public class ChildA implements Parent {
private String key = "Key1";
...
因此,当我从父类调用 getChild 时,它会引用相应的 Child。
基本上,我要求一种根据传递的参数动态引用父级的子级的方法。
最佳答案
您可以使用service provider mechanism 。当积极使用 Java 的模块系统时,这种方法效果最好,必要的部分甚至集成到了 Java 语言中。
如果可以在其他模块中实现,则包含父接口(interface)的模块必须导出其包。然后,想要查找接口(interface)实现的模块必须有一个
uses yourpackage.Parent;
模块信息中的指令。提供实现的模块必须有一个类似的指令
provides yourpackage.Parent with yourpackage.ChildA, yourpackage.ChildB;
在其模块信息中。可以使用接口(interface)并在同一模块内提供实现。这也可能是包含接口(interface)声明的模块。在这种情况下,甚至可以仅在单个模块内使用该机制,而根本不导出接口(interface)。
编译器将检查指定的实现类是否为 public
、是否具有 public
无参数构造函数,以及是否真正实现了服务接口(interface)。这些实现不需要驻留在导出的包中;该接口(interface)可能是从另一个模块访问实现的唯一方法。
由于编译器会预先检查所需的不变量,因此您无需处理反射固有的问题。例如,您不需要声明或处理 InstantiationException
或 IllegalAccessException
。
一个简单的设置可能是
模块信息module ExampleApp {
uses yourpackage.Parent;
provides yourpackage.Parent with yourpackage.ChildA, yourpackage.ChildB;
}
你的包裹/ parent
package yourpackage;
public interface Parent {
}
你的包裹/TheKey
package yourpackage;
import java.lang.annotation.*;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TheKey {
String value();
}
你的包裹/ChildA
package yourpackage;
@TheKey("Key1")
public class ChildA implements Parent {
}
你的包裹/ChildB
package yourpackage;
@TheKey("Key2")
public class ChildB implements Parent {
}
独立包/使用接口(interface)
package independentpackage;
import java.util.ServiceLoader;
import yourpackage.Parent;
import yourpackage.TheKey;
public class UsingTheInterfaces {
public static void main(String[] args) {
Parent p = getChild("Key1");
System.out.println(p);
}
static Parent getChild(String key) {
return ServiceLoader.load(Parent.class).stream()
.filter(p -> {
TheKey keyAnno = p.type().getAnnotation(TheKey.class);
return keyAnno != null && keyAnno.value().equals(key);
})
.findAny()
.map(ServiceLoader.Provider::get)
.orElse(null); // decide how to handle absent keys
}
}
如上所述,当您有一个 exports yourpackage;
指令时,其他模块和 ServiceLoader
可以提供实现。将为运行时存在的所有模块动态发现这些实现。使用模块不需要在编译时知道它们。
对于没有模块系统的旧 Java 版本,有一个前身机制,它也由 ServiceLoader
以向后兼容的方式处理。这些提供程序必须打包在一个 jar 文件中,该文件包含一个文件 META-INF/services/yourpackage.Parent
,该文件包含实现该接口(interface)的 jar 文件中所有类的列表。在 Java 9 之前,ServiceLoader
还缺乏 Stream API 支持,该支持允许在实例化实现之前查询注释。您只能使用已经实例化类并返回实例的Iterator
。
避免旧 API 不必要的开销的一种方法是将接口(interface)分为服务提供者接口(interface)和实际服务接口(interface)。提供者接口(interface)将提供元信息并充当实际服务实现的工厂。与CharsetProvider
之间的关系进行比较和 Charset
或FileSystemProvider
和 FileSystem
。这些例子也表明服务提供者机制已经被广泛使用。更多示例可以在 java.base
module documentation 中找到.
这只是一个概述; API documentation of ServiceLoader
包含有关该机制的更多详细信息。
关于java - 根据传递的参数选择接口(interface)的实现,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62752219/