0

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:

  • I have a object 'car' with attribute 'name'
  • I have an Hashmap<Integer,car>, containing items: key 1, object with name=Ford --- key 2, object with name=Audi --- key 3, object with name=Fiat
  • The first element of the map has key 1, the second has key 2, the third has key 3
  • I would like to have in output an Arraylist where: - The first element is object 'Audi', the second is object 'Fiat', the third is object 'Ford', so to have the 3 names sorted.

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;
    }    
Fausto70
  • 541
  • 5
  • 20
  • Hi, firstly some pointers: 1. Your code is hard to read. 2. Can you provide your function: MY_AttributeValueExtractor__Arg 3. Can you explain clearly on what you are trying to accomplish? 4.if MyMap_Arg!=null, then MyMap_Arg.values()!=null will always be true 5. Does a LinkedHashMap satisfy your use case if you want to order by insertion? Maybe a SortedMap? – sbsatter Jul 06 '20 at 08:22
  • Why would `MyMap_Arg.values()` be null if the map's size is non-zero? – Andy Turner Jul 06 '20 at 08:28
  • Hi, I edited the question tying to reply to some your questions. I don't know it LinkedHashMap or SortedMap can help me, I'm just asking if and how that code could be corrected in order to work. And I think that this method, if working, could be usefull to many people.... – Fausto70 Jul 06 '20 at 09:08
  • 1
    like @sbsatter said, your codes are hard to read. I don't know how experienced you are with Java, but it's better to code with conventional way before you start to optimize your codes with sophisticated features such as chaining, Lambda, etc. –  Jul 06 '20 at 09:09
  • _I need a method to order a Map of generic object by a generic attribute of the object._ Please provide an example Map and your expected result. Also (maybe I misunderstand) if a map with arbitrary objects needs to be sorted based on an arbitrary attribute then I don't see how'll be able to define an ordering independent of the used mechanic. – Ivo Mori Jul 06 '20 at 09:12
  • @Ivo Mori, I edited the question, tyring to do an example. I developped for example some methods (and they work) where I get an arraylist of filtered generic objects under some generic conditions, So now I needed a sorting... – Fausto70 Jul 06 '20 at 09:53
  • @Fausto70, you can simply try doing so like this: list = new ArrayList<>(map.values()); Collections.sort(list, (f, s) -> f.getName().compareTo(s.getName())); Excuse the brevity! – sbsatter Jul 06 '20 at 10:21
  • @Fausto70 otherwise, if you're feeling adventurous, give this a try: https://stackoverflow.com/questions/2748829/create-a-sortedmap-in-java-with-a-custom-comparator – sbsatter Jul 06 '20 at 10:24
  • @sbsatter ciao, it doesn't satisy the target about the generic attribute, because getName() is an example, but it could be whatever attribute: getColor, getDriver, ecc... – Fausto70 Jul 06 '20 at 11:01
  • @Fausto70 the main idea is you would order them based on a specific property right? That's how you'd use the comparator. If you're unfamiliar with a comparator, look it up. Also check this out: https://www.baeldung.com/java-hashmap-sort (Number 2) – sbsatter Jul 06 '20 at 11:57

1 Answers1

1

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());
}
Ivo Mori
  • 2,177
  • 5
  • 24
  • 35
  • Thank you. very much - I'm aslo trying to do some change to best fit to my case. I hope to be able... – Fausto70 Jul 07 '20 at 08:25