Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to declare method's return type the as return type of last lambda in array passed to the method

I ask for something which I see impossible and I'll delete question if it is.

I have got method:

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

and those functions are :

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 those interfaces return values of different types. I pass lambdas to my convertBy method.

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

Is this is possible?


EDIT:

I've changed the signature of the method and the signature of the methods inside the interface. It works but only if I do cast in the marked places in the main posted below. The weird things it needs cast only in 3 out of 4 method's invocations, I would like to get rid of casts at all in the main.

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 don't understand why compiler understands what sum returns but don't infer what for instance collectInts returns.

like image 811
Yoda Avatar asked Oct 23 '15 09:10

Yoda


2 Answers

It seems, you have some misunderstanding about generic type hierarchies. When you want to extend a generic type, you have to make a fundamental decision about the actual types of the extended class or interface. You may specify exact types like in

interface StringTransformer extends Function<String,String> {}

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

or you can create a generic type which uses its own type parameter for specifying the actual type argument of the super class:

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

Note, how we create a new type parameter N with its own constraints and use it to parametrize the superclass to require its type parameters to match ours.

In contrast, when you declare a class like

interface FLines<T, R> extends Function

you are extending the raw type Function and create new type parameters <T, R> which are entirely useless in your scenario.

To stay at the above examples, you may implement them as

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

and since they inherit properly typed methods, you may use these to combine the functions:

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

Applying this to your scenario, you could define the interfaces like

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 redesign your InputConverter to accept only one function which may 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 type 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)
);
like image 183
Holger Avatar answered Oct 14 '22 01:10

Holger


You have to change the method signature and inline the last vararg value as a separate parameter.

If you have this parameter as the last one, then you won't be able a use vararg parameter, as it has always to be last one and must be represented as an array in case it's not the last one:

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

If you, however, insist to use varargs, then you can move the "special" Function as first parameter:

public <T, R> R convertBy(Function<T, R> special, Function... functions) { }
like image 34
Konstantin Yovkov Avatar answered Oct 14 '22 02:10

Konstantin Yovkov