简体   繁体   中英

Is there a generic way to get the intersection of two lists in Java?

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?

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());
}

The definition of Collection.contains only allows elements to be checked using equality; instances of different classes are typically not equal (eg 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, eg

BiPredicate<Number, Number> biPredicate = (t, u) -> t.longValue() == u.longValue();

Maybe you can use Object class directly. İf you are just using subclasses of Object class. Hope it helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM