Fausto70
Fausto70

Reputation: 551

Order element of a Map of generic object --- by a generic attribute of the object --- Lamda - Java

I need a method to order a Map of generic object by a generic attribute of the object. I tried the code below, similar to other example I found on StackOverFlow, but I didn't find any example with a generic attribute. I'm not expert of lamda, so for me it is hard to understand clearly some logics.

I get error on compareTo; Ntebeans says me:

"cannot find symbol symbol: method compareTo(CAP#1) location: class Object where CAP#1 is a fresh type-variable: CAP#1 extends Object from capture of ?"

Example:

In order to invoke this method I would use for example:

ArrayList<Car> SORTED_Cars = get_ListOfObject_SortedByAttribute(myHashMap, car -> car.getName() );

I should get an ArrayList of object 'car' ordered by attribute 'name'.

The final task is to have a method that I'll use with Map of different Objects, then with different attributes.

Note that I use this checking condition

if (MyMap_Arg!=null &&  MyMap_Arg.size()>0 && MyMap_Arg.values()!=null)

because I prefer get null when the ordering is not possible or the map is empty.

How should be the code below to work?

        private static <T> List<T> get_ListOfObject_SortedByAttribute(final Map<?, T> MyMap_Arg,       final Function<T, ?> MY_AttributeValueExtractor__Arg     ) {
        List<T> result = null;
        try {
            if (MyMap_Arg!=null &&  MyMap_Arg.size()>0 && MyMap_Arg.values()!=null){
                if (MY_AttributeValueExtractor__Arg!=null ) {
                    
                    //_____________________________________________________
                    //Crea una lista di oggetti che hanno MY_AttributeValueExtractor_1_Arg!=null; altrimenti applicando '.compare' darebbe exception
                    List<T> MY_LIST__SenzaNull =  MyMap_Arg.values().stream().filter(    o -> MY_AttributeValueExtractor__Arg.apply(o)!=null   ).collect(Collectors.toList()); 
                        
                    //_____________________________________________________
                    //TEST  ********* Ordina la lista di oggetti alfabeticamente in base a MY_AttributeValueExtractor_1_Arg
                    result = MY_LIST__SenzaNull.stream().sorted(  
                        (o1, o2)->  MY_AttributeValueExtractor__Arg.apply(o1).
                        compareTo(  MY_AttributeValueExtractor__Arg.apply(o2)  )  
                            
                    ).
                    collect(Collectors.toList());                        
                        
                    //_____________________________________________________
                        
                } 
            }
        } catch (Exception ex) {
            result=null;
        }
        return result;
    }    

Upvotes: 0

Views: 132

Answers (1)

Ivo Mori
Ivo Mori

Reputation: 2338

Given your code (with refactorings for clarity)

static <T> List<T> getListSortedByAttribute(Map<?, T> aMap,
                                       Function<T, ?> attributeValueExtractor) {

    List<T> result = null;
    try {
        if (aMap != null && aMap.size() > 0 && aMap.values() != null) {
            if (attributeValueExtractor != null) {

                List<T> listWithoutElementsReturingNullAsAttributeValue = aMap
                        .values()
                        .stream()
                        .filter(o -> attributeValueExtractor.apply(o) != null)
                        .collect(Collectors.toList());

                result = listWithoutElementsReturingNullAsAttributeValue
                        .stream()
                        .sorted((o1, o2) -> attributeValueExtractor.apply(o1).
                                compareTo(attributeValueExtractor.apply(o2)))
                        .collect(Collectors.toList());
            }
        }
    } catch (Exception ex) {
        result = null;
    }
    return result;
}

you want to use a compareTo method to compare (sort) list elements by one of their attributes given as function

(o1, o2) -> attributeValueExtractor.apply(o1)
                                   .compareTo(attributeValueExtractor.apply(o2))

With which you get the compile-time error

