Getting only first element of ArrayList that matches condition - java

I've been trying to find possible answers, but found none.
I've got an ArrayList full of custom objects. One of their fields is a boolean.
I want to put this object first, keeping the rest of elements
For instance, if I've got this list and obj5 is the one with this boolean set to true:
obj3, obj2, obj5, obj7, obj9
I'd like to get this:
obj5, obj3, obj2, obj7, obj9
EDIT: CAN'T USE LAMBDAS, JAVA 6
EDIT 2: PLEASE NOTE THAT THE REST OF THE LIST MUST KEEP THE OLD ORDER
EDIT 3: In short words, I need this program to output [B, A, C, D, E]:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Trip {
#Override
public String toString() {
return name;
}
private String name;
private boolean freeCancellation;
public Trip(String name, boolean freeCancellation) {
this.name = name;
this.freeCancellation = freeCancellation;
}
static Comparator<Trip> myOrder = new Comparator<Trip>() {
public int compare(Trip a, Trip b) {
if (a.freeCancellation == b.freeCancellation) return 0;
return a.freeCancellation ? -1 : 1;
}
};
public static void main(String [] args){
Trip t1 = new Trip("A", false);
Trip t2 = new Trip("B", true);
Trip t3 = new Trip("C", false);
Trip t4 = new Trip("D", true);
Trip t5 = new Trip("E", false);
List<Trip> tripList = new ArrayList<>();
tripList.add(t1);
tripList.add(t2);
tripList.add(t3);
tripList.add(t4);
tripList.add(t5);
System.out.println(Arrays.toString(tripList.toArray()));
Collections.sort(tripList, myOrder);
//result should be [B, A, C, D, E]
System.out.println(Arrays.toString(tripList.toArray()));
}
}

Write a Comparator.
Comparator<MyType> myOrder = new Comparator<MyType>() {
public int compare(MyType a, MyType b) {
return (b.booleanField() ? 1 : 0) - (a.booleanField() ? 1 : 0);
}
}
Sort using this comparator.
Collections.sort(myList, myOrder);
See Collections.sort
Edit
So it seems that what you're actually asking for is to move just one matching element to the front of your list. That ought to be pretty easy.
Find the index of the element you want to move:
int foundIndex = -1;
for (int i = 0; i < tripList.size(); ++i) {
if (tripList.get(i).freeCancellation) {
foundIndex = i;
break;
}
}
If you find such an element, and it is not already at the start, move it to the start:
if (foundIndex > 0) {
tripList.add(0, tripList.remove(foundIndex));
}

List<Object> objList = findObj(name);Collections.sort(objList, new Comparator<Object>() {
#Override
public int compare(Object a1, Object a2) {
return (a1.getBooleanField()== a2.getBooleanField())?0:(a1.getBooleanField()?1:-1);
}});
This might help you to resolve this. You modify the results by changing the compare logic

Here is an example of how to achieve this:
class Element {
public boolean shouldBeFirst();
}
List<Element> elements;
elements.sort(Comparator.comparing(Element::shouldBeFirst));
This works because the natural ordering of booleans is true first.
If you can't use Java 8 then the equivalent would be something like:
Collections.sort(elements, new Comparator() {
int compareTo(Element el1, Element el2) {
return (el1.shouldBeFirst() ? 1 : 0) - (el2.shouldBeFirst() ? 1 : 0);
}
}

import java.util.*;
public class Test {
public static void main(String[] args) {
List<A> list = new ArrayList<A>();
list.add(new A(true));
list.add(new A(false));
list.add(new A(true));
list.add(new A(false));
Collections.sort(list);
System.out.println(list);
}
}
class A implements Comparable<A> {
private boolean b;
public A(boolean b) {
this.b = b;
}
public boolean isB() {
return b;
}
public void setB(boolean b) {
this.b = b;
}
#Override
public int compareTo(A a) {
return a.isB() ? 1 : -1;
}
#Override
public String toString() {
return "A [b=" + b + "]";
}
}
Maybe this is what you are looking for.
This is solution if you want to give natural ordering to object, then implement Comparable and use Collections.sort - https://docs.oracle.com/javase/6/docs/api/java/util/Collections.html#sort(java.util.List).
If you have various members inside class, then maybe go with Comparator implementation, that way you can achieve many ways of sorting your objects based on different members.

If I understood what are you asking ,you need to create a new class called "Comparators".
in this class you need to define your methods and they need to be static final ...
then you can use it by calling to Collections.sort(-your array-, Comparator method name);

Related

Iterating over three lists simultaneously [duplicate]

This question already has answers here:
Combine two lists of same size (and different type) into list of domain objects using java streams
(2 answers)
Closed 2 years ago.
The following code is using indexed for loop:
List<A> a = ..;
List<B> b = ..;
List<C> c = ..;
// a.size() == b.size() == c.size() - fundamental business logic assumption
int size = a.size();
for (int i = 0; i < size; i++) {
doSthWith(a.get(i));
doSthWith(b.get(i));
doSthWith(c.get(i));
}
Is this possible to further simplify the code and not use the index to iterate over three lists simultaneously?
Since you are referring to the list by its interface, you should always prefer using an Iterator as opposed to get(), because you do not know whether the latter will be equally efficient.
Iterator<A> aIterator = aList.iterator();
Iterator<B> bIterator = bList.iterator();
Iterator<C> cIterator = cList.iterator();
while (aIterator.hasNext()) {
assert bIterator.hasNext();
assert cIterator.hasNext();
A a = aIterator.next();
B b = bIterator.next();
C c = cIterator.next();
}
assert !bIterator.hasNext();
assert !cIterator.hasNext();
You could create your own Iterator which encapsulates the above logic:
for (Three<A, B, C> three : ThreeIterator.iterate(aList, bList, cList)) {
A a = three.getA();
B b = three.getB();
C c = three.getC();
}
public class ThreeIterator<A, B, C> implements Iterator<Three<A, B, C>> {
private final Iterator<A> aIterator;
private final Iterator<B> bIterator;
private final Iterator<C> cIterator;
public ThreeIterator(Collection<A> aCollection, Collection<B> bCollection, Collection<C> cCollection) {
aIterator = aCollection.iterator();
bIterator = bCollection.iterator();
cIterator = cCollection.iterator();
}
#Override
public boolean hasNext() {
boolean result = aIterator.hasNext();
if (result) {
assert bIterator.hasNext();
assert cIterator.hasNext();
} else {
assert !bIterator.hasNext();
assert !cIterator.hasNext();
}
return result;
}
#Override
public Three<A, B, C> next() {
return new Three<>(aIterator.next(), bIterator.next(), cIterator.next());
}
#Override
public void remove() {
aIterator.remove();
bIterator.remove();
cIterator.remove();
}
public static <A, B, C> Iterable<Three<A, B, C>> iterate(List<A> aList, List<B> bList, List<C> cList) {
return new Iterable<Three<A, B, C>>() {
#Override
public Iterator<Three<A, B, C>> iterator() {
return new ThreeIterator<>(aList, bList, cList);
}
#Override
public Spliterator<Three<A, B, C>> spliterator() {
int size = aList.size();
assert bList.size() == size;
assert cList.size() == size;
return Spliterators.spliterator(iterator(), size, 0);
}
};
}
}
public class Three<A, B, C> {
private final A a;
private final B b;
private final C c;
public Three(A a, B b, C c) {
this.a = a;
this.b = b;
this.c = c;
}
public A getA() {
return a;
}
public B getB() {
return b;
}
public C getC() {
return c;
}
}
If all three lists are to be iterated together as you expect, that means they are tightly related and qualify to be an object of some type. It involves a design simplification beforehand.
Class MyClass {
A a;
B b;
C c;
}
Then I'll be having a List<MyClass> instead of three lists with related data. This will simplify your code in long run as well.
And, to answer your question, whatever you are using currently seems very naive but the most readable way of doing this if you are not taking the toil of making a class.
You can use forEach function:
a.forEach(o -> doSthWith(o));
Also you can use function for handling each list:
Functional interface:
#FunctionalInterface
public interface ListConsumer<T extends Iterable> {
void accept(T ... t);
}
Realization:
ListConsumer<List> function = (list) -> {
for (List l:
list) {
doSthWith(l);
}
};
Using:
List a = new ArrayList();
List b = new ArrayList();
function.accept(a, b);

Retrieve ArrayList object by parameter value [duplicate]

This question already has answers here:
How to find an object in an ArrayList by property
(8 answers)
Closed 4 years ago.
I am maintaining a sorted ArrayList of objects (by overwriting the add method as shown here) where each object has 2 attributes: a and b. How can I retrieve an object for which a equals 5?
I cannot use a map, because the value which I want to sort the list on must be able to accept duplicates (which is why this answer is not applicable here).
Code:
class TimeMap {
List<MyType> list = new ArrayList<KVT>() {
public boolean add(KVT mt) {
int index = Collections.binarySearch(this, mt, new SortByTime());
if (index < 0) index = ~index;
super.add(index, mt);
return true;
}
};
}
class KVT{//value-timestamp object
String value;
int timestamp;
public VT(String v, int t){
value=v;
timestamp=t;
}
}
class SortByTimestamp implements Comparator<KVT>{
public int compare(KVT a, KVT b){
return a.timestamp.compareTo(b.timestamp);
}
}
I have written a small example using java8 streams where you can get the object from the ArrayList by a property of the object.
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Test> list = Arrays.asList(new Test(1, 2), new Test(5, 6), new Test(3, 4));
Test test = list.stream().filter(obj -> obj.a == 5).findFirst().orElse(null);
System.out.println(test.a);
}
}
class Test {
int a;
int b;
Test(int a, int b) {
this.a = a;
this.b = b;
}
}
Hope this will give you an idea
Here is an mcve demonstrating retrieval by timestamp as well as some other enhancement:
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class TimeMap {
private List<KVT> list;
TimeMap() {
list = new ArrayList<>() {
#Override
public boolean add(KVT mt) {
super.add(mt); //add
Collections.sort(this, new SortByTimestamp()); //resort after add
return true;
}
};
}
boolean add(KVT mt){return list.add(mt);}
KVT getByTimeStamp(int timestamp){
for(KVT mt : list){
if(timestamp == mt.timestamp)
return mt;
}
return null;
}
//returns a copy of list
List<KVT> getListCopy() { return new ArrayList<>(list) ;};
//test
public static void main(String[] args) {
TimeMap tm = new TimeMap();
tm.add(new KVT("A", 2));
tm.add(new KVT("B", -3));
tm.add(new KVT("C", 1));
System.out.println(tm.getListCopy());
System.out.println(tm.getByTimeStamp(1));
}
}
class KVT{
String value;
int timestamp;
public KVT(String v, int t){
value=v;
timestamp=t;
}
#Override
public String toString(){ return value+" ("+timestamp+")";}
//todo add getters
}
class SortByTimestamp implements Comparator<KVT>{
#Override
public int compare(KVT a, KVT b){
//compareTo can not be applied to primitives
return Integer.valueOf(a.timestamp).compareTo(b.timestamp);
}
}

