Best possible "generic" implementation of Comparator? - java

We recently had some troubles migrating from Java 6 to 7 because we have tonns of Comparator implemented in a way they not fullfill the Comparable contract and throw a Exception using the new Timsort algorithms.
Edit: Input from User Marco Forberg and class signature
I now found a Comparator many of the others are extending from, the compare method looked like this:
#Override
public int compare(final T o1, final T o2) {
return 0;
}
And i changed it to this in the hope i cover most of the cases:
#Override
public int compare(final T o1, final T o2) {
//Case: XOR check for either s1 or s2 being null
if(o1 == null ^ o2 == null){
return (o1 == null) ? -1 : 1;
}
//Case: both are null
if(o1 == o2){
return 0;
}
//Case: Its Comparable!
if(o1 instanceof Comparable && o2 instanceof Comparable){
Comparable c1 = (Comparable) o1;
Comparable c2 = (Comparable) o2;
return ObjectUtils.compare(c1, c2);
}
//Case: We don't know (ran for years this way)
return 0;
}
The class signature looks like this:
public class MyComparator<T> implements Comparator<T> {
While ObjectUtils is from the Apache Commons 3 and provides a null-safe compare method (I know the time i call this none of the both objects can be null because of the previous checks and i could use c1.compare(c2) as well)
Did this improve the behavior of the "Base"-Comparator? (which i assume)
and i am right that this would basically cover the Comparable contract since the value 0 is now returned for non-comparable Objects?

Related

Java comparator issue when passed to Collections.sort

When testing with less load in local it worked fine.
private static class CoordinateComparator implements Comparator<Coordinate> {
#Override
public int compare(Coordinate o1, Coordinate o2) {
return o1.x <= o2.x ? -1 : 1;
}
}
Here x is primitive and it was giving runtime error when tests were run. Under heavy load it was breaking.
Then i changed comparator to:-
private static class CoordinateComparator implements Comparator<Coordinate> {
#Override
public int compare(Coordinate o1, Coordinate o2) {
return o1.x.compareTo(o2.x);
}
}
In this case x is Integer. Then it started working fine.
Any ideas or thoughts why this was happening. I was passing this comparator to Collections.sort(array, comp)
public static <T> void sort(List<T> list, Comparator<? super T> c) would throw
IllegalArgumentException if the comparator is found to violate the Comparator contract.
In your code, the first compare method is inconsistent for the case where o1.x is equal to o2.x. It will return either -1 or 1 depending on the order in which the instances are compared. It should return 0 in this case.
You can fix it as follows:
public int compare(Coordinate o1, Coordinate o2) {
return o1.x < o2.x ? -1 : o1.x > o2.x ? 1 : 0;
}
Though your o1.x.compareTo(o2.x) alternative seems cleaner to me.

Is there a simple way in Java to get the difference between two collections using a custom equals function without overriding the equals?

I'm open to use a lib. I just want something simple to diff two collections on a different criteria than the normal equals function.
Right now I use something like :
collection1.stream()
.filter(element -> !collection2.stream()
.anyMatch(element2 -> element2.equalsWithoutSomeField(element)))
.collect(Collectors.toSet());
and I would like something like :
Collections.diff(collection1, collection2, Foo::equalsWithoutSomeField);
(edit) More context:
Should of mentioned that I'm looking for something that exists already and not to code it myself. I might code a small utils from your ideas if nothing exists.
Also, Real duplicates aren't possible in my case: the collections are Sets. However, duplicates according to the custom equals are possible and should not be removed by this operation. It seems to be a limitation in a lot of possible solutions.
We use similar methods in our project to shorten repetitive collection filtering. We started with some basic building blocks:
static <T> boolean anyMatch(Collection<T> set, Predicate<T> match) {
for (T object : set)
if (match.test(object))
return true;
return false;
}
Based on this, we can easily implement methods like noneMatch and more complicated ones like isSubset or your diff:
static <E> Collection<E> disjunctiveUnion(Collection<E> c1, Collection<E> c2, BiPredicate<E, E> match)
{
ArrayList<E> diff = new ArrayList<>();
diff.addAll(c1);
diff.addAll(c2);
diff.removeIf(e -> anyMatch(c1, e1 -> match.test(e, e1))
&& anyMatch(c2, e2 -> match.test(e, e2)));
return diff;
}
Note that there are for sure some possibilities for perfomance tuning. But keeping it separated into small methods help understanding and using them with ease. Used in code they read quite nice.
You would then use it as you already said:
CollectionUtils.disjunctiveUnion(collection1, collection2, Foo::equalsWithoutSomeField);
Taking Jose Da Silva's suggestion into account, you could even use Comparator to build your criteria on the fly:
Comparator<E> special = Comparator.comparing(Foo::thisField)
.thenComparing(Foo::thatField);
BiPredicate specialMatch = (e1, e2) -> special.compare(e1, e2) == 0;
You can use UnifiedSetWithHashingStrategy from Eclipse Collections. UnifiedSetWithHashingStrategy allows you to create a Set with a custom HashingStrategy.
HashingStrategy allows the user to use a custom hashCode() and equals(). The Object's hashCode() and equals() is not used.
Edit based on requirement from OP via comment:
You can use reject() or removeIf() depending on your requirement.
Code Example:
// Common code
Person person1 = new Person("A", "A");
Person person2 = new Person("B", "B");
Person person3 = new Person("C", "A");
Person person4 = new Person("A", "D");
Person person5 = new Person("E", "E");
MutableSet<Person> personSet1 = Sets.mutable.with(person1, person2, person3);
MutableSet<Person> personSet2 = Sets.mutable.with(person2, person4, person5);
HashingStrategy<Person> hashingStrategy =
HashingStrategies.fromFunction(Person::getLastName);
1) Using reject(): Creates a new Set which contains all the elements which do not satisfy the Predicate.
#Test
public void reject()
{
MutableSet<Person> personHashingStrategySet = HashingStrategySets.mutable.withAll(
hashingStrategy, personSet2);
// reject creates a new copy
MutableSet<Person> rejectSet = personSet1.reject(personHashingStrategySet::contains);
Assert.assertEquals(Sets.mutable.with(person1, person3), rejectSet);
}
2) Using removeIf(): Mutates the original Set by removing the elements which satisfy the Predicate.
#Test
public void removeIfTest()
{
MutableSet<Person> personHashingStrategySet = HashingStrategySets.mutable.withAll(
hashingStrategy, personSet2);
// removeIf mutates the personSet1
personSet1.removeIf(personHashingStrategySet::contains);
Assert.assertEquals(Sets.mutable.with(person1, person3), personSet1);
}
Answer before requirement from OP via comment: Kept for reference if others might find it useful.
3) Using Sets.differenceInto() API available in Eclipse Collections:
In the code below, set1 and set2 are the two sets which use Person's equals() and hashCode(). The differenceSet is a UnifiedSetWithHashingStrategy so, it uses the lastNameHashingStrategy to define uniqueness. Hence, even though set2 does not contain person3 however it has the same lastName as person1 the differenceSet contains only person1.
#Test
public void differenceTest()
{
MutableSet<Person> differenceSet = Sets.differenceInto(
HashingStrategySets.mutable.with(hashingStrategy),
set1,
set2);
Assert.assertEquals(Sets.mutable.with(person1), differenceSet);
}
Person class common to both code blocks:
public class Person
{
private final String firstName;
private final String lastName;
public Person(String firstName, String lastName)
{
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName()
{
return firstName;
}
public String getLastName()
{
return lastName;
}
#Override
public boolean equals(Object o)
{
if (this == o)
{
return true;
}
if (o == null || getClass() != o.getClass())
{
return false;
}
Person person = (Person) o;
return Objects.equals(firstName, person.firstName) &&
Objects.equals(lastName, person.lastName);
}
#Override
public int hashCode()
{
return Objects.hash(firstName, lastName);
}
}
Javadocs: MutableSet, UnifiedSet, UnifiedSetWithHashingStrategy, HashingStrategy, Sets, reject, removeIf
Note: I am a committer on Eclipse Collections
static <T> Collection<T> diff(Collection<T> minuend, Collection<T> subtrahend, BiPredicate<T, T> equals) {
Set<Wrapper<T>> w1 = minuend.stream().map(item -> new Wrapper<>(item, equals)).collect(Collectors.toSet());
Set<Wrapper<T>> w2 = subtrahend.stream().map(item -> new Wrapper<>(item, equals)).collect(Collectors.toSet());
w1.removeAll(w2);
return w1.stream().map(w -> w.item).collect(Collectors.toList());
}
static class Wrapper<T> {
T item;
BiPredicate<T, T> equals;
Wrapper(T item, BiPredicate<T, T> equals) {
this.item = item;
this.equals = equals;
}
#Override
public int hashCode() {
// all items have same hash code, check equals
return 1;
}
#Override
public boolean equals(Object that) {
return equals.test(this.item, ((Wrapper<T>) that).item);
}
}
Comparing
You can achieve this without the use of any library, just using java's Comparator
For instance, with the following object
public class A {
private String a;
private Double b;
private String c;
private int d;
// getters and setters
}
You can use a comparator like
Comparator<AA> comparator = Comparator.comparing(AA::getA)
.thenComparing(AA::getB)
.thenComparingInt(AA::getD);
This compares the fields a, b and the int d, skipping c.
The only problem here is that this won't work with null values.
Comparing nulls
One possible solution to do a fine grained configuration, that is allow to check for specific null fields is using a Comparator class similar to:
// Comparator for properties only, only writed to be used with Comparator#comparing
public final class PropertyNullComparator<T extends Comparable<? super T>>
implements Comparator<Object> {
private PropertyNullComparator() { }
public static <T extends Comparable<? super T>> PropertyNullComparator<T> of() {
return new PropertyNullComparator<>();
}
#Override
public int compare(Object o1, Object o2) {
if (o1 != null && o2 != null) {
if (o1 instanceof Comparable) {
#SuppressWarnings({ "unchecked" })
Comparable<Object> comparable = (Comparable<Object>) o1;
return comparable.compareTo(o2);
} else {
// this will throw a ccn exception when object is not comparable
#SuppressWarnings({ "unchecked" })
Comparable<Object> comparable = (Comparable<Object>) o2;
return comparable.compareTo(o1) * -1; // * -1 to keep order
}
} else {
return o1 == o2 ? 0 : (o1 == null ? -1 : 1); // nulls first
}
}
}
This way you can use a comparator specifying the allowed null fields.
Comparator<AA> comparator = Comparator.comparing(AA::getA)
.thenComparing(AA::getB, PropertyNullComparator.of())
.thenComparingInt(AA::getD);
If you don't want to define a custom comparator you can use something like:
Comparator<AA> comparator = Comparator.comparing(AA::getA)
.thenComparing(AA::getB, Comparator.nullsFirst(Comparator.naturalOrder()))
.thenComparingInt(AA::getD);
Difference method
The difference (A - B) method could be implemented using two TreeSets.
static <T> TreeSet<T> difference(Collection<T> c1,
Collection<T> c2,
Comparator<T> comparator) {
TreeSet<T> treeSet1 = new TreeSet<>(comparator); treeSet1.addAll(c1);
if (treeSet1.size() > c2.size()) {
treeSet1.removeAll(c2);
} else {
TreeSet<T> treeSet2 = new TreeSet<>(comparator); treeSet2.addAll(c2);
treeSet1.removeAll(treeSet2);
}
return treeSet1;
}
note: a TreeSet makes sense to be used since we are talking of uniqueness with a specific comparator. Also could perform better, the contains method of TreeSet is O(log(n)), compared to a common ArrayList that is O(n).
Why only a TreeSet is used when treeSet1.size() > c2.size(), this is because when the condition is not met, the TreeSet#removeAll, uses the contains method of the second collection, this second collection could be any java collection and its contains method its not guaranteed to work exactly the same as the contains of the first TreeSet (with custom comparator).
Edit (Given the more context of the question)
Since collection1 is a set that could contains repeated elements acording to the custom equals (not the equals of the object) the solution already provided in the question could be used, since it does exactly that, without modifying any of the input collections and creating a new output set.
So you can create your own static function (because at least i am not aware of a library that provides a similar method), and use the Comparator or a BiPredicate.
static <T> Set<T> difference(Collection<T> collection1,
Collection<T> collection2,
Comparator<T> comparator) {
collection1.stream()
.filter(element1 -> !collection2.stream()
.anyMatch(element2 -> comparator.compare(element1, element2) == 0))
.collect(Collectors.toSet());
}
Edit (To Eugene)
"Why would you want to implement a null safe comparator yourself"
At least to my knowledge there isn't a comparator to compare fields when this are a simple and common null, the closest that i know of is (to raplace my sugested PropertyNullComparator.of() [clearer/shorter/better name can be used]):
Comparator.nullsFirst(Comparator.naturalOrder())
So you would have to write that line for every field that you want to compare. Is this doable?, of course it is, is it practical?, i think not.
Easy solution, create a helper method.
static class ComparatorUtils {
public static <T extends Comparable<? super T>> Comparator<T> shnp() { // super short null comparator
return Comparator.nullsFirst(Comparator.<T>naturalOrder());
}
}
Do this work?, yes this works, is it practical?, it looks like, is it a great solution? well that depends, many people consider the exaggerated (and/or unnecessary) use of helper methods as an anti-pattern, (a good old article by Nick Malik). There are some reasons listed there, but to make things short, this is an OO language, so OO solutions are normally preferred to static helper methods.
"As stated in the documentation : Note that the ordering maintained by a set (whether or not an explicit comparator is provided must be consistent with equals if it is to correctly implement the Set interface. Further, the same problem would arise in the other case, when size() > c.size() because ultimately this would still call equals in the remove method. So they both have to implement Comparator and equals consistently for this to work correctly"
The javadoc says of TreeSet the following, but with a clear if:
Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with equals if it is to correctly implement the Set interface
Then says this:
See Comparable or Comparator for a precise definition of consistent with equals
If you go to the Comparable javadoc says:
It is strongly recommended (though not required) that natural orderings be consistent with equals
If we continue to read the javadoc again from Comparable (even in the same paragraph) says the following:
This is so because the Set interface is defined in terms of the equals operation, but a TreeSet instance performs all key comparisons using its compareTo (or compare ) method, so two keys that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is well-defined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.
By this last quote and with a very simple code debug, or even a reading, you can see the use of an internal TreeMap, and that all its derivated methods are based on the comparator, not the equals method;
"Why is this so implemented? because there is a difference when removing many elements from a little set and the other way around, as a matter of fact same stands for addAll"
If you go to the definition of removeAll you can see that its implementation is in AbstractSet, it is not overrided. And this implementation uses a contains from the argument collection when this is larger, the beavior of this contains is uncertain, it isn't necessary (nor probable) that the received collection (e.g. list, queue, etc) has/can define the same comparator.
Update 1:
This jdk bug is being discussed (and considerated to be fixed) in here https://bugs.openjdk.java.net/browse/JDK-6394757
pom.xml:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
code/test:
package com.my;
import lombok.Builder;
import lombok.Getter;
import lombok.ToString;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Equator;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
public class Diff {
public static class FieldEquator<T> implements Equator<T> {
private final Function<T, Object>[] functions;
#SafeVarargs
public FieldEquator(Function<T, Object>... functions) {
if (Objects.isNull(functions) || functions.length < 1) {
throw new UnsupportedOperationException();
}
this.functions = functions;
}
#Override
public boolean equate(T o1, T o2) {
if (Objects.isNull(o1) && Objects.isNull(o2)) {
return true;
}
if (Objects.isNull(o1) || Objects.isNull(o2)) {
return false;
}
for (Function<T, ?> function : functions) {
if (!Objects.equals(function.apply(o1), function.apply(o2))) {
return false;
}
}
return true;
}
#Override
public int hash(T o) {
if (Objects.isNull(o)) {
return -1;
}
int i = 0;
Object[] vals = new Object[functions.length];
for (Function<T, Object> function : functions) {
vals[i] = function.apply(o);
i++;
}
return Objects.hash(vals);
}
}
#SafeVarargs
private static <T> Set<T> difference(Collection<T> a, Collection<T> b, Function<T, Object>... functions) {
if ((Objects.isNull(a) || a.isEmpty()) && Objects.nonNull(b) && !b.isEmpty()) {
return new HashSet<>(b);
} else if ((Objects.isNull(b) || b.isEmpty()) && Objects.nonNull(a) && !a.isEmpty()) {
return new HashSet<>(a);
}
Equator<T> eq = new FieldEquator<>(functions);
Collection<T> res = CollectionUtils.removeAll(a, b, eq);
res.addAll(CollectionUtils.removeAll(b, a, eq));
return new HashSet<>(res);
}
/**
* Test
*/
#Builder
#Getter
#ToString
public static class A {
String a;
String b;
String c;
}
public static void main(String[] args) {
Set<A> as1 = new HashSet<>();
Set<A> as2 = new HashSet<>();
A a1 = A.builder().a("1").b("1").c("1").build();
A a2 = A.builder().a("1").b("1").c("2").build();
A a3 = A.builder().a("2").b("1").c("1").build();
A a4 = A.builder().a("1").b("3").c("1").build();
A a5 = A.builder().a("1").b("1").c("1").build();
A a6 = A.builder().a("1").b("1").c("2").build();
A a7 = A.builder().a("1").b("1").c("6").build();
as1.add(a1);
as1.add(a2);
as1.add(a3);
as2.add(a4);
as2.add(a5);
as2.add(a6);
as2.add(a7);
System.out.println("Set1: " + as1);
System.out.println("Set2: " + as2);
// Check A::getA, A::getB ignore A::getC
Collection<A> difference = difference(as1, as2, A::getA, A::getB);
System.out.println("Diff: " + difference);
}
}
result:
Set1: [Diff.A(a=2, b=1, c=1), Diff.A(a=1, b=1, c=1), Diff.A(a=1, b=1, c=2)]
Set2: [Diff.A(a=1, b=1, c=6), Diff.A(a=1, b=1, c=2), Diff.A(a=1, b=3, c=1), Diff.A(a=1, b=1, c=1)]
Diff: [Diff.A(a=1, b=3, c=1), Diff.A(a=2, b=1, c=1)]

Java get String CompareTo as a comparator object

I would like to sort and binary search a static array of strings via the String.CompareTo comparator.
The problem is that both sorting, and binary searching requires that a Comparator object be passed in -- So how do I pass in the built in string comparator?
You may write your own comparator
public class ExampleComparator implements Comparator<String> {
public int compare(String obj1, String obj2) {
if (obj1 == obj2) {
return 0;
}
if (obj1 == null) {
return -1;
}
if (obj2 == null) {
return 1;
}
return obj1.compareTo(obj2);
}
}
Solution for Java 8 based on java.util.Comparator.comparing(...):
Comparator<String> c = Comparator.comparing(String::toString);
or
Comparator<String> c = Comparator.comparing((String x) -> x);
The Arrays class has versions of sort() and binarySearch() which don't require a Comparator. For example, you can use the version of Arrays.sort() which just takes an array of objects. These methods call the compareTo() method of the objects in the array.
Ok this is a few years later but with java 8 you can use Comparator.naturalOrder():
http://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html#naturalOrder--
From javadoc:
static <T extends Comparable<? super T>> Comparator<T> naturalOrder()
Returns a comparator that compares Comparable objects in natural order.
The returned comparator is serializable and throws NullPointerException when comparing null.
If you do find yourslef needing a Comparator, and you already use Guava, you can use Ordering.natural().
This is a generic Comparator for any kind of Comparable object, not just String:
package util;
import java.util.Comparator;
/**
* The Default Comparator for classes implementing Comparable.
*
* #param <E> the type of the comparable objects.
*
* #author Michael Belivanakis (michael.gr)
*/
public final class DefaultComparator<E extends Comparable<E>> implements Comparator<E>
{
#SuppressWarnings( "rawtypes" )
private static final DefaultComparator<?> INSTANCE = new DefaultComparator();
/**
* Get an instance of DefaultComparator for any type of Comparable.
*
* #param <T> the type of Comparable of interest.
*
* #return an instance of DefaultComparator for comparing instances of the requested type.
*/
public static <T extends Comparable<T>> Comparator<T> getInstance()
{
#SuppressWarnings("unchecked")
Comparator<T> result = (Comparator<T>)INSTANCE;
return result;
}
private DefaultComparator()
{
}
#Override
public int compare( E o1, E o2 )
{
if( o1 == o2 )
return 0;
if( o1 == null )
return 1;
if( o2 == null )
return -1;
return o1.compareTo( o2 );
}
}
How to use with String:
Comparator<String> stringComparator = DefaultComparator.getInstance();
Again, don't need the comparator for Arrays.binarySearch(Object[] a, Object key) so long as the types of objects are comparable, but with lambda expressions this is now way easier.
Simply replace the comparator with the method reference: String::compareTo
E.g.:
Arrays.binarySearch(someStringArray, "The String to find.", String::compareTo);
You could also use
Arrays.binarySearch(someStringArray, "The String to find.", (a,b) -> a.compareTo(b));
but even before lambdas, there were always anonymous classes:
Arrays.binarySearch(
someStringArray,
"The String to find.",
new Comparator<String>() {
#Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
});
Also, if you want case-insensitive comparison, in recent versions of Java the String class contains a public static final field called CASE_INSENSITIVE_ORDER which is of type Comparator<String>, as I just recently found out. So, you can get your job done using String.CASE_INSENSITIVE_ORDER.
We can use the String.CASE_INSENSITIVE_ORDER comparator to compare the strings in case insensitive order.
Arrays.binarySearch(someStringArray, "The String to find.",String.CASE_INSENSITIVE_ORDER);
To generalize the good answer of Mike Nakis with String.CASE_INSENSITIVE_ORDER, you can also use :
Collator.getInstance();
See Collator
Regarding Nambari's answer there was a mistake. If you compare values using double equal sign == program will never reach compare method, unless someone will use new keyword to create String object which is not the best practice. This might be a bit better solution:
public int compare(String o1, String o2) {
if (o1 == null && o2 == null){return 0;}
if (o1 == null) { return -1;}
if (o2 == null) { return 1;}
return o1.compareTo(o2);
}
P.S. Thanks for comments ;)
You can use the StringUtils.compare("a", "b")

