Reputation: 551
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
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)
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) NullPointerException
s down the road.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
.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).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).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