Today I encountered a following Java assignment, and I can't figure out how to get past type erasure.
The task is to create a generic InputConverter class, which takes an input of type T and converts it using chain of multiple functions received as a method argument. It has to support a following notation:
Function<String, List<String>> lambda1 = ...;
Function<List<String>, String> lambda2 = ...;
Function<String, Integer> lambda3 = ...;
String input = ...;
List<String> res1 = new InputConverter(input).convertBy(lambda1);
Integer res2 = new InputConverter(input).convertBy(lambda1, lambda2, lambda3);
This is what I came up with:
import java.util.Arrays;
import java.util.function.Function;
public class InputConverter<T> {
private final T input;
public InputConverter(T input) {
this.input = input;
}
public <B> B convertBy(Function<T, ?> first, Function<?, ?>... functions) {
var res = first.apply(input);
Function<?, B> composed = Arrays.stream(functions)
.reduce(Function::andThen)
.orElse(Function.identity());
return composed.apply(res);
}
}
This doesn't work of course, since I can't find a way to determine the return type of the last function.
Notes:
InputConverter should define only one convertBy method, so method overloading is not an option.
This method should return the result of last function in the chain without the need of explicit casting.
Problem
You would need to chain the generics for each number of expected functions and chain the generic parameters as on the snippet below with five functions:
public <D> E convertBy(
Function<T, A> first, Function<A, B> second, Function<B, C> third,
Function<C, D> fourth, Function<D, E> fifth) {
...
}
However, this is not possible for unknown number of parameters (varargs). There is no such thing as "vargenerics" which would dynamically create and chain the generic parameters as above.
Solution
You can instead treat the InputConverter as a builder instead which returns self with each convertBy call and finally packs a result. This recursive behavior allows indefinite number of calls. Try it out:
public static class InputConverter<T> {
private final T data;
public InputConverter(T data) {
this.data = data;
}
public <U> InputConverter<U> convertBy(Function<T, U> function) {
return new InputConverter<>(function.apply(data));
}
public T pack() {
return data;
}
}
Pretty neat, isn't it? Let's see the usage on a minimal sample:
// let lambda1 split String by characters and create a List
Function<String, List<String>> lambda1 = str -> Arrays.stream(str.split(""))
.collect(Collectors.toList());
// let lambda2 get the first item
Function<List<String>, String> lambda2 = list -> list.get(0);
// let lambda3 parse a String into the Integer
Function<String, Integer> lambda3 = Integer::parseInt;
String input = "123"; // out sample numeric input
List<String> res1 = new InputConverter<String>(input) // don't forget the generics
.convertBy(lambda1)
.pack();
Integer res2 = new InputConverter<String>(input)
.convertBy(lambda1)
.convertBy(lambda2)
.convertBy(lambda3)
.pack();
System.out.println(res1); // [1, 2, 3]
System.out.println(res2); // 1
So, according to task's author the correct solution is this:
import java.util.Arrays;
import java.util.function.Function;
public class InputConverter<T> {
private final T input;
public InputConverter(T input) {
this.input = input;
}
public <B> B convertBy(Function<T, ?> first, Function... functions) {
var res = first.apply(input);
Function<Object, B> composed = Arrays.stream(functions)
.reduce(Function::andThen)
.orElse(Function.identity());
return composed.apply(res);
}
}
Which is not satisfying to me at all. It allows for using vararg parameters, but using raw, unparameterized Function makes no sense. Nikolas Charalambidis' answer is a much better solution as we preserve return type information and safety.
Related
I have a Function and BiFunction and I would like to chain them
Function<Integer, String> function = n -> "" + n;
BiFunction<String, Boolean, List<Character>> biFunction =
(str, isOK) -> Collections.EMPTY_LIST;
Is there a way to chain these two Functions such as the returned value from Function is used as an input to BiFunction?
Pseudocode:
public List<Character> myMethod(int n, boolean isOK) {
return function.andThen(biFunction).apply([output_of_function], isOK)
}
I couldn't find a way to provide the integer n to Function nor to supply BiFunction with the output of the first Function.
Is it doable?
Default methods andThen() and compose() declared in the interface Function expect another Function as an argument. Hence, it's not possible to fuse Function and BiFunction using these methods (BiFunction and Function doesn't extend each other).
On the other hand method BiFunction.andThen() expects a Function as argument. But unfortunately it would be applied after BiFunction (i.e. on the result produced by the BiFunction), but you need the opposite, so this option doesn't fit into your use-case.
As a possible workaround, you can combine a Function and a BiFunction into an aggregate BiFunction expecting the input of the Function function and a boolean value and producing the result generated by the by BiFunction like this:
public static <T, R, RR> BiFunction<T, Boolean, RR> getCombinedFunction(
Function<T, R> fun, BiFunction<R, Boolean, RR> biFun
) {
return (t, isOk) -> biFun.apply(fun.apply(t), isOk);
}
It can be used in the following way:
Function<Integer, String> function = // initializing function
BiFunction<String, Boolean, List<Character>> biFunction = // initializing biFunction
List<Character> chars = getCombinedFunction(function, biFunction).apply(12345, true);
Sidenote:
The preferred way of converting an int into a String is to use static method String.valueOf(). And the function from your example could be expressed as the following method reference:
Function<Integer, String> function = String::valueOf;
You can define a generic method that compose Function and BiFunction like this.
public static <A, B, C, D> BiFunction<A, C, D> compose(Function<A, B> f, BiFunction<B, C, D> bf) {
return (a, c) -> bf.apply(f.apply(a), c);
}
And you can use like this.
Function<Integer, String> function = n -> ""+n;
BiFunction<String, Boolean, List<Character>> biFunction = (str, isOK) -> Collections.emptyList();
public List<Character> myMethod(int n, boolean isOK) {
return compose(function, biFunction).apply(n, isOK);
}
Node: You should use Collections.emptyList() instead of Collections.EMPTY_LIST.
The latter gives a warning.
I have a method:
private List<ApplicationForVacancy> createListOfApplicationsForVacancy(List<Document> documents) {
return documents.stream()
.filter(d -> d instanceof ApplicationForVacancy)
.map(d -> (ApplicationForVacancy) d)
.collect(Collectors.toList());
}
but I'd like to pass the name of the class/the type of the class in parameter and return type of the method like this:
private List<TypeOfSomeClass> createList (List<Document> documents, String or Type typeOfSomeClass) {
return documents.stream()
.filter(d -> d instanceof typeOfSomeClass)
.map(d -> (typeOfSomeClass) d)
.collect(Collectors.toList());
}
Is it possible? How can I do that?
You have to provide a Class object to declare the type and add a type parameter to the method, like this:
private static <T> List<T> filterType(List<?> input, Class<T> clazz) {
return input.stream()
.filter(clazz::isInstance)
.map(clazz::cast)
.collect(Collectors.toList());
}
You then call the method like this:
List<Object> stuff = List.of("String", Integer.valueOf(3), Double.valueOf(42), new Object());
List<String> strings = filterType(stuff, String.class);
List<Number> numbers = filterType(stuff, Number.class);
strings will only contain String and numbers will contain the 2 numeric values.
To my knowledge you can't perform a type conversion like this. But you could create a new List instance at the start of your method, containing the specified type. Then loop through your existing list, filtering out all instances that should be contained in the result. Add those, then return the new List. Here some pseudo code.
public <T> List<T> filterInstances(List<?> input, Class<T> type){
List<T> result = new ArrayList<>();
for(Object o : input){
if(type.isAssignableFrom(o.getClass()){
result.add((T)o);
}
}
return result;
}
Let's say, our method receives input String and returns some List output. This output is the result of some number of generators, some of which depend on input and some of them not - they just add predefined values.
I want to implement these generators like a list of some function interfaces (Consumer for example), then combine them into one Consumer and just apply it to the input String.
So, I will able to change small generators easy and independently. But the problem is that not all my generators need input String as a parameter and I just pass this parameter there for only one reason - have an ability to combine such Consumers with others.
public class ConsumersTest {
private static <T, U> BiConsumer<T, U> combine(List<BiConsumer<T, U>> consumers) {
return consumers.stream().reduce((arg1, arg2) -> {}, BiConsumer::andThen);
}
List<String> generate(String input) {
ArrayList<String> output = new ArrayList<>();
combine(getGenerators()).accept(input, output);
return output;
}
private List<BiConsumer<String, List<String>>> getGenerators() {
return Arrays.asList(
this::addFirstDependent,
this::addSecondIndependent
);
}
private void addFirstDependent(String input, List<String> output) {
if (input.contains("some string")) {
output.add("First-Dependent");
}
}
private void addSecondIndependent(String input, List<String> output) {
output.add("Predefined Output");
}}
Is it possible to combine different consumers under one umbrella and apply them in one place? Or this is a bad idea and not the right way to do such things?
It is not an unusual pattern to have a common interface in a modular software and adapters, to make particular implementations fit. E.g.
public class ConsumersTest {
List<String> generate(String input) {
ArrayList<String> output = new ArrayList<>();
generators.accept(input, output);
return output;
}
private static <T, U> BiConsumer<T, U> ignoreFirstArg(Consumer<U> consumer) {
return (t, u) -> consumer.accept(u);
}
private final BiConsumer<String, List<String>> generators =
Stream.<BiConsumer<String, List<String>>>of(
this::addFirstDependent,
ignoreFirstArg(this::addSecondIndependent)
).reduce(BiConsumer::andThen).orElse((arg1, arg2) -> {});
private void addFirstDependent(String input, List<String> output) {
if (input.contains("some string")) {
output.add("First-Dependent");
}
}
private void addSecondIndependent(List<String> output) {
output.add("Predefined Output");
}
}
So ignoreFirstArg is the general adapter for methods not having that first parameter. There can be an arbitrary number of adapter methods. But note that if an adapter is very specific and thus, only use a single time, it’s also possible to write a lambda expression instead of a method reference, right in the combining code. Note that I changed that code, to not get repeatedly evaluated for every generate(String input) call, as otherwise, there would be no point in combining them when you don’t reuse the combined function, as you could also use
List<String> generate(String input) {
ArrayList<String> output = new ArrayList<>();
Stream.<BiConsumer<String, List<String>>>of(
this::addFirstDependent,
ignoreFirstArg(this::addSecondIndependent)
).forEach(g -> g.accept(input, output));
return output;
}
or even simpler
List<String> generate(String input) {
ArrayList<String> output = new ArrayList<>();
this.addFirstDependent(input, output);
this.addSecondIndependent(output);
return output;
}
which is not worse to maintain than the functional code, as still, every generator consists of a single line.
Suppose you have a method like this that computes the maximum of a Collection for some ToIntFunction:
static <T> void foo1(Collection<? extends T> collection, ToIntFunction<? super T> function) {
if (collection.isEmpty())
throw new NoSuchElementException();
int max = Integer.MIN_VALUE;
T maxT = null;
for (T t : collection) {
int result = function.applyAsInt(t);
if (result >= max) {
max = result;
maxT = t;
}
}
// do something with maxT
}
With Java 8, this could be translated into
static <T> void foo2(Collection<? extends T> collection, ToIntFunction<? super T> function) {
T maxT = collection.stream()
.max(Comparator.comparingInt(function))
.get();
// do something with maxT
}
A disadvantage with the new version is that function.applyAsInt is invoked repeatedly for the same value of T. (Specifically if the collection has size n, foo1 invokes applyAsInt n times whereas foo2 invokes it 2n - 2 times).
Disadvantages of the first approach are that the code is less clear and you can't modify it to use parallelism.
Suppose you wanted to do this using parallel streams and only invoke applyAsInt once per element. Can this be written in a simple way?
You can use a custom collector that keeps running pair of the maximum value and the maximum element:
static <T> void foo3(Collection<? extends T> collection, ToIntFunction<? super T> function) {
class Pair {
int max = Integer.MIN_VALUE;
T maxT = null;
}
T maxT = collection.stream().collect(Collector.of(
Pair::new,
(p, t) -> {
int result = function.applyAsInt(t);
if (result >= p.max) {
p.max = result;
p.maxT = t;
}
},
(p1, p2) -> p2.max > p1.max ? p2 : p1,
p -> p.maxT
));
// do something with maxT
}
One advantage is that this creates a single Pair intermediate object that is used through-out the collecting process. Each time an element is accepted, this holder is updated with the new maximum. The finisher operation just returns the maximum element and disgards the maximum value.
As I stated in the comments I would suggest introducing an intermediate datastructure like:
static <T> void foo2(Collection<? extends T> collection, ToIntFunction<? super T> function) {
if (collection.isEmpty()) {
throw new IllegalArgumentException();
}
class Pair {
final T value;
final int result;
public Pair(T value, int result) {
this.value = value;
this.result = result;
}
public T getValue() {
return value;
}
public int getResult() {
return result;
}
}
T maxT = collection.stream().map(t -> new Pair(t, function.applyAsInt(t)))
.max(Comparator.comparingInt(Pair::getResult)).get().getValue();
// do something with maxT
}
Another way would be to use a memoized version of function:
static <T> void foo2(Collection<? extends T> collection,
ToIntFunction<? super T> function, T defaultValue) {
T maxT = collection.parallelStream()
.max(Comparator.comparingInt(ToIntMemoizer.memoize(function)))
.orElse(defaultValue);
// do something with maxT
}
Where ToIntMemoizer.memoize(function) code would be as follows:
public class ToIntMemoizer<T> {
private final Map<T, Integer> cache = new ConcurrentHashMap<>();
private ToIntMemoizer() {
}
private ToIntFunction<T> doMemoize(ToIntFunction<T> function) {
return input -> cache.computeIfAbsent(input, function::apply);
}
public static <T> ToIntFunction<T> memoize(ToIntFunction<T> function) {
return new ToIntMemoizer<T>().doMemoize(function);
}
}
This uses a ConcurrentHashMap to cache already computed results. If you don't need to support parallelism, you can perfectly use a HashMap.
One disadvantage is that the result of the function needs to be boxed/unboxed. On the other hand, as the function is memoized, a result will be computed only once for each repeated element of the collection. Then, if the function is invoked with a repeated input value, the result will be returned from the cache.
If you don't mind using third-party library, my StreamEx optimizes all these cases in special methods like maxByInt and so on. So you can simply use:
static <T> void foo3(Collection<? extends T> collection, ToIntFunction<? super T> function) {
T maxT = StreamEx.of(collection).parallel()
.maxByInt(function)
.get();
// do something with maxT
}
The implementation uses reduce with mutable container. This probably abuses API a little, but works fine for sequential and parallel streams and unlike collect solution defers the container allocation to the first accumulated element (thus no container is allocated if parallel subtask covers no elements which occurs quite often if you have the filtering operation upstream).
Is it possible perform multiple mapping on collection?
Following code compilation error:
... in Stream cannot be applied to java.util.function.Function<capture<?>,capture<?>>
private static List<?> multipleMapping(final Collection<?> collection, final List<Function<?, ?>> functions) {
Stream<?> stream = collection.stream();
for (Function<?, ?> function : functions) {
stream = stream.map(function);
}
return stream.collect(Collectors.toList());
}
I would like to generic solution.
The problem comes from the fact that you're using a generic wildcard ?. What you want is to have a parameterized type T, that will represent the type of the Stream element. Assuming the function would return the same type as their input, you could have:
private static <T> List<T> multipleMapping(final Collection<T> collection, final List<Function<T, T>> functions) {
Stream<T> stream = collection.stream();
for (Function<T, T> function : functions) {
stream = stream.map(function);
}
return stream.collect(Collectors.toList());
}
This compiles fine: the mapper given to map correcly accepts a T and returns a T. However, if the functions don't return the same type as their input then you won't be able to keep type-safety and will have to resort to using List<Function<Object, Object>>.
Note that we could use a UnaryOperator<T> instead of Function<T, T>.
Also, you could avoid the for loop and reduce all functions into a single one using andThen:
private static <T> List<T> multipleMapping(final Collection<T> collection, final List<Function<T, T>> functions) {
return collection.stream()
.map(functions.stream().reduce(Function.identity(), Function::andThen))
.collect(Collectors.toList());
}
If you have few functions (i.e. if you can write them down), then I suggest you don't add them to a list. Instead, compose them into a single function, and then apply that single function to each element of the given collection.
Your multipleMapping() method would now receive a single function:
public static <T, R> List<R> multipleMapping(
Collection<T> collection, Function<T, R> function) {
return collection.stream()
.map(function)
.collect(Collectors.toList());
}
Then, in the calling code, you could create a function composed of many functions (you will have all the functions anyway) and invoke the multipleMapping() method with that function.
For example, suppose we have a list of candidates:
List<String> candidates = Arrays.asList(
"Hillary", "Donald",
"Bernie", "Ted", "John");
And four functions:
Function<String, Integer> f1 = String::length;
Function<Integer, Long> f2 = i -> i * 10_000L;
Function<Long, LocalDate> f3 = LocalDate::ofEpochDay;
Function<LocalDate, Integer> f4 = LocalDate::getYear;
These functions can be used to compose a new function, as follows:
Function<String, Integer> function = f1.andThen(f2).andThen(f3).andThen(f4);
Or also this way:
Function<String, Integer> composed = f4.compose(f3).compose(f2).compose(f1);
Now, you can invoke your multipleMapping() method with the list of candidates and the composed function:
List<Integer> scores = multipleMapping(candidates, function);
So we have transformed our list of candidates into a list of scores, by explicitly composing a new function from four different functions and applying this composed function to each candidate.
If you want to know who will win the election, you could check which candidate has the highest score, but I will let that as an exercise for whoever is interested in politics ;)