【问题标题】:Store classes in a Map so that they can be instantiated将类存储在 Map 中,以便可以实例化它们
【发布时间】:2018-09-16 17:47:31
【问题描述】:

我希望能够基于 HashMap 条目创建类的实例。

例如这就是我想写的:

public class One implements Interface {
  public void sayName() {
    System.out.println("One");
  }
}

public class Two implements Interface {
  public void sayName() {
    System.out.println("Two");
  }
}

Map<String, Interface> associations = new HashMap<String, Interface>();

associations.put("first", One);
associations.put("second", Two);

Interface instance = new associations.get("first")();

instance.sayName(); // outputs "One"

但我强烈怀疑这在 Java 中不起作用。


我的情况:我想创建一种将String 名称与类相关联的方法。

用户可以通过使用其“名称”来创建类的实例。

我想尝试:制作一个名称到类的映射(我不知道如何在映射中存储类),并从映射中获取与“名称”匹配的项目,然后实例化它。

那行不通。


如何将类与 String 名称关联并使用我给它的“名称”实例化这些类?

【问题讨论】:

  • 您将需要研究 Java 反射 API。特别是有一个 class.forName 方法
  • 制作您的地图Map&lt;String, Supplier&gt; 并使用One::newTwo::new 而不是OneTwo。然后你可以做associations.get("first").get()
  • 此外,您实际上不想存储类,而是构造函数。从某种角度来看,构造函数只是一个Provider,即一个在调用时会生成一些东西的函数。正如@Kon 所说,这肯定会涉及一些反思。您可能还想查看Factory method- 和/或Builder-Pattern
  • 您的地图输入错误。对于 Class.forName 方法,它应该是 Map 。或者 Map>(@lexicore 有更好的建议)

标签: java class oop dictionary first-class


【解决方案1】:

您可以使用Supplier 功能接口和对默认构造函数的方法引用:

Map<String, Supplier<Interface>> associations = new HashMap<>();

associations.put("first", One::new);
associations.put("second", Two::new);

要实例化一个新对象,请调用Supplier.get

Interface foo = associations.get("first").get();

如果您的构造函数需要参数,则需要使用另一个functional interface。对于一个和两个参数的构造函数,您可以分别使用FunctionBiFunction。再多的话,您将需要定义自己的功能接口。假设构造函数都接受一个字符串,你可以这样做:

class One implements Interface
{
    One(String foo){ }

    public void sayName() {
        System.out.println("One");
    }
}

Map<String, Function<String, Interface>> associations = new HashMap<>();
associations.put("first", One::new);

然后使用Function.apply获取实例:

Interface a = associations.get("first").apply("some string");

如果您的构造函数采用不同数量的参数,那么您就不走运了。

【讨论】:

  • 我需要创建一个接受“构造函数”(One::newTwo::new)的函数,Class::new 的类型签名是什么?
  • 它没有类型。它将匹配任何接受相同数量参数并返回 Interface 的函数式接口。
  • 我需要创建一个包装associations.put 的函数,该函数采用String 键和Class::new 值。该函数看起来像void addAssoc(String name, XXX constructor)XXX 会变成 Function&lt;String, Interface&gt; 吗?
  • 如果构造函数都取 1 个字符串,那么是的。
【解决方案2】:

我强烈建议在Michael's answer 中使用Supplier。或者,您应该能够使用以下内容:

var associations = Map.of("first", One.class, "second", Two.class);

var foo = associations.get("first").getConstructor().newInstance();

如果您的构造函数需要参数,只需将类传递给getConstructor 并将值传递给newInstance。例如,如果Two 在其构造函数中采用int

var foo = associations.get("two").getConstructor(int.class).newInstance(5);

注意:这里使用的是 Java 10。

【讨论】:

  • 从地图获取后如何将参数传递给构造函数?
  • 我确实看到了,你为什么使用var 关键字?是Two 类型的最后一个foo 吗?
  • 我以前没有见过这个Map.of 表示法,我如何一次向它添加单个元素? (创建的时候不知道里面会有多少元素)
  • 是的,我使用 var 只是为了缩短代码(因为它是 Java 10 中的新功能)。我不喜欢输入Map&lt;String, Class&lt;Interface&gt;&gt;Map#of 是在 Java 9 中引入的。要逐个添加元素,只需在其中初始化 HashMapput 条目。
【解决方案3】:

您是要存储类并按需创建新实例,还是要存储实例并直接使用它们?

public class ClassInHashMap {
    public static void main(String[] args) {
    Map<String,Class<? extends SomeInterface>> associations = new HashMap<String,Class<? extends SomeInterface>>();
        associations.put("first", One.class);
        associations.put("second", Two.class);

        try {
            Class<? extends SomeInterface> someCls = associations.get("first");
            SomeInterface instance = someCls.getDeclaredConstructor().newInstance();
            instance.sayName(); // outputs "One"
        }
        catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        catch (SecurityException e) {
            e.printStackTrace();
        }
        catch (InstantiationException e) {
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

interface SomeInterface {
    public void sayName();
}

class One implements SomeInterface {
    public void sayName() {
        System.out.println("One");
    }
}

class Two implements SomeInterface {
    public void sayName() {
        System.out.println("Two");
    }
}

【讨论】:

    最近更新 更多