Perfoming null-checks while collecting with Collectors.groupingBy - java

I have the following piece of code which groups the given entries (activities, which is Iterable<activity>) based on IDs.
For the final result, I want it to return a Map of ID to Iterables of the entries grouped by that ID.
For example: Map<String, Iterables<activity>>.
Right now, it returns a Map<String, List<activity>>.
stream(activities)
.collect(
groupingBy(
activity -> {
if (activity.getID()) {
return activity.getID();
} else {
return activity.getName();
}
}));
I am unable to figure out a way to do this.

There's no such notion in Java as truthy values, which exists in languages like javascript. I.e. String can't be resolved into boolean automatically (what your code attempts to do).
There are multiple ways of how you can check whether the given value is null and provide an alternative value.
If name attribute is guaranteed to be non-null you can use static method requireNonNullElse() of the Objects utility class:
.collect(Collectors.groupingBy(act -> Objects.requireNonNullElse(act.getID(), act.getName()));
If name attribute is nullable, then you have to provide a default value that will be used in case if both id and name equal to null. Because null key is not allowed with Collectors.groupingBy() and will result in NullPointerException at runtime.
For the case when both field could be null I suggest extracting the logic for obtaining the key into a separate method.
public static String getKey(Action action) {
return action.getID() != null ? action.getID() :
action.getName() != null ? action.getName() : "defaultKey";
}
Which can be used inside the collector like that:
.collect(Collectors.groupingBy(act -> getKey(act)); // or as a method reference MyClass::getKey
Sidenote: by convention, names of classes in Java should start with a capital letter: Student, Employee, Activity.

Related

Can I check in a criteriabuilder chain if value is empty?

I have a criteria builder chain of gets, but they can be null which means they will fail.
example:
predicates.add(cb.equal(house.get("adressInfo").get("streetname"),value)
If for example house.get(adressInfo) is empty I still want it returned with an empty list or just null values for everything is fine.
I only need to filter out for as an example houses with street name "A" but must also include all houses that have an empty adressInfo.
Now I get a null.streetname invalid access error because a house has an adressInfo of null
You could maybe work around this by introducing helper methods like this:
private String getStreet(House house) {
AddressInfo addressInfo = (AdressInfo) house.get("adressInfo");
return addressInfo == null ? null : addressInfo.get("streetname");
}
And then you can do:
predicates.add(cb.equal(getStreet(house), value)

Java records with nullable components

I really like the addition of records in Java 14, at least as a preview feature, as it helps to reduce my need to use lombok for simple, immutable "data holders". But I'm having an issue with the implementation of nullable components. I'm trying to avoid returning null in my codebase to indicate that a value might not be present. Therefore I currently often use something like the following pattern with lombok.
#Value
public class MyClass {
String id;
#Nullable String value;
Optional<String> getValue() { // overwrite the generated getter
return Optional.ofNullable(this.value);
}
}
When I try the same pattern now with records, this is not allowed stating incorrect component accessor return type.
record MyRecord (String id, #Nullable String value){
Optional<String> value(){
return Optional.ofNullable(this.value);
}
}
Since I thought the usage of Optionals as return types is now preferred, I'm really wondering why this restriction is in place. Is my understanding of the usage wrong? How can I achieve the same, without adding another accessor with another signature which does not hide the default one? Should Optional not be used in this case at all?
A record comprises attributes that primarily define its state. The derivation of the accessors, constructors, etc. is completely based on this state of the records.
Now in your example, the state of the attribute value is null, hence the access using the default implementation ends up providing the true state. To provide customized access to this attribute you are instead looking for an overridden API that wraps the actual state and further provides an Optional return type.
Of course, as you mentioned one of the ways to deal with it would be to have a custom implementation included in the record definition itself
record MyClass(String id, String value) {
Optional<String> getValue() {
return Optional.ofNullable(value());
}
}
Alternatively, you could decouple the read and write APIs from the data carrier in a separate class and pass on the record instance to them for custom accesses.
The most relevant quote from JEP 384: Records that I found would be(formatting mine):
A record declares its state -- the group of variables -- and commits
to an API that matches that state. This means that records give up a
freedom that classes usually enjoy -- the ability to decouple a
class's API from its internal representation -- but in return, records
become significantly more concise.
Due to restrictions placed on records, namely that canonical constructor type needs to match accessor type, a pragmatic way to use Optional with records would be to define it as a property type:
record MyRecord (String id, Optional<String> value){
}
A point has been made that this is problematic due to the fact that null might be passed as a value to the constructor. This can be solved by forbidding such MyRecord invariants through canonical constructor:
record MyRecord(String id, Optional<String> value) {
MyRecord(String id, Optional<String> value) {
this.id = id;
this.value = Objects.requireNonNull(value);
}
}
In practice most common libraries or frameworks (e.g. Jackson, Spring) have support for recognizing Optional type and translating null into Optional.empty() automatically so whether this is an issue that needs to be tackled in your particular instance depends on context. I recommend researching support for Optional in your codebase before cluttering your code possibly unnecessary.
Credits go to Holger! I really like his proposed way of questioning the actual need of null. Thus with a short example, I wanted to give his approach a bit more space, even if a bit convoluted for this use-case.
interface ConversionResult<T> {
String raw();
default Optional<T> value(){
return Optional.empty();
}
default Optional<String> error(){
return Optional.empty();
}
default void ifOk(Consumer<T> okAction) {
value().ifPresent(okAction);
}
default void okOrError(Consumer<T> okAction, Consumer<String> errorAction){
value().ifPresent(okAction);
error().ifPresent(errorAction);
}
static ConversionResult<LocalDate> ofDate(String raw, String pattern){
try {
var value = LocalDate.parse(raw, DateTimeFormatter.ofPattern(pattern));
return new Ok<>(raw, value);
} catch (Exception e){
var error = String.format("Invalid date value '%s'. Expected pattern '%s'.", raw, pattern);
return new Error<>(raw, error);
}
}
// more conversion operations
}
record Ok<T>(String raw, T actualValue) implements ConversionResult<T> {
public Optional<T> value(){
return Optional.of(actualValue);
}
}
record Error<T>(String raw, String actualError) implements ConversionResult<T> {
public Optional<String> error(){
return Optional.of(actualError);
}
}
Usage would be something like
var okConv = ConversionResult.ofDate("12.03.2020", "dd.MM.yyyy");
okConv.okOrError(
v -> System.out.println("SUCCESS: "+v),
e -> System.err.println("FAILURE: "+e)
);
System.out.println(okConv);
System.out.println();
var failedConv = ConversionResult.ofDate("12.03.2020", "yyyy-MM-dd");
failedConv.okOrError(
v -> System.out.println("SUCCESS: "+v),
e -> System.err.println("FAILURE: "+e)
);
System.out.println(failedConv);
which leads to the following output...
SUCCESS: 2020-03-12
Ok[raw=12.03.2020, actualValue=2020-03-12]
FAILURE: Invalid date value '12.03.2020'. Expected pattern 'yyyy-MM-dd'.
Error[raw=12.03.2020, actualError=Invalid date value '12.03.2020'. Expected pattern 'yyyy-MM-dd'.]
The only minor issue is that the toString prints now the actual... variants. And of course we do not NEED to use records for this.
Don't have the rep to comment, but I just wanted to point out that you've essentially reinvented the Either datatype. https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-Either.html or https://www.scala-lang.org/api/2.9.3/scala/Either.html. I find Try, Either, and Validation to be incredibly useful for parsing and there are a few java libraries with this functionality that I use: https://github.com/aol/cyclops/tree/master/cyclops and https://www.vavr.io/vavr-docs/#_either.
Unfortunately, I think your main question is still open (and I'd be interested in finding an answer).
doing something like
RecordA(String a)
RecordAandB(String a, Integer b)
to deal with an immutable data carrier with a null b seems bad, but wrapping recordA(String a, Integer b) to have an Optional getB somewhere else seems contra-productive. There's almost no point to the record class then and I think the lombok #Value is still the best answer. I'm just concerned that it won't play well with deconstruction for pattern matching.

For what reason Hashmap replaces old object with a new one in case of equal objects and hashcode?

Recently on the interview I was asked the question:
What will happen if we have two equal objects and we put them as
values using the same key? Will the first value be replaced or does
hashmap uses equals() for values in order to determine whether or not
the element already exists?
I answered that if element already present in the bucket than it won't be replaced nor duplicate element will be added.
However, I tried to code this and I see that it's not true. The old object will be replaced.
I have User entity(randomId is used to determine which object is currently in the HashMap):
class User {
private String userInfo;
private String randomId = UUID.randomUUID().toString();
public User(String userInfo) {
this.userInfo = userInfo;
}
public String getRandomId() {
return randomId;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(userInfo, user.userInfo);
}
#Override
public int hashCode() {
return Objects.hash(userInfo);
}
}
Now I'm testing the code:
public class HashMapTest {
public static void main(String[] args) {
Map<String, User> userLogins = new HashMap<>();
User user1 = new User("regular user");
User user2 = new User("regular user");
System.out.println(user1.getRandomId());
System.out.println(user2.getRandomId());
userLogins.put("login", user1);
userLogins.put("login", user2);
for(Map.Entry<String, User> entry : userLogins.entrySet()) {
System.out.println(entry.getKey() + " | " + entry.getValue().getRandomId());
}
}
}
Output:
63d21f34-c58c-4c73-8ee3-975951faf491
9b493ee7-33f2-4f93-92ae-8b44cba6e7c3
login | 9b493ee7-33f2-4f93-92ae-8b44cba6e7c3
Can anyone explain why hashmap replaces old value? I thought that hashcode & equals contract implies that there shouldn't be difference between user1 and user2 objects. So, from my point of view, it's logical to check if an equal object already exists in the bucket and don't replace it.
Because Map.put() javadoc states that :
If the map previously contained a mapping for the key, the old value
is replaced by the specified value.
So put() doesn't matter of the actual value for an existing key, just it overwrites it with the new value.
I thought that hashcode & equals contract implies that there shouldn't
be difference between user1 and user2 objects.
Yes but the javadoc of Map.put() doesn't say that it checks the equality of the value for existing mapping before effectively putting the value.
It is your guesswork.
HashMap<K,V> uses hashCode and equals only on the key objects. It never compares its value objects, treating them as a data load.
This behavior is specified in the documentation:
public V put(K key, V value)
Associates the specified value with the specified key in this map. If the map previously contained a mapping for the key, the old value is replaced.
Note the return type of put. It's not void, it's V, because when a value gets replaced with a new value, you get the old one back:
User old1 = userLogins.put("login", user1);
User old2 = userLogins.put("login", user2);
Above, old1 would be null, while old2 would be user1:
System.out.println(old2.getRandomId());
The documentation for put says:
Associates the specified value with the specified key in this map (optional operation). If the map previously contained a mapping for the key, the old value is replaced by the specified value. (A map m is said to contain a mapping for a key k if and only if m.containsKey(k) would return true.)
(my emphasis)
Note that it says nothing about checking whether the value equals anything, nor should it. The coder has expressly called put, saying "Put this value in the map under this key." It's not for the map to second-guess the programmer writing that code. It would also be unnecessary overhead.
There can be all kinds of reasons a coder wants to have a specific object, not just an equivalent one, in the map. Canonical caching (à la String#intern) is probably the first that comes to mind.
If you did it this way, there's no confusion; so why would a HashMap (or any Map) be different?
User user1 = new User("regular user");
User user2 = new User("regular user");
User login = user1;
login = user2;
You used the exact same key each time. There can only be one value for a key in a HashMap.
The javadoc for Map.put() says that the method:
Returns:
the previous value associated with key, or null if there was no mapping for key.
So actually, you could write this:
userLogins.put("login", user1);
User oldvalue = userLogins.put("login", user2);
assert(oldvalue == user1);
Map designed to work with key-value basis. Meaning that, at any point of time, key have only one value and if you keep adding the value with same key, that value gets overridden for the provided key.
Nothing to do with the value's. Just key is the thing here.
You just used the same key twice and earlier one replaced with new value. That is what exactly how map should work.
When asking why, one should consider the alternatives. The alternatives I see are either to silently ignore the second put or to throw an exception. I suggest the actual behavior is much better.
A basic assumption of the question is wrong:
I thought that hashcode & equals contract implies that there shouldn't
be difference between user1 and user2 objects.
Neither hashcode not equals require that the object be identical. The hashcode contract specifically does not preclude different objects having the same hash:
It is not required
that if two objects are unequal according to the
equals(java.lang.Object) method, then calling the hashCode method on
each of the two objects must produce distinct integer results.
Regarding equals, the objects in the example are not equal, but even if the equals were not to compare the userInfo, making them equal, the equals would be valid.

Compare the object with a list of contents to another object

I have an object with a list of contents in it and the other object with contents in it without the list like the code below
List<LoginInformation> allUsersLoginInfo
LoginInformation loginInformation
Now I want to compare these two and see if the elements of loginInformation exists in allUsersLoginInfo
LoginInformation is a model class.
It has Name and Rollnumber.
So, allUserLoginInfo is a list which contains multiple values for Name and Rollnumber.
Now, I want to compare and see if any value of loginInformation (i.e.,either the value of Name or RollNumber) presents in allUserLoginInfo then gives me true else false noting that no values are equal.
Thanks in advance
You can use Stream#anyMatch to accomplish the task at hand.
if(allUsersLoginInfo.stream().anyMatch(m -> m.getName().equals(loginInformation.getName())
|| m.getRollNumber() == loginInformation.getRollNumber())){
//exists
}else{
// does not exist
}
You could do:
allUsersLoginInfo.contains(loginInformation);
but you need to override properly the equals method in the class LoginInformation, if not, the list will never find the element and will return false...
lUsersLoginInfo.contains(loginInformation)
Could be what you're looking for
If there's a lot of info in the list I would suggest using a set instead. But you better not be saving the passwords in the user infos...
You could use Apache Commons for java prior to 1.8
This example find if any value of loginInformation (i.e.,either the value of Name or RollNumber) presents in allUserLoginInfo :
LoginInformation res = CollectionUtils.find(allUsersLoginInfo, new Predicate<LoginInformation >() {
#Override
public boolean evaluate(LoginInformation o) {
return o.getName ().equals(yourloginInformation.getName()) || o.getRollnumber () == yourloginInformation.getRollnumber();
}
});
For java 1.8 use Aominè answer

Jackson include null set by user?

I am serializing a POJO using jackosn, and I want that all the values for which the user sets some value irrespective whether it's null or not must be included in the serialization.
So currently:
POJO:
public class YourItem {
public String key;
public String item;
}
Currently when user does:
YourItem item = new YourItem();
item.setKey("abc");
The serialization gives:
{
"key" : "abc"
}
as I configured ObjectMapper as objectMapper.setInclude(Include.NON_NULL)
However now if the user specifically calls the setter and sets the value as null, then I want that item in my serialized string.
So if user does
YourItem item = new YourItem();
item.setKey("abc");
item.setItem(null);
I want in serialzation both key and item values are present like:
{
"key" : "abc",
"item" : null
}
How do I differentiate between the user set null and the default null.
Is there a configuration in ObjectMapper ??
Some people consider using null to be bad practice (The book Clean Code, for instance)
Disregarding that, you cannot differentiate between the default initialization null and a user-set null by language-design
You need some sort of state that tracks if a field has been accessed by a setter. If it hasn't been accessed by a setter and is null, ignore it.
One way to do this is Jackson Filters, which allows you to define various conditions for serializing a field during runtime (your condition being that your setter-access-state indicates that the field was set by a user)
http://www.baeldung.com/jackson-serialize-field-custom-criteria

Categories

Resources