1

I have the following java code

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();

}

}

and I would like my getQuestion method to return me a new instance of the class that was specified as a value in the map as is intended via my code. Howerver the last line of code does not compile :

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

Am I thinking of this wrongly? i.e. is there a better way to approach this?

2
  • 4
    What is the compile error you get? Commented Dec 16, 2014 at 0:29
  • questionIdMap = new HashMap(); should be questionIdMap = new HashMap<>(); Commented Dec 16, 2014 at 13:18

2 Answers 2

1

You just need to catch an exception:

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

This should compile fine.

Sign up to request clarification or add additional context in comments.

7 Comments

Thanks. As a followup...is this a fail-safe operation? is there any reason why this statement would throw a runtime exception after i've verified the key is in the map an the class has a public default constructor??
You are invoking the object's constructor. It could throw an exception. If it does, the exception will be wrapped into an InstantiationException, and caught here. Because this is reflection, java cannot know at compile time what constructor is going to be called, and what it may throw so newInstance() is declared to throw an InstantiationException no matter what. Now, why the hell you have to always catch all exceptions that can possibly be thrown in java, is a different question ... I don't think anyone knows an answer to that one :)
@Dima It's annoying, but I find it useful as it warns you about potential problems and makes you consider alternative approaches. In this example, the checked exception is a clue that reflection isn't the best approach at all. Instead of questionIdMap there should be an enum with values LICENCE_NUMBER, USP... and a method getInstance overridden by each enum constant which returns a new instance without using reflection. That way Questions are not required to have parameterless constructors, and you don't need the BusinessProfileInputException either...
I'm not familiar with coding up the idiom "an enum with values and a method overridden by each enum constant"? Mind posting a minor example of what that looks like? Somewhat new to java and enums seem to be more powerful than in other languages.
@pbabcdefp it's debatable. I am not sure what exactly you have in mind when you are saying that "reflection isn't best approach at all". The only potential problem with reflection I can think of is performance, unless you are creating hundreds of these objects per second, it doesn't matter. Enum approach may be faster, but it is more code (=== more bugs), harder to read and understand what's going on, and harder to maintain and extend (adding new subclasses requires making changes to unrelated code).
|
0

I would do it like this:

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();
    }
}

With your solution the user of the class has to write

Question question = QuestionBuilder.getQuestion(1);

This isn't great because it's not clear what "1" here means, and she is going to have to learn the meaning of a load of numbers. Also, if you pass in a number that doesn't mean anything, there's a problem (hence the need for a BusinessProfileInputException).

With the enum approach, the user of the class would write

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

Now this is obviously longer, but there are 3 major advantages. (1) The user of the class doesn't need to remember any abstract code numbers. In fact if the user is using a decent IDE, she should actually be presented with a meaningful list of choices as she types. (2) There is no longer the need for the BusinessProfileInputException because it is now impossible to pass something that doesn't mean anything (except null, but in this case a NullPointerException would be thrown anyway). (3) You no longer need reflection to create the new Question, so there is no need for the irritating try block.

But it's even better than this. You'll notice that since we've got rid of the Map, the class QuestionBuilder doesn't actually do anything at all. You could improve things further by getting rid of the class completely, and making the enum a top-level class with a simple name like TypeOfQuestion. Then all the user would have to type is

Question question = TypeOfQuestion.LICENSE_NUMBER.getQuestion();

enums in Java are absolutely brilliant. They are far superior to the counterparts in other languages. I strongly recommend learning about them.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.