Reputation: 691
Is there a generic way of getting the intersection of two lists in Java?
I would like to have a method that can find the elements included in both list1 and list2 no matter if the elements in the lists are of type String, Long, Integer or BigDecimal.
I have tried to achieve this with the below code which works fine until both the arguments include elements of the same type, otherwise it returns an empty list at runtime.
private <T> List<T> intersect(List<T> list1, List<T> list2) {
List<T> list = new ArrayList<T>();
System.out.println("list1= "+list1);
System.out.println("list2= "+list2);
for (T t2 : list2) {
System.out.println("t2= "+t2 + ", class: " + t2.getClass());
}
for (T t : list1) {
System.out.println("t= "+t + ", class: " + t.getClass());
if(list2.contains(t)) {
System.out.println("list2 containts t");
list.add(t);
}
}
System.out.println("list= "+list);
return list;
}
The code calling the intersect method:
import javax.ws.rs.core.Response;
public boolean process(Response response, List<Object> sentObjects) {
String body = response.readEntity(String.class);
....
List<Object> parsedResponse = parseJson(body);
List<Object> intersection = intersect(sentObjects, parsedResponse);
...
}
A sample output with different types of elements in the lists:
list1= [11190, 11191, 11213]
list2= [11190, 11191, 11213]
t2= 11190, class: class java.lang.Long
t2= 11191, class: class java.lang.Long
t2= 11213, class: class java.lang.Long
t= 11190, class: class java.math.BigDecimal
t= 11191, class: class java.math.BigDecimal
t= 11213, class: class java.math.BigDecimal
list= []
Question: Is there any way to enforce that type T of list1 is the same as type T of list2 without explicitly telling the exact type?
Upvotes: 1
Views: 752
Reputation: 269667
You could select a common type to which all elements can be converted before checking equality. The Object.equals()
method is implementation-dependent, but it rarely returns true
for an object of a different type, especially if the argument is not a derived type.
In this particular case, it seems like the common type might be String
. That is, Long
, Integer
, and BigDecimal
can all be converted consistently to string representation.
Presumably, the String
objects you are working with are decimal numbers with no separators (for thousands, etc.). If that's the case, you can simply call toString()
on your numeric types to create an equivalent representation. Otherwise, you could use a NumberFormat
suitably configured to match the expected format of the String
elements.
List<T> intersect(List<? extends T> t1, Collection<?> t2) {
Set<String> tmp = t2.stream().map(Object::toString).collect(Collectors.toSet());
return t1.stream()
.filter(e -> tmp.contains(e.toString()))
.collect(Collectors.toList());
}
Upvotes: 2
Reputation: 140318
The definition of Collection.contains
only allows elements to be checked using equality; instances of different classes are typically not equal (e.g. Long zero and BigDecimal zero aren't equal; however empty ArrayList and empty LinkedList are).
In order to fix this, you need to use something more generic than equals, something like a BiPredicate<T, T>
:
if(list2.stream().anyMatch(u -> biPredicate.apply(t, u))) {
You need to pass in the BiPredicate as a parameter to your method, e.g.
BiPredicate<Number, Number> biPredicate = (t, u) -> t.longValue() == u.longValue();
Upvotes: 1
Reputation: 164
Maybe you can use Object class directly. İf you are just using subclasses of Object class. Hope it helps.
Upvotes: 0