Error:(149, 78) java: cannot find symbol
  symbol:   method compareTo(capture#1 of ?)
  location: class java.lang.Object

Meaning, there's nothing in your code which ensures that your attributes have such a method (and can be compared for sorting). In particular, Function<T, ?> attributeValueExtractor) says that something type T is mapped (by your function) to type ? which can only be Object; and Object doesn't have a compareTo method. (Note that Object doesn't have such a method because there's simply no meaningful way of comparing arbitrary objects with each other).

To fix it (at the very least), you need to ensure that your objects implement such a method. So your method signature

static <T> List<T> getListSortedByAttribute(
                          Map<?, T> aMap, Function<T, ?> attributeValueExtractor)

needs to change to

static <T, U extends Comparable<U>> List<T> getListSortedByAttribute(
                          Map<?, T> aMap, Function<T, U> attributeValueExtractor)

where U types are required to implement the interface Comparable which has the method compareTo.

With that you get (with a few more refactorings and addition of an example)

public static void main(String[] args) {
    Map<Integer, Car> map = new HashMap<>();
    map.put(1, new Car("Ford"));
    map.put(2, new Car("Audi"));
    map.put(3, new Car("Fiat"));
    map.put(4, new Car(null));

    List<Car> list = getListSortedByAttribute(map, Car::getName);
    System.out.println(list);
}

static <T, U extends Comparable<U>> List<T> getSortedValues(
                   Map<?, T> aMap, Function<T, U> attributeExtractor) {

    List<T> result = null;
    try {
        if (aMap != null && aMap.size() > 0) {
            if (attributeExtractor != null) {
                result = aMap
                          .values()
                          .stream()
                          .filter(o -> attributeExtractor.apply(o) != null)
                          .sorted((o1, o2) -> attributeExtractor.apply(o1).
                                compareTo(attributeExtractor.apply(o2)))
                          .collect(Collectors.toList());
            }
        }
    } catch (Exception ex) {
        result = null;
    }
    return result;
}

static class Car {
    private final String name;

    Car(String name) { this.name = name; }

    String getName() { return name; }

    @Override
    public String toString() { return name; }

    // code for equals() and hashCode() omitted for brevity
}

which prints

[Audi, Fiat, Ford]

As for List<Car> list = getListSortedByAttribute(map, Car::getName); note that you can use a lambda expression (Car car) -> car.getName(), or a method reference Car::getName as method argument. I chose the later because I find it shorter.

The invocation then works because the return type of Car::getName is a String which implements Comparable and therefore has a compareTo method implementation.

Notes about your code (my opinion)

  1. When using a method which returns a Collection type like yours it's very surprising if such a method returns null instead of an empty collection – and it'll be the cause of many (and also surprising) NullPointerExceptions down the road.
  2. As pointed out in the comments aMap.values() != null is always true because (surprise) the Java API designers/implementers decided that this method should always return a non-null collection, i.e. an empty collection if there are no values. Anyhow that condition has no effect, it's always true.
  3. aMap != null and attributeValueExtractor != null, simply throw instead because calling your method with null arguments simply shouldn't be a valid invocation which is allowed to continue (fail fast principle).
  4. aMap.size() > 0 isn't really needed as the subsequent stream-code handles that without any problem, i.e. the result is simply an empty list. And intentionally returing null for that case isn't something I'd every do (as explained above).
  5. Don't swallow exceptions, either throw (fail fast principle) or recover meaningfully. But returning null as outlined above isn't a meaningful recovery.

With respect to the above and further changes you then get

static <T, U extends Comparable<U>> List<T> getSortedValues(
                   Map<?, T> aMap, Function<T, U> attributeExtractor) {

    Objects.requireNonNull(aMap, "Map to be sorted cannot be null");
    Objects.requireNonNull(attributeExtractor, "Function to extract a value cannot be null");

    return aMap
            .values()
            .stream()
            .filter(o -> attributeExtractor.apply(o) != null)
            .sorted(Comparator.comparing(attributeExtractor))
            .collect(Collectors.toList());
}

Upvotes: 1

Related Questions