I have configuration files which can be populated with enums and their respective values and will then be read by my program.
For example, a configuration file (yaml format) may look like this:
SomeEnumClass:
- VALUE_A_OF_SOME_ENUM
- VALUE_B_OF_SOME_ENUM
- ANOTHER_VALUE
AnotherEnumClass:
- VALUE_1
- VALUE_3
- VALUE_3
- VALUE_7
[etc...]
Unfortunately this leads to duplication in my code (java) like this:
if (enumNameString.equals("SomeEnumClass")) {
Collection<SomeEnumClass> values = new ArrayList<>;
for (String listEntry : yamlConfig.getStringList(enumNameString)) {
values.add(SomeEnumClass.valueOf(listEntry));
}
return values;
} else if (enumNameString.equals("AnotherEnumClass")) {
Collection<AnotherEnumClass> values = new ArrayList<>;
for (String listEntry : yamlConfig.getStringList(enumNameString)) {
values.add(AnotherEnumClass.valueOf(listEntry));
}
return values;
} else if ...
} else if ...
} else if ...
(please keep in mind that this example is pseudo code)
So of course i'm trying to get rid of the duplicate code. But how?
Is it possible to:
Get a class from a string? ("SomeEnumClass" -> SomeEnumClass.class)
Then check if that class is castable to Enum or something?
Access the enum's valueOf() method from that cast?
As usual, all things reflection are typically evil. However to get all the enum constants for a fully named class:
Class.forName(enumNameString).getEnumConstants()
<T extends Enum<T> Enum.valueOf(Class<T>,String) is great, but I don't know of a reasonable, obviously safe way to narrow a Class<?> to a Class<T extends Enum<T>> (Class.asSubclass will get you as far as Class<T extends Enum>).
Slightly better it to switch (or keep a Map) onto available constants:
Enum<?>[] values = switch (enumNameString) {
case "SomeEnumClass" -> SomeEnumClass .values();
case "AnotherEnumClass" -> AnotherEnumClass.values();
default -> throw new Error();
};
Enum<?> en = Arrays.stream(values)
.filter(e -> e.name() == listEntry).findFirst().get();
If building a Map derived from classes, it may be easier to use EnumSet.allOf(Class<?>) than MyEnum.values(), Enum.valueOf() or Class.getEnumConstants()
You can create a Map<String, Class<?>> which contains the mapping like this:
private static final Map<String, Class<Enum<?>>> MAP;
static {
Map<String, Class<Enum<?>>> map = new HashMap<>();
map.put(SomeEnumClass.class.getSimpleName(), SomeEnumClass.class);
// your other enum classes
MAP = Collections.unmodifiableMap(map);
}
And then you can make use of Enum.valueOf(Class<Enum>, String):
Class<Enum<?>> enumClass = MAP.get(enumNameString);
if (enumClass != null) {
Collection<Enum<?>> values = new ArrayList<>;
for (String listEntry : yamlConfig.getStringList(enumNameString)) {
values.add(Enum.valueOf(enumClass, listEntry));
}
return values;
}
Related
The code below works fine for me now, but it is not future proof, becuase the numbers of if else statments and instanceof. I would like to extend the Transport list with more objects like bicyles, motors etc.... but every time when I add new object I need to add more if else statements and create more instanceof. Does anyone have a better idea or better solution?
private static Transport filterObjects(List<Transport> listOfTransport, int refNr) {
List<Transport> cars = listOfTransport.stream()
.filter(transport -> transport instanceof Cars)
.collect(Collectors.toList());
List<Transport> airPlanes = listOfTransport.stream()
.filter(transport -> transport instanceof Airplanes)
.collect(Collectors.toList());
if (!cars.isEmpty()){
return cars.get(refNr);
} else if (!airPlanes.isEmpty()) {
return airPlanes.get(refNr);
} else {
return null;
}
}
Pass in the subtype you want. Maybe this would work:
private static Transport filterObjects(List<Transport> listOfTransport, Class clazz, int refNr) {
List<Transport> transports = listOfTransport.stream().filter(clazz::isInstance).collect(Collectors.toList());
return !transports.isEmpty() ? transports.get(refNr) : null;
}
Just as you currently prioritize cars over planes, as your transport types grow you also need some kind of priority on which to return preferentially. You can solve this with an enum. You only need to expand your enum accordingly as soon as you add a new transport type. The enum could look something like:
enum Priority{
Car(1),
Airplane(2);
private int value;
Priority (int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
Then you can refactor your method by grouping the elements of your list by their simple class names and adding them to a sorted map using the priority you define in your enum. You can then use the first entry of the map to determine the return value. Example:
private static Transport filterObjects(List<Transport> listOfTransport, int refNr) {
Comparator<String> comp = Comparator.comparingInt(e -> Priority.valueOf(e).getValue());
List<Transport> result =
listOfTransport.stream()
.collect(Collectors.groupingBy(
e -> e.getClass().getSimpleName(),
() -> new TreeMap<>(comp),
Collectors.toList()))
.firstEntry().getValue();
return (result != null && 0 <= refNr && refNr < result.size()) ?
result.get(refNr) : null;
}
First group the list elements into a map based on subtype, then create a list of subtypes of transport. Iterate this list and then check if corresponding entry exists in the map:
private static final List<Class> subTypes = List.of(Cars.class, Airplanes.class);
private static Transport filterObjects(List<Transport> listOfTransport, int refNr) {
Map<Class, List<Transport>> map = listOfTransport.stream()
.collect(Collectors.groupingBy(t -> t.getClass()));
Optional<List<Transport>> op = subTypes.stream()
.filter(map::containsKey)
.findFirst();
if(op.isPresent()) {
return op.get().get(refNr); // This could cause IndexOutOfBoundsException
}else{
return null;
}
}
Well, you could do the following.
First, define your order:
static final List<Class<? extends Transport>> ORDER = List.of(
Car.class,
Airplane.class
);
Then, you could write the following method:
private static Transport filterObjects(List<Transport> listOfTransport, int refNr) {
Map<Class<? extends Transport>, Transport> map = listOfTransport.stream()
.collect(Collectors.groupingBy(Transport::getClass, Collectors.collectingAndThen(Collectors.toList(), list -> list.get(refNr))));
return ORDER.stream()
.filter(map::containsKey)
.map(map::get)
.findFirst()
.orElse(null);
}
What this does, is mapping each distinct Class to the refNrth element which is a subtype of the respective class.
Then it walks over ORDER and checks if an element has been found within the original listOfTransport. The key won't exist in the map if listOfTransport does not contain any element of the particular class.
Note that if any element of a particular class exists in the map, the number of elements of that class is assumed to be at least refNr, otherwise an IndexOutOfBoundsException is thrown. With other words, each transport must occur 0 or at least refNr times within the listOfTransport.
Also note that getClass() does not necessarily yield the same result as instanceof. However, I have assumed here that each respective transport does not have further subclasses.
I have a map of class names to their enum class and I have method that parses a string like "SomeEnum.FIRST" into the actual object. But Enum.valueOf doesn't accept Class<? extends Enum<?>> while the map cannot store Class<T extends Enum<T>>.
For the code, the map looks something like this:
private static final HashMap<String, Class<? extends Enum<?>>> enumsMap;
static {
enumsMap = new HashMap<>();
// These are two DIFFERENT enum classes!
registerEnum(SomeEnum.class);
registerEnum(AnotherEnum.class);
}
private static void registerEnum(Class<? extends Enum<?>> enumClass) {
enumsMap.put(enumClass.getSimpleName(), enumClass);
}
And here is the parser (removed unnecessary code):
public <T extends Enum<T>> Object[] parse(List<String> strParameters) {
Object[] parameters = new Object[strParameters.size()];
for (int i = 0; i < parameters.length; i++) {
String strParameter = strParameters.get(i);
int delim = strParameter.lastIndexOf('.');
String className = strParameter.substring(0, delim - 1);
String enumName = strParameter.substring(delim + 1);
Class<T> enumClass = (Class<T>) enumsMap.get(className);
parameters[i] = Enum.valueOf(enumClass, enumName);
}
return parameters;
}
And now if I call this parse, my IDE (Android Studio) tells me, that "Unchecked method 'parse(List)' invocation", and afaik this is because of that generic type. If I remove it in parse, it wouldn't compile but the warning disappears. Is there a good way around it?
If you have enums like:
enum Foo {
A, B, C
}
enum Bar {
D, E, F
}
Then you can implement the kind of map you're talking about with the following code.
class MyEnums {
private final Map<String, Class<? extends Enum<?>>> map = new HashMap<>();
public void addEnum(Class<? extends Enum<?>> e) {
map.put(e.getSimpleName(), e);
}
private <T extends Enum<T>> T parseUnsafely(String name) {
final int split = name.lastIndexOf(".");
final String enumName = name.substring(0, split);
final String memberName = name.substring(split + 1);
#SuppressWarnings("unchecked")
Class<T> enumType = (Class<T>) map.get(enumName);
return Enum.valueOf(enumType, memberName);
}
public Object parse(String name) {
return parseUnsafely(name);
}
public Object[] parseAll(String... names) {
return Stream.of(names)
.map(this::parse)
.collect(toList())
.toArray();
}
}
This does not get around an unchecked cast, though; it only hides it from you temporarily. You can see where where SuppressWarnings is used to muffle the warning about enumType. It's generally good practice to apply the warning suppression in as limited a scope as possible. In this case, it's for that single assignment. While this could be a red flag in general, in the present case we know that the only values in the map are, in fact, enum classes, since they must have been added by addEnum.
Then, it can be used as:
MyEnums me = new MyEnums();
me.addEnum(Foo.class);
me.addEnum(Bar.class);
System.out.println(me.parse("Foo.A"));
System.out.println(me.parse("Bar.E"));
System.out.println(Arrays.toString(me.parseAll("Foo.B", "Bar.D", "Foo.C")));
which prints:
A
E
[B, D, C]
You'll notice that I broke parseUnsafely and parse into separate methods. The reason that we don't want to expose parseUnsafely directly is that it makes a guarantee by its return type that we cannot actually enforce. If it were exposed, then we could write code like
Bar bar = me.parseUnsafely("Foo.B");
which compiles, but fails at runtime with a cast class exception.
There is no safe way to have Map values whose generic type depends on the corresponding key.
You can, however, store the enum constants yourself:
private static final Map<String, Map<String, ?>> enumsMap;
static {
enumsMap = new HashMap<>();
// These are two DIFFERENT enum classes!
registerEnum(SomeEnum.class);
registerEnum(AnotherEnum.class);
}
private static <T extends Enum<T>> void registerEnum(Class<T> enumClass) {
Map<String, ?> valuesByName =
EnumSet.allOf(enumClass).stream().collect(
Collectors.toMap(Enum::name, Function.identity()));
enumsMap.put(enumClass.getSimpleName(), valuesByName);
}
public Object[] parse(List<String> strParameters) {
Object[] parameters = new Object[strParameters.size()];
for (int i = 0; i < parameters.length; i++) {
String strParameter = strParameters.get(i);
int delim = strParameter.lastIndexOf('.');
String className = strParameter.substring(0, delim);
String enumName = strParameter.substring(delim + 1);
Map<String, ?> enumValues = enumsMap.get(className);
parameters[i] = enumValues.get(enumName);
if (parameters[i] == null) {
throw new IllegalArgumentException("Class " + className
+ " does not contain constant " + enumName);
}
}
return parameters;
}
What I’ve changed:
enumsMap is now Map<String, Map<String, ?>>. Each value is a Map of enum constants keyed by constant name. ? is sufficient; there is no benefit to remembering that the constant values are enums, since parse returns Object[].
registerEnum has a generic type, to guarantee its argument is a valid enum type. Instead of storing the class argument, it stores that enum’s constants.
parse doesn’t need a generic type, since it returns Object[].
parse does not use any methods of Enum, so generic type safety is no longer a concern.
I fixed a bug: strParameter.substring(0, delim); instead of delim - 1. You want the entire substring up to but not including the period.
I'm kind of new to generics in Java and I've faced such a problem: Let's say you have your own List implementation and you want to provide a mechanism to simultaneously convert all elements using some kind of mapping (functional interface) and collect them into a new list.
While the idea and use of functional interface (IMapper in my case) is straightforwad I can't quite think of what signature a function performing mapping should have?
Here's a little use case example and what I thought of as well. It does not work unfortunately and I guess the main problem is: How the second V param type should be passed in such case?
public interface IMapper<T,V> { V map(T v); }
public class MyList<T> extends ArrayList<T> {
public MyList<V> map(IMapper <T,V> mapper) {
MyList<V> list = new MyList<>();
for(T v : this) {
list.add(mapper.map(v));
}
return list;
}
}
// in main
MyList<Integer> list1 = new MyList<>();
// fill etc..
IMapper<Integer,String> m = (i) -> { return i.toString(); };
// "Change" list
MyList<String> list2 = list1.map(m);
PS:
I think that such thing is most probably already implemented in Java (stream() and what follows I guess?) however it suppose to be exercise for me. Any tip would be much appreciated :)
You can add the map result type to you function definition as following:
class MyList<T> extends ArrayList<T> {
public <V> MyList<V> map(IMapper<T, V> mapper) {
MyList<V> list = new MyList<>();
for (T v : this) {
list.add(mapper.map(v));
}
return list;
}
}
Example:
MyList<Integer> number = new MyList<>();
number.add(1);
number.add(2);
number.add(3);
number.map(v -> "#" + v).forEach(System.out::println);
And you can have the same result using Java8 streams as following:
List<Integer> numberStream = new ArrayList<>();
numberStream.add(1);
numberStream.add(2);
numberStream.add(3);
numberStream.stream().map(v -> "#" + v).forEach(System.out::println);
You can define the generic parameter as a type parameter on your map method. Like this:
public <V> MyList<V> map(IMapper <T,V> mapper) {
...
}
Type parameters can be defined in two ways, either on a class or on a method. If it's defined on a class, it can be used throughout the class. If it's defined on a method, it can only be used in that method.
In your case, the T parameter is defined on the class, while the V parameter can be defined on the method.
I'm looking to check the field level security of all of the fields on the Opportunity object.
So I'm getting the full map of fields and storing them in a static variable so I can access them from where ever I need.
private static Map<String, Schema.SObjectField> myMap;
private static void initMaps() {
myMap = Schema.SObjectType.Opportunity.fields.getMap();
}
I want to iterate over all of the fields on the object and check .isAccessible() on each.
I'm a little stuck as to how to iterate over the fields however, and also how to check .isAccessible() on eash.
//THIS BIT DOESN'T WORK...
private doCheckMap(myMap){
for (Id key : myMap.keySet()) {
if(false == Schema.SObjectType.???.fields.isAccessible()) {
System.debug('nope!');
}
}
}
Any suggestions or advice would be greatly appreciated.
Cheers.
You could iterate through yout map like this:
Map<String, MyType> myMap = new HashMap<String, MyType>();
Iterator<Map.Entry<String, MyType>> iter = myMap.entrySet().iterator();
while(iter.hasNext()){
MyType entry = iter.next().getValue();
for (Field f : entry.getClass().getFields()) {
if (f.isAccessible()) {
//...
}
}
}
Then again why do you even need a map? Couldn'you just use reflexion api getFields() - like this:
for (Field f : Opportunity.getClass().getFields()) {
if (f.isAccessible()) {
//...
}
}
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Maps with multiple types of values in java
I have an odd question. Maybe I'm going about this the wrong way, but let's see where this question goes :)
I would like a Map container that contains either Strings or lists of Strings. I want to enforce this rule during construction of this object so that you can't create a map with values that aren't either of those.
e.g.
class Record {
public Record(String key, Map<String,Object> attrs) {
// check that attrs only contains Objects which are Strings or List<Strings>
}
}
Other ways I have thought of to solve the problem might be...
1)
class Record {
public Record(String key, Map<String,String> attrs, Map<String,List<String>> multiAttrs) {
// ...
}
}
2)
class Record {
public Record(String key, Map<String,Value> attrs) {
// ...
}
}
class Value {
// Create some funky class that encapsulates lists.
// Perhaps returning the only element in the list if the size is 1,
// but returning the list otherwise
}
I am not immediately excited at the alternatives, but I'm just putting it there as stuff I've already considered. Really I want the distinction between Strings and List to be transparent to the user of the class.
Have you considered ListMultimap? For the single value case the list would only have one element. Multimap allows multiple elements (values) to be mapped to each key. So your method would be:
public Record(String key, ListMultimap<String, String> attrs)...
Also, since your Record seems to be another mapping, consider using Table which allows for two-key mapping.
Check out ArrayListMultimap from Google which will help with this need
You can continue calling put on this map, if you need to get the map in its simplified form you can use this method, or modify it :)
public static Map<Field, String> toSingularMap(ArrayListMultimap<Field, String> map) {
Map<Field, String> singular_map = new HashMap<Field, String>();
if (map != null && !map.isEmpty()) {
Map<Field, Collection<String>> real_map = map.asMap();
for (Iterator<Entry<Field, Collection<String>>> it = real_map
.entrySet().iterator(); it.hasNext();) {
Entry<Field, Collection<String>> entry = it.next();
Field field = entry.getKey();
Collection<String> values = entry.getValue();
String value = null;
if (values != null && !values.isEmpty()) {
ArrayList<String> list = new ArrayList<String>(values);
value = list.get(0);
}
singular_map.put(field, value);
}
}
return singular_map;
}
Or if you do not want to use an extra library, you can create a simple Wrapper class
class Wrap {
String value;
String[] values
}
and have your map use Map<String, Wrap> map, when looping you can then determine either through use of your class methods or just testing, which one of the Wrapper variables are populated
I would use only List<String>. You could maybe add some methods to allow adding a single String and wrap the passed argument using Arrays.asList(...). Using only a single type of objects will reduce the quantity of code to write and avoid many if/else.
Why not create a class
class MyFunkyValue{
private String onlyOneString;
private List<String> stringValues;
public MyFunkyValue(String s){
...
}
public MyFunkyValue(List<String>ls){
...
}
}
and use it like this:
Map<KeyClass,MyFunkyValue> m;