执行map-reduce操作的通用方法。 (爪哇-8)

时间:2015-06-14 06:47:59

标签: java function generics java-8 overloading

如何在Java 8中使用泛型参数重载函数?

public class Test<T> {

    List<T> list = new ArrayList<>();

    public int sum(Function<T, Integer> function) {
        return list.stream().map(function).reduce(Integer::sum).get();
    }


    public double sum(Function<T, Double> function) {
        return list.stream().map(function).reduce(Double::sum).get();
    }
}
  

错误:java:name clash:   sum(java.util.function.Function&lt; T,java.lang.Double&gt;)和   sum(java.util.function.Function&lt; T,java.lang.Integer&gt;)具有相同的擦除

3 个答案:

答案 0 :(得分:6)

Benji Weber once wrote of a way to circumvent this.您需要做的是定义扩展参数类型的自定义功能接口:

public class Test<T> {

    List<T> list = new ArrayList<>();

    @FunctionalInterface
    public interface ToIntFunction extends Function<T, Integer>{}
    public int sum(ToIntegerFunction function) {
        return list.stream().map(function).reduce(Integer::sum).get();
    }


    @FunctionalInterface
    public interface ToDoubleFunction extends Function<T, Double>{}
    public double sum(ToDoubleFunction function) {
        return list.stream().map(function).reduce(Double::sum).get();
    }
}

另一种方法是使用java.util.function.ToIntFunction和java.util.function.ToDoubleFunction代替:

public class Test<T> {

    List<T> list = new ArrayList<>();

    @FunctionalInterface
    public int sum(ToIntFunction function) {
        return list.stream().mapToInt(function).sum();
    }

    public double sum(ToDoubleFunction function) {
        return list.stream().mapToDouble(function).sum();
    }
}

答案 1 :(得分:3)

您在问题中提出的示例与Java 8无关,而且与泛型在Java中的工作方式有关。 Function<T, Integer> functionFunction<T, Double> function在编译后会经过type-erasure,并会转换为Function。方法重载的经验法则是具有不同的数量,类型或参数序列。由于您的两个方法都将转换为采用Function参数,因此编译器会抱怨它。

话虽如此,srborlongan已经提供了一种解决问题的方法。该解决方案的问题在于您必须在不同类型(整数,双精度等)上为每种类型的操作(加法,减法等)继续修改Test类。另一种解决方案是使用method overriding代替method overloading

稍微更改Test类,如下所示:

public abstract class Test<I,O extends Number> {

    List<I> list = new ArrayList<>();

    public O performOperation(Function<I,O> function) {
        return list.stream().map(function).reduce((a,b)->operation(a,b)).get();
    }

    public void add(I i) {
        list.add(i);
    }

    public abstract O operation(O a,O b);
}

创建一个Test的子类,它将添加两个Integer

public class MapStringToIntAddtionOperation extends Test<String,Integer> {

    @Override
    public Integer operation(Integer a,Integer b) {
        return a+b;
    }

}

客户端代码可以使用上面的代码,如下所示:

public static void main(String []args) {
    Test<String,Integer> test = new MapStringToIntAddtionOperation();
    test.add("1");
    test.add("2");
    System.out.println(test.performOperation(Integer::parseInt));
}

使用此方法的优点是您的Test类符合open-closed原则。要添加乘法等新操作,您只需添加Testoverride operation方法的新子类,即可将两个数相乘。使用Decorator模式对此进行管理,您甚至可以最大限度地减少必须创建的子类的数量。

注意此答案中的示例是指示性的。有很多方面的改进(比如make Test是一个功能界面而不是抽象类),这些都超出了问题的范围。

答案 2 :(得分:0)

@srborlongan的解决方案不能很好地运作:)

查看类似示例 - Comparator方法 - comparingDouble(ToDoubleFunction)comparingInt(ToIntFunction)等。方法名称不同,因为重载不是一个好主意。

原因是,当你执行sum(t->{...})时,编译器无法推断调用哪个方法;实际上它需要首先解决方法重载,选择一个方法,然后推断出隐含的lambda表达式的类型(基于该方法的签名)

这令人失望。在早期阶段,Java8有一个更复杂的推理引擎,Comparator有重载comparing()方法;并且sum(t->{...})也会被正确推断。不幸的是,他们决定简单地说:(现在我们在这里。

使用功能参数重载方法的经验法则:功能接口的arities必须不同,除非两者都是0。

// OK, different arity
m1( X->Y )
m1( (X1, X2)->Y )

// not OK, both are arity 1
m2( X->Y )
m2( A->B )

    m2( t->{...} ); // fail; type of `t` cannot be inferred 

// OK! both are arity 0
m3( ()->Y )
m3( ()->B )

使用arity 0进行重载的原因是lambda表达式不会是隐式的 - 所有参数类型都是已知的(因为没有参数!),我们不需要上下文推断lambda类型的信息

m3( ()-> return new Y() );   // lambda type is ()->Y
m3( ()-> return new B() );   // lambda type is ()->B