Java Collection Utils using Java Stream API - java

I am trying to write my own CollectionUtils helper class that other application will use. Here is my first method
public static <T, K, V>
Map<K, List<V>> listToMap(List<T> inputList,
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper)
{
Collector c = Collectors.toMap(keyMapper, valueMapper);
return inputList.stream()
.collect(c);
}
public void test()
{
// trying to get a Map<String, List<String>> showing student's name and their activities.
listToMap(StudentDatabase.getAllStudents(), Student::getName, Student::getActivites);
}
However, I am getting lots of compilation error that I do not understand how to solve. Can I get some help here?
Is there any third party library that already does that (but it has to be using java stream api) I can use so that I do not need to write my own?
I tried above and having compilation issue.

There's a couple of problems with the current code:
The Collector interface is generic. You should parameterize it like you're doing for all the other generic types in the code. See What is a raw type and why shouldn't we use it? for more information.
You've defined the return type as Map<K, List<V>>. That would seem to indicate you're trying to implement a grouping operation. However, there are three other parts of your code indicating otherwise:
You use Collectors#toMap(Function,Function)
Your valueMapper maps to V, not List<V>
You call listToMap with Student::getActivities as an argument for the valueMapper, and I can only assume that method returns a list of activities (or some other collection).
So, given all that, you should change the return type to Map<K, V>. That gives the caller full control over the value type of the map, rather than forcing them to use a list. But if you are trying to implement a grouping operation, and you always want the value type to be a List<V>, then consider using Collectors#groupingBy(Function,Collector) instead.
Fixing those two things will give you something like:
public static <T, K, V> Map<K, V> listToMap(
List<T> list,
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper) {
Collector<T, ?, Map<K, V>> collector = Collectors.toMap(keyMapper, valueMapper);
return list.stream().collect(collector);
}
And here's a minimal example using the above:
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
public class Main {
public record Student(String name, List<String> activities) {}
public static <T, K, V> Map<K, V> listToMap(
List<T> list,
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper) {
Collector<T, ?, Map<K, V>> collector = Collectors.toMap(keyMapper, valueMapper);
return list.stream().collect(collector);
}
public static void main(String[] args) {
List<Student> students = List.of(
new Student("John", List.of("Piano", "Running")),
new Student("Jane", List.of("Soccer", "Video Games")),
new Student("Bob", List.of("Snowboarding"))
);
Map<String, List<String>> map = listToMap(students, Student::name, Student::activities);
System.out.println(map);
}
}
Output:
{Bob=[Snowboarding], John=[Piano, Running], Jane=[Soccer, Video Games]}

The method should return a Map instead of a Collector. Also, the Collectors.toMap is not enough to convert a List to a Map of List. You need to use a groupingBy collector instead of toMap.
return inputList.stream().collect(Collectors.groupingBy(keyMapper, Collectors.mapping(valueMapper, Collectors.toList())));

Related

Force Stream::filter method to fail compile time when Predicate<? super Object> is passed rather than Predicate<? super T>

