Java: rewrite comparator to Lambda - nullsafe - java

How can i rewrite this:
private Comparator<Domain> byRank;
...
byRank = new Comparator<Domain>() {
#Override
public int compare(Domain d1, Domain d2) {
float tmp1 = d1.getDomainRank() == null ? 0 : d1.getDomainRank();
float tmp2 = d2.getDomainRank() == null ? 0 : d2.getDomainRank();
return Float.compare(tmp1, tmp2);
}
};
into lambda?
According to check null value before sorting using lambda expression, I tried this:
byRank = Comparator.nullsFirst(Comparator.comparing(Domain::getDomainRank));
However, it fails with:
java.lang.NullPointerException: null
at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
at java.util.Comparators$NullComparator.compare(Comparators.java:83)
at java.util.PriorityQueue.siftUpUsingComparator(PriorityQueue.java:669)
at java.util.PriorityQueue.siftUp(PriorityQueue.java:645)
at java.util.PriorityQueue.offer(PriorityQueue.java:344)
at java.util.PriorityQueue.add(PriorityQueue.java:321)
Edit: the lambda fails even if I check compared objects for null before comparison:
Queue<Domain> topByRank = new PriorityQueue<>(TOP, byRank);
...
for (Domain domain : domains) {
if (domain == null) { // check here
continue;
}
topByRank.add(domain); // here it fails
}

It should be:
Comparator.comparing(Domain::getDomainRank,
Comparator.nullsFirst(Comparator.naturalOrder()))
So we sort a list based on domainRank. But what are we going to do with Domain objects whose domainRank value is null? We going to keep them at the head of our collection:
Comparator.nullsFirst(Comparator.naturalOrder())

Your code will put null Domains first. If you want to check for null rank, you need to use this:
Comparator.comparing(Domain::getDomainRank, Comparator.nullsFirst(Comparator.naturalOrder()))
But keep in mind that this is only equivalent to your original comparator if the rank can't be less than 0. Otherwise, you'll have to test a similar expression:
Comparator.comparing(d -> d.getDomainRank() == null ? 0 : d.getDomainRank())
Alternatively, you might have meant to use Float.MIN_VALUE instead of 0 in your original code.

Related

Sort a Java collection object based on one field in it and apply checks

retList.sort((comp1, comp2) ->
compartmentOrderMap.get(comp2.getCompartment()).compareTo(compartmentOrderMap
.get(comp1.getCompartment())));
I want to add a null check before comparing. How can I do that?
retList.sort((comp1, comp2) ->
if(compartmentOrderMap.get(comp2.getCompartment()) != null && compartmentOrderMap.get(comp1.getCompartment()) != null)
compartmentOrderMap.get(comp2.getCompartment()).compareTo(compartmentOrderMap
.get(comp1.getCompartment()));
);
//I want to do something like this
Your operation
retList.sort((comp1, comp2) ->
compartmentOrderMap.get(comp2.getCompartment())
.compareTo(compartmentOrderMap.get(comp1.getCompartment())));
is equivalent to
retList.sort(Comparator.comparing(
c -> compartmentOrderMap.get(c.getCompartment()),
Comparator.reverseOrder()));
With this factory based form, you can easily replace the value comparator with a null safe variant, e.g.
retList.sort(Comparator.comparing(
c -> compartmentOrderMap.get(c.getCompartment()),
Comparator.nullsFirst(Comparator.reverseOrder())));
You have to decide for a policy. Instead of nullsFirst you can also use nullsLast.
you have to put {} inside the lambda for multiple line code:
retList.sort((comp1, comp2) -> {
if(compartmentOrderMap.get(comp2.getCompartment()) != null && compartmentOrderMap.get(comp1.getCompartment()) != null)
return compartmentOrderMap.get(comp2.getCompartment()).compareTo(compartmentOrderMap
.get(comp1.getCompartment()));
else
// throw a RuntimeException or return some integer value based on your logic
});
Use if/then/else to specify your needs. If you want all of this within one line, check the ternary operator on
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html
It is explained including some examples here:
https://www.tutorialspoint.com/Java-Ternary-Operator-Examples

Getting java.lang.illegalArgumentException : comparison method violates its general contract ! while using Comparator to sort a list [duplicate]

