Im trying to create a HashSet using the .map and streams functions.
s is an object with an "id" parameter, Long type.
Here is my failed attempt:
HashSet<Long> output = s.stream()
.map(v -> v.getId())
.collect(Collectors.toSet());
In your case the result of the stream will be Set<Long> and you want to assign this to a variable of HashSet type. Since HashSet is a subtype of Set you cannot do this. Either you change the type of your output variable to Set<Long> or you explicitly cast the collect result to HashSet<Long>. Since Collectors::toSet uses HashMap by default - it should work.
EDIT
As shmosel pointed out correctly it might be a bad idea to make assumptions about the return type so if you want HashSet specifically use toCollection(HashSet::new) :
HashSet<Long> output = s.stream()
.map(v -> v.getId())
.collect(Collectors.toCollection(HashSet::new));
Now the result of collect operation will be HashSet<Long> so you will be able to assign it to HashSet<Long> or Set<Long> variable.
Related
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'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).
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.
When I am trying to convert, I am getting below exception
java.lang.ArrayStoreException: java.util.HashSet
at java.util.AbstractCollection.toArray(Unknown Source)
This is my code
Map<String, Set<String>> map = new HashMap<>();
String[] keySet = map.keySet().toArray(new String[map.size()]);
Collection<Set<String>> collections = map.values();
String[] values = collection.toArray(new String[collection.size()]);// In this line getting Exception
What you're attempting to do is not possible. This is made explicit in the documentation.
The toArray method is documented to throw a java.lang.ArrayStoreException:
if the runtime type of the specified array is not a supertype of the
runtime type of every element in this collection
instead, what you can do is create a stream from the map values, flatMap it! (i.e. collapse the nested sequences) then collect to an array:
map.values() // Collection<Set<String>>
.stream() // Stream<Set<String>>
.flatMap(Collection::stream) // Stream<String>
.toArray(String[]::new); // String[]
You can simply use Stream.flatMap as you stream over the values to collect them later into an array. This can be done as:
String[] values = map.values().stream()
.flatMap(Collection::stream)
.toArray(String[]::new);
Note: The reason why your code compiles successfully even with
toArray(new String[collection.size()])
is that Collection.toArray(T[] a) because its hard for the compiler to determine the type prior to execution for a generic type. This is the same reason why even
Integer[] values = collections.toArray(new Integer[collections.size()]);
would compile in your case, but as you can now clearly see that nowhere in your collections do you have an Integer type. Hence at runtime, a new array is allocated with the runtime type of the specified array and the size of this collection.
That is where the ArrayStoreException in your case results from since now you have a type mismatch as your collection is of type Set<String> instead of String.
Important: You cannot possibly convert to a generic array as you may further think of.
I have a collection which has a field of type Set with some values. I need to create a new set collecting all these values.
I am wondering if this is possible using lambda expressions.
Below is the code line :
Set<String> teacherId = batches.stream()
.filter(b -> !CollectionUtils.isEmpty(b.getTeacherIds()))
.map(b -> b.getTeacherIds())
.collect(Collectors.toSet());
The problem is post map operation, it contains a collection of set of strings. So collect operation returns a Set<Set<String>> but i am looking to aggregate all the values to a single set.
You need to use flatMap instead of map:
Set<String> teacherIds =
batches.stream()
.flatMap(b -> b.getTeacherIds().stream())
.collect(Collectors.toSet());
Note that the filtering is redundant for empty collections - streaming an empty collection will just result in an empty stream, which won't affect the final result.
If getTeacherIds() could return null, however, you'd still have to handle it. Using filter(Objects::nonNull) would suffice, and save you the dependency on Apache Commons.
You can use flatMap to obtain a flat Stream of all the values, and then collect to a Set<String>:
Set<String> teacherId =
batches.stream()
.filter(b -> !CollectionUtils.isEmpty(b.getTeacherIds()))
.flatMap(b -> b.getTeacherIds().stream())
.collect(Collectors.toSet());
If you care that that getTeacherIds() is not null, use it explicitly via !=, that CollectionUtils.isEmpty just hides stuff. Especially since if getTeacherIds() returns an Empty collection - that is handled just fine by flatMap, so to me that is not needed at all.
Set<String> teacherIds = batches
.stream()
.filter(x -> x.getTeacherIds() != null)
.flatMap(x -> x.getTeacherIds().stream())
.collect(Collectors.toSet());
I am wondering if this is possible using lambda expressions.
I capture the last fish, :).
Set<String> teacherIds = batches.stream()//v--- the class of `x`
.map(XClass::getTeacherIds)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
Note: I'm sorry I'm forget to tell you if the getTeacherIds copy the internal IDs to a new set of IDs, the code above is appropriate for you. since it is read the IDs from XClass once.