If I have a set of enums, and want to have them all implement an interface, is this the correct way to do generically?
Enum:
public enum MentalSkill implements SkillType {
ACADEMICS,COMPUTER,CRAFTS,INVESTIGATION,MEDICINE,OCCULT,POLITICS,SCIENCE;
private static final int UNTRAINED_PENALTY = -3;
@Override
public SkillType fromValue(String value) {
return valueOf(value);
}
@Override
public int getUntrainedPenalty() {
return UNTRAINED_PENALTY;
}
}
Interface:
public interface SkillType {
SkillType fromValue(String value);
int getUntrainedPenalty();
}
Holding Class (where my suspcions lie that this is not quite right):
public class SkillsSet<T extends SkillType> {
T t;
Map<T,Integer> skills = new HashMap<>();
public SkillsSet(String[] skills) {
for (String string : skills) {
addSkill(string,t.getUntrainedPenalty());
}
}
private void addSkill(String skillString,Integer value) {
skills.put((T) t.fromValue(skillString), 0);
}
}
The issue comes in my T t
which will obviously give a NPE as I don't instantiate my inferred type. The issue is that I can't, as it's an enum.
What's the Java way of saying 'Use this interface to access an enum method generically.'? Throwing in skills.put(T.fromValue(skillString), 0);
doesn't work, which was my next guess.
I've looked over the tutorials (which is how I've gotten this far) and I couldn't seem to see how to get any further. How do I get this code to work?
Your problem lies in SkillType
- it should be generic:
public interface SkillType<T extends SkillType<T>> {
T fromValue(String value);
int getUntrainedPenalty();
}
This is called a self referencing generic parameter.
Without this self reference, you could return an instance of a different class from the fromValue()
method.
Here's how it would look:
public enum MentalSkill implements SkillType<MentalSkill> {
public MentalSkill fromValue(...) {}
...
}
And
public class SkillsSet<T extends SkillType<T>> {
...
}
Once you make this change, it should all fall into place.
One solution is through using reflection. I'm not sure if there are other solutions that might look cleaner though. The following does compile and run though.
public class SkillsSet<T extends SkillType> {
private Map<T,Integer> skills = new HashMap<>();
// These don't have to be class members, they could be passed
// around as parameters
private T[] enumConstants; // needed only for getEnumValue
private Class<T> enumClass; // needed only for getEnumValueAlternate
public SkillsSet(String[] skills, Class<T> enumClass) {
// Though all implementers of SkillType are currently enums, it is safer
// to do some type checking before we do any reflection things
enumConstants = enumClass.getEnumConstants();
if (enumConstants == null)
throw new IllegalArgumentException("enumClass is not an enum");
for (String string : skills) {
T t = getEnumValue(string)
if (t == null) {
// or use continue if you dont want to throw an exception
throw new IllegalArgumentException();
}
this.skills.put(t, t.getPenalty());
}
}
// These don't even need to be methods, but I separated them for clarity.
// SuppressWarnings annotation is used since we checked types in the constructor
@SuppressWarnings( "unchecked" )
public T getEnumValue( String string ) {
try {
return (T) enumConstants[0].fromValue(string);
}
// If valueOf does not find a match, it throws IllegalArgumentExecption
catch ( IllegalArgumentException e ) {
return null;
}
}
// An alternate version of getEnumValue that does not require the 'fromValue'
// method on the SkillType interface.
@SuppressWarnings( "unchecked" )
public T getEnumValueAlternate( String string ) {
try {
return (T) enumClass.getMethod("valueOf", String.class).invoke(null, string)
}
// Any reflection exceptions are treated as 'not found' on valueOf
catch (Exception e) {
return null;
}
}
...
}
There are two different versions of getEnumValue
. I'd recommend the version that uses the getEnumConstants
method on the enumClass
parameter. It also allows you to handle special cases in fromValue
that might not typically match the enum's valueOf
function.
Instantiating the above works with
SkillsSet<MentalSkill> = new SkillsSet<MentalSkill>(new String[] {"COMPUTER", "CRAFTS"}, MentalSkill.class);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With