This question already has answers here:
Comparison Method violates its general contract in Java 7
(3 answers)
Closed 4 years ago.
I am trying to sort a list in a specific way in Java, and I found that Comparator is a good way to do so.
I will share with you a pseudo code of the problem.
I have a list of DTOs and let's say I want to sort it by a property(String) in a specific order, for example properties starting with "Hi" should be on top and the rest should be below.
Here is my pseudo code :
list.sort(new Comparator<myDto>(){
#Override
public int compare(myDto o1, myDto o2){
if(o1.getProperty1() != null && o2.getProperty1() == null)
return -1;
else if(o1.getProperty1() == null && o2.getProperty1() != null)
return 1;
else if(o1.getProperty1().startsWith("Hi") && o2.getProperty1().startsWith("Hi"))
return 0;
else if(o1.getProperty1().startsWith("Hi") && !o2.getProperty1().startsWith("Hi"))
return -1;
return 1;
}
});
I used like 4, 5 DTO's I created myself to test, but when I inject a file of 14k DTO's I get a java.lang.illegalArgumentException.
Any ideas ?
Change your final return 1 to return o1.getProperty1().compareTo(o2.getProperty1()) the JVM can compare elements a, b or b, a - if you just return 1 at the end then you will always violate the general contract.
In the other answers, you can find explanation why your Comparator doesn't work - in short, your returning 1 at the end makes the Comparator inconsistent (compare(a,b) != -compare(b,a)).
Direct Comparator implementations are hard to both write and read. That's why in Java 8, you can use the functional approach using various Comparator methods.
Translating your Comparator to the functional approach yields:
Comparator<String> property1Comparator = Comparator.nullsLast(Comparator.comparing(
property1 -> !property1.startsWith("Hi")
));
Comparator<MyDto> myDtoComparator = Comparator.comparing(MyDto::getProperty1, property1Comparator);
I believe this approach is much more readable than all the direct Comparator implementations.
PS. If you wanted to achieve the same result as in Elliot's solution (which additionally sorts the strings not prefixed with "Hi" in a natural order), you'd need the following property1Comparator:
Comparator<String> property1Comparator = Comparator.nullsLast(Comparator.<String, Boolean>comparing(
property1 -> !property1.startsWith("Hi")
).thenComparing(Comparator.naturalOrder()));
In your text, you say you want those objects starting with "Hi" before (less than) the other ones. In addition, your code implies that you want nulls at the end (higher than anything else). So your Comparator has to consider 9 cases (Hi, non-Hi, null for o1 in combination with Hi, non-Hi, null for o2) and return the following values:
o1=Hi: 0,-1,-1 for o2=Hi,non-Hi,null
o1=non-Hi: 1, 0,-1 for o2=Hi,non-Hi,null
o1=null: 1, 1, 0 for o2=Hi,non-Hi,null
Your code doesn't follow that table, e.g. for a non-Hi/non-Hi you'll always return 1 instead of 0, e.g. when doing compare("Peter","John") as well as compare("John","Peter"). As Elliot already pointed out, it's crucial that compare(a,b) and compare(b,a) either both return 0 or return results with opposite signs.
P.S. The table assumes you don't care for the ordering within the three groups. If you want one, you can replace the zeroes with the result of e.g. a lexical comparator.
You have to consider that a.compareTo(b) == -b.compareTo(a). Your last test just assumed that if either start with "Hi" you can return 1 but this breaks the rule above. What you can do is something like this.
list.sort((o1, o2) -> {
String o1p1 = o1.getProperty1(), o2p1 = o2.getProperty1();
boolean isNull1 = o1p1 == null, isNull2 = o2p1 == null;
if (isNull1)
return isNull2 ? 0 : -1;
else if (isNull2)
return +1;
boolean o1p1hi = o1p1.startsWith("Hi"), o2p1hi = o1p1.startsWith("Hi");
if (o1p1hi)
return o2p1hi ? 0 : -1;
else if (o2p1hi)
return +1;
return o1p1.compareTo(o2p1);
});

Manage null in stream none match

I try to use stream to manage dto to bean.
List<CarDto> carsDto = dto.getCars();
List<Cars> cars = bean.getCars();
for (Cars car : cars) {
if (carsDto==null || carsDto.stream().noneMatch(c -> c.getId().intValue() == car.getId())) {
bean.removeCars(car);
}
}
actually when element of carsDto is null, i get null pointer exception.
when element is null, I would like to do
bean.removeCars(car);
A stacktrace or some code about the classes would be quite helpful. My bet is the NPE occurs in the line containing if and comes from a null being casted to a primitive int in one of these two:
c.getId() // returns null or
car.getId() // returns null
If the NPE doesn't come from one of these, then dto.getCars() might return a List with null values or bean.removeCars(car) throws an NPE...
Neither Stream.allMatch, .noneMatch, anyMatch nor any reduction function that reduces to a single boolean can do the trick alone because while you want to remove the car when no dto matches the car, you want to remove it when any dto is null.
You could always use two stream pipelines if you don't mind having twice as bad performances, but I will instead suggest the following :
List<CarDto> carsDto = dto.getCars();
List<Cars> cars = bean.getCars();
if (carsDto == null || carsDto.stream().anyMatch(Objects::isNull)) {
cars.forEach(car -> bean.removeCars(car)); //remove all cars when the dto list is null or contains a null dto
} else {
cars.stream().filter(car -> //select cars with a corresponding dto
carsDto.stream().anyMatch(dto ->
car.getId() == dto.getId().intValue()
)
).forEach(car -> bean.removeCars(car)); //and remove them
}

