【问题标题】:Java instantiate generic hashmap valueJava实例化泛型hashmap值
【发布时间】:2014-12-16 00:27:25
【问题描述】:

我有以下java代码

public class QuestionBuilder {

private QuestionBuilder(){}

static HashMap<Long,Class<? extends Question>> questionIdMap;

static{
    questionIdMap = new HashMap();
    questionIdMap.put(1L, LicenseNumberQuestion.class);
    questionIdMap.put(2L, USPQuestion.class);
}



static Question getQuestion(long questionId)
{
    if(!questionIdMap.containsKey(questionId))
    {
        throw new BusinessProfileInputException("Add an id to question class map entry");
    }

     return questionIdMap.get(questionId).newInstance();

}

}

我希望我的 getQuestion 方法返回一个类的新实例,该实例在地图中指定为一个值,正如我的代码所预期的那样。但是最后一行代码无法编译:

return questionIdMap.get(questionId).newInstance();

我是不是想错了?即有没有更好的方法来解决这个问题?

【问题讨论】:

  • 你得到什么编译错误?
  • questionIdMap = new HashMap(); 应该是questionIdMap = new HashMap&lt;&gt;();

标签: java generics


【解决方案1】:

你只需要捕获一个异常:

try {
    return questionIdMap.get(questionId).newInstance();
} catch(InstantiationException e) {
    System.out.println("Constructor failed: );
    e.printStackTrace();
    return null;
}

这应该可以正常编译。

【讨论】:

  • 谢谢。作为后续行动......这是一个故障安全操作吗?在我验证密钥在地图中并且该类具有公共默认构造函数之后,该语句是否会引发运行时异常??
  • 您正在调用对象的构造函数。它可能会引发异常。如果是这样,异常将被包装到InstantiationException 中,并在此处捕获。因为这是反射,java 在编译时无法知道将要调用什么构造函数,以及它可能会抛出什么,所以newInstance() 被声明为无论如何都会抛出一个InstantiationException。现在,为什么你必须总是捕获所有可能在 java 中抛出的异常,这是一个不同的问题......我不认为 任何人 知道这个问题的答案:)跨度>
  • @Dima 这很烦人,但我发现它很有用,因为它会警告您潜在的问题并让您考虑替代方法。在这个例子中,检查异常表明反射根本不是最好的方法。而不是 questionIdMap 应该有一个枚举值 LICENCE_NUMBER、USP... 和一个方法 getInstance 被每个枚举常量覆盖,它返回一个新实例而不使用反射。这样 Questions 不需要有无参数的构造函数,你也不需要 BusinessProfileInputException ...
  • 我不熟悉编写成语“具有值的枚举和每个枚举常量覆盖的方法”的编码?介意张贴一个看起来像什么的小例子吗? Java 有点新,枚举似乎比其他语言更强大。
  • @pbabcdefp 这值得商榷。当您说“反思根本不是最好的方法”时,我不确定您到底在想什么。我能想到的唯一潜在的反射问题是性能,除非你每秒创建数百个这样的对象,否则没关系。枚举方法可能更快,但它的代码更多(=== 更多错误),更难阅读和理解正在发生的事情,更难维护和扩展(添加新的子类需要对不相关的代码进行更改)。
【解决方案2】:

我会这样做:

public final class QuestionBuilder {

    private QuestionBuilder(){}

    public enum Type {

        LICENSE_NUMBER {
            @Override
            Question getQuestion() { return new LicenseNumberQuestion(); }
        },
        USP {
            @Override
            Question getQuestion() { return new USPQuestion(); }
        };

        abstract Question getQuestion();
    }

    public static Question getQuestion(Type type) {
        return type.getQuestion();
    }
}

使用您的解决方案,该类的用户必须编写

Question question = QuestionBuilder.getQuestion(1);

这不是很好,因为不清楚这里的“1”是什么意思,而且她将不得不学习大量数字的含义。另外,如果你传入一个没有任何意义的数字,就会出现问题(因此需要BusinessProfileInputException)。

使用enum 方法,该类的用户将编写

Question question = QuestionBuilder.getQuestion(QuestionBuilder.Type.LICENSE_NUMBER);

现在这显然更长了,但有 3 个主要优点。 (1) 类的用户不需要记住任何抽象代码数字。事实上,如果用户正在使用一个体面的 IDE,那么在她键入时实际上应该向她呈现一个有意义的选项列表。 (2) 不再需要 BusinessProfileInputException,因为现在 不可能 传递没有任何意义的东西(null 除外,但在这种情况下 NullPointerException 会无论如何都要扔掉)。 (3) 你不再需要反射来创建新的Question,因此不需要烦人的try 块。

但它比这更好。您会注意到,由于我们已经摆脱了Map,因此QuestionBuilder 类实际上并没有做任何事情。您可以通过完全摆脱该类来进一步改进事情,并使enum 成为一个具有简单名称(如TypeOfQuestion)的顶级类。那么用户只需输入

Question question = TypeOfQuestion.LICENSE_NUMBER.getQuestion();

enums 在 Java 中绝对出色。它们远远优于其他语言的对应物。我强烈建议了解它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-09-18
    • 1970-01-01
    • 2014-01-09
    • 2011-06-09
    • 1970-01-01
    • 1970-01-01
    • 2013-10-03
    相关资源
    最近更新 更多