How to compare two Collections for "equivalence" based on fields from different Java classes?

Given any two classes, e.g. ClassA and ClassB below:
class ClassA {
private int intA;
private String strA;
private boolean boolA;
// Constructor
public ClassA (int intA, String strA, boolean boolA) {
this.intA = intA; this.strA = strA; this.boolA = boolA;
} // Getters and setters etc. below...
}
class ClassB {
private int intB;
private String strB;
private boolean boolB;
// Constructor
public ClassB (int intB, String strB, boolean boolB) {
this.intB = intB; this.strB = strB; this.boolB = boolB;
} // Getters and setters etc. below...
}
And any two different Collection types, one with ClassA elements and the other with ClassB elements, e.g:
List<Object> myList = Arrays.asList(new ClassA(1, "A", true),
new ClassA(2, "B", true));
Set<Object> mySet = new HashSet<Object>(
Arrays.asList(new ClassB(1, "A", false),
new ClassB(2, "B", false)));
What's the simplest way of telling whether the two Collections are "equivalent"(*) in terms of a specified subset of fields?
(*) The word "equivalent" is used rather then "equal" since this is contextual - i.e. such "equivalence" may be defined differently in another context.
Worked example from above:
Suppose we specify that intA and strA should match with intB and strB respectively (but the boolA / boolB values can be ignored). This would make the two collection objects defined above be considered equivalent - but if an element were added to or removed from one of the collections then they no longer would be.
Preferred solution: The method used should be generic for any Collection type. Ideally Java 7 as am confined to using this (but Java 8 may be of additional interest to others). Happy to use Guava or Apache Commons but would prefer not to use more obscure external libraries.
Here's a Java 8 version using lambdas and higher-order functions. It's probably possible to convert this to Java 7 using anonymous inner classes instead of lambdas. (I believe most IDEs have a refactoring operation that does this.) I'll leave that as an exercise for interested readers.
There are actually two distinct problems here:
Given two objects of different types, evaluate them by examining respective fields of each. This differs from "equals" and "compare" operations which are already defined by the JDK library APIs, so I'll use the term "equivalent" instead.
Given two collections containing elements of those types, determine if they are "equals" for some definition of that term. This is actually quite subtle; see the discussion below.
1. Equivalence
Given two objects of types T and U we want to determine whether they're equivalent. The result is a boolean. This can be represented by a function of type BiPredicate<T,U>. But we can't necessarily examine the objects directly; instead, we need to extract respective fields from each object and evaluate the results of extraction against each other. If the field extracted from T is of type TR and the field extracted from U is of type UR, then the extractors are represented by the function types
Function<T, TR>
Function<U, UR>
Now we have extracted results of type TR and UR. We could just call equals() on them, but that's unnecessarily restrictive. Instead, we can provide another equivalence function that will be called to evaluate these two results against each other. That's a BiPredicate<TR,UR>.
Given all this, we can write a higher-order function that takes all of these functions and produces and equivalence function for us (wildcards included for completeness):
static <T,U,TR,UR> BiPredicate<T,U> equiv(Function<? super T, TR> tf,
Function<? super U, UR> uf,
BiPredicate<? super TR, ? super UR> pred) {
return (t, u) -> pred.test(tf.apply(t), uf.apply(u));
}
It's probably a common case for the results of field extraction to be evaluated using equals(), so we can provide an overload for that:
static <T,U> BiPredicate<T,U> equiv(Function<? super T, ?> tf,
Function<? super U, ?> uf) {
return (t, u) -> equiv(tf, uf, Object::equals).test(t, u);
}
I could have provided another type variable R as the result type of both functions, to ensure they're the same type, but it turns out this isn't necessary. Since equals() is defined on Object and it takes an Object argument, we don't actually care what the function return types are, hence the wildcards.
Here's how to use this to evaluate the OP's example classes using just the string fields:
ClassA a = ... ;
ClassB b = ... ;
if (equiv(ClassA::getStrA, ClassB::getStrB).test(a, b)) {
// they're equivalent
}
As a variation, we might also want a primitive specialization in order to avoid unnecessary boxing:
static <T,U> BiPredicate<T,U> equivInt(ToIntFunction<? super T> tf,
ToIntFunction<? super U> uf) {
return (t, u) -> tf.applyAsInt(t) == uf.applyAsInt(u);
}
This lets us construct equivalence functions based on a single field. What if we want to evaluate equivalence based on multiple fields? We can combine an arbitrary number of BiPredicates by chaining the and() method. Here's how to create a function that evaluates equivalence using the int and String fields of the classes from the OP's example. For this, it's probably best to store the function in a variable separately from using it, though this can probably all be inlined (which I think will make it unreadable):
BiPredicate<ClassA, ClassB> abEquiv =
equivInt(ClassA::getIntA, ClassB::getIntB)
.and(equiv(ClassA::getStrA, ClassB::getStrB));
if (abEquiv.test(a, b)) {
// they're equivalent
}
As a final example, it's quite powerful to be able to provide an equivalence function for the field extraction results when creating an equivalence function for two classes. For example, suppose we want to extract two String fields and consider them equivalent if the extracted strings are equals, ignoring case. The following code results in true:
equiv(ClassA::getStrA, ClassB::getStrB, String::equalsIgnoreCase)
.test(new ClassA(2, "foo", true),
new ClassB(3, "FOO", false))
2. Collection “Equality”
The second part is to evaluate whether two collections are "equals" in some sense. The problem is that in the Collections Framework, the notion of equality for is defined such that a List can only be equal to another List, and a Set can only be equal to another Set. It follows that a Collection of some other type can never be equal to either a List or a Set. See the specification of Collection.equals() for some discussion of this point.
This is clearly at odds with what the OP wants. As suggested by the OP, we don't really want "equality," but we want some other property for which we need to provide a definition. Based on the OP's examples, and some suggestions in other answers by Przemek Gumula and janos, it seems like we want the elements in the two collections to somehow be in one-for-one correspondence. I'll call this a bijection which might not be mathematically precise, but it seems close enough. Furthermore, the correspondence between each pair of elements should be equivalence as defined above.
Computing this is a bit subtle, since we have our own equivalence relation. We can't use many of the built-in Collections operations, since they all use equals(). My first attempt was this:
// INCORRECT
static <T,U> boolean isBijection(Collection<T> c1,
Collection<U> c2,
BiPredicate<? super T, ? super U> pred) {
return c1.size() == c2.size() &&
c1.stream().allMatch(t -> c2.stream()
.anyMatch(u -> pred.test(t, u)));
}
(This is essentially the same as given by Przemek Gumula.) This has problems, which boil down to the possibility of more than one element in the one collection corresponding to a single element in the other collection, leaving elements unmatched. This gives strange results if given two multisets, using equality as the equivalence function:
{a x 2, b} // essentially {a, a, b}
{a, b x 2} // essentially {a, b, b}
This function considers these two multisets to be a bijection, which clearly isn't the case. Another problem occurs if the equivalence function allows many-to-one matching:
Set<String> set1 = new HashSet<>(Arrays.asList("foo", "FOO", "bar"));
Set<String> set2 = new HashSet<>(Arrays.asList("fOo", "bar", "quux"));
isBijection(set1, set2, equiv(s -> s, s -> s, String::equalsIgnoreCase))
The result is true, but if the sets are given in the opposite order, the result is false. That's clearly wrong.
An alternative algorithm is to create a temporary structure and remove elements as they're matched. The structure has to account for duplicates, so we need to decrement the count and only remove the element when the count reaches zero. Fortunately, various Java 8 features make this pretty simple. This is quite similar to the algorithm used in the answer from janos, though I've extracted the equivalence function into a method parameter. Alas, since my equivalence function can have nested equivalence functions, it means I can't probe the map (which is defined by equality). Instead, I have to search the map's keys, which means the algorithm is O(N^2). Oh well.
The code, however, is pretty simple. First, the frequency map is generated from the second collection using groupingBy. Then, the elements of the first collection are iterated, and the frequency map's keys are searched for an equivalent. If one is found, its occurrence count is decremented. Note the return value of null from the remapping function passed to Map.compute(). This has the side effect of removing the entry, not setting the mapping to null. It's a bit of an API hack, but it's quite effective.
For every element in the first collection, an equivalent element in the second collection must be found, otherwise it bails out. After all elements of the first collection have been processed, all elements from the frequency map should also have been processed, so it's simply tested for being empty.
Here's the code:
static <T,U> boolean isBijection(Collection<T> c1,
Collection<U> c2,
BiPredicate<? super T, ? super U> pred) {
Map<U, Long> freq = c2.stream()
.collect(Collectors.groupingBy(u -> u, Collectors.counting()));
for (T t : c1) {
Optional<U> ou = freq.keySet()
.stream()
.filter(u -> pred.test(t, u))
.findAny();
if (ou.isPresent()) {
freq.compute(ou.get(), (u, c) -> c == 1L ? null : c - 1L);
} else {
return false;
}
}
return freq.isEmpty();
}
It's not entirely clear whether this definition is the correct one. But it seems intuitively to be what people want. It's fragile, though. If the equivalence function isn't symmetric, isBijection will fail. There are also some degrees of freedom aren't accounted for. For example, suppose the collections are
{a, b}
{x, y}
And a is equivalent to both x and y, but b is only equivalent to x. If a is matched to x, the result of isBijection is false. But if a were matched to y, the result would be true.
Putting it Together
Here's the OP's example, coded up using the equiv(), equivInt(), and isBijection functions:
List<ClassA> myList = Arrays.asList(new ClassA(1, "A", true),
new ClassA(2, "B", true));
Set<ClassB> mySet = new HashSet<>(Arrays.asList(new ClassB(1, "A", false),
new ClassB(2, "B", false)));
BiPredicate<ClassA, ClassB> abEquiv =
equivInt(ClassA::getIntA, ClassB::getIntB)
.and(equiv(ClassA::getStrA, ClassB::getStrB));
isBijection(myList, mySet, abEquiv)
The result of this is true.
Another possible solution is writing a simple comparing method with a predicate (so you can explicitly specify the condition for two classes to be similar on your terms). I created this in Java 8:
<T, U> boolean compareCollections(Collection<T> coll1, Collection<U> coll2, BiPredicate<T, U> predicate) {
return coll1.size() == coll2.size()
&& coll1.stream().allMatch(
coll1Item -> coll2.stream().anyMatch(col2Item -> predicate.test(coll1Item, col2Item))
);
}
As you can see, it compares the size and then checks if every element in collection has a counterpart in the second collection (it's not comparing order though). It's in Java 8, but you can port it to Java 7 by implementing a simple BiPredicate code, allMatch and anyMatch (one for-loop for each of them should be sufficient)
Edit: Java 7 code:
<T, U> boolean compareCollections(Collection<T> coll1, Collection<U> coll2, BiPredicate<T, U> predicate) {
if (coll1.size() != coll2.size()) {
return false;
}
for (T item1 : coll1) {
boolean matched = false;
for (U item2 : coll2) {
if (predicate.test(item1, item2)) {
matched = true;
}
}
if (!matched) {
return false;
}
}
return true;
}}
interface BiPredicate <T, U> {
boolean test(T t, U u);
}
Here's a usage example.
Unit Tests:
class UnitTestAppleClass {
private int appleKey;
private String appleName;
public UnitTestAppleClass(int appleKey, String appleName) {
this.appleKey = appleKey;
this.appleName = appleName;
}
public int getAppleKey() {
return appleKey;
}
public String getAppleName() {
return appleName;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
}
class UnitTestOrangeClass {
private int orangeKey;
private String orangeName;
public UnitTestOrangeClass(int orangeKey, String orangeName) {
this.orangeKey = orangeKey;
this.orangeName = orangeName;
}
public int getOrangeKey() {
return orangeKey;
}
public String getOrangeName() {
return orangeName;
}
}
and
#Test
public void compareNotSameTypeCollectionsOkTest() {
BiPredicate<UnitTestAppleClass, UnitTestOrangeClass> myApplesToOrangesCompareBiPred = (app, org) -> {
/* you CAN compare apples and oranges */
boolean returnValue =
app.getAppleKey() == org.getOrangeKey() && app.getAppleName().equals(org.getOrangeName());
return returnValue;
};
UnitTestAppleClass apple1 = new UnitTestAppleClass(1111, "Fruit1111");
UnitTestAppleClass apple2 = new UnitTestAppleClass(1112, "Fruit1112");
UnitTestAppleClass apple3 = new UnitTestAppleClass(1113, "Fruit1113");
UnitTestAppleClass apple4 = new UnitTestAppleClass(1114, "Fruit1114");
Collection<UnitTestAppleClass> apples = asList(apple1, apple2, apple3, apple4);
/* same "key" VALUES, and "name" VALUES, but different property names */
UnitTestOrangeClass orange1 = new UnitTestOrangeClass(1111, "Fruit1111");
UnitTestOrangeClass orange2 = new UnitTestOrangeClass(1112, "Fruit1112");
UnitTestOrangeClass orange3 = new UnitTestOrangeClass(1113, "Fruit1113");
UnitTestOrangeClass orange4 = new UnitTestOrangeClass(1114, "Fruit1114");
Collection<UnitTestOrangeClass> oranges = asList(orange1, orange2, orange3, orange4);
boolean applesAndOrangeCheck = EqualsHelper.<UnitTestAppleClass, UnitTestOrangeClass> compareCollections(apples, oranges, myApplesToOrangesCompareBiPred);
assertTrue(applesAndOrangeCheck);
/* alter one of the apples */
if( apples.stream().findFirst().isPresent())
{
apples.stream().findFirst().get().setAppleName("AppleChangedNameOne");
boolean alteredAppleAndOrangeCheck = EqualsHelper.<UnitTestAppleClass, UnitTestOrangeClass> compareCollections(apples, oranges, myApplesToOrangesCompareBiPred);
assertFalse(alteredAppleAndOrangeCheck);
}
Collection<UnitTestAppleClass> reducedApples = asList(apple1, apple2, apple3);
boolean reducedApplesAndOrangeCheck = EqualsHelper.<UnitTestAppleClass, UnitTestOrangeClass> compareCollections(reducedApples, oranges, myApplesToOrangesCompareBiPred);
assertFalse(reducedApplesAndOrangeCheck);
}
There's no very easy way.
The most generic that would work with regular Java collections would be to create a wrapper class that would take either ClassA or ClassB as input, and then override equals/hashcode as defined by you.
In some cases you can abuse Comparator, but that would limit you to TreeMap/TreeSet.
You can also implement equals() method to work so that classA.equals(classB); returns true, but that can cause tricky bugs if you're not being careful. It can also result in interesting situations where a.equals(b) and b.equals(c) but !a.equals(c).
Some library (Guava?) also had a Comparator style mechanism for equality testing, but that would work only with the library's collections.
Apache Commons Lang has EqualsBuilder#reflectionEquals(Object, Object):
This method uses reflection to determine if the two Objects are equal.
It uses AccessibleObject.setAccessible to gain access to private
fields. This means that it will throw a security exception if run
under a security manager, if the permissions are not set up correctly.
It is also not as efficient as testing explicitly. Non-primitive
fields are compared using equals().
Transient members will be not be tested, as they are likely derived
fields, and not part of the value of the Object.
Static fields will not be tested. Superclass fields will be included.
So this should cover your use case. Obvious disclaimer: it uses reflection ;)
EDIT: This, of course, assumes fields have same names, not types. In latter case one can inspect source code and adjust it to their use case.
A combination of two existing answers: The generic version of a wrapper class Kayaman suggested (Just a List). Using ArrayList::equals as predicate for Przemek Gumula approach.
I added a Builder to make it a bit nicer to use:
StructureEqual<ClassA, ClassB> struct = StructureEqual.<ClassA, ClassB>builder()
.field(ClassA::getIntA, ClassB::getIntB) // Declare what fields should be checked
.field(ClassA::getStrA, ClassB::getStrB)
.build();
System.out.println(struct.isEqual(myList, mySet));
The actual code:
public class StructureEqual<A, B> {
private List<EqualPoint<A, B>> points;
public StructureEqual(List<EqualPoint<A, B>> points) {
this.points = points;
}
private List<Object> sampleA(A a) {
return points.stream().map(p -> p.getAPoint().apply(a)).collect(Collectors.toList());
}
private List<Object> sampleB(B b) {
return points.stream().map(p -> p.getBPoint().apply(b)).collect(Collectors.toList());
}
public boolean isEqual(Collection<A> as, Collection<B> bs) {
Set<List<Object>> aSamples = as.stream().map(this::sampleA).collect(Collectors.toSet());
Set<List<Object>> bSamples = bs.stream().map(this::sampleB).collect(Collectors.toSet());
return aSamples.equals(bSamples);
}
private static class EqualPoint<PA, PB> {
private final Function<PA, ?> aPoint;
private final Function<PB, ?> bPoint;
public <T> EqualPoint(Function<PA, T> aPoint, Function<PB, T> bPoint) {
this.aPoint = aPoint;
this.bPoint = bPoint;
}
Function<PA, ?> getAPoint() {
return aPoint;
}
Function<PB, ?> getBPoint() {
return bPoint;
}
}
public static <BA, BB> Builder<BA, BB> builder() {
return new Builder<>();
}
public static class Builder<BA, BB> {
private List<EqualPoint<BA, BB>> points = new ArrayList<>();
public <T> Builder<BA, BB> field(Function<BA, T> a, Function<BB, T> b) {
points.add(new EqualPoint<>(a, b));
return this;
}
public StructureEqual<BA, BB> build() {
return new StructureEqual<>(Collections.unmodifiableList(points));
}
}
}
What's the simplest way of telling whether the two Collections are equal in terms of a specified subset of fields?
Based on your description, your requirements of equality are:
The collections have equal sizes.
For each item1 in collection1, there exists item2 in collection2 such that item1.field_x is equal to item2.field_y, for multiple defined field_x-field_y pairs.
If we can assume that there are no duplicate elements in either collection,
that is, then this the "simplest way" could be something like this:
public boolean areEqual(Collection<ClassA> c1, Collection<ClassB> c2) {
if (c1.size() != c2.size()) {
return false;
}
OUTER:
for (ClassA a : c1) {
for (ClassB b : c2) {
if (a.getIntA() == b.getIntB() && Objects.equals(a.getStringA(), b.getStringB())) {
continue OUTER;
}
}
return false;
}
return true;
}
This is a straightforward implementation of the requirements.
But as it may compare each element with every other element in the other collection,
it has very poor performance,
O(n^2) where n is the size of the collection.
This may also not work if equal elements can appear multiple times in a collection:
List<ClassA> list = new ArrayList<>(Arrays.asList(
new ClassA(1, "A", true),
new ClassA(1, "A", false),
new ClassA(2, "B", true)
));
Set<ClassB> set = new HashSet<>(Arrays.asList(
new ClassB(1, "A", false),
new ClassB(2, "B", false),
new ClassB(2, "B", true)
));
Here ClassA(1, "A", true) and ClassA(1, "A", false) are considered equivalent in the first list, and new ClassB(2, "B", false) and new ClassB(2, "B", true) are considered equivalent in the second list.
The above algorithm will find these two collections equal, which is incorrect.
It's possible to handle the case of duplicates, and at the same time improve time-complexity at the expense of using extra space:
Iterate over the first collection to build a map of counts of (int, String) tuples
Iterate over the second collection while checking the map of counts:
If the count of maps doesn't contain the corresponding (int, String) tuple, return false, as this means that an element doesn't have a matching pair
If of corresponding tuple exists, decrease its count
If the count reaches 0, from the tuple from the map
If the end of the loop is reached, that must mean that all items were matched (the map should be empty), so you can simply return true.
Implementation:
class FieldExtractingEqual {
public boolean areEqual(Collection<ClassA> c1, Collection<ClassB> c2) {
if (c1.size() != c2.size()) {
return false;
}
Map<Tuple, Integer> counts = new HashMap<>();
for (ClassA a : c1) {
Tuple tuple = new Tuple(a.getIntA(), a.getStringA());
Integer count = counts.get(tuple);
if (count == null) {
count = 0;
}
counts.put(tuple, count + 1);
}
for (ClassB b : c2) {
Tuple tuple = new Tuple(b.getIntB(), b.getStringB());
Integer count = counts.get(tuple);
if (count == null) {
return false;
}
if (count == 1) {
counts.remove(tuple);
} else {
counts.put(tuple, count - 1);
}
}
return true;
}
private static class Tuple {
private final Object[] values;
public Tuple(Object... values) {
this.values = values;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tuple tuple = (Tuple) o;
return Arrays.equals(values, tuple.values);
}
#Override
public int hashCode() {
return Arrays.hashCode(values);
}
}
}
Some assertj tests to verify the implementation:
List<ClassA> myList = new ArrayList<>(Arrays.asList(
new ClassA(1, "A", true),
new ClassA(1, "A", true),
new ClassA(2, "B", true)
));
Set<ClassB> mySet = new HashSet<>(Arrays.asList(
new ClassB(1, "A", false),
new ClassB(1, "A", true),
new ClassB(2, "B", false)
));
FieldExtractingEqual comp = new FieldExtractingEqual();
assertThat(comp.areEqual(myList, mySet)).isTrue();
myList.add(new ClassA(3, "X", true));
mySet.add(new ClassB(3, "Y", true));
assertThat(comp.areEqual(myList, mySet)).isFalse();
As a further improvement,
it's possible to make the implementation of FieldExtractingEqual generic,
so that it can take arbitrary Collection<A> and Collection<B> parameters,
and pairs of extractors to create tuples from A and B.
Here's one way to implement that:
interface FieldExtractor<T, V> {
V apply(T arg);
}
class GenericFieldExtractingEqual<T, U> {
private final List<FieldExtractor<T, ?>> extractors1;
private final List<FieldExtractor<U, ?>> extractors2;
private GenericFieldExtractingEqual(List<FieldExtractor<T, ?>> extractors1, List<FieldExtractor<U, ?>> extractors2) {
this.extractors1 = extractors1;
this.extractors2 = extractors2;
}
public boolean areEqual(Collection<T> c1, Collection<U> c2) {
if (c1.size() != c2.size()) {
return false;
}
Map<Tuple, Integer> counts = new HashMap<>();
for (T a : c1) {
Tuple tuple = newTuple1(a);
Integer count = counts.get(tuple);
if (count == null) {
count = 0;
}
counts.put(tuple, count + 1);
}
for (U b : c2) {
Tuple tuple = newTuple2(b);
Integer count = counts.get(tuple);
if (count == null) {
return false;
}
if (count == 1) {
counts.remove(tuple);
} else {
counts.put(tuple, count - 1);
}
}
return true;
}
private Tuple newTuple1(T a) {
return new Tuple(extractors1.stream().map(x -> x.apply(a)).toArray());
}
private Tuple newTuple2(U b) {
return new Tuple(extractors2.stream().map(x -> x.apply(b)).toArray());
}
private static class Tuple {
private final Object[] values;
public Tuple(Object... values) {
this.values = values;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tuple tuple = (Tuple) o;
return Arrays.equals(values, tuple.values);
}
#Override
public int hashCode() {
return Arrays.hashCode(values);
}
}
public static class Builder<T, U> {
List<FieldExtractor<T, ?>> extractors1 = new ArrayList<>();
List<FieldExtractor<U, ?>> extractors2 = new ArrayList<>();
<V> Builder<T, U> addExtractors(FieldExtractor<T, V> extractor1, FieldExtractor<U, V> extractor2) {
extractors1.add(extractor1);
extractors2.add(extractor2);
return this;
}
GenericFieldExtractingEqual<T, U> build() {
return new GenericFieldExtractingEqual<>(new ArrayList<>(extractors1), new ArrayList<>(extractors2));
}
}
}
Example usage and some assertj tests:
GenericFieldExtractingEqual<ClassA, ClassB> comp2 = new GenericFieldExtractingEqual.Builder<ClassA, ClassB>()
.addExtractors(ClassA::getIntA, ClassB::getIntB)
.addExtractors(ClassA::getStringA, ClassB::getStringB)
.build();
assertThat(comp2.areEqual(myList, mySet)).isTrue();
myList.add(new ClassA(3, "X", true));
mySet.add(new ClassB(3, "Y", true));
assertThat(comp2.areEqual(myList, mySet)).isFalse();
That is, you build an GenericFieldExtractingEqual instance from pairs of extractors, for example:
.addExtractors(ClassA::getIntA, ClassB::getIntB)
The first parameter is an object that extracts a field in the first class,
and the second parameter is an object that extracts the corresponding field in the second class.
You add as many extractor pairs as you want to compare for the equality condition.
Although I used the Java8 writing style ClassA::getIntA for compactness,
it's easy (but lengthy) to convert to FieldExtractor implementations:
.addExtractors(
new FieldExtractor<ClassA, Integer>() {
#Override
public Integer apply(ClassA arg) {
return arg.getIntA();
}
},
new FieldExtractor<ClassB, Integer>() {
#Override
public Integer apply(ClassB arg) {
return arg.getIntB();
}
}
)
The same goes for the newTuple* utility methods.
Here's a runnable version on RexTester.
Here is my answer:
public class StackOverFlow {
static class Testy {
int id;
String name;
public Testy(int id, String name) {
this.id = id;
this.name = name;
}
#Override
public int hashCode() {
int hash = 3;
hash = 89 * hash + this.id;
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Testy other = (Testy) obj;
if (this.id != other.id || !this.name.equals(other.name)) {
return false;
}
return true;
}
}
static class AnotherTesty {
int id;
String name;
public AnotherTesty(int id, String name) {
this.id = id;
this.name = name;
}
#Override
public int hashCode() {
int hash = 5;
hash = 41 * hash + this.id;
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final AnotherTesty other = (AnotherTesty) obj;
if (this.id != other.id || !this.name.equals(other.name)) {
return false;
}
return true;
}
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
List<Object> list = Arrays.asList(new Testy(5, "test"), new AnotherTesty(5, "test"));
Set<Object> set = new HashSet<>(Arrays.asList(new Testy(5, "test"), new AnotherTesty(5, "test")));
System.out.println(compareCollections(list, set, Testy.class, AnotherTesty.class));
}
private static boolean compareCollections(Collection<?> c1, Collection<?> c2, Class cls, Class cls2) {
List<Object> listOfCls = c1.stream().filter(p -> cls.isInstance(p)).map(o -> cls.cast(o)).collect(Collectors.toList());
List<Object> listOfCls2 = c1.stream().filter(p -> cls2.isInstance(p)).map(o -> cls2.cast(o)).collect(Collectors.toList());
List<Object> list2OfCls = c2.stream().filter(p -> cls.isInstance(p)).map(o -> cls.cast(o)).collect(Collectors.toList());
List<Object> list2OfCls2 = c2.stream().filter(p -> cls2.isInstance(p)).map(o -> cls2.cast(o)).collect(Collectors.toList());
if (listOfCls.size() != list2OfCls.size()||listOfCls2.size() != list2OfCls2.size()) {
return false;
}
boolean clsFlag = true, cls2Flag = true;
for (int i = 0; i < listOfCls.size(); i++) {
if (!listOfCls.get(i).equals(list2OfCls.get(i))) {
clsFlag = false;
break;
}
}
for (int i = 0; i < list2OfCls2.size(); i++) {
if (!listOfCls2.get(i).equals(list2OfCls2.get(i))) {
cls2Flag = false;
break;
}
}
return clsFlag && cls2Flag;
}
}
quick prototype:
package stackoverflow;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiFunction;
import org.junit.Test;
public class CompareTwoList {
static class ClassA {
int intA;
String strA;
boolean boolA;
// Constructor
public ClassA(int intA, String strA, boolean boolA) {
this.intA = intA;
this.strA = strA;
this.boolA = boolA;
} // Getters and setters etc. below...
}
static class ClassB {
int intB;
String strB;
boolean boolB;
// Constructor
public ClassB(int intB, String strB, boolean boolB) {
this.intB = intB;
this.strB = strB;
this.boolB = boolB;
} // Getters and setters etc. below...
}
#FunctionalInterface
private interface IncopatibeEqualsOperator<A, B> extends BiFunction<A, B, Boolean> {
}
#Test
public void CompareListOfClassAAndclassBObjects() throws Exception {
List<ClassA> myList = Arrays.asList(
new ClassA(1, "A", true),
new ClassA(2, "B", true));
Set<ClassB> mySet = new HashSet<ClassB>(Arrays.asList(
new ClassB(1, "A", false),
new ClassB(2, "B", false)));
// can be extract to separate file
IncopatibeEqualsOperator<ClassA, ClassB> equalsOperatorFlavor1 = (ClassA o1, ClassB o2) -> {
// custom logic here
return o1.intA == o2.intB &&
java.util.Objects.equals(o1.strA, o2.strB);
};
boolean areEquals = areEquals(myList, mySet, equalsOperatorFlavor1);
assertThat(areEquals, is(true));
}
// Add in utility class
private <A, B> boolean areEquals(Collection<A> o1, Collection<B> o2, IncopatibeEqualsOperator<A, B> comparator) {
if (o1.size() == o2.size()) { // if size different; they are not equals
for (A obj1 : o1) {
boolean found = false; // search item of o1 into o2; algorithm
// can be improve
for (B obj2 : o2) {
if (comparator.apply(obj1, obj2)) { // call custom code of
// comparision
found = true;
break;
}
}
if (!found) {// if current element of o1 is not equals with any
// one return false
return false;
}
}
return true;// all are matched
}
return false;
}
}
Make sure Class A and B have toString() methods.
ClassA
public class ClassA {
private int intA;
private String strA;
private boolean boolA;
// Constructor
public ClassA (int intA, String strA, boolean boolA) {
this.intA = intA; this.strA = strA; this.boolA = boolA;
} //
#Override
public String toString()
{
return intA + " " + strA + " " + boolA;
}
}
ClassB
public class ClassB {
private int intB;
private String strB;
private boolean boolB;
// Constructor
public ClassB (int intB, String strB, boolean boolB) {
this.intB = intB; this.strB = strB; this.boolB = boolB;
} // Gett
#Override
public String toString()
{
return intB + " " + strB + " " + boolB;
}
}
Main/Test
public class JavaApplication11 {
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
// TODO code application logic here
List<Object> myList = Arrays.asList(new ClassA(1, "A", true),
new ClassA(2, "B", true));
Set<Object> mySet = new HashSet<Object>(
Arrays.asList(new ClassB(1, "A", false),
new ClassB(2, "B", false)));
System.out.println("is equal: " + isEqual(myList, mySet));
}
static boolean isEqual(Object list, Object set)
{
System.out.println(list.toString());
System.out.println(set.toString());
String tempStringA = list.toString();
tempStringA = tempStringA.replaceAll("true", "");
tempStringA = tempStringA.replaceAll("false", "");
String tempStringB = set.toString();
tempStringB = tempStringB.replaceAll("true", "");
tempStringB = tempStringB.replaceAll("false", "");
return tempStringA.equals(tempStringB);
}
}
You should take the basic idea of EqualsBuilder but modified to your needs: Create some kind of a list with the members (or better getters) to compare, eg. a HashMap. Now iterate this map, search for the functions in class A with the key entry of the map. Next search for the function of class B with the value entry of the map. Call (invoke) both and compare the output.
HashMap<String,String> mymap=new HashMap<>();
mymap.put("intA","intB");
mymap.put("boolA","boolB");
for(Map.Entry<String,String> e:mymap.entrySet()) {
// names not ok, maybe take a bean helper class
Method m1=a.getClass().getMethod("get"+e.getKey()); // or look for fields if you dont have getters
Method m2=b.getClass().getMethod("get"+e.getValue());
Object r1=m1.invoke(a);
Object r2=m2.invoke(b);
if (!r1.equals(r2))
return false;
}
Sorry for no real code. Null checks have to be added!
public class Compare {
public static void main(String[] args) {
// TODO Auto-generated method stub
Compare compare= new Compare();
List<ClassA> myList = Arrays.asList(new ClassA(1, "A", false), new ClassA(2, "B", false));
Set<ClassB> mySet = new HashSet<ClassB>(Arrays.asList(new ClassB(1, "A", false), new ClassB(2, "B", false)));
System.out.println( compare.areEqual(myList,mySet));
}
public boolean areEqual(Collection<ClassA> colA,Collection<ClassB> colB){
boolean equal =false;
if(colA.size()!=colB.size()){
return equal;
}
Set<Integer> setA=new HashSet<Integer>();
Set<Integer> setB= new HashSet<Integer>();
for(ClassA obj : colA){
setA.add(obj.hashCode());
}
for(ClassB obj : colB){
setB.add(obj.hashCode());
}
if(setA.equals(setB)){
equal=true;
}
return equal;
}
}
class ClassA {
private int intA;
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + intA;
result = prime * result + ((strA == null) ? 0 : strA.hashCode());
return result;
}
private String strA;
private boolean boolA;
// Constructor
public ClassA(int intA, String strA, boolean boolA) {
this.intA = intA;
this.strA = strA;
this.boolA = boolA;
} // Getters and setters etc. below...
}
class ClassB {
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + intB;
result = prime * result + ((strB == null) ? 0 : strB.hashCode());
return result;
}
private int intB;
private String strB;
private boolean boolB;
// Constructor
public ClassB(int intB, String strB, boolean boolB) {
this.intB = intB;
this.strB = strB;
this.boolB = boolB;
} // Getters and setters etc. below...
}
Well i override the hash code method of both class to create hashcode on basis of int and str and a method to create to sets of Intergers , Integer being hash code of each class if you don't want even the hashcode to be overridden let me know will update for that as well
May it help..
class ClassA {
private int intA;
private String strA;
private boolean boolA;
// Constructor
public ClassA(int intA, String strA, boolean boolA) {
this.intA = intA;
this.strA = strA;
this.boolA = boolA;
} // Getters and setters etc. below...
#Override
public boolean equals(Object obj) {
if (obj instanceof ClassA) {
ClassA obj2 = (ClassA) obj;
return (this.intA == obj2.intA && this.strA.equals(obj2.strA) && this.boolA == obj2.boolA);
} else {
ClassB obj2 = (ClassB) obj;
return (this.intA == obj2.intB && this.strA.equals(obj2.strB) && this.boolA == obj2.boolB);
}
}
#Override
public int hashCode() {
int hash = 3;
hash = 71 * hash + this.intA;
hash = 71 * hash + Objects.hashCode(this.strA);
hash = 71 * hash + (this.boolA ? 1 : 0);
return hash;
}
}
class ClassB {
private int intB;
private String strB;
private boolean boolB;
// Constructor
public ClassB(int intB, String strB, boolean boolB) {
this.intB = intB;
this.strB = strB;
this.boolB = boolB;
} // Getters and setters etc. below...
#Override
public boolean equals(Object obj) {
if (obj instanceof ClassB) {
ClassB obj2 = (ClassB) obj;
return (this.intB == obj2.intB && this.strB.equals(obj2.strB) && this.boolB == obj2.boolB);
} else {
ClassA obj2 = (ClassA) obj;
return (this.intB == obj2.intA && this.strB.equals(obj2.strA) && this.boolB == obj2.boolA);
}
}
#Override
public int hashCode() {
int hash = 5;
hash = 79 * hash + this.intB;
hash = 79 * hash + Objects.hashCode(this.strB);
hash = 79 * hash + (this.boolB ? 1 : 0);
return hash;
}
}
public void test() {
List<Object> myList = Arrays.asList(new ClassA(1, "A", true),
new ClassA(1, "A", true));
System.out.println(myList.get(0).equals(myList.get(1)));
}
Whereas for two single elements the equivalent comparison is unambiguously defined, for collections several variants of the equivalent comparison are possible. One aspect is whether to consider element ordering. Further when ordering is not significant, then the cardinality of equivalent elements (number of matches) might or might not be significant.
Therefore the proposal of using an EquivalenceComparisonBuilder on which together with the two collections and an EquivalenceComparator also the ComparisonType is configured - ComparisonType.ORDERING for strict ordering, ComparisonType.DUPLICATES for strict matches count and ComparisonType.SIMPLE for loose equivalence comparison, where it suffices that for each element in one collection is at least one equivalent element in another collection.
Please note that the implementation of EquivalenceComparator needs to consider null arguments if the collections might contains null elements.
package equivalence;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
public class Equivalence {
public static interface EquivalenceComparison<S, T> {
boolean equivalent();
}
public static interface EquivalenceComparator<S, T> {
boolean equivalent(S s, T t);
}
static public class EquivalenceComparisonBuilder<S, T> {
enum ComparisonType {
ORDERING, DUPLICATES, SIMPLE
};
private Collection<S> ss;
private Collection<T> ts;
private EquivalenceComparator<S, T> ec;
private ComparisonType comparisonType;
public EquivalenceComparisonBuilder<S, T> setCollections(Collection<S> ss, Collection<T> ts) {
this.ss = ss;
this.ts = ts;
return this;
}
public EquivalenceComparisonBuilder<S, T> setEquivalenceComparator(EquivalenceComparator<S, T> ec) {
this.ec = ec;
return this;
}
public EquivalenceComparisonBuilder<S, T> setComparisonType(ComparisonType comparisonType) {
this.comparisonType = comparisonType;
return this;
}
public EquivalenceComparison<S, T> comparison() {
if (comparisonType == null || ss == null || ts == null) {
throw new NullPointerException();
}
switch (comparisonType) {
case ORDERING:
return new OrderingComparison<S, T>(ss, ts, ec);
case DUPLICATES:
return new DuplicatesComparison<S, T>(ss, ts, ec);
case SIMPLE:
return new SimpleComparison<S, T>(ss, ts, ec);
default:
throw new IllegalArgumentException("Unknown comparison type");
}
}
}
private static <S, T> EquivalenceComparator<T, S> mirrored(EquivalenceComparator<S, T> ec) {
return new EquivalenceComparator<T, S>() {
#Override
public boolean equivalent(T t, S s) {
return ec.equivalent(s, t);
}
};
}
private static class EquivalencePredicate<S, T> implements Predicate<T> {
private S s;
private EquivalenceComparator<S, T> equivalenceComparator;
public EquivalencePredicate(S s, EquivalenceComparator<S, T> equivalenceComparator) {
this.s = s;
this.equivalenceComparator = equivalenceComparator;
}
#Override
public boolean evaluate(T t) {
return equivalenceComparator.equivalent(s, t);
}
}
static private class OrderingComparison<S, T> implements EquivalenceComparison<S, T> {
private Collection<S> ss;
private Collection<T> ts;
private EquivalenceComparator<S, T> ec;
public OrderingComparison(Collection<S> ss, Collection<T> ts, EquivalenceComparator<S, T> ec) {
this.ss = ss;
this.ts = ts;
this.ec = ec;
}
#Override
public boolean equivalent() {
if (ss.size() != ts.size()) {
return false;
}
List<S> ssl = new ArrayList<S>(ss);
List<T> tsl = new ArrayList<T>(ts);
for (int i = 0; i < ssl.size(); i++) {
S s = ssl.get(i);
T t = tsl.get(i);
if (!ec.equivalent(s, t)) {
return false;
}
}
return true;
}
}
static private class DuplicatesComparison<S, T> implements EquivalenceComparison<S, T> {
private Collection<S> ss;
private Collection<T> ts;
private EquivalenceComparator<S, T> ec;
public DuplicatesComparison(Collection<S> ss, Collection<T> ts, EquivalenceComparator<S, T> ec) {
this.ss = ss;
this.ts = ts;
this.ec = ec;
}
#Override
public boolean equivalent() {
if (ss.size() != ts.size()) {
return false;
}
for (S s : ss) {
Collection<T> matchingTs = CollectionUtils.select(ts, new EquivalencePredicate(s, ec));
if (matchingTs.size() == 0) {
return false;
}
T t = matchingTs.iterator().next();
Collection<S> matchingSs = CollectionUtils.select(ss, new EquivalencePredicate(t, mirrored(ec)));
if (matchingTs.size() != matchingSs.size()) {
return false;
}
}
return true;
}
}
static private class SimpleComparison<S, T> implements EquivalenceComparison<S, T> {
private Collection<S> ss;
private Collection<T> ts;
private EquivalenceComparator<S, T> ec;
public SimpleComparison(Collection<S> ss, Collection<T> ts, EquivalenceComparator<S, T> ec) {
this.ss = ss;
this.ts = ts;
this.ec = ec;
}
#Override
public boolean equivalent() {
for (S s : ss) {
if (!CollectionUtils.exists(ts, new EquivalencePredicate(s, ec))) {
return false;
}
}
for(T t :ts) {
if (!CollectionUtils.exists(ss, new EquivalencePredicate(t, mirrored(ec)))) {
return false;
}
}
return true;
}
}
}
Here are few test cases:
package equivalence;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.junit.Assert;
import org.junit.Test;
import equivalence.Equivalence.EquivalenceComparator;
import equivalence.Equivalence.EquivalenceComparisonBuilder;
import equivalence.Equivalence.EquivalenceComparisonBuilder.ComparisonType;
public class EquivalenceExample {
static class A {
private int ia;
private String is;
private long a;
public A(int ia, String is, long a) {
this.ia = ia;
this.is = is;
this.a = a;
}
public int getIa() {
return ia;
}
public String getIs() {
return is;
}
public long getA() {
return a;
}
}
static class B {
private int ib;
private String is;
private long b;
public B(int ib, String is, long b) {
this.ib = ib;
this.is = is;
this.b = b;
}
public int getIb() {
return ib;
}
public String getIs() {
return is;
}
public long getB() {
return b;
}
}
static class ABEquivalenceComparator implements EquivalenceComparator<A, B> {
static public ABEquivalenceComparator INSTANCE = new ABEquivalenceComparator();
#Override
public boolean equivalent(A a, B b) {
return new EqualsBuilder().append(a.getIa(), b.getIb()).append(a.getIs(), b.getIs()).isEquals();
}
}
#Test
public void thatOrderingEquivalenceMatchesEquivalentElementsWhenInSameOrder() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(1, "1", 99l), new B(2, "2", 99l)));
Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.ORDERING)
.comparison().equivalent());
}
#Test
public void thatOrderingEquivalenceDoesNotMatchEquivalentElementsWhenNotSameOrdering() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l)));
Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.ORDERING)
.comparison().equivalent());
}
#Test
public void thatOrderingEquivalenceDoesNotMatchNonEquivalentElements() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(1, "1", 99l), new B(1, "1", 99l)));
Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.ORDERING)
.comparison().equivalent());
}
#Test
public void thatDuplicatesEquivalenceMatchesEquivalentElementsRegardlessOrder() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l)));
Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.DUPLICATES)
.comparison().equivalent());
}
#Test
public void thatDuplicatesEquivalenceDoesNotMatchesWhenElementsCardinlityDoNotMatch() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l), new A(1, "1", 99l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(
Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l), new B(2, "2", 99l)));
Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.DUPLICATES)
.comparison().equivalent());
}
#Test
public void thatSimpleEquivalenceMatchesRegardlessEquivalentElementCardinality() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l), new A(1, "1", 99l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(
Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l), new B(2, "2", 99l)));
Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.SIMPLE)
.comparison().equivalent());
}
#Test
public void thatSimpleEquivalenceMatchesRegardlessElementsCount() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(
Arrays.asList(new B(2, "2", 99l), new B(1, "1", 99l), new B(2, "2", 99l)));
Assert.assertTrue(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.SIMPLE)
.comparison().equivalent());
}
#Test
public void thatSimpleEquivalenceDoesMatchesWhenElementsDoNotMatch() {
List<A> as = Arrays.asList(new A(1, "1", 99l), new A(2, "2", 98l));
LinkedHashSet<B> bs = new LinkedHashSet<B>(Arrays.asList(new B(2, "2", 99l), new B(3, "3", 99l)));
Assert.assertFalse(new EquivalenceComparisonBuilder<A, B>().setCollections(as, bs)
.setEquivalenceComparator(ABEquivalenceComparator.INSTANCE).setComparisonType(ComparisonType.SIMPLE)
.comparison().equivalent());
}
}

