get a getter method from field name to avoid if-else - java

I have this code, which obviously doesn't look nice - it seems all the if-else can somehow be avoided.
if(sortBy.equals("firstName"))
personList.sort(Comparator.comparing(Person::getFirstName));
else if(sortBy.equals("lastName"))
personList.sort(Comparator.comparing(Person::getLastName));
else if(sortBy.equals("age"))
personList.sort(Comparator.comparing(Person::getAge));
else if(sortBy.equals("city"))
personList.sort(Comparator.comparing(Person::getCity));
else if(sortBy.equals("state"))
personList.sort(Comparator.comparing(Person::getState));
else if(sortBy.equals("zipCode"))
personList.sort(Comparator.comparing(Person::getZipCode));
the function takes sortBy, which is the name of one of the attributes of a Person, and applies a sorting to a personList based on that field. How can I avoid the if-else and write a better looking, possibily one line code?
Currently I have found that I can use a HashMap to create a mapping between a field name and a corresponding comparator.
map.put("age", Comparator.comparing(Person::getAge));
map.put("firstName", Comparator.comparing(Person::getFirstName))
...
And use personList.sort(map.get(sortBy)).
But still felt like it can further be improved without an extra step, to the point where it follows the open-closed principle, and adding a new field to Person would not need us to modify the code. I'm looking for something like
personList.sort(Comparator.comparing(Person::getterOfField(sortBy)))
UPDATE-1
For now, I decided to stick with using a Map<String, Function<Person, Comparable<?>> and I do not like to consider reflection based solutions. But still searching if I can find a similar way as this one where sort is a parameter.
UPDATE-2
I think a one-liner is not a good solution, cuz you wouldn't get a compile time error if one of the fields does not implement Comparator.