Java Interface Comparator static compare

int compare(Object o1, Object o2)
Compares its two arguments for order.
For compare 2 objects o1 and o2 need do something like:
MyClass o1=new MyClass();
MyClass o2=new MyClass();
if (o1.compare(o1,o2)>0) ......
Why this methtod not static?
If method was static possible like:
if (MyClass.compare(o1,o2)>0) ....
If it were static, how could it be called polymorphically? The point of Comparator is that you can pass an instance into something like sort... which has to then call the compare method on the comparator instance.
If a class is capable of comparing one instance of itself to another, it should implement Comparable instead, so you'd write:
if (o1.compareTo(o2))
Your question shows a lack of understanding of Comparable and Comparator.
A Comparator is capable of comparing two other objects;
MyClass o1 = new MyClass();
MyClass o2 = new MyClass();
MyComparator c1 = new MyComparator();
if (c1.compare(o1, o2) > 0) {
...
}
Something which is Comparable is able to be compared to other objects;
MyClass o1 = new MyClass();
MyClass o2 = new MyClass();
if (o1.compareTo(o2)) > 0) {
...
}
It is very rare to compare a Comparator, so your example;
if (o1.compare(o1, o2) > 0) {
...
}
doesn't really make sense. Anyway, onto the answer, why isn't compareTo() static? Basically, Comparator is an interface, and interfaces can't have static methods. Why? Well, it doesn't make sense for them. An interface is about defining a contract, but providing no implementation.
The answer of John is fine unless null pointers are possible. If you want to support null pointers an option is to define a static method on some Util class
// null pointers first
public static int compareTo(#Nullable Comparable v1, #Nullable Comparable v2)
{
return v1 == null ? (v2 == null ? 0 : -1) : v2 == null ? 1 : v1.compareTo(v2);
}
// null pointers last
public static int compareTo(#Nullable Comparable v1, #Nullable Comparable v2)
{
return v1 == null ? (v2 == null ? 0 : 1) : v2 == null ? -1 : v1.compareTo(v2);
}

Comparing the values of two generic Numbers

I want to compare to variables, both of type T extends Number. Now I want to know which of the two variables is greater than the other or equal. Unfortunately I don't know the exact type yet, I only know that it will be a subtype of java.lang.Number. How can I do that?
EDIT: I tried another workaround using TreeSets, which actually worked with natural ordering (of course it works, all subclasses of Number implement Comparable except for AtomicInteger and AtomicLong). Thus I'll lose duplicate values. When using Lists, Collection.sort() will not accept my list due to bound mismatchs. Very unsatisfactory.
This should work for all classes that extend Number, and are Comparable to themselves. By adding the & Comparable you allow to remove all the type checks and provides runtime type checks and error throwing for free when compared to Sarmun answer.
class NumberComparator<T extends Number & Comparable> implements Comparator<T> {
public int compare( T a, T b ) throws ClassCastException {
return a.compareTo( b );
}
}
A working (but brittle) solution is something like this:
class NumberComparator implements Comparator<Number> {
public int compare(Number a, Number b){
return new BigDecimal(a.toString()).compareTo(new BigDecimal(b.toString()));
}
}
It's still not great, though, since it counts on toString returning a value parsable by BigDecimal (which the standard Java Number classes do, but which the Number contract doesn't demand).
Edit, seven years later: As pointed out in the comments, there are (at least?) three special cases toString can produce that you need to take into regard:
Infinity, which is greater than everything, except itself to which it is equal
-Infinity, which is less than everything, except itself to which it is equal
NaN, which is extremely hairy/impossible to compare since all comparisons with NaN result in false, including checking equality with itself.
After having asked a similar question and studying the answers here, I came up with the following. I think it is more efficient and more robust than the solution given by gustafc:
public int compare(Number x, Number y) {
if (isSpecial(x) || isSpecial(y))
return Double.compare(x.doubleValue(), y.doubleValue());
else
return toBigDecimal(x).compareTo(toBigDecimal(y));
}
private static boolean isSpecial(Number x) {
var specialDouble = x instanceof Double d
&& (Double.isNaN(d) || Double.isInfinite(d));
var specialFloat = x instanceof Float f
&& (Float.isNaN(f) || Float.isInfinite(f));
return specialDouble || specialFloat;
}
private static BigDecimal toBigDecimal(Number number) {
if (number instanceof BigDecimal d)
return d;
if (number instanceof BigInteger i)
return new BigDecimal(i);
if (number instanceof Byte || number instanceof Short
|| number instanceof Integer || number instanceof Long)
return new BigDecimal(number.longValue());
if (number instanceof Float || number instanceof Double)
return new BigDecimal(number.doubleValue());
try {
return new BigDecimal(number.toString());
} catch(NumberFormatException e) {
throw new RuntimeException("The given number (\"" + number + "\" of class " + number.getClass().getName() + ") does not have a parsable string representation", e);
}
}
One solution that might work for you is to work not with T extends Number but with T extends Number & Comparable. This type means: "T can only be set to types that implements both the interfaces."
That allows you to write code that works with all comparable numbers. Statically typed and elegant.
This is the same solution that BennyBoy proposes, but it works with all kinds of methods, not only with comparator classes.
public static <T extends Number & Comparable<T>> void compfunc(T n1, T n2) {
if (n1.compareTo(n2) > 0) System.out.println("n1 is bigger");
}
public void test() {
compfunc(2, 1); // Works with Integer.
compfunc(2.0, 1.0); // And all other types that are subtypes of both Number and Comparable.
compfunc(2, 1.0); // Compilation error! Different types.
compfunc(new AtomicInteger(1), new AtomicInteger(2)); // Compilation error! Not subtype of Comparable
}
The most "generic" Java primitive number is double, so using simply
a.doubleValue() > b.doubleValue()
should be enough in most cases, but... there are subtle issues here when converting numbers to double. For example the following is possible with BigInteger:
BigInteger a = new BigInteger("9999999999999992");
BigInteger b = new BigInteger("9999999999999991");
System.out.println(a.doubleValue() > b.doubleValue());
System.out.println(a.doubleValue() == b.doubleValue());
results in:
false
true
Although I expect this to be very extreme case this is possible. And no - there is no generic 100% accurate way. Number interface have no method like exactValue() converting to some type able to represent number in perfect way without loosing any information.
Actually having such perfect numbers is impossible in general - for example representing number Pi is impossible using any arithmetic using finite space.
What about this one? Definitely not nice, but it deals with all necessary cases mentioned.
public class SimpleNumberComparator implements Comparator<Number>
{
#Override
public int compare(Number o1, Number o2)
{
if(o1 instanceof Short && o2 instanceof Short)
{
return ((Short) o1).compareTo((Short) o2);
}
else if(o1 instanceof Long && o2 instanceof Long)
{
return ((Long) o1).compareTo((Long) o2);
}
else if(o1 instanceof Integer && o2 instanceof Integer)
{
return ((Integer) o1).compareTo((Integer) o2);
}
else if(o1 instanceof Float && o2 instanceof Float)
{
return ((Float) o1).compareTo((Float) o2);
}
else if(o1 instanceof Double && o2 instanceof Double)
{
return ((Double) o1).compareTo((Double) o2);
}
else if(o1 instanceof Byte && o2 instanceof Byte)
{
return ((Byte) o1).compareTo((Byte) o2);
}
else if(o1 instanceof BigInteger && o2 instanceof BigInteger)
{
return ((BigInteger) o1).compareTo((BigInteger) o2);
}
else if(o1 instanceof BigDecimal && o2 instanceof BigDecimal)
{
return ((BigDecimal) o1).compareTo((BigDecimal) o2);
}
else
{
throw new RuntimeException("Ooopps!");
}
}
}
This should work for all classes that extend Number, and are Comparable to themselves.
class NumberComparator<T extends Number> implements Comparator<T> {
public int compare(T a, T b){
if (a instanceof Comparable)
if (a.getClass().equals(b.getClass()))
return ((Comparable<T>)a).compareTo(b);
throw new UnsupportedOperationException();
}
}
if(yourNumber instanceof Double) {
boolean greaterThanOtherNumber = yourNumber.doubleValue() > otherNumber.doubleValue();
// [...]
}
Note: The instanceof check isn't necessarily needed - depends on how exactly you want to compare them. You could of course simply always use .doubleValue(), as every Number should provide the methods listed here.
Edit: As stated in the comments, you will (always) have to check for BigDecimal and friends. But they provide a .compareTo() method:
if(yourNumber instanceof BigDecimal && otherNumber instanceof BigDecimal) {
boolean greaterThanOtherNumber = ((BigDecimal)yourNumber).compareTo((BigDecimal)otherNumber) > 0;
}
You can simply use Number's doubleValue() method to compare them; however you may find the results are not accurate enough for your needs.
Let's assume that you have some method like:
public <T extends Number> T max (T a, T b) {
...
//return maximum of a and b
}
If you know that there are only integers, longs and doubles can be passed as parameters then you can change method signature to:
public <T extends Number> T max(double a, double b) {
return (T)Math.max (a, b);
}
This will work for byte, short, integer, long and double.
If you presume that BigInteger's or BigDecimal's or mix of floats and doubles can be passed then you cannot create one common method to compare all these types of parameters.
If your Number instances are never Atomic (ie AtomicInteger) then you can do something like:
private Integer compare(Number n1, Number n2) throws SecurityException, NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Class<? extends Number> n1Class = n1.getClass();
if (n1Class.isInstance(n2)) {
Method compareTo = n1Class.getMethod("compareTo", n1Class);
return (Integer) compareTo.invoke(n1, n2);
}
return -23;
}
This is since all non-Atomic Numbers implement Comparable
EDIT:
This is costly due to reflection: I know
EDIT 2:
This of course does not take of a case in which you want to compare decimals to ints or some such...
EDIT 3:
This assumes that there are no custom-defined descendants of Number that do not implement Comparable (thanks #DJClayworth)
In my use case, I was looking for a general Comparator that works with the autoboxed primitives (64 bit max precision), not arbitrary precision types like BigInteger and BigDecimal. Here's a first shot at it..
public class PrimitiveComparator implements Comparator<Number> {
#Override
public int compare(Number a, Number b) {
if (a == b)
return 0;
double aD = a.doubleValue();
double bD = b.doubleValue();
int comp = Double.compare(aD, bD);
if (comp == 0 && inLongBounds(aD))
comp = Long.compare(a.longValue(), b.longValue());
return comp;
}
private boolean inLongBounds(double value) {
return
Double.compare(value, Long.MAX_VALUE) <= 0 &&
Double.compare(value, Long.MIN_VALUE) >= 0;
}
}
The objective is to be able to compare mixed types (e.g. Floats against Longs). This should also work with those AtomicXxx types (or any hand rolled Number subclass that uses no more than 64 bits).
In this ordering, btw, Double.NaN > Double.POSITVE_INFINITY > { everything else }.

Categories