How to create 2 depends Comparators in java?

Lets say I have a Product class in Java and 2 Comparators:
1st is price Comparator for asc order.
2nd is price Comparator for desc order.
It can be that if I changed the 1st to be product name Comparator, so, the 2nd will change automatic to name Comparator as well?
Thanks alot!
Exmaple:
class ProductComparatorByPriceDesc implements Comparator<Customer> {
#Override
public int compare(Product o1, Product o2) {
return o1.getPrice() - o2.getPrice();
}
}
Class ProductComparatorByPriceAsc implements Comparator<Customer> {
#Override
public int compare(Customer o1, Customer o2) {
return o2.getPrice() - o1.getPrice();
}
}
So if i changed the 1st comparator to sort by name, not price, the 2nd will changed as well, but not the opposite!
One way would be:
import java.util.*;
class SomeClass {
public int price;
public String name;
public SomeClass(String name, int price) {
this.name = name;
this.price = price;
}
}
class PriceOrNameComparator implements Comparator<SomeClass> {
boolean compareByPrice;
public PriceOrNameComparator byPrice() {
this.compareByPrice = true;
return this;
}
public PriceOrNameComparator byName() {
this.compareByPrice = false;
return this;
}
public int compare(SomeClass a, SomeClass b) {
if (compareByPrice) {
return a.price - b.price;
} else {
return a.name.compareTo(b.name);
}
}
public Comparator<SomeClass> reverseComparator() {
return new Comparator<SomeClass>() {
public int compare(SomeClass a, SomeClass b) {
int res = PriceOrNameComparator.this.compare(a, b);
if (res == 0) {
return 0;
} else {
return (res > 0) ? -1 : 1;
}
}
};
}
}
class Test {
public static void main(String[] args) {
SomeClass s1 = new SomeClass("a", 5);
SomeClass s2 = new SomeClass("b", 4);
PriceOrNameComparator c = new PriceOrNameComparator().byPrice();
Comparator<SomeClass> r = c.reverseComparator();
System.out.println(c.compare(s1, s2)); // 1
System.out.println(r.compare(s1, s2)); // -1
c.byName();
System.out.println(c.compare(s1, s2)); // -1
System.out.println(r.compare(s1, s2)); // 1
}
}
Basically, the outer comparator is configurable, and the inner, reverse order, comparator, being an anonymous inner class, has an implicit reference to the outer comparator and can observe changes in its state.
I would suggest only having a single comparator class for comparing by price, and a separate comparator class to compare by name (or no classes - see the end of my answer). Each class does one thing, and does it well.
Then you can reverse any comparator using the Comparator.reversed default method... and likewise you can chain them together using Comparator.thenComparing, should you wish to order by name and then price, for example:
Comparator<Product> nameThenPrice =
new NameComparator().thenComparing(new PriceComparator());
(If you're not using Java 8, it's easy enough to write a ReversingComparator which takes an existing one, and a CompoundComparator which takes two existing ones.)
You can also use Java 8's static methods in Comparator:
Comparator<Product> byName = Comparator.comparing(p -> p.getName());
Comparator<Product> byPrice = Comparator.comparing(p -> p.getPrice());
Comparator<Product> nameThenPrice = byName.thenComparing(byPrice);
That way you often don't need to implement Comparator at all manually.

