简体   繁体   English

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

[英]Generic method to perform a map-reduce operation. (Java-8)

How to overload a Function with generic parameter in Java 8? 如何在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();
    }
}

Error: java: name clash: sum(java.util.function.Function<T,java.lang.Double>) and sum(java.util.function.Function<T,java.lang.Integer>) have the same erasure 错误:java:name clash:sum(java.util.function.Function <T,java.lang.Double>)和sum(java.util.function.Function <T,java.lang.Integer>)具有相同的擦除

Benji Weber once wrote of a way to circumvent this. Benji Weber曾写过一种规避这种方法的方法。 What you need to do is to define custom functional interfaces that extend the types for your parameters: 您需要做的是定义扩展参数类型的自定义功能接口:

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

Another way is to use java.util.function.ToIntFunction and java.util.function.ToDoubleFunction instead: 另一种方法是使用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();
    }
}

The example you present in your question has got nothing to do with Java 8 and everything to do with how generics work in Java. 您在问题中提出的示例与Java 8无关,而且与泛型在Java中的工作方式有关。 Function<T, Integer> function and Function<T, Double> function will go through type-erasure when compiled and will be transformed to Function . Function<T, Integer> functionFunction<T, Double> function在编译时将经过类型擦除 ,并将转换为Function The rule of thumb for method overloading is to have different number, type or sequence of parameters. 方法重载的经验法则是具有不同的数量,类型或参数序列。 Since both your methods will transform to take a Function argument, the compiler complains about it. 由于您的两个方法都将转换为采用Function参数,因此编译器会抱怨它。

That being said, srborlongan has already provided one way to resolve the issue. 话虽这么说, srborlongan已经提供了一种解决问题的方法。 The problem with that solution is that you have to keep modifying your Test class for each and every type of operation (addition,subtraction,etc) on different types (Integer,Double, etc). 该解决方案的问题在于您必须在不同类型(Integer,Double等)上为每种类型的操作(加法,减法等)继续修改Test类。 An alternate solution would be to use method overriding instead of method overloading : 另一种解决方案是使用method overriding而不是method overloading

Change the Test class a bit as follows : 稍微更改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);
}

Create a subclass of Test that will add two Integer s. 创建一个Test的子类,它将添加两个Integer

public class MapStringToIntAddtionOperation extends Test<String,Integer> {

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

}

Client code can then use the above code as follows : 然后客户端代码可以使用上面的代码,如下所示:

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

The advantage of using this approach is that your Test class is in line with the open-closed principle. 使用此方法的优点是您的Test类符合open-closed原则。 To add a new operation such as multiplication, all you have to do is add a new subclass of Test and override the operation method to multiply two numbers. 要添加诸如乘法之类的新操作,您只需添加Test的新子类并override operation方法以乘以两个数字。 Club this with the Decorator pattern and you can even minimize the number of sub-classes that you have to create. 使用Decorator模式对其进行分析,您甚至可以最大限度地减少必须创建的子类的数量。

Note The example in this answer is indicative. 注意此答案中的示例是指示性的。 There are a lot of areas of improvement (such as make Test a functional interface instead of an abstract class) which are beyond the scope of the question. 有很多方面的改进(例如make Test是一个功能界面而不是抽象类)超出了问题的范围。

@srborlongan 's solution won't work very well :) @srborlongan的解决方案不会很好用:)

See a similar example - Comparator methods - comparingDouble(ToDoubleFunction) , comparingInt(ToIntFunction) , etc. The methods have different names, because overloading is not a good idea here. 看一个类似的例子 - 比较器方法 - comparingDouble(ToDoubleFunction)comparingInt(ToIntFunction)等。方法有不同的名称,因为重载在这里不是一个好主意。

The reason is, when you do sum(t->{...}) , the compiler is unable to infer which method to call; 原因是,当你做sum(t->{...}) ,编译器无法推断出要调用的方法; actually it needs to resolve method overloading first, to pick one method, before inferring the type of the implicit lambda expression (based on that method's signature) 实际上它需要首先解决方法重载,选择一个方法,然后推断隐式lambda表达式的类型(基于该方法的签名)

This is disappointing. 这令人失望。 In the earlier stage, Java8 had a more sophisticated inference engine, and Comparator had overloaded comparing() methods; 在早期阶段,Java8有一个更复杂的推理引擎, Comparator重载了compare comparing()方法; and sum(t->{...}) would be correctly inferred too. sum(t->{...})也将被正确推断。 Unfortunately, they decided to simply it :( And here we are now. 不幸的是,他们决定简单地说:(现在我们在这里。

Rule of thumb for overloading methods with functional arguments: the arities of the functional interfaces must be different, unless both are 0. 使用函数参数重载方法的经验法则:功能接口的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 )

The reason why overloading with arity 0 is OK is that the lambda expressions won't be implicit - all argument types are known (because there's no argument!), we don't need contextual information for inferring the lambda type 使用arity 0进行重载的原因是lambda表达式不是隐式的 - 所有参数类型都是已知的(因为没有参数!),我们不需要上下文信息来推断lambda类型

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM