执行 map-reduce 操作的通用方法。 (Java-8)
Generic method to perform a map-reduce operation. (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
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();
}
}
您在问题中提出的示例与 Java 8 无关,而与泛型在 Java 中的工作方式无关。 Function<T, Integer> function
和Function<T, Double> function
在编译的时候会经过type-erasure,会转化为Function
。方法重载的经验法则是具有不同数量、类型或顺序的参数。由于您的两个方法都将转换为采用 Function
参数,因此编译器会抱怨它。
也就是说,srborlongan 已经提供了一种解决问题的方法。该解决方案的问题在于,您必须针对不同类型(Integer、Double 等)的每种类型的操作(加法、减法等)不断修改 Test
class。另一种解决方案是使用 method overriding
而不是 method overloading
:
将 Test
class 稍微更改如下:
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
的子class,这将添加两个 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
class符合open-closed
原则。要添加乘法等新运算,只需添加一个新的subclass of Test
and override
the operation
method to multiply two numbers。将此与 Decorator 模式结合起来,您甚至可以最大限度地减少必须创建的子 classes 的数量。
注意 此答案中的示例是指示性的。有很多方面的改进(例如使 Test
成为功能接口而不是抽象 class)超出了问题的范围。
@srborlongan 的解决方案不会很好用:)
查看类似示例 - Comparator 方法 - comparingDouble(ToDoubleFunction)
、comparingInt(ToIntFunction)
等。这些方法有不同的名称,因为重载在这里不是一个好主意。
原因是,当您执行sum(t->{...})
时,编译器无法推断调用哪个方法;实际上它需要先解决方法重载,选择一个方法,然后推断隐式 lambda 表达式的类型(基于该方法的签名)
这真令人失望。前期,Java8有更复杂的推理引擎,Comparator
重载了comparing()
方法; sum(t->{...})
也可以正确推断。不幸的是,他们决定简化它:( 现在我们到了。
使用函数参数重载方法的经验法则:函数接口的参数必须不同,除非两者都为 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
如何在 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
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();
}
}
您在问题中提出的示例与 Java 8 无关,而与泛型在 Java 中的工作方式无关。 Function<T, Integer> function
和Function<T, Double> function
在编译的时候会经过type-erasure,会转化为Function
。方法重载的经验法则是具有不同数量、类型或顺序的参数。由于您的两个方法都将转换为采用 Function
参数,因此编译器会抱怨它。
也就是说,srborlongan 已经提供了一种解决问题的方法。该解决方案的问题在于,您必须针对不同类型(Integer、Double 等)的每种类型的操作(加法、减法等)不断修改 Test
class。另一种解决方案是使用 method overriding
而不是 method overloading
:
将 Test
class 稍微更改如下:
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
的子class,这将添加两个 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
class符合open-closed
原则。要添加乘法等新运算,只需添加一个新的subclass of Test
and override
the operation
method to multiply two numbers。将此与 Decorator 模式结合起来,您甚至可以最大限度地减少必须创建的子 classes 的数量。
注意 此答案中的示例是指示性的。有很多方面的改进(例如使 Test
成为功能接口而不是抽象 class)超出了问题的范围。
@srborlongan 的解决方案不会很好用:)
查看类似示例 - Comparator 方法 - comparingDouble(ToDoubleFunction)
、comparingInt(ToIntFunction)
等。这些方法有不同的名称,因为重载在这里不是一个好主意。
原因是,当您执行sum(t->{...})
时,编译器无法推断调用哪个方法;实际上它需要先解决方法重载,选择一个方法,然后推断隐式 lambda 表达式的类型(基于该方法的签名)
这真令人失望。前期,Java8有更复杂的推理引擎,Comparator
重载了comparing()
方法; sum(t->{...})
也可以正确推断。不幸的是,他们决定简化它:( 现在我们到了。
使用函数参数重载方法的经验法则:函数接口的参数必须不同,除非两者都为 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