Collections.sort with multiple fields

I have a list of "Report" objects with three fields (All String type)-
ReportKey
StudentNumber
School
I have a sort code goes like-
Collections.sort(reportList, new Comparator<Report>() {
#Override
public int compare(final Report record1, final Report record2) {
return (record1.getReportKey() + record1.getStudentNumber() + record1.getSchool())
.compareTo(record2.getReportKey() + record2.getStudentNumber() + record2.getSchool());
}
});
For some reason, I don't have the sorted order. One advised to put spaces in between fields, but why?
Do you see anything wrong with the code?
(originally from Ways to sort lists of objects in Java based on multiple fields)
Original working code in this gist
Using Java 8 lambda's (added April 10, 2019)
Java 8 solves this nicely by lambda's (though Guava and Apache Commons might still offer more flexibility):
Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
.thenComparing(Report::getStudentNumber)
.thenComparing(Report::getSchool));
Thanks to #gaoagong's answer below.
Note that one advantage here is that the getters are evaluated lazily (eg. getSchool() is only evaluated if relevant).
Messy and convoluted: Sorting by hand
Collections.sort(pizzas, new Comparator<Pizza>() {
#Override
public int compare(Pizza p1, Pizza p2) {
int sizeCmp = p1.size.compareTo(p2.size);
if (sizeCmp != 0) {
return sizeCmp;
}
int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);
if (nrOfToppingsCmp != 0) {
return nrOfToppingsCmp;
}
return p1.name.compareTo(p2.name);
}
});
This requires a lot of typing, maintenance and is error prone. The only advantage is that getters are only invoked when relevant.
The reflective way: Sorting with BeanComparator
ComparatorChain chain = new ComparatorChain(Arrays.asList(
new BeanComparator("size"),
new BeanComparator("nrOfToppings"),
new BeanComparator("name")));
Collections.sort(pizzas, chain);
Obviously this is more concise, but even more error prone as you lose your direct reference to the fields by using Strings instead (no typesafety, auto-refactorings). Now if a field is renamed, the compiler won’t even report a problem. Moreover, because this solution uses reflection, the sorting is much slower.
Getting there: Sorting with Google Guava’s ComparisonChain
Collections.sort(pizzas, new Comparator<Pizza>() {
#Override
public int compare(Pizza p1, Pizza p2) {
return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();
// or in case the fields can be null:
/*
return ComparisonChain.start()
.compare(p1.size, p2.size, Ordering.natural().nullsLast())
.compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast())
.compare(p1.name, p2.name, Ordering.natural().nullsLast())
.result();
*/
}
});
This is much better, but requires some boiler plate code for the most common use case: null-values should be valued less by default. For null-fields, you have to provide an extra directive to Guava what to do in that case. This is a flexible mechanism if you want to do something specific, but often you want the default case (ie. 1, a, b, z, null).
And as noted in the comments below, these getters are all evaluated immediately for each comparison.
Sorting with Apache Commons CompareToBuilder
Collections.sort(pizzas, new Comparator<Pizza>() {
#Override
public int compare(Pizza p1, Pizza p2) {
return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();
}
});
Like Guava’s ComparisonChain, this library class sorts easily on multiple fields, but also defines default behavior for null values (ie. 1, a, b, z, null). However, you can’t specify anything else either, unless you provide your own Comparator.
Again, as noted in the comments below, these getters are all evaluated immediately for each comparison.
Thus
Ultimately it comes down to flavor and the need for flexibility (Guava’s ComparisonChain) vs. concise code (Apache’s CompareToBuilder).
Bonus method
I found a nice solution that combines multiple comparators in order of priority on CodeReview in a MultiComparator:
class MultiComparator<T> implements Comparator<T> {
private final List<Comparator<T>> comparators;
public MultiComparator(List<Comparator<? super T>> comparators) {
this.comparators = comparators;
}
public MultiComparator(Comparator<? super T>... comparators) {
this(Arrays.asList(comparators));
}
public int compare(T o1, T o2) {
for (Comparator<T> c : comparators) {
int result = c.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
Collections.sort(list, new MultiComparator<T>(comparators));
}
}
Ofcourse Apache Commons Collections has a util for this already:
ComparatorUtils.chainedComparator(comparatorCollection)
Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
Do you see anything wrong with the code?
Yes. Why are you adding the three fields together before you compare them?
I would probably do something like this: (assuming the fields are in the order you wish to sort them in)
#Override public int compare(final Report record1, final Report record2) {
int c;
c = record1.getReportKey().compareTo(record2.getReportKey());
if (c == 0)
c = record1.getStudentNumber().compareTo(record2.getStudentNumber());
if (c == 0)
c = record1.getSchool().compareTo(record2.getSchool());
return c;
}
I'd make a comparator using Guava's ComparisonChain:
public class ReportComparator implements Comparator<Report> {
public int compare(Report r1, Report r2) {
return ComparisonChain.start()
.compare(r1.getReportKey(), r2.getReportKey())
.compare(r1.getStudentNumber(), r2.getStudentNumber())
.compare(r1.getSchool(), r2.getSchool())
.result();
}
}
This is an old question so I don't see a Java 8 equivalent. Here is an example for this specific case.
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Compares multiple parts of the Report object.
*/
public class SimpleJava8ComparatorClass {
public static void main(String[] args) {
List<Report> reportList = new ArrayList<>();
reportList.add(new Report("reportKey2", "studentNumber2", "school1"));
reportList.add(new Report("reportKey4", "studentNumber4", "school6"));
reportList.add(new Report("reportKey1", "studentNumber1", "school1"));
reportList.add(new Report("reportKey3", "studentNumber2", "school4"));
reportList.add(new Report("reportKey2", "studentNumber2", "school3"));
System.out.println("pre-sorting");
System.out.println(reportList);
System.out.println();
Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
.thenComparing(Report::getStudentNumber)
.thenComparing(Report::getSchool));
System.out.println("post-sorting");
System.out.println(reportList);
}
private static class Report {
private String reportKey;
private String studentNumber;
private String school;
public Report(String reportKey, String studentNumber, String school) {
this.reportKey = reportKey;
this.studentNumber = studentNumber;
this.school = school;
}
public String getReportKey() {
return reportKey;
}
public void setReportKey(String reportKey) {
this.reportKey = reportKey;
}
public String getStudentNumber() {
return studentNumber;
}
public void setStudentNumber(String studentNumber) {
this.studentNumber = studentNumber;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
#Override
public String toString() {
return "Report{" +
"reportKey='" + reportKey + '\'' +
", studentNumber='" + studentNumber + '\'' +
", school='" + school + '\'' +
'}';
}
}
}
If you want to sort by report key, then student number, then school, you should do something like this:
public class ReportComparator implements Comparator<Report>
{
public int compare(Report r1, Report r2)
{
int result = r1.getReportKey().compareTo(r2.getReportKey());
if (result != 0)
{
return result;
}
result = r1.getStudentNumber().compareTo(r2.getStudentNumber());
if (result != 0)
{
return result;
}
return r1.getSchool().compareTo(r2.getSchool());
}
}
This assumes none of the values can be null, of course - it gets more complicated if you need to allow for null values for the report, report key, student number or school.
While you could get the string concatenation version to work using spaces, it would still fail in strange cases if you had odd data which itself included spaces etc. The above code is the logical code you want... compare by report key first, then only bother with the student number if the report keys are the same, etc.
I suggest to use Java 8 Lambda approach:
List<Report> reportList = new ArrayList<Report>();
reportList.sort(Comparator.comparing(Report::getRecord1).thenComparing(Report::getRecord2));
Sorting with multiple fields in Java8
package com.java8.chapter1;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static java.util.Comparator.*;
public class Example1 {
public static void main(String[] args) {
List<Employee> empList = getEmpList();
// Before Java 8
empList.sort(new Comparator<Employee>() {
#Override
public int compare(Employee o1, Employee o2) {
int res = o1.getDesignation().compareTo(o2.getDesignation());
if (res == 0) {
return o1.getSalary() > o2.getSalary() ? 1 : o1.getSalary() < o2.getSalary() ? -1 : 0;
} else {
return res;
}
}
});
for (Employee emp : empList) {
System.out.println(emp);
}
System.out.println("---------------------------------------------------------------------------");
// In Java 8
empList.sort(comparing(Employee::getDesignation).thenComparing(Employee::getSalary));
empList.stream().forEach(System.out::println);
}
private static List<Employee> getEmpList() {
return Arrays.asList(new Employee("Lakshman A", "Consultent", 450000),
new Employee("Chaitra S", "Developer", 250000), new Employee("Manoj PVN", "Developer", 250000),
new Employee("Ramesh R", "Developer", 280000), new Employee("Suresh S", "Developer", 270000),
new Employee("Jaishree", "Opearations HR", 350000));
}
}
class Employee {
private String fullName;
private String designation;
private double salary;
public Employee(String fullName, String designation, double salary) {
super();
this.fullName = fullName;
this.designation = designation;
this.salary = salary;
}
public String getFullName() {
return fullName;
}
public String getDesignation() {
return designation;
}
public double getSalary() {
return salary;
}
#Override
public String toString() {
return "Employee [fullName=" + fullName + ", designation=" + designation + ", salary=" + salary + "]";
}
}
If the StudentNumber is numeric it will not be sorted numeric but alphanumeric.
Do not expect
"2" < "11"
it will be:
"11" < "2"
Use Comparator interface with methods introduced in JDK1.8: comparing and thenComparing, or more concrete methods: comparingXXX and thenComparingXXX.
For example, if we wanna sort a list of persons by their id firstly, then age, then name:
Comparator<Person> comparator = Comparator.comparingLong(Person::getId)
.thenComparingInt(Person::getAge)
.thenComparing(Person::getName);
personList.sort(comparator);
If you want to sort based on ReportKey first then Student Number then School, you need to compare each String instead of concatenating them. Your method might work if you pad the strings with spaces so that each ReportKey is the same length and so on, but it is not really worth the effort. Instead just change the compare method to compare the ReportKeys, if compareTo returns 0 then try StudentNumber, then School.
I had the same issue and I needed an algorithm using a config file. In This way you can use multiple fields define by a configuration file (simulate just by a List<String) config)
public static void test() {
// Associate your configName with your Comparator
Map<String, Comparator<DocumentDto>> map = new HashMap<>();
map.put("id", new IdSort());
map.put("createUser", new DocumentUserSort());
map.put("documentType", new DocumentTypeSort());
/**
In your config.yml file, you'll have something like
sortlist:
- documentType
- createUser
- id
*/
List<String> config = new ArrayList<>();
config.add("documentType");
config.add("createUser");
config.add("id");
List<Comparator<DocumentDto>> sorts = new ArrayList<>();
for (String comparator : config) {
sorts.add(map.get(comparator));
}
// Begin creation of the list
DocumentDto d1 = new DocumentDto();
d1.setDocumentType(new DocumentTypeDto());
d1.getDocumentType().setCode("A");
d1.setId(1);
d1.setCreateUser("Djory");
DocumentDto d2 = new DocumentDto();
d2.setDocumentType(new DocumentTypeDto());
d2.getDocumentType().setCode("A");
d2.setId(2);
d2.setCreateUser("Alex");
DocumentDto d3 = new DocumentDto();
d3.setDocumentType(new DocumentTypeDto());
d3.getDocumentType().setCode("A");
d3.setId(3);
d3.setCreateUser("Djory");
DocumentDto d4 = new DocumentDto();
d4.setDocumentType(new DocumentTypeDto());
d4.getDocumentType().setCode("A");
d4.setId(4);
d4.setCreateUser("Alex");
DocumentDto d5 = new DocumentDto();
d5.setDocumentType(new DocumentTypeDto());
d5.getDocumentType().setCode("D");
d5.setId(5);
d5.setCreateUser("Djory");
DocumentDto d6 = new DocumentDto();
d6.setDocumentType(new DocumentTypeDto());
d6.getDocumentType().setCode("B");
d6.setId(6);
d6.setCreateUser("Alex");
DocumentDto d7 = new DocumentDto();
d7.setDocumentType(new DocumentTypeDto());
d7.getDocumentType().setCode("B");
d7.setId(7);
d7.setCreateUser("Alex");
List<DocumentDto> documents = new ArrayList<>();
documents.add(d1);
documents.add(d2);
documents.add(d3);
documents.add(d4);
documents.add(d5);
documents.add(d6);
documents.add(d7);
// End creation of the list
// The Sort
Stream<DocumentDto> docStream = documents.stream();
// we need to reverse this list in order to sort by documentType first because stream are pull-based, last sorted() will have the priority
Collections.reverse(sorts);
for(Comparator<DocumentDto> entitySort : sorts){
docStream = docStream.sorted(entitySort);
}
documents = docStream.collect(Collectors.toList());
// documents has been sorted has you configured
// in case of equality second sort will be used.
System.out.println(documents);
}
Comparator objects are really simple.
public class IdSort implements Comparator<DocumentDto> {
#Override
public int compare(DocumentDto o1, DocumentDto o2) {
return o1.getId().compareTo(o2.getId());
}
}
public class DocumentUserSort implements Comparator<DocumentDto> {
#Override
public int compare(DocumentDto o1, DocumentDto o2) {
return o1.getCreateUser().compareTo(o2.getCreateUser());
}
}
public class DocumentTypeSort implements Comparator<DocumentDto> {
#Override
public int compare(DocumentDto o1, DocumentDto o2) {
return o1.getDocumentType().getCode().compareTo(o2.getDocumentType().getCode());
}
}
Conclusion : this method isn't has efficient but you can create generic sort using a file configuration in this way.
Here is a full example comparing 2 fields in an object, one String and one int, also using Collator to sort.
public class Test {
public static void main(String[] args) {
Collator myCollator;
myCollator = Collator.getInstance(Locale.US);
List<Item> items = new ArrayList<Item>();
items.add(new Item("costrels", 1039737, ""));
items.add(new Item("Costs", 1570019, ""));
items.add(new Item("costs", 310831, ""));
items.add(new Item("costs", 310832, ""));
Collections.sort(items, new Comparator<Item>() {
#Override
public int compare(final Item record1, final Item record2) {
int c;
//c = record1.item1.compareTo(record2.item1); //optional comparison without Collator
c = myCollator.compare(record1.item1, record2.item1);
if (c == 0)
{
return record1.item2 < record2.item2 ? -1
: record1.item2 > record2.item2 ? 1
: 0;
}
return c;
}
});
for (Item item : items)
{
System.out.println(item.item1);
System.out.println(item.item2);
}
}
public static class Item
{
public String item1;
public int item2;
public String item3;
public Item(String item1, int item2, String item3)
{
this.item1 = item1;
this.item2 = item2;
this.item3 = item3;
}
}
}
Output:
costrels
1039737
costs
310831
costs
310832
Costs
1570019
A lot of answers above have fields compared in single comparator method which is not actually working. There are some answers though with different comparators implemented for each field, I am posting this because this example would be much more clearer and simple to understand I am believing.
class Student{
Integer bornYear;
Integer bornMonth;
Integer bornDay;
public Student(int bornYear, int bornMonth, int bornDay) {
this.bornYear = bornYear;
this.bornMonth = bornMonth;
this.bornDay = bornDay;
}
public Student(int bornYear, int bornMonth) {
this.bornYear = bornYear;
this.bornMonth = bornMonth;
}
public Student(int bornYear) {
this.bornYear = bornYear;
}
public Integer getBornYear() {
return bornYear;
}
public void setBornYear(int bornYear) {
this.bornYear = bornYear;
}
public Integer getBornMonth() {
return bornMonth;
}
public void setBornMonth(int bornMonth) {
this.bornMonth = bornMonth;
}
public Integer getBornDay() {
return bornDay;
}
public void setBornDay(int bornDay) {
this.bornDay = bornDay;
}
#Override
public String toString() {
return "Student [bornYear=" + bornYear + ", bornMonth=" + bornMonth + ", bornDay=" + bornDay + "]";
}
}
class TestClass
{
// Comparator problem in JAVA for sorting objects based on multiple fields
public static void main(String[] args)
{
int N,c;// Number of threads
Student s1=new Student(2018,12);
Student s2=new Student(2018,12);
Student s3=new Student(2018,11);
Student s4=new Student(2017,6);
Student s5=new Student(2017,4);
Student s6=new Student(2016,8);
Student s7=new Student(2018);
Student s8=new Student(2017,8);
Student s9=new Student(2017,2);
Student s10=new Student(2017,9);
List<Student> studentList=new ArrayList<>();
studentList.add(s1);
studentList.add(s2);
studentList.add(s3);
studentList.add(s4);
studentList.add(s5);
studentList.add(s6);
studentList.add(s7);
studentList.add(s8);
studentList.add(s9);
studentList.add(s10);
Comparator<Student> byMonth=new Comparator<Student>() {
#Override
public int compare(Student st1,Student st2) {
if(st1.getBornMonth()!=null && st2.getBornMonth()!=null) {
return st2.getBornMonth()-st1.getBornMonth();
}
else if(st1.getBornMonth()!=null) {
return 1;
}
else {
return -1;
}
}};
Collections.sort(studentList, new Comparator<Student>() {
#Override
public int compare(Student st1,Student st2) {
return st2.getBornYear()-st1.getBornYear();
}}.thenComparing(byMonth));
System.out.println("The sorted students list in descending is"+Arrays.deepToString(studentList.toArray()));
}
}
OUTPUT
The sorted students list in descending is[Student [bornYear=2018, bornMonth=null, bornDay=null], Student [bornYear=2018, bornMonth=12, bornDay=null], Student [bornYear=2018, bornMonth=12, bornDay=null], Student [bornYear=2018, bornMonth=11, bornDay=null], Student [bornYear=2017, bornMonth=9, bornDay=null], Student [bornYear=2017, bornMonth=8, bornDay=null], Student [bornYear=2017, bornMonth=6, bornDay=null], Student [bornYear=2017, bornMonth=4, bornDay=null], Student [bornYear=2017, bornMonth=2, bornDay=null], Student [bornYear=2016, bornMonth=8, bornDay=null]]
im my case List of Lists (in the approximation examle):
List<T>.steam
.map(Class1.StaticInnerClass1::Field1)
.flatMap(x -> x.getField11ListStaticInnerClass2OfField1.stream())
.max(Comparator.comparing(Class1.StaticInnerClass2::Field21,Collections.reverseOrder())
.thenCompare(Class1.StaticInnerClass2::Field22));
For my case, I had 3 fields (For example - int index, bool isArchive ,bool isClassPrivate)
and I summed their comparison result like this-
Collections.sort(getData(), (o1, o2) ->
Integer.compare(o1.getIndex(getContext()), o2.getIndex(getContext()))
+ Boolean.compare(o1.isArchive(), o2.isArchive())
+ Boolean.compare(o1.isClassPrivate(), o2.isClassPrivate()
));

Categories