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
Related
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())));
When you have a Stream of Objects you can filter on them pretty elegantly.
swimmingAnimalStream = animalStream
.filter(Animal::canSwim);
When you have slightly more complex filters instead of using Method references you have to use Lambdas.
greenAnimals = animalStream
.filter(animal -> animal.getColor().equals(Colors.GREEN));
Is there a way to map the value before filtering on it, but still have the complete object after the filter?
So the fallowing is not what I want:
animalStream
.map(Animal::getColor)
.filter(Colors.GREEN::equals)
With this I would be left with color information only.
What I also would like to avoid is extracting the method. I am looking for a more streamlined way of doing this. Something like this for example:
animalStream
.filter(Colors.GREEN::equals, Animal::getColor);
The method signature of this filter method would look like this.
<MAPPED> Stream<T> filter(Predicate<MAPPED> filter, Function<? super T, MAPPED> mappingFunction);
Even better would be a version where you could join multiple mapping functions. On the fly one could maybe use a varargs for the mappingFunction. But I honestly don’t know how that would be possible with Generics. But that’s a different story.
The solution should also be able to use whatever Predicate that one could imagine. Equals is just an Example. Another example would be to check if a field from the object is present.
animalWithMotherStream = animalStream
.filter(Optional::isPresent, Animal::getMother);
Does anyone now a cleaner Solution, or a library that does this already?
Java 16
Yes, there is a solution using Stream#mapMulti as of Java 16.
animalStream
.mapMulti((animal, consumer) -> {
if (Colors.GREEN.equals(animal.getColor())) { // condition
consumer.accept(animal); // qualify the instance
}
})
... // further operations
The provided Consumer<R> accepts the instance that is qualified based on your criteria.
Pros: The performance-wise advantage of this imperative approach is that you don't necessarily invoke the two Stream operations but just one, for example, a combination of Stream#map and Stream#filter can be substituted. Though the biggest advantage is that Consumer#accept can be invoked as many times as you want, so you can effectively increase a number of entries in a Stream.
Cons: However, you lost a bit of the declarative approach and if used only as on the snippet without further processing, it's worth using rather a simple for-loop or sticking with the filter operation (see below):
Older Java versions
Simply write the condition down to Stream#filter, it's a correct Stream usage:
animalStream
.filter(animal -> Colors.GREEN.equals(animal.getColor()))
...
You can use Guava's Predicates.compose(), or create your own:
public static <A, B> Predicate<A> compose(
Predicate<B> predicate, Function<A, ? extends B> function) {
return a -> predicate.test(function.apply(a));
}
Now just pass that into your filter:
animalStream.filter(compose(Colors.GREEN::equals, Animal::getColor))
As for the varargs concept, I doubt that's possible under Java's generics, unless they're all of the same type, in which case you'd just apply() each in a loop or reduce them with Function.andThen().
StreamEx, a library that provides extended stream methods and classes, has filterBy:
public <K> StreamEx<T> filterBy(Function<? super T,? extends K> mapper, K value)
Returns a stream consisting of the elements of this stream for which the supplied mapper function returns the given value.
This method behaves like filter(t -> Objects.equals(value, mapper.apply(t))).
filter accepts a Predicate, whose function can only return a boolean. There's no other method signature.
If you want to filter by all green animals, you'd use
animalStream
.filter(a -> Colors.GREEN.equals(a.getColor()))
or
Predicate<Animal> isGreen = (a) -> Colors.GREEN.equals(a.getColor());
Stream<Animal> greenAnimals = animalStream.filter(isGreen);
Don't use map unless you want a Stream<COLOR>
join multiple mapping functions
You can chain them, rather than join - .stream().map().map(), but as you discovered, this does not preserve the original type.
Operation Stream.filter() expects a Predicate which is a function producing a boolean result. This definition of the filter is very intuitive and self-contained, and there are no other flavors of filter (and I doubt if they will appear in the future).
However, you can create your implementation of Predicate and give it all the behavior you need. And as a Predicate, it would be eligible to be used in the filter.
Before introducing the implementation, I'll show some of the capabilities that can be given to such custom Predicate.
Stream<Animal> greenAnimals = animalStream
.filter(
MultiPred.ofOr(Animal::getColor, Color.WHITE::equals, Color.GREEN::equals)
.or(
MultiPred.of(Animal::getType, AnimalType.CARNIVORE::equals)
.and(Animal::canFly) // we can chain custom Predicates with regular ones
)
.or(
MultiPred.of(Animal::getType, AnimalType.HERBIVORE::equals)
.and(MultiPred.of(Animal::getColor, Color.PURPLE::equals)
.or(Animal::canSwim)
)
)
);
Here's a dummy class Animal and enums used in the example above:
public class Animal {
private AnimalType type;
private boolean canSwim;
private boolean canFly;
private Color color;
// getters
}
public enum AnimalType {
CARNIVORE, HERBIVORE
}
public enum Color {
GREEN, WHITE, PURPLE
}
Implementation
You can provide a custom Predicate with any capabilities you require.
The following predicate expects exposes methods expecting a keyExtractor function and a predicate, or a group of predicates that has to be chained with either logical OR ||, or logical AND &&.
public class MultiPred<T, K> implements Predicate<T> {
private final BiFunction<Function<T, K>, Predicate<K>, Predicate<T>>
predicateProducer = (f, p) -> t -> p.test(f.apply(t));
private final Predicate<T> p;
private MultiPred(Function<T, K> keyExtractor,
Predicate<K> predicate) {
this.p = predicateProducer.apply(keyExtractor, predicate);
}
#SafeVarargs
public static <T, K> MultiPred<T, K> ofAnd(Function<T, K> keyExtractor,
Predicate<K>... predicates) {
return of(keyExtractor, k -> true, Predicate::and, predicates);
}
#SafeVarargs
public static <T, K> MultiPred<T, K> ofOr(Function<T, K> keyExtractor,
Predicate<K>... predicates) {
return of(keyExtractor, k -> false, Predicate::or, predicates);
}
#SafeVarargs
public static <T, K> MultiPred<T, K> of(Function<T, K> keyExtractor,
Predicate<K> identity,
BinaryOperator<Predicate<K>> op,
Predicate<K>... predicates) {
Objects.requireNonNull(predicates);
Predicate<K> predicate = Arrays.stream(predicates).reduce(identity, op);
return new MultiPred<>(keyExtractor, predicate);
}
public static <T, K> MultiPred<T, K> of(Function<T, K> keyExtractor,
Predicate<K> predicate) {
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(predicate);
return new MultiPred<>(keyExtractor, predicate);
}
#Override
public boolean test(T t) {
Objects.requireNonNull(t);
return p.test(t);
}
#Override
public Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return p.and(other);
}
#Override
public Predicate<T> negate() {
return p.negate();
}
#Override
public Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return p.or(other);
}
}
Utility Class for constructing Predicates
The logic from the class above can materialized as a utility class exposing a bunch static method for generating Predicates. It would preserve all the capacities shown in usage example at the beginning.
(Credits for this idea belong to #Holger and #shmosel, since he posted the static method producing a predicate earlier, hence the code shown below should be considered as built on the answer by #shmosel)
public static class MultiPred {
private MultiPred() {}
#SafeVarargs
public static <T, K> Predicate<T> ofAnd(Function<T, K> keyExtractor,
Predicate<K>... predicates) {
return of(keyExtractor, k -> true, Predicate::and, predicates);
}
#SafeVarargs
public static <T, K> Predicate<T> ofOr(Function<T, K> keyExtractor,
Predicate<K>... predicates) {
return of(keyExtractor, k -> false, Predicate::or, predicates);
}
#SafeVarargs
public static <T, K> Predicate<T> of(Function<T, K> keyExtractor,
Predicate<K> identity,
BinaryOperator<Predicate<K>> op,
Predicate<K>... predicates) {
Objects.requireNonNull(predicates);
Predicate<K> predicate = Arrays.stream(predicates).reduce(identity, op);
return getPredicateProducer(keyExtractor, predicate);
}
public static <T, K> Predicate<T> of(Function<T, K> keyExtractor,
Predicate<K> predicate) {
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(predicate);
return getPredicateProducer(keyExtractor, predicate);
}
private static <T, K> Predicate<T> getPredicateProducer(Function<T, K> keyExtractor,
Predicate<K> predicate) {
return t -> predicate.test(keyExtractor.apply(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>.
I have a set of domain objects that inherit from a shared type (i.e. GroupRecord extends Record, RequestRecord extends Record). The subtypes have specific properties (i.e. GroupRecord::getCumulativeTime, RequestRecord::getResponseTime).
Further, I have a list of records with mixed subtypes as a result of parsing a logfile.
List<Record> records = parseLog(...);
In order to compute statistics on the log records, I want to apply math functions only on a subset of the records that matches a specific subtype, i.e. only on GroupRecords. Therefore I want to have a filtered stream of specific subtypes. I know that I can apply a filter and map to a subtype using
records.stream()
.filter(GroupRecord.class::isInstance)
.map(GroupRecord.class::cast)
.collect(...
Apply this filter&cast on the stream multiple times (especially when doing it for the same subtype multiple times for different computations) is not only cumbersome but produces lots of duplication.
My current approach is to use a TypeFilter
class TypeFilter<T>{
private final Class<T> type;
public TypeFilter(final Class<T> type) {
this.type = type;
}
public Stream<T> filter(Stream<?> inStream) {
return inStream.filter(type::isInstance).map(type::cast);
}
}
To be applied to a stream:
TypeFilter<GroupRecord> groupFilter = new TypeFilter(GroupRecord.class);
SomeStatsResult stats1 = groupFilter.filter(records.stream())
.collect(...)
SomeStatsResult stats2 = groupFilter.filter(records.stream())
.collect(...)
It works, but I find this approach a bit much for such a simple task. Therefore I wonder, is there a better or what is the best way for making this behavior reusable using streams and functions in a concise and readable way?
It depends on what do you find "more concise and readable". I myself would argue that the way you already implemented is fine as it is.
However, there is indeed a way to do this in a way that is slightly shorter from the point of where you use it, by using Stream.flatMap:
static <E, T> Function<E, Stream<T>> onlyTypes(Class<T> cls) {
return el -> cls.isInstance(el) ? Stream.of((T) el) : Stream.empty();
}
What it would do is it will convert each original stream element to either a Stream of one element if the element has expected type, or to an empty Stream if it does not.
And the use is:
records.stream()
.flatMap(onlyTypes(GroupRecord.class))
.forEach(...);
There are obvious tradeoffs in this approach:
You do lose the "filter" word from your pipeline definition. That may be more confusing that the original, so maybe a better name than onlyTypes is needed.
Stream objects are relatively heavyweight, and creating so much of them may result in performance degradation. But you should not trust my word here and profile both variants under heavy load.
Edit:
Since the question asks about reusing filter and map in slightly more general terms, I feel like this answer can also discuss a little more abstraction. So, to reuse filter and map in general terms, you need the following:
static <E, R> Function<E, Stream<R>> filterAndMap(Predicate<? super E> filter, Function<? super E, R> mapper) {
return e -> filter.test(e) ? Stream.of(mapper.apply(e)) : Stream.empty();
}
And original onlyTypes implementation now becomes:
static <E, R> Function<E, Stream<R>> onlyTypes(Class<T> cls) {
return filterAndMap(cls::isInstance, cls::cast);
}
But then, there is yet again a tradeoff: resulting flat mapper function will now hold captured two objects (predicate and mapper) instead of single Class object in above implementation. It may also be a case of over-abstracting, but that one depends on where and why you would need that code.
You don’t need an entire class to encapsulate a piece of code. The smallest code unit for that purpose, would be a method:
public static <T> Stream<T> filter(Collection<?> source, Class<T> type) {
return source.stream().filter(type::isInstance).map(type::cast);
}
This method can be used as
SomeStatsResult stats1 = filter(records, GroupRecord.class)
.collect(...);
SomeStatsResult stats2 = filter(records, GroupRecord.class)
.collect(...);
If the filtering operation isn’t always the first step in your chain, you may overload the method:
public static <T> Stream<T> filter(Collection<?> source, Class<T> type) {
return filter(source.stream(), type);
}
public static <T> Stream<T> filter(Stream<?> stream, Class<T> type) {
return stream.filter(type::isInstance).map(type::cast);
}
However, if you have to repeat this operation multiple times for the same type, it might be beneficial to do
List<GroupRecord> groupRecords = filter(records, GroupRecord.class)
.collect(Collectors.toList());
SomeStatsResult stats1 = groupRecords.stream().collect(...);
SomeStatsResult stats2 = groupRecords.stream().collect(...);
not only eliminating the code duplication in source code, but also performing the runtime type checks only once. The impact of the required additional heap space depends on the actual use case.
WHAT you actually need is a Collector to collecting all elements in the stream that is instance of special type. It can solving your problem easily and avoiding filtering the stream twice:
List<GroupRecord> result = records.stream().collect(
instanceOf(GroupRecord.class, Collectors.toList())
);
SomeStatsResult stats1 = result.stream().collect(...);
SomeStatsResult stats2 = result.stream().collect(...);
AND you can do something as further like as Stream#map by using Collectors#mapping, for example:
List<Integer> result = Stream.of(1, 2L, 3, 4.)
.collect(instanceOf(Integer.class, mapping(it -> it * 2, Collectors.toList())));
| |
| [2,6]
[1,3]
WHERE you only want to consuming the Stream once, you can easily composing the last Collector as below:
SomeStatsResult stats = records.stream().collect(
instanceOf(GroupRecord.class, ...)
);
static <T, U extends T, A, R> Collector<T, ?, R> instanceOf(Class<U> type
, Collector<U, A, R> downstream) {
return new Collector<T, A, R>() {
#Override
public Supplier<A> supplier() {
return downstream.supplier();
}
#Override
public BiConsumer<A, T> accumulator() {
BiConsumer<A, U> target = downstream.accumulator();
return (result, it) -> {
if (type.isInstance(it)) {
target.accept(result, type.cast(it));
}
};
}
#Override
public BinaryOperator<A> combiner() {
return downstream.combiner();
}
#Override
public Function<A, R> finisher() {
return downstream.finisher();
}
#Override
public Set<Characteristics> characteristics() {
return downstream.characteristics();
}
};
}
Why did you need to composes Collectors?
Did you remember Composition over Inheritance Principle? Did you remember assertThat(foo).isEqualTo(bar) and assertThat(foo, is(bar)) in unit-test?
Composition is much more flexible, it can reuses a piece of code and composeing components together on runtime, that is why I prefer hamcrest rather than fest-assert since it can composing all possible Matchers together. and that is why functional programming is most popular since it can reuses any smaller piece of function code than class level reusing. and you can see jdk has introduced Collectors#filtering in jdk-9 that will make the execution routes shorter without losing its expressiveness.
AND you can refactoring the code above according to Separation of Concerns as further, then filtering can be reused like as jdk-9 Collectors#filtering:
static <T, U extends T, A, R> Collector<T, ?, R> instanceOf(Class<U> type
, Collector<U, A, R> downstream) {
return filtering(type::isInstance, Collectors.mapping(type::cast, downstream));
}
static <T, A, R>
Collector<T, ?, R> filtering(Predicate<? super T> predicate
, Collector<T, A, R> downstream) {
return new Collector<T, A, R>() {
#Override
public Supplier<A> supplier() {
return downstream.supplier();
}
#Override
public BiConsumer<A, T> accumulator() {
BiConsumer<A, T> target = downstream.accumulator();
return (result, it) -> {
if (predicate.test(it)) {
target.accept(result, it);
}
};
}
#Override
public BinaryOperator<A> combiner() {
return downstream.combiner();
}
#Override
public Function<A, R> finisher() {
return downstream.finisher();
}
#Override
public Set<Characteristics> characteristics() {
return downstream.characteristics();
}
};
}
I want to create an IdentityHashMap<Class<T>, Consumer<T>>. Basically, I want to map a type with a method saying what to do with this type.
I want to dynamically be able to say with objects X, execute Y. I can do
private IdentityHashMap<Class<?>, Consumer<?>> interceptor = new IdentityHashMap<>();
but it sucks because then I have to cast the object in the lamba when using it.
Example:
interceptor.put(Train.class, train -> {
System.out.println(((Train)train).getSpeed());
});
What I would like to do is
private <T> IdentityHashMap<Class<T>, Consumer<T>> interceptor = new IdentityHashMap<>();
But it doesn't seem to be allowed. Is there a way to do this ? What is the best workaround to map types with a method for this type ?
This is essentially just like the type-safe heterogeneous container described by Joshua Bloch, except you can't use the Class to cast the result.
Weirdly, I can't find a great example existing on SO, so here is one:
package mcve;
import java.util.*;
import java.util.function.*;
class ClassToConsumerMap {
private final Map<Class<?>, Consumer<?>> map =
new HashMap<>();
#SuppressWarnings("unchecked")
public <T> Consumer<? super T> put(Class<T> key, Consumer<? super T> c) {
return (Consumer<? super T>) map.put(key, c);
}
#SuppressWarnings("unchecked")
public <T> Consumer<? super T> get(Class<T> key) {
return (Consumer<? super T>) map.get(key);
}
}
That's type-safe, because the relation between keys and values is enforced by the signature of the put method.
One annoying thing about the limitations of Java's generics is that one of these containers can't be written for a generic value type, because there's no way to do e.g.:
class ClassToGenericValueMap<V> {
...
public <T> V<T> put(Class<T> key, V<T> val) {...}
public <T> V<T> get(Class<T> key) {...}
}
Other notes:
I would use a regular HashMap or a LinkedHashMap for this. HashMap is better maintained and has many optimizations that IdentityHashMap doesn't have.
If it's necessary to use generic types, like Consumer<List<String>>, then you need to use something like Guava TypeToken as the key, because Class can only represent the erasure of a type.
Guava has a ClassToInstanceMap for when you need a Map<Class<T>, T>.
Sometimes people want to do something like this, with a class-to-consumer map:
public <T> void accept(T obj) {
Consumer<? super T> c = get(obj.getClass());
if (c != null)
c.accept(obj);
}
That is, given any object, find the consumer in the map bound to that object's class and pass the object to the consumer's accept method.
That example won't compile, though, because getClass() is actually specified to return a Class<? extends |T|>, where |T| means the erasure of T. (See JLS §4.3.2.) In the above example, the erasure of T is Object, so obj.getClass() returns a plain Class<?>.
This issue can be solved with a capturing helper method:
public void accept(Object obj) {
accept(obj.getClass(), obj);
}
private <T> void accept(Class<T> key, Object obj) {
Consumer<? super T> c = get(key);
if (c != null)
c.accept(key.cast(obj));
}
Also, if you want a modified version of get which returns any applicable consumer, you could use something like this:
public <T> Consumer<? super T> findApplicable(Class<T> key) {
Consumer<? super T> c = get(key);
if (c == null) {
for (Map.Entry<Class<?>, Consumer<?>> e : map.entrySet()) {
if (e.getKey().isAssignableFrom(key)) {
#SuppressWarnings("unchecked")
Consumer<? super T> value =
(Consumer<? super T>) e.getValue();
c = value;
break;
}
}
}
return c;
}
That lets us put general supertype consumers in the map, like this:
ctcm.put(Object.class, System.out::println);
And then retrieve with a subtype class:
Consumer<? super String> c = ctcm.findApplicable(String.class);
c.accept("hello world");
Here's a slightly more general example, this time using UnaryOperator and no bounded wildcards:
package mcve;
import java.util.*;
import java.util.function.*;
public class ClassToUnaryOpMap {
private final Map<Class<?>, UnaryOperator<?>> map =
new HashMap<>();
#SuppressWarnings("unchecked")
public <T> UnaryOperator<T> put(Class<T> key, UnaryOperator<T> op) {
return (UnaryOperator<T>) map.put(key, op);
}
#SuppressWarnings("unchecked")
public <T> UnaryOperator<T> get(Class<T> key) {
return (UnaryOperator<T>) map.get(key);
}
}
The ? super bounded wildcard in the first example is specific to consumers, and I thought an example without wildcards might be easier to read.
It is possible to implement this in a type-safe manner without any unchecked cast. The solution resides in wrapping the Consumer<T> into a more general Consumer<Object> that casts and then delegates to the original consumer:
public class ClassToConsumerMap {
private final Map<Class<?>, Consumer<Object>> map = new IdentityHashMap<>();
public <T> Consumer<? super T> put(Class<T> key, Consumer<? super T> c) {
return map.put(key, o -> c.accept(key.cast(o)));
}
public <T> Consumer<? super T> get(Class<T> key) {
return map.get(key);
}
}
Depending on your needs, get() could also simply return a Consumer<Object>. This would be necessary if you only know the type at runtime, e.g.
classToConsumerMap.get(someObject.getClass()).accept(someObject);
I am pretty sure I saw this solution (or something similar) in a talk # Devoxx Belgium 2016, possibly from Venkat Subramaniam, but I definitively cannot find it back…
I can just let the IdentityHashMap with the usual Class<?> and Consumer<?>
private IdentityHashMap<Class<?>, Consumer<?>> interceptor = new IdentityHashMap<>();
And then I wrap the put operation in a method. This method accepts a type and a consumer of the same generic.
public <T> void intercept(Class<T> type, Consumer<T> consumer)
{
interceptor.put(type, consumer);
}
This lets me write
intercept(Train.class, train -> {
System.out.println(train.getSpeed());
});