How to declare a return type method as the return type of the last lambda in an array passed to a method

I ask for something that I see impossible, and I will delete the question if there is one.

I have a method:

public Object convertBy(Function... functions) { } 

and these functions:

 interface FLines extends Function { @Override default Object apply(Object t) { return null; }; public List<String> getLines(String fileName); } interface Join extends Function { @Override default Object apply(Object t) { return null; }; public String join(List<String> lines);//lines to join } interface CollectInts extends Function { @Override default Object apply(Object t) { return null; }; public List<Integer> collectInts(String s); } interface Sum<T, R> extends Function<T, R> { @Override default Object apply(Object t) { return null; }; public R sum(T list);//list of Integers } 

Abstract methods in these interfaces return values โ€‹โ€‹of different types. I pass lambdas to my convertBy method.

I would like to set the return type to convertBy in the same way as the return type of functions[functions.length - 1] .

Is it possible?


EDIT:

I changed the method signature and the method signature inside the interface. It works, but only if I put in the marked places mostly located below. Strange things that he needs to use only in 3 of 4 methods, I would like to get rid of ghosts altogether.

 import java.util.List; import java.util.function.Function; public class InputConverter<T> { private T value; public InputConverter(T value) { this.value = value; } public <T, R> R convertBy(Function<T, R> special, Function... functions) { if (functions.length == 0) { FLines flines = (FLines) special; return (R) flines.getLines((value instanceof String) ? (String) value : null); } else if (functions.length == 1) { FLines flines = (FLines) functions[0]; Join join = (Join) special; return (R) join.join(flines.getLines((String) value)); } else if (functions.length == 2) { if (functions[0] instanceof FLines) { FLines flines = (FLines) functions[0]; Join join = (Join) functions[1]; CollectInts collectInts = (CollectInts) special; return (R) collectInts.collectInts(join.join(flines.getLines((String) value))); } else { Join join = (Join) functions[0]; CollectInts collectInts = (CollectInts) functions[1]; Sum sum = (Sum) special; return (R) sum.sum(collectInts.collectInts(join.join((List<String>) value))); } } else { FLines flines = (FLines) functions[0]; Join join = (Join) functions[1]; CollectInts collectInts = (CollectInts) functions[2]; Sum sum = (Sum) special; return (R) sum.sum(collectInts.collectInts(join.join(flines.getLines((String) value)))); } } /* public Integer convertBy(Join join, CollectInts collectInts, Sum sum) { return sum.sum(collectInts.collectInts(join.join((List<String>) value))); }*/ } interface FLines<T, R> extends Function { @Override default Object apply(Object t) { return null; }; public R getLines(T fileName); // public List<String> getLines(String fileName); } interface Join<T,R> extends Function { @Override default Object apply(Object t) { return null; }; public R join(T lines);//lines to join // public String join(List<String> lines);//lines to join } interface CollectInts<T, R> extends Function { @Override default Object apply(Object t) { return null; }; public R collectInts(T t); // public List<Integer> collectInts(String s); } interface Sum<T, R> extends Function<T, R> { @Override default Object apply(Object t) { return null; }; public R sum(T list);//list of Integers } 

The main method:

  FLines<String, List<String>> flines .... Join<List<String>, String> join ... CollectInts<String, List<Integer>> collectInts ... Sum<List<Integer>, Integer> sum ... String fname =/* System.getProperty("user.home") + "/*/ "LamComFile.txt"; InputConverter<String> fileConv = new InputConverter<>(fname); List<String> lines = fileConv.convertBy(flines);//cannot cast from Object to List<String> String text = fileConv.convertBy( join, flines);//cannot cast from Object to String List<Integer> ints = fileConv.convertBy(collectInts,flines, join);//cannot cast from Object to List<Integer> Integer sumints = fileConv.convertBy(sum, flines, join, collectInts);//works without cast! 

I do not understand why the compiler understands that it returns sum , but does not conclude that it returns an instance of collectInts .

+6
source share
3 answers

You seem to have a misunderstanding about type hierarchies. If you want to extend a generic type, you must make a fundamental decision about the actual types of the extended class or interface. You can specify exact types, for example, in

 interface StringTransformer extends Function<String,String> {} 

(here we create a type that extends the generic type but is not generic itself)

or you can create a generic type that uses its own type parameter to indicate the actual type argument of the superclass:

 interface NumberFunc<N extends Number> extends Function<N,N> {} 

Notice how we create a new parameter N with its own restrictions and use it to parameterize the superclass so that its type parameters match ours.

In contrast, when you declare a class like

 interface FLines<T, R> extends Function 

you extend the original Function type and create new parameters of type <T, R> , which are completely useless in your script.

To stay in the examples above, you can implement them as

 StringTransformer reverse = s -> new StringBuilder(s).reverse().toString(); NumberFunc<Integer> dbl = i -> i*2; 

and since they inherit correctly typed methods, you can use them to combine functions:

 Function<String,Integer> f = reverse.andThen(Integer::valueOf).andThen(dbl); System.out.println(f.apply("1234")); 

By applying this to your scenario, you can define interfaces, for example

 interface FLines extends Function<String,List<String>> { @Override default List<String> apply(String fileName) { return getLines(fileName); } public List<String> getLines(String fileName); } interface Join extends Function<List<String>,String> { @Override default String apply(List<String> lines) { return join(lines); } public String join(List<String> lines); } interface CollectInts extends Function<String,List<Integer>> { @Override default List<Integer> apply(String s) { return collectInts(s); } public List<Integer> collectInts(String s); } interface Sum extends Function<List<Integer>, Integer> { @Override default Integer apply(List<Integer> list) { return sum(list); } public Integer sum(List<Integer> list); } 

and reverse engineer your InputConverter to accept only one function, which can be a combined function:

 public class InputConverter<T> { private T value; public InputConverter(T value) { this.value = value; } public <R> R convertBy(Function<? super T, ? extends R> f) { return f.apply(value); } } 

This can be used in a safe manner:

 FLines flines = name -> { try { return Files.readAllLines(Paths.get(name)); } catch(IOException ex) { throw new UncheckedIOException(ex); } }; Join join = list -> String.join(",", list); CollectInts collectInts= s -> Arrays.stream(s.split(",")).map(Integer::parseInt).collect(Collectors.toList()); Sum sum = l -> l.stream().reduce(0, Integer::sum); InputConverter<String> fileConv = new InputConverter<>("LamComFile.txt"); List<String> lines = fileConv.convertBy(flines); String text = fileConv.convertBy(flines.andThen(join)); List<Integer> ints = fileConv.convertBy(flines.andThen(join).andThen(collectInts)); Integer sumints = fileConv.convertBy( flines.andThen(join).andThen(collectInts).andThen(sum) ); 
+4
source

You must change the method signature and insert the last vararg value as a separate parameter.

If you have this parameter as the last one, then you cannot use the vararg parameter, since it should always be the last and should be presented as an array if it is not the last:

 public <T, R> R convertBy(Function[] functions, Function<T, R> special) { } 

If you, however, insist on using varargs, then you can transfer the "special" Function as the first parameter:

 public <T, R> R convertBy(Function<T, R> special, Function... functions) { } 
+2
source

Thanks to everyone who dwells on this issue in detail, your decisions are much better in the real world.

As an author, I would like to publish my solution, which allowed convertBy() not to change the calls to convertBy() int main() one bit. It is very short and ugly, but it works.

Main :

 Function<String, List<String>> flines ... lambda here Function<List<String>, String> join ... lambda here Function<String, List<Integer>> collectInts ... lambda here Function<List<Integer>, Integer> sum ... lambda here String fname = System.getProperty("user.home") + "/LamComFile.txt"; InputConverter<String> fileConv = new InputConverter<>(fname); List<String> lines = fileConv.convertBy(flines); String text = fileConv.convertBy(flines, join); List<Integer> ints = fileConv.convertBy(flines, join, collectInts); Integer sumints = fileConv.convertBy(flines, join, collectInts, sum); System.out.println(lines); System.out.println(text); System.out.println(ints); System.out.println(sumints); List<String> arglist = Arrays.asList(args); InputConverter<List<String>> slistConv = new InputConverter<>(arglist); sumints = slistConv.convertBy(join, collectInts, sum); System.out.println(sumints); 

InputConverter Class:

 public class InputConverter<T> { private T value; public InputConverter(T value) { this.value = value; } public <T, R> R convertBy(Function... functions) { Object result = value; for (int i = 0; i < functions.length; i++) { result = functions[i].apply(result); } return (R) result; } } 
+1
source

All Articles