Reputation:
Lets say I have a bunch of logically distinct java enums that cannot be merged together since certain prior operations depend on each independent enum.
enum Enum1 {
ENUM1VAL1,
ENUM1VAL2
}
enum Enum2 {
ENUM2VAL1,
ENUM2VAL2
}
void existingOperationOnEnum1(Enum1 enum1);
void existingOperationOnEnum2(Enum2 enum2);
Now lets say I wish to define a new operation that checks whether a string can be converted to any of these enums
void isValidStringConversionForEnums(final String input) {
ENUM1.valueOf(input); // Throws IllegalArgumentException if conversion not possible
ENUM2.valueOf(input); // Throws IllegalArgumentException if conversion not possible
}
So this is great, but its not extensible. The business logic is such that we can have new enums be created in the future (with its own set of enum specific operations), however isValidStringConversionForEnums needs to be updated every time a new one is added, which a programmer might easily forget.
So my question is how would I modify isValidStringConversionForEnums so that it works with future extensions.
One way I thought was to create an interface EnumBase, get the Enums to extend that and then use reflection to get all Enum impls, but I do not wish to use Reflection in Runtime unless I have to.
Is there an alternate way in which this can be written such that I add a new enum and somehow isValidStringConversionForEnums handles that new enum.
Upvotes: 1
Views: 68
Reputation: 102872
You have 2 independent problems here:
These 2 problems are completely unrelated. So, you solve them separately.
Class<? extends Enum>
is probably your best bet.
enum Test1 {
FOO, BAR;
}
enum Test2 {
FOO, BAZ;
}
public static void isValidStringConversionForEnums(String in, Class<? extends Enum>... enumTypes) {
for (var enumType : enumTypes) {
for (var c : enumType.getEnumConstants()) {
if (c.name().equals(in)) return true;
}
}
return false;
}
This isn't particularly efficient - but you get the gist. You can pre-make a HashSet containing every valid value and then it's just a .containsKey()
call, using the above code to instead serve as a one-off "init" that makes that HashSet for example. If you actually need the enum constants, make a Map<String, List<? extends Enum<?>>
instead. computeIfAbsent
is a nice way to create those lists on the fly in a one-liner.
The java classloader abstraction system simply doesn't have 'give me a list of all files in the jar' or 'give me a list of all classes'. Some libraries appear to provide those features but they are hacks that do not always work, so you probably don't want to use those.
The standard solution for this problem, used by the JVM itself too, is 'SPI' - with module-info.java it's baked in, if you don't use that (and you probably shouldn't, it's kinda tricky, that), there's ServiceLoader
. To make the service files, you can use annotation processors, I think there are a few around that automate it.
Then the process flow is simply, to make a new enum, stick a @ProviderFor(Something.class)
on it, where Something.class
is some marker interface of your design.
Note that wanting to scan all enums on the entire classpath is just plain bad. There are all sorts of enums that java uses internally, what if one of those so happens to have a value in there equal to one of your strings? That does NOT all of a sudden make that a valid string. No, only enums you explicitly mark as applying to whatever this question is, qualify, this, you need to do something to mark them. Either with an annotation, or explicitly by writing out the full list.
Upvotes: 5