I'm using streams to try and create an arraylist of the keys in a map sorted first by the values (integers) then sort the keys alphabetically. I have them sorted by the values, but I get an error when trying to compare them alphabetically:
return map.keySet()
.stream()
.sorted(Comparator.comparing( (k1) -> map.get(k1)).thenComparing(String::compareTo)) //ErrorHere
.toArray(String[]::new);
Coc-java gives me a The method thenComparing(Comparator<? super Object>) in the type Comparator<Object> is not applicable for the arguments (String::compareTo) error. I have used thenComparing before, but the .sorted method looked like this:
.sorted(Comparator.comparing(String::length).thenComparing(String::compareTo))
This produced no errors and worked fine. I'm supposing that it might have something to do with what the lamda returns?
You probably just need to explicitly specify the type, e.g. Comparator.comparing((String k1) -> map.get(k1)), or Comparator.<String, WhateverTheValueTypeIs>comparing(map::get).
Related
items is a List<List<String>>. When I add reversed, I see a compile error Cannot resolve method 'get' in 'Object'. I get this on x.get(0). Why do I get this and how can I get the reverse order? I would just like to sort the list based on the item name.
List<List<String>> items = new ArrayList<>();
List<String> item2 = new ArrayList<>();
item2.add("item2");
item2.add("3");
item2.add("4");
List<String> item1 = new ArrayList<>();
item1.add("item1");
item1.add("10");
item1.add("15");
List<String> item3 = new ArrayList<>();
item3.add("item3");
item3.add("17");
item3.add("8");
items.add(item1);
items.add(item2);
items.add(item3);
items.stream().sorted(Comparator.comparing(x -> x.get(0)).reversed()).collect(Collectors.toList())
Java's type inference fails here. When running Comparator.comparing(x -> x.get(0)).reversed(), it is not able to infer the types of the Comparator from the return type because of the .reversed(). Since they cannot be inferred, Java just uses Object.
In order to fix that, you need to somehow specify the type that is compared.
This can be done explicitly with Comparator.<List<String>, String>> comparing(x->x.get(0)).reversed(). Here, List<String>, String> are type arguments for the Comparator.comparing call. This means they specify the values of the generic types at compile-time so they don't have to be inferred. The first type parameter (List<String>) is the type to compare and second parameter (String) is the Comparable that is extracted from the type to compare (the result of List#get).
Alternatively, you could also specify it in the lambda expression's parameters and let Java infer it from that: Comparator.comparing((List<String> x) -> x.get(0)).reversed(). Since Java knows that x is of the type List<String>, it is able to infer the type of the lambda (Function<List<String>,String>) and the Comparator.comparing method.
When Sequenced Collections are added to Java, you could also use use method references. If you use Comparator.comparing(List::getFirst).reversed(), Java knows that the type to compare is a List and is able to use the get method. Note that List::getFirst is not yet part of the JDK as of the time of writing this.
As #Slaw mentioned in the comments, you could also use an entirely different approach by getting rid of the .reversed and including it in the Comparator.comparing like this: Comparator.comparing(x -> x.get(0), Comparator.reverseOrder()). The second parameter is another Comparator that is used for comparing the values extracted from the lambda x -> x.get(0).
List<List<String>> sorted_list = items.stream()
.sorted(Comparator.comparing(x -> x.get(0), Comparator.reverseOrder()))
.collect(Collectors.toList());
I was trying to something pretty simple, but it fails on compilation, and I can't understand who
I have a list of headers, I need to convert it to
Map<Index, String> meaning the key (index) and the value is the header name
I know how to make it with for each, but I want to have it in Collectors.to map
any help would be appreciated
final String[] headerDisplayName = getHeaderDisplayName(harmonizationComponentDataFixRequest);
IntStream.of(0, headerDisplayName.length).collect(Collectors.toMap(Function.identity(), index-> headerDisplayName[index]));
You can use range method in combination with boxed method of IntStream.
(When you use the of method like in your example, only 0 and the size of the array are in this stream. In addition this would lead to an ArrayIndexOutOfBoundsException)
A possible solution would look like this (first parameter of the range method is included, the second parameter is excluded)
Map<Integer, String> map = IntStream.range(0, headerDisplayName.length)
.boxed()
.collect(Collectors.toMap(
Function.identity(),
i -> headerDisplayName[i])
);
Adding to the #csalmhof's answer, I think it's to explain here why using boxed is working.
If you don't use boxed() method and simply write the following:
Map<Integer, String> map = IntStream.range(0, headerDisplayName.length)
.collect(Collectors.toMap(
Function.identity(),
index -> headerDisplayName[index])
);
Java will have to take index as of type Object and there's no implicit conversion that can happen and so you'll get error.
But if you put boxed() like in the following code, you won't get error:
Map<Integer, String> map = IntStream.range(0, headerDisplayName.length)
.boxed()
.collect(Collectors.toMap(
Function.identity(),
index -> headerDisplayName[index])
);
you're not getting error here because java interprets index as an Integer and there's implicit casting happening from Integer to int.
A good IDE can help you with this type of explanation. In IntelliJ if you press ctrl + space after keeping your cursor on index (with Eclipse key-map enabled) you will get the following without boxed().
And this is what you get when you've boxed() placed.
I hope this clarifies why using boxed() is saving you from compilation error. Also, you can use this thing in future to find actual type of parameter in lambda which can be helpful in case cases (one of the case is the one that OP pointed out)
I am doing a group by on a list of Objects as shown in the below code
Map<String, List<InventoryAdjustmentsModel>> buildDrawNumEquipmentMap = equipmentsAndCargoDetails.stream().
collect(Collectors.groupingBy(InventoryAdjustmentsModel :: getBuildDrawNum));
Now I know the values for all the keys would have only one element, so how can I reduce it to just
Map<String, InventoryAdjustmentsModel>
instead of having to iterate through or get the 0th element for all the keys.
You may use the toMap collector with a merge function like this.
Map<String, InventoryAdjustmentsModel> resultMap = equipmentsAndCargoDetails.stream().
collect(Collectors.toMap(InventoryAdjustmentsModel::getBuildDrawNum,
e -> e, (a, b) -> a));
Try it like this. By using toMap you can specify the key and the value. Since you said there were no duplicate keys this does not include the merge method. This means you will get an error if duplicate keys are discovered. Something I presumed you would want to know about.
Map<String, InventoryAdjustmentsModel> buildDrawNumEquipmentMap =
equipmentsAndCargoDetails.stream().
collect(Collectors.toMap(InventoryAdjustmentsModel::getBuildDrawNum,
model->model));
I have a list of objects. Each object has three fields: id, secNumber and type. Type is enum which can have values 'new' or 'legacy'. Sometimes it happens that there are objects in that list which have the same secNumber a but different type.
in such a situation, I need to remove the one with type 'legacy'. How to do it using Java 8 streams?
use toMap with something like this:
Collection<T> result = list.stream()
.collect(toMap(T::getSecNumber,
Function.identity(),
(l, r) -> l.getType() == Type.LEGACY ? r : l))
.values();
where T is the class that contains secNumber, id etc.
The keyMapper (T::getSecNumber) extracts each secNumber from each object.
The valueMapper (Function.identity()) extracts the objects we want as the map values i.e. the objects from the source them selves.
The mergeFunction (l, r) -> is where we say " if two given objects have the same key i.e. getSecNumber then keep the one where their type is 'NEW' and discard the one with 'LEGACY'" and finally we call values() to accumulate the map values into a Collection.
Edit:
following #Tomer Aberbach's comment you may be looking for:
List<T> result =
list.stream()
.collect(groupingBy(T::getSecNumber))
.values()
.stream()
.flatMap(l -> l.stream().anyMatch(e -> e.getType() == Type.NEW) ?
l.stream().filter(e -> e.getType() != Type.LEGACY) :
l.stream())
.collect(toList());
The first solution using toMap assumes there can't be multiple objects with the same secNumber and type.
Assume objects is a List<ClassName> which has been declared and initialized:
List<ClassName> filteredObjects = objects.stream()
.collect(Collectors.groupingBy(ClassName::getSecNumber))
.values().stream()
.flatMap(os -> os.stream().anyMatch(o -> o.getType() == Type.NEW) ?
os.stream().filter(o -> o.getType() != Type.LEGACY) :
os.stream()
).collect(Collectors.toList());
I made the assumption that objects of type Type.LEGACY should only be filtered out if there exists another object of type Type.NEW which has the same secNumber. I also made the assumption that you could have multiple objects of the same type and secNumber and that those may need to be retained.
Note that the collect(Collectors.groupingBy(ClassName::getSecNumber)) returns a map from whatever type secNumber is to List<ClassName> so calling values() on it returns a Collection<List<ClassName>> which represents a collection of the groupings of objects with the same secNumber.
The flatMap part takes each grouping by secNumber, checks if the grouping has at least one object of Type.NEW, and if so, filters out the objects of type Type.LEGACY, otherwise it just passes along the objects to be flattened into the final List<ClassName>. This is primarily so that if a grouping only has objects of type Type.LEGACY then they are not left out of the final collection.
I had a HashMap called map which stored Characters as the key and Integers as the value which I then stored into an ArrayList called entries using the following code:
Set<Entry<Character, Integer>> s = map.entrySet();
ArrayList<Entry<Character, Integer>> entries = new ArrayList<>(s);
Now I am trying to sort these entries based on the Integer value, not the key. I tried to use a lambda expression to implement the Comparator interface, but it is not working. This is my code:
Collections.sort(sortedEntries, (sortedEntries.get(0), sortedEntries.get(1)) -> {
sortedEntries.get(0).getValue().compareTo(sortedEntries.get(1).getValue())
});
These are the errors I get:
Multiple markers at this line
Syntax error, insert ")" to complete Expression
The method sort(List, Comparator) in the type Collections is not applicable for the arguments (ArrayList>, Map.Entry, Map.Entry)
Syntax error on token "->", ; expected
You could sort the list by the values this way:
list.sort(Comparator.comparing(Entry::getValue));
This is strictly as per your code pls see
Collections.sort(sortedEntries, (Entry<Character,Integer> o1, Entry<Character,Integer> o2)-> {return o1.getValue().compareTo(o2.getValue());});
There is nothing special about the map, the list, or the type you're trying to sort. You sort such a list the same way you sort any List whose elements are of a type that doesn't implement Comparable - by using a Comparator, like you're attempting to do. So you're on the right track.
Perhaps if you aren't comfortable with lambda expressions, try implementing the Comparator interface instead. The type system will force you to "do it right", rather than relying on you to get it right so it can infer the types for you.
(I'm using the entry's key here, but you can just as well use the value)
public class EntryKeyComparator implements Comparator<Entry<Character,Integer>> {
#Override
public int compare(Entry<Character,Integer> a, Entry<Character,Integer> b) {
return a.getKey().compareTo(b.getKey());
}
}
Now, if you really want to use lambda expressions, you need only look at the compare method in this implementation. The lambda expression must take two parameters, a and b, and it must return an int. Thus, you'd get something like:
Collections.sort(list, (a, b) -> a.getKey().compareTo(b.getKey()));
But since you're already in the Java 8 world, you might as well use the static comparing() method on the Comparator class. It takes as argument a Function that extracts the key you want to use to compare the elements by. In my examples, I've compared the entries by their keys via the getKey(). We can reference this method with the :: operator, so you end up with an expression like this (using the new sort() method on the List interface):
list.sort(Comparator.comparing(Entry::getKey));
You can sort the entities using Java Stream API like this:
Set<Entry<Character, Integer>> s = map.entrySet();
List<Entry<Character, Integer>> sortedEntries = s.stream()
.sorted((a, b)-> Integer.compare(a.getValue(), b.getValue()))
.collect(Collectors.toList());