使用Lambdas和Generics

时间:2015-09-17 21:58:25

标签: java java-8

我正在学习Java 8,我正在尝试使用lambdas和泛型,我写了这个小例子

import java.util.function.*;

public class LambdaTest<T> {

    public T calculate(T x, T y, BiFunction<T, T, T> func) {
        return func.apply(x, y);
    }

    public static void main(String args[]) {
        LambdaTest<Integer> l = new LambdaTest<Integer>();
        System.out.println("" + l.calculate(10, 10, (x, y) -> x + y));
        System.out.println("" + l.calculate(10, 10, (x, y) -> x * y));
        System.out.println("" + l.calculate(10, 10, (x, y) -> x / y));
        System.out.println("" + l.calculate(10, 10, (x, y) -> x - y));
        LambdaTest<Double> l2 = new LambdaTest<Double>();
        System.out.println("" + l2.calculate(10.0, 10.0, (x, y) -> x + y));
    }
}

我的问题很少

  1. 我的lambdas被定义了两次(x, y) -> x + y。是否有可能只定义一次。

  2. 似乎每次运行此代码时,它都会将10封装到Integer,然后运行代码。是否有可能为int而不是Integer定义此内容。我尝试过做new LambdaTest<int>但是没有用。

4 个答案:

答案 0 :(得分:6)

  1. 否。一个是BiFunction<Integer, Integer, Integer>类型,而另一个是BiFunction<Double, Double, Double>类型。因此它们彼此不相容。
  2. 要避免取消装箱和装箱,您必须使用DoubleBinaryOperator和IntBinaryOperator,它们使用基本类型。但是那时你需要两个不同的接口。

答案 1 :(得分:5)

  1. 不可能,因为lambda实际上是不同的。在第一个中,xy都属于Integer类型,而在第二个中,xy都属于{{1}类型}}。很遗憾,DoubleInteger都是NumberDouble操作未定义为一般+

  2. 也不可能使用带有泛型的基本类型。但是,您可以使用IntBinaryOperator:这是一个功能界面,对两个Number值进行操作并返回int值。

答案 2 :(得分:2)

对于求和,您可以使用对相应方法的引用:

System.out.println("" + l.calculate(10, 10, Integer::sum));
System.out.println("" + l2.calculate(10.0, 10.0, Double::sum));

在这里你可以看到它实际上不是相同的代码,只是类似。

至于装箱,对于float / double类型,JIT编译器可以内联你的lambda,看看盒装值不会逃脱并消除装箱。例如,让我们考虑这样的方法:

public static void testCalculate(double a, double b) {
    LambdaTest<Double> l = new LambdaTest<Double>();
    double d = l.calculate(a, b, (x, y) -> x + y);
    System.out.println(d);
}

x64汇编程序如下所示:

# {method} {0x0000000051710670} 'testCalculate' '(DD)V' in 'LambdaTest'
# parm0:    xmm0:xmm0   = double
# parm1:    xmm1:xmm1   = double
#           [sp+0xa0]  (sp of caller)
mov %eax,-0x6000(%rsp)
push %rbp
sub $0x90,%rsp  ;*synchronization entry
                ; - LambdaTest::testCalculate@-1 (line 10)
vaddsd %xmm1,%xmm0,%xmm2  ;*dadd
                          ; - LambdaTest::lambda$testCalculate$0@8 (line 11)
                          ; - LambdaTest$$Lambda$1/1555845260::apply@8
                          ; - LambdaTest::calculate@3 (line 6)
                          ; - LambdaTest::testCalculate@24 (line 11)
movabs $0x8c900c78,%r10  ;   {oop(a 'java/lang/Class' = 'java/lang/System')}
mov 0x6c(%r10),%r10d
mov (%r10),%rax  ; implicit exception: dispatches to 0x0000000005bd3426
mov %rax,%r11
and $0x7,%r11
mov %r10,%r9  ;*getstatic out
              ; - LambdaTest::testCalculate@35 (line 12)
... the rest is inlined code from PrintStream.println(double);

正如您所看到的,整个lambda调用和装箱是unwinded并简化为单vaddsd指令。没有电话,没有装箱,没有内存访问,只有两个xmm寄存器。真的很快。

不幸的是,对于Integer/Long,由于某些值的缓存,生成的汇编程序更不干净。 JIT编译器不能只用简单的add替换它,因为它并不确切地知道,例如,对于缓存的Integer.valueOf(10),您实际上会获得原始10值。谁知道,例如,您可能使用反射来替换某些值以生成2+2=5。然而,不会创建新对象:如果缓存中没有参数,则实际上不会调用new Integer

答案 3 :(得分:1)

  
      
  1. 我的lambdas定义了两次(x,y) - &gt; x + y。是否可以只定义一次。
  2.   

您无法避免重复(x, y) -> x + y,但可以避免创建两个LambdaTest实例

实际上,在使用静态泛型函数而不是泛型类时,不需要任何LambdaTest实例。代码如下:

import java.util.function.BiFunction;

public class LambdaTest2 {

    public static <T> T calculate(T x, T y, BiFunction<T, T, T> func) {
        return func.apply(x, y);
    }

    public static void main(String args[]) {
        System.out.println("" + calculate(10, 10, (x, y) -> x + y));
        System.out.println("" + calculate(10, 10, (x, y) -> x * y));
        System.out.println("" + calculate(10, 10, (x, y) -> x / y));
        System.out.println("" + calculate(10, 10, (x, y) -> x - y));
        System.out.println("" + calculate(10.0, 10.0, (x, y) -> x + y));
    }
}
  
      
  1. 似乎每次运行此代码时,它都会将10封装到Integer,然后运行代码。是否有可能我可以为int而不是Integer定义它。我尝试了新的LambdaTest,但它没有用。
  2.   

正如jb-nizet所说:

  

要避免拆箱和装箱,您必须使用DoubleBinaryOperator和IntBinaryOperator,它们使用基本类型。但是你需要两个不同的接口。