Java MessagePack null check

I am trying to get familar with Messagepack for Java.
I get the data via Mqtt. If the variable is not null everything is fine but the variable can also be null and in this case I will get this Exception: Expected Int, but got Nil (c0)
MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(m.getPayload());
int someInt = unpacker.unpackInt();
String someString = unpacker.unpackString();
So far I was not able to figure out how to get NULL back
I want to avoid to use TRY/CATCH so currently I am using this way
int someInt = unpacker.getNextFormat().equals("NIL") ? unpacker.unpackInt() : null;
Is there a better way ?
I looked the javadoc of MessageUnpacker and it doesn't seem provide a better way.
The example code is very close of your way :
MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(...);
while(unpacker.hasNext()) {
MessageFormat f = unpacker.getNextFormat();
switch(f) {
case MessageFormat.POSFIXINT:
case MessageFormat.INT8:
case MessageFormat.UINT8: {
int v = unpacker.unpackInt();
break;
}
case MessageFormat.STRING: {
String v = unpacker.unpackString();
break;
}
// ...
}
}
So I think that you are in the good path.
But if you repeat this retrieval multiple times(and it is very likely), you could introduce utility methods that does the job for you.
For example for unpackInt() :
public Integer unpackIntOrNull (MessageUnpacker unpacker){
return unpacker.getNextFormat() == MessageFormat.INT8 ?
unpacker.unpackInt() : null;
}
And now, it is very straight to unpack elements :
Integer einInt = unpackIntOrNull(unpacker);
Integer einAndereInt = unpackIntOrNull(unpacker);
...
MessageUnpacker has a method named tryUnpackNil. If the next byte is a nil value, this method reads it and returns true, otherwise reads nothing and returns false.
This can be used to skip over nil values, and unpack non-nil values with e.g.:
final MessageUnpacker unpacker = MessagePack.newDefaultUnpacker(data);
final Integer value = unpacker.tryUnpackNil() ? null : unpacker.unpackInt();

an elegant way to check for null

Working with hibernate I use this sort of code many times:
int someId = entity.getSomething() == null ? null : entity.getSomething().getId();
This code becomes a little more messy when trying to apply on a longer hierarchy:
int someId = entity.getParent() == null ? null :
entity.getParent().getParent() == null ? null :
entity.getParent().getParent().getSomething() == null ? null :
entity.getParent().getParent().getSomething().getId();
Is there a more elegant way to do it?
As per Louis Wasserman's comment, Optional can almost be used as a NullObject a'la flogy's solution.
Using Java 8 Optional and lambdas it looks like this
Integer value = Optional.ofNullable(entity)
.map( Entity::getParent )
.map( Entity::getParent )
.map( Entity::getSomething )
.map( Something::getId )
.orElse(null);
In case those entity objects are written by yourself and not in a library, I would consider refactoring them and use NullObjects. Like that you could then directly call
Integer someId = entity.getParent().getParent().getSomething().getId();
as this would then return your nulled integer.
Basically, it works like this:
entity.getParent() will return a NullParent instance
this NullParent class has a method getParent(), which also will return a NullParent instance
again, this NullParent class has a method getSomething(), which will return a NullSomething instance
the NullSomething class has a method getId(), which finally will return your nulled integer (e.g. 0 or another NullInteger object).
Here is an interesting post on Why NULL is bad?
Is there a more elegant way to do it?
You may simply go for try-catch for null check:
try{
int id = entity.getParent().getParent().getSomething().getId();
// do something with id
} catch(NullPointerException ex) {
// got null
}
You're assigning the same null value if it's null.
Why don't you try giving it only if it is not null.
if(x != null) x = y
Readability is very important factor as well as the simplification of code. It is better to use simple if condition here.
if(entity.getParent()!=null && entity.getParent().getParent() !=null &&
entity.getParent().getParent().getSomething()!=null &&
entity.getParent().getParent().getSomething().getId()!=null){
}
And because of &&("short-circuit and"), if one condition false non other condition in the right execute.

Categories

Resources