In general java doesn't want you to work with it this way1; it is not a structurally typed language, and unlike e.g. javascript or python, objects aren't "hashmaps of strings to thingies".
Also, your request more fundamentally doesn't add up: You can't just go from "field name" to "sort on that": What if the field's type isn't inherently sortable (is not a subtype of Comparator<Self>?)
What if there is a column in whatever view we're talking about / config file that is 'generated'? Imagine you have a field LocalDate birthDate; but you have a column 'birth month'2. You can sort on birth month, no problem. However, given that it's a 'generated value' (not backed directly by a field, instead, derived from a calculation based on field(s)), you can't just sort on this. You can't even sort on the backing field (as that would sort by birth year first, not what you want), nor does 'backing field' make sense; what if the virtual column is based on multiple fields?
It is certainly possible that currently you aren't imagining either virtual columns or fields whose type isn't self-sortable and that therefore you want to deposit a rule that for this class, you close the door on these two notions until a pretty major refactor, but it goes to show perhaps why "java does not work that way" is in fact somewhat 'good' (closely meshes with real life concerns), and why your example isn't as boilerplatey as you may have initially thought: No, it is not, in fact, inevitable. Specifically, you seem to want:
There is an exact 1-to-1 match between 'column sort keys' and field names.
The strategy to deliver on the request to sort on a given column sort key is always the same: Take the column sort key. Find the field (it has the same name); now find its getter. Create a comparator based on comparing get calls; this getter returns a type that has a natural sorting order guaranteed.
Which are 2 non-obvious preconditions that seem to have gotten a bit lost. At any rate, a statement like:
if(sortBy.equals("firstName"))
personList.sort(Comparator.comparing(Person::getFirstName));
encodes these 2 non-obvious properties, and trivially, therefore means it is also possible to add virtual columns as well as sort keys that work differently (for example, sorts on birth month, or, sorts on some explicit comparator you write for this purpose. Or even sorts case insensitively; strings by default do not do that, you'd have to sort by String.CASE_INSENSITIVE_COMPARATOR instead.
It strikes me as a rather badly written app if a change request comes in with: "Hey, could you make the sort option that sorts on patient name be case insensitive?" and you go: "Hooo boy that'll be a personweek+ of refactoring work!", no?
But, if you insist, you have 2 broad options:
Reflection
Reflection lets you write code that programatically gets a list of field names, method names, and can also be used to programatically call them. You can fetch a list of method names and filter out everything except:
instance methods
with no arguments
whose name starts with get
And do a simple-ish get-prefix-to-sort-key conversion (basically, .substring(3) to lop off the get, then lowercase the first character, though note that the rules for getter to field name get contradictory if the first 'word' of the field is a single letter, such as getXAxis, where half of the beanspec documents say the field name is definitely XAxis, as xAxis would have become getxAxis, and the other half say it is ambiguous and could mean the field name is XAxis or xAxis).
It looks something like this:
// intentionally raw type!
Map comparators = new HashMap();
for (Method m : Person.class.getMethods()) {
if (Modifiers.isStatic(m.getModifiers()) continue;
if (m.getParameterCount() != 0) continue;
String n = m.getName();
if (!n.startsWith("get") || n.length() < 4) continue;
n = Character.toLowerCase(n.charAt(3)) + n.substring(4);
comparators.put(n, (a, b) -> {
Object aa = m.invoke(a);
Object bb = m.invoke(b);
return ((Comparable) aa).compareTo(bb);
});
}
MyClass.COMPARATORS = (Map<String, Comparator<?>>) Collections.unmodifiableMap(comparators);
Note how this causes a boatload of errors because you just chucked type checking out the window - there is no actual way to ensure that any given getter type actually is an appropriate Comparable. The warnings are correct and you have to ignore them, no fixing that, if you go by this route.
You also get a ton of checked exceptions issues that you'll have to deal with by catching them and rethrowing something appropriate; possibly RuntimeException or similar if you want to disregard the need to deal with them by callers (some RuntimeException is appropriate if you consider any attempt to add a field of a type that isn't naturally comparable 'a bug').
Annotation Processors
This is a lot more complicated: You can stick annotations on a method, and then have an annotation processor that sees these and generates a source file that does what you want. This is more flexible and more 'compile time checked', in that you can e.g. check that things are of an appropriate type, or add support for mentioning a class in the annotation that is an implementation of Comparable<T>, T being compatible with the type of the field you so annotate. You can also annotate methods themselves (e.g. a public Month getBirthMonth() method). I suggest you search the web for an annotation processor tutorial, it'd be a bit much to stuff an example in an SO answer. Expect to spend a few days learning and writing it, it won't be trivial.
[1] This is a largely objective statement. Falsifiable elements: There are no field-based 'lambda accessors'; no foo::fieldName support. Java does not support structural typing and there is no way to refer to things in the language by name alone, only by fully qualified name (you can let the compiler infer things, but the compiler always translates what you write to a fully "named" (package name, type name that the thing you are referring to is in, and finally the name of the method or field) and then sticks that in the class file).
[2] At least in the Netherlands it is somewhat common to split patient populations up by birth month (as a convenient way to split a population into 12 roughly equally sized, mostly arbitrary chunks) e.g. for inviting them in for a checkup or a flu shot or whatnot.

Assuming that the sortBy values and the corresponding getters are known at compile, this would be a good place to use a string switch statement:
Function<Person.String> getter = null;
switch (sortBy) {
case "firstName":
getter = Person::getFirstName; break;
case "lastName":
getter = Person::getLastName; break;
...
}
personList.sort(Comparator.comparing(getter));
If you use a recent version of Java (Java 12 and later) you could use a switch expression rather than a switch statement.
Function<Person.String> getter;
getter = switch (sortBy) {
case "firstName" -> Person::getFirstName;
case "lastName" -> Person::getLastName;
...
default -> null;
}
personList.sort(Comparator.comparing(getter));
Note: you should do a better job (than my dodgy code) of dealing with the case where the sortBy value is not recognized.

As keshlam suggested, I think using the reflection API is the best fitting answer to your question, but keep in mind that using it in production code is generally discouraged.
Note: if you add a new Person-attribute which isn't itself Comparable, you'll have to resort to a custom Comparator anyway. With that in mind, you might want to keep the Map<String, Comparator<?>> solution you already have.

Related

What are the intended means for passing missing values into the list using short hand syntax

Generally I've been quite a fan of using List.of / Arrays.asList, but unfortunately they don't accept nulls. The usecase I most often stumble upon this, is when dealing with DB calls, and parameters are abstracted to be passed via list. So it always have to be of certain length for any given procedure, and optional values are given as nulls.
I found Why does Map.of not allow null keys and values? which mentions List.of, but mainly talks about maps. And in the case of maps, I agree -- ambiguity of .get seems troublesome (key missing, or value intentionally null?), but this doesn't seem to apply to a list. If you get a null from a list, then you know for sure someone intentionally had put it there.
Some people might say "use optional", but I strongly believe it should be reserved for return types, and others seem to agree Uses for Optional
having an Optional in a class field or in a data structure, is considered a misuse of the API
What is the intended clean, standard solution? Does it really boil down to "use boilerplaty ArrayList initialization without these nice shorthand syntaxes" or "write your own util methods"?
Generally I've been quite a fan of using List.of / Arrays.asList, but unfortunately they don't accept nulls
This is incorrect:
List<String> list = Arrays.asList("a", "b", null);
String nullRef = list.get(2);
System.out.println(nullRef);
Works fine. List.of indeed doesn't accept null refs.
Some people might say "use optional"
Yeah, ignore those people.
What is the intended clean, standard solution?
Clean is a weaselword that is generally best read as: "I like it personally but am unwilling to just say that it's a taste preference" - it means nothing. You can't argue about taste, after all.
The standard solution, that is borderline objective language, we can work with that.
I think your API design that takes a list of db values is the problem. Presumably, you've written your own abstraction layer for this.
I'm having a hard time imaginine DB abstraction APIs where 'list of objects that includes null refs' is the right call.
If you're trying to weave prepared statement 'parameters', this would appear to be a nice API for that, and doesn't require lists at all:
db.select()
.append("SELECT a, b FROM c INNER JOIN d ON c.e = d.f ")
.append("WHERE (signup IS NULL OR signup < ?) ", LocalDate.now())
.append("AND LEFT(username, ?) = ?", 2, "AA")
.query()
If you're trying to abstract an insert/merge/upsert statement, you need some way of linking values with the column name that the value is a value for. This would suggest an API along the lines of:
db.insert("tableName")
.mergeOn("key", key)
.mergeOn("date", someDate)
.put("value", value)
.exec();
You can also re-use Map.of for this purpose, in which case 'I want the db to use the default value for this column' is done by simply not including that key/value pair in your map, thus sidestepping your needs to put null in such a map.
Perhaps if you show your specific API use-case, more insights can be provided.
If your question boils down to:
"I want to put null refs and use List.of, what is the standard clean way to do that"
then the answer is a rather obvious: There is nothing - as that does not work.
There is no --shut-up-List.of--let-me-add-nulls command line switch.

Dynamic Named SQL Fields

So i've got a bot that serves as a roleplaying mamager handeling combat, skill points and the like, i'm trying to make my code a bit more general so i can have less pages since they all do the same thing they just have different initilizers but i ran into a snag i need to check if the user has a minimum in a particular stat Strength, perceptions, agility, etc
so i call
mainSPECIAL = rows[0].Strength;
Here's the rub, weathers it strength, percpetion, intelligence, luck, whatever i'm always going to be checking Rows[0].that attribute ie Rows[0].Luck for luck perks, and i already set earlier in my initilizers
var PERKSPECIALName = "Strength";
But i can't call
mainSPECIAL = rows[0].PERKSPECIALName but there should be a way to do that right? so that when it sees "rows[0].PERKSPECIALName" it looks up "PERKSPECIALName" and then fetches the value of rows[0].Strength
For this you need to use reflection:
Field f1 = rows[0].getClass().getField(PERKSPECIALName);
Integer attribute = (Integer) f1.get(rows[0]);
Where "Integer" is the type of the element your pulling from the object (the type of strength)
The field must be declared as public! I think there is a way to obtain them when they are not public but it requires more code.
Seems like you have a set of integers that you need to identify with a constant identifier. You might find an EnumMap useful. Have a look at How to use enumMap in java.
Or if you want to only use a string to identify which perk you want to reference, just use a Map.
Java doesn't have reference-to-member like some other languages, so if you don't want to change your data structure, you are looking at using lambda functions or heavier language features to increase re-use, which seems like overkill for what you're trying to do.

Is it bad practice to have the order of an enum declaration matter?

Let's say I have the enum AccessRole with 3 declarations:
public Enum AccessRole
{
Admin,
User,
ReadOnly;
}
I use the ordinal value of this enum to denote the concept of "hierarchical" access role. For example,
if (currentRole.ordinal() < otherRole.ordinal())
{
//We know currentRole has more user rights than otherRole, because it's ordinal is smaller.
}
I don't base any rights of the ordinal, but I use the ordinal to sort and compare the enum values when necessary (for example, in the user interface, to sort all possible access roles by ordinal from "many rights" to "limited rights").
This works fine, but I'm concerned that this is not clear. If any other developer were to insert a new accessrole at the bottom of the enum instead of at the correct ordinal, the whole system is messed up.
Is there any way to deal with this? That this, impose an order on enum declarations, yet make it clear in the code semantically that the order is important/being used?
You are rightly concerned. The order of enums is often not considered important by developers.
Yet adding another artifical ordinal just make things worse, because now you have take care that the numbers are different, and the meaning of the new ordering is still not clear.
I think what would help is to move the check for privileges in the Enum. So have method in your Enum that looks like this:
public boolean includes(Role otherRole){
return (this.ordinal() < otherRole.ordinal())
}
Now you can add tests. And every developer worth her money will understand that changing the order of things in the enum is actually a change in business logic.
While the order of enums is not normally considered as being important, the way I would address this case (where it does matter) would be to put a prominent comment in the source code.
Sure, you can't protect against programmers who modify (butcher!) your code without reading the comments. But on the other hand:
they are liable to butcher your code in other ways anyway, and
ultimately, if they ignore your comments and break the code as a result, they should take the blame.
How about adding an integer attribute to the Enum and document that it is used for maintaining the hierarchy of roles. This also allows to define two roles at the same level.

4 Key Value HashMap? Array? Best Approach?

I've got loads of the following to implement.
validateParameter(field_name, field_type, field_validationMessage, visibleBoolean);
Instead of having 50-60 of these in a row, is there some form of nested hashmap/4d array I can use to build it up and loop through them?
Whats the best approach for doing something like that?
Thanks!
EDIT: Was 4 items.
What you could do is create a new Class that holds three values. (The type, the boolean, and name, or the fourth value (you didn't list it)). Then, when creating the HashMap, all you have to do is call the method to get your three values. It may seem like more work, but all you would have to do is create a simple loop to go through all of the values you need. Since I don't know exactly what it is that you're trying to do, all I can do is provide an example of what I'm trying to do. Hope it applies to your problem.
Anyways, creating the Class to hold the three(or four) values you need.
For example,
Class Fields{
String field_name;
Integer field_type;
Boolean validationMessageVisible;
Fields(String name, Integer type, Boolean mv) {
// this.field_name = name;
this.field_type = type;
this.validationMessageVisible = mv;
}
Then put them in a HashMap somewhat like this:
HashMap map = new HashMap<String, Triple>();
map.put(LOCAL STRING FOR NAME OF FIELD, new Field(new Integer(YOUR INTEGER),new Boolean(YOUR BOOLEAN)));
NOTE: This is only going to work as long as these three or four values can all be stored together. For example if you need all of the values to be stored separately for whatever reason it may be, then this won't work. Only if they can be grouped together without it affecting the function of the program, that this will work.
This was a quick brainstorm. Not sure if it will work, but think along these lines and I believe it should work out for you.
You may have to make a few edits, but this should get you in the right direction
P.S. Sorry for it being so wordy, just tried to get as many details out as possible.
The other answer is close but you don't need a key in this case.
Just define a class to contain your three fields. Create a List or array of that class. Loop over the list or array calling the method for each combination.
The approach I'd use is to create a POJO (or some POJOs) to store the values as attributes and validate attribute by attribute.
Since many times you're going to have the same validation per attribute type (e.g. dates and numbers can be validated by range, strings can be validated to ensure they´re not null or empty, etc), you could just iterate on these attributes using reflection (or even better, using annotations).
If you need to validate on the POJO level, you can still reuse these attribute-level validators via composition, while you add more specific validations are you´re going up in the abstraction level (going up means basic attributes -> pojos -> pojos that contain other pojos -> etc).
Passing several basic types as parameters of the same method is not good because the parameters themselves don't tell much and you can easily exchange two parameters of the same type by accident in the method call.

Java classes with dynamic fields

I'm looking for clever ways to build dynamic Java classes, that is classes where you can add/remove fields at runtime. Usage scenario: I have an editor where users should be able to add fields to the model at runtime or maybe even create the whole model at runtime.
Some design goals:
Type safe without casts if possible for custom code that works on the dynamic fields (that code would come from plugins which extend the model in unforeseen ways).
Good performance (can you beat HashMap? Maybe use an array and assign indexes to the fields during setup?)
Field "reuse" (i.e. if you use the same type of field in several places, it should be possible to define it once and then reuse it).
Calculated fields which depend on the value of other fields
Signals should be sent when fields change value (no necessarily via the Beans API)
"Automatic" parent child relations (when you add a child to a parent, then the parent pointer in the child should be set for "free").
Easy to understand
Easy to use
Note that this is a "think outside the circle" question. I'll post an example below to get you in the mood :-)
Type safe without casts if possible for custom code that works on the dynamic fields (that code would come from plugins which extend the model in unforeseen ways)
AFAIK, this is not possible. You can only get type-safety without type casts if you use static typing. Static typing means method signatures (in classes or interfaces) that are known at compile time.
The best you can do is have an interface with a bunch of methods like String getStringValue(String field), int getIntValue(String field) and so on. And of course you can only do that for a predetermined set of types. Any field whose type is not in that set will require a typecast.
The obvious answer is to use a HashMap (or a LinkedHashMap if you care for the order of fields). Then, you can add dynamic fields via a get(String name) and a set(String name, Object value) method.
This code can be implemented in a common base class. Since there are only a few methods, it's also simple to use delegation if you need to extend something else.
To avoid the casting issue, you can use a type-safe object map:
TypedMap map = new TypedMap();
String expected = "Hallo";
map.set( KEY1, expected );
String value = map.get( KEY1 ); // Look Ma, no cast!
assertEquals( expected, value );
List<String> list = new ArrayList<String> ();
map.set( KEY2, list );
List<String> valueList = map.get( KEY2 ); // Even with generics
assertEquals( list, valueList );
The trick here is the key which contains the type information:
TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );
The performance will be OK.
Field reuse is by using the same value type or by extending the key class of the type-safe object map with additional functionality.
Calculated fields could be implemented with a second map that stores Future instances which do the calculation.
Since all the manipulation happens in just two (or at least a few) methods, sending signals is simple and can be done any way you like.
To implement automatic parent/child handling, install a signal listener on the "set parent" signal of the child and then add the child to the new parent (and remove it from the old one if necessary).
Since no framework is used and no tricks are necessary, the resulting code should be pretty clean and easy to understand. Not using String as keys has the additional benefit that people won't litter the code with string literals.
So basically you're trying to create a new kind of object model with more dynamic properties, a bit like a dynamic language?
Might be worth looking at the source code for Rhino (i.e. Javascript implemented in Java), which faces a similar challenge of implementing a dynamic type system in Java.
Off the top of my head, I suspect you will find that internal HashMaps ultimately work best for your purposes.
I wrote a little game (Tyrant - GPL source available) using a similar sort of dynamic object model featuring HashMaps, it worked great and performance was not an issue. I used a few tricks in the get and set methods to allow dynamic property modifiers, I'm sure you could do the same kind of thing to implement your signals and parent/child relations etc.
[EDIT] See the source of BaseObject how it is implemented.
You can use the bytecode manipulation libraries for it. Shortcoming of this approach is that you need to do create own classloader to load changes in classes dynamically.
I do almost the same, it's pure Java solution:
Users generate their own models, which are stored as JAXB schema.
Schema is compiled in Java classes on the fly and stored in
user jars
All classes are forced to extend one "root" class, where you could put every extra functionality you want.
Appropriate classloaders are implemented with "model change"
listeners.
Speaking of performance (which is important in my case), you can hardly beat this solution. Reusability is the same of XML document.

Categories

Resources