I'm playing around with predefined Identity filters for use with the stream api. Unfortunately I'm unable to properly return a generic predicate that is compliant with the stream api documentation.
According to the de-compiler here is the Stream::filter definition:
public interface Stream<T> extends BaseStream<T, Stream<T>> {
Stream<T> filter(Predicate<? super T> var1);
I'm facing the issue with any Java version that has Streams support (8~15). The issue has nothing to do with my implementation. This code actually is enough in order to reproduce it:
Collection<String> result = Stream.of("A", "B", "C")
.filter(new Object()::equals)
.filter(Integer.valueOf(-1)::equals)
.collect(Collectors.toSet());
Here, two predicates are applied where both of them aren't <? super String> compliant...
According to this answer this behavior seems to be strange...
How should I prevent users of my library from filtering on ServerState by random Object equality check, etc...?
Ideally I would like to always return proper Predicate<? super T> unfortunately that is not backed up by any compile time error...
Using a linter is not a solution in that case.
Even though I know how lower bounded wildcards work what I've been missing is that a Predicate<? super Integer> could be successfully casted to Predicate<? super String>.
Where:
Predicate<? super String> stringPredicate = (Predicate<? super String>)Filters.is_tClass(Integer.class, 4);
Predicate<? super Server> serverPredicate = (Predicate<? super Server>)Filters.is_comparable(5);
Collection<Integer> result = Stream.of(1, 2, 3)
.filter((Predicate<? super Integer>)stringPredicate)
.filter((Predicate<? super Integer>)serverPredicate)
.filter(Filters.is(new Object()))
.collect(Collectors.toSet());
results in [] empty resultset.
Here is what I have so far, but not happy with any of it:
import java.util.Collection;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Main {
public static void main(String[] args) {
Collection<Integer> result = Stream.of(1, 2, 3)
//.filter(Filters.is_tClass(Integer.class, 4)) // enforce user to provide target class
//.filter(Filters.is_comparable(5)) // use only Comparable
.filter(Filters.is(new Server())) // fail runtime with custom exception
.collect(Collectors.toSet());
System.out.println(result);
}
private static class Server {
}
private static class Filters {
private static <T> Predicate<? super T> is(T other) {
return t -> {
// simple class equality check - error prone!
Class<?> tClass = t.getClass();
Class<?> otherClass = other.getClass();
if (!tClass.equals(otherClass)) {
throw new RuntimeException(
String.format("Check equality for [%s ? %s] seems odd. Can not continue...", tClass, otherClass));
}
return t.equals(other);
};
}
static <T> Predicate<? super T> is_tClass(Class<T> tClass, T other) {
return is(other);
}
static <T extends Comparable<T>> Predicate<? super T> is_comparable(T other) {
return is(other);
}
}
}
Methods with names of the type is_* did not exist before posting the sample in here and therefor will be removed...
EDIT
Even though I know how lower bounded wildcards work what I've been missing is that a Predicate<? super Integer> could be successfully casted to Predicate<? super String>.
Where:
Predicate<? super String> stringPredicate = (Predicate<? super String>)Filters.is_tClass(Integer.class, 4);
Predicate<? super Server> serverPredicate = (Predicate<? super Server>)Filters.is_comparable(5);
Collection<Integer> result = Stream.of(1, 2, 3)
.filter((Predicate<? super Integer>)stringPredicate)
.filter((Predicate<? super Integer>)serverPredicate)
.filter(Filters.is(new Object()))
.collect(Collectors.toSet());
results in [] empty resultset.
Here, two predicates are applied where both of them aren't <? super String> compliant
It's not true: the 2 predicates do consume an Object, which is the parent of String.
<? super String> must not be confused with <? extends String>.

Java map Collection<T> to Collection<U>

I need to transform an arbitrary Collection<T> into another arbitrary Collection<U>. For example, I would like to transform an ArrayList<String> into a HashSet<Integer>.
I wrote the following code, which gives me a compile-time error on UCollection::new (Cannot resolve constructor 'UCollection'). I tried replacing it with () -> new UCollection(), which gives me another compile-time error (Type parameter 'UCollection' cannot be instantiated directly).
import java.util.Collection;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Utils {
public static <T, U, TCollection extends Collection<T>, UCollection extends Collection<U>>
UCollection MappedCollection(TCollection collection, Function<T, U> function) {
return MappedStream(collection.stream(), function).collect(Collectors.toCollection(UCollection::new));
}
public static <T, U> Stream<U> MappedStream(Stream<T> stream, Function<T, U> function) {
return stream.map(function);
}
}
UCollection::new is invalid because UCollection is a type variable. You can't construct a type that you don't know in the method.
The easiest fix here is to make your caller supply a UCollection factory:
public static <T, U, TCollection extends Collection<T>,
UCollection extends Collection<U>>
UCollection MappedCollection(TCollection collection,
Function<T, U> function,
Supplier<UCollection> uCollectionSupplier) {
return MappedStream(collection.stream(), function)
.collect(Collectors.toCollection(uCollectionSupplier));
}
As a side note, I think you have one type variable too many. You could dispense with TCollection (using C for UCollection below)...
public static <T, U, C extends Collection<U>>
C mappedCollection(Collection<T> collection,
Function<T, U> function,
Supplier<C> collectionSupplier) {
return MappedStream(collection.stream(), function)
.collect(Collectors.toCollection(collectionSupplier));
}
I would suggest an output parameter instead of a result, as it allows interface based variables, already providing the correct implementation class. The other answer requires actually passing an ArrayList::new which is a bit unfortunately, though a result is more functional programming style.
public <P, R> void convert(Collection<P> cp, Collection<R> cr, Function<P, R> mapper) {
cp.stream().map(mapper).forEach(cr::add);
}
List<String> slist = new ArrayList<>();
Collections.addAll(slist, "2", "3", "5", "7", "5", "3");
Set<Integer> iset = new HashSet<>();
convert(slist, iset, Integer::valueOf);
System.out.println(iset);
Stream aggregating and collecting often has such a new collection supplier as parameter as in the other answer. But where the resulting collection is created seems irrelevant. And a result sometimes requires type inference (no interface vars, or needing a cast).
I would suggest a different, in my opinion more readable, solution:
public static <T, U> List<U> convert(Collection<T> collection, Function<T, U> mapper) {
return collection.stream()
.map(mapper)
.collect(Collectors.toList());
}
Ideone Demo
If you really need to control the exact collection type, then use ernest_k's approach by passing a Supplier<UCollection> supplier to the method:
public static <T, U, UCollection extends Collection<U>> UCollection convert(
Collection<T> collection,
Function<T, U> mapper,
Supplier<UCollection> collectionSupplier) {
return collection.stream()
.map(mapper)
.collect(Collectors.toCollection(collectionSupplier));
}
Ideone Demo

why iterating a received generic HashMap can only be done with enhanced for-loop?

I am implementing a putAll() method.
I tried to iterate the map using entrySet and iterator methods, but received a compiling error that m.entrySet().iterator() return value could not be converted to Iterator<Entry<? extends K, ? extends V>>.
why? if the map is of that type, it's iterator should be as well, no?
thank you!
doesn't compile:
#Override
public void putAll(Map<? extends K, ? extends V> m) {
Iterator<Entry<? extends K, ? extends V>> iter = m.entrySet().iterator();
while (iter.hasNext()) {
Entry<? extends K, ? extends V> entry = iter.next();
put(entry.getKey(), entry.getValue());
}
}
compiles and works fine:
#Override
public void putAll(Map<? extends K, ? extends V> m) {
Objects.requireNonNull(m, "putAll(): argument is null");
for (Entry<? extends K, ? extends V> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
Change
Iterator<Entry<? extends K, ? extends V>> iter
To
Iterator<? extends Entry<? extends K, ? extends V>> iter
Think about it this way, each time the method is called, m, which has type Map<? extends K, ? extends V>, points to an object with a "real" type of Map<some actual subtype of K we don't know, some actual subtype of V we don't know>. (These unknown types might be different in each call of the method, but in each call, the passed object must have some specific type for each of the type parameters, though we don't know it.)
Therefore, m.entrySet().iterator() has type Iterator<Map.Entry<some unknown subtype of K, some unknown subtype of V>>. This is not a subtype of Iterator<Map.Entry<? extends K, ? extends V>>, because when the top-level type argument is not a wildcard, the top-level type arguments must match exactly, and they don't here.
Iterator<Map.Entry<some unknown subtype of K, some unknown subtype of V>> is not a subtype of Iterator<Map.Entry<? extends K, ? extends V>> even though Map.Entry<some unknown subtype of K, some unknown subtype of V> is a subtype of Map.Entry<? extends K, ? extends V>, just like how List<String> is not a subtype of List<Object>, even though String is a subtype of Object.
One solution is to add a wildcard at the top level, like in Lino's answer: Iterator<? extends Map.Entry<? extends K, ? extends V>>.

How to generate a Map backed by a List

I have a data model that looks like this:
class CustomField {
String key;
String value;
}
From an API that I can get instances of List<CustomField>. The keys are unique in the list, which means that this collection really should be a Map<String, String>. Operating on this list is a pain, since every operation requires iteration to check for existing keys (CustomField doesn't implement equals either)
How can I create a Map<String, String> "view" backed by this list, so that I can operate on it using the Map interface?
I want a generic method like: <T, K, V> Map<K, V> createMapBackedByList(List<T> list, BiFunction<K, V, T> elementMapper, Function<T, K> keyMapper, Function<T, V> valueMapper) or similar.
It would use the functions to map between the list elements and map keys and values.
The important thing here is that I want changes to the map to be reflected in the underlying list, which is why the Streams API does not work here...
EDIT: I can't modify the API or the CustomField class.
Simple:
Write your own
public class ListBackedMap<K, V> implements Map<K, V> {
which takes some sort of List<Pair<K,V>> on creation; and "defers" to that. Of course, that requires that your CustomField class implements that Pair interface. (which you would probably need to invent, too)
( alternatively: your new class extends AbstractMap<K,V> to safe you most of the work ).
And now your methods simply return an instance of such a Map.
In other words: I am not aware of a built-in wrapper that meets your requirements. But implementing one yourself should be pretty straight forward.
Edit: given the fact that the OP can't change the CustomField class, a simple helper such as
interface <K, V> MapEntryAdapter {
K getKey();
V getValue();
}
would be required; together with a specific implementation that knows how to retrieve key/value from an instance of CustomField. In this case, the map would be backed by a List<MapEntryAdapter<K, V>> instead.
I ended up trying to implement it myself and basing it on an AbstractList. It was actually easier than I first though...
public class ListBackedMap<T, K, V> extends AbstractMap<K, V> {
private final List<T> list;
private final BiFunction<K, V, T> keyValueToElement;
private final Function<T, K> elementToKey;
private final Function<T, V> elementToValue;
public ListBackedMap(List<T> list, BiFunction<K, V, T> keyValueToElement, Function<T, K> elementToKey, Function<T, V> elementToValue) {
this.list = list;
this.keyValueToElement = keyValueToElement;
this.elementToKey = elementToKey;
this.elementToValue = elementToValue;
}
#Override
public Set<Entry<K, V>> entrySet() {
return list.stream()
.collect(toMap(elementToKey, elementToValue))
.entrySet();
}
#Override
public V put(K key, V value) {
V previousValue = remove(key);
list.add(keyValueToElement.apply(key, value));
return previousValue;
}
public List<T> getList() {
return list;
}
}
It's not very performant (or thread safe), but it seems to do the job well enough.
Example:
List<CustomField> list = getList();
ListBackedMap<CustomField, String, String> map = new ListBackedMap<>(
list,
(key, value) -> new CustomField(key, value),
CustomField::getKey,
CustomField::getValue);

Filter and translating functionality for a custom map with Java 8

I have a task where I need to implement some functionallity to abstract methods. The idea is to use Java 8, but I'm kinda new to programming with Java 8. The following is the abstract class that I need to implement:
public abstract class SortedMap<K extends Comparable<K>, V> implements Iterable<Pair<K, V>>
{
/**
* Returns a map where all values have been translated using the function
* <code>f</code>.
*/
public abstract <C> SortedMap<K, C> map(Function<? super V, ? extends C> f);
/**
* Returns a map containing only the keys that satisfies
* the predicate <code>p</code>.
*/
public abstract SortedMap<K, V> filter(Predicate<? super K> p);
// ...
}
What I've got so far (with Java 8) is:
public final class SortedMapImpl<K extends Comparable<K>, V> extends SortedMap<K,V>
{
private final Map<K, V> map;
private SortedMapImpl(Map<K, V> map)
{
this.map = new HashMap<K, V>(map);
}
#Override
public <C> SortedMap<K, C> map(Function<? super V, ? extends C> f)
{
// TODO Auto-generated method stub
return null;
}
#Override
public SortedMap<K, V> filter(Predicate<? super K> p)
{
final Map<K, V> filteredMap = map.entrySet()
.stream()
.filter(Predicate<? super Entry<K, V>> p)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
return new SortedMapImpl<K, V>(filteredMap);
}
// ...
}
As you can see I've got no clue at the moment how to implement the map() method, and the filter method is at least partly wrong. Any help is greatly appreciated!
I'll help you implement the filter, and hopefully you should get the idea and implement the map by yourself.
You need to filter the entries of the original map, and only keep the ones for which the key satisifes the key predicate:
public SortedMap<K, V> filter(Predicate<? super K> predicate) {
Map<K, V> filteredMap =
map.entrySet()
.stream()
.filter(entry -> predicate.test(entry.getKey()))
.collect(Collectors.toMap(entry -> entry.getKey(),
entry -> entry.getValue()));
return new SortedMapImpl<K, V>(filteredMap);
}
I would not use the name SortedMap, though: your map is not a map, it's not sorted either, and SortedMap is already a standard collection name, which will make your class confusing and cumbersome to use.

Categories

Resources