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) );