简体   繁体   中英

Using Lambdas and Generics

I am learning Java 8 and I am trying to use lambdas and generics together and I wrote this small example

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

Few questions I have are

  1. My lambdas are being defined twice (x, y) -> x + y . is it possible to define these only once.

  2. It seems that everytime this code is run, it will box 10 to Integer and then run the code. is it possible that I can define this for int rather than Integer . I tried doing new LambdaTest<int> but it did not work.

  1. No. One is of type BiFunction<Integer, Integer, Integer> , whereas the other is of type BiFunction<Double, Double, Double> . They're thus not compatible with each other.
  2. To avoid the unboxing and the boxing, you would have to use DoubleBinaryOperator and IntBinaryOperator, which use primitive types. But then you would need two different interfaces.
  1. No it is not possible because the lambda are actually different. In the first one, both x and y are of type Integer , whereas in the second one, both x and y are of type Double . Unfortunately, Integer and Double are both Number and the + operation is not defined for general Number .

  2. It is also not possible to use a primitive type with generics. However, you could use IntBinaryOperator : this is a functional interface operating on two int values and returning an int value.

For summing you can use a reference to the corresponding method:

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

Here you can see that it's actually not the same code, just similar.

As for boxing, for float/double types the JIT compiler can inline your lambda, see that boxed value does not escape and eliminate the boxing. For example, let's consider such method:

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

The x64 assembler starts like this:

# {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);

So as you can see, the whole lambda call and boxing is unwinded and simplified to single vaddsd instruction. No calls, no boxing, no memory access, just two xmm registers. Really fast.

Unfortunately for Integer/Long the resulting assembler is much less clean due to caching of some values. The JIT compiler cannot just replace this with simple add , because it's not known exactly that, for example, for cached Integer.valueOf(10) you will actually get the primitive 10 value. Who knows, probably you replaced some values using the reflection to make 2+2=5 , for example. Nevertheless new objects are not created: if the arguments absent in cache, then new Integer is actually not called.

  1. My lambdas are being defined twice (x, y) -> x + y. is it possible to define these only once.

You cannot avoid repeating (x, y) -> x + y however you can avoid creating two LambdaTest instances .

Actually you don't need any LambdaTest instance when using a static generic function instead of a generic class. Here the code:

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. It seems that everytime this code is run, it will box 10 to Integer and then run the code. is it possible that I can define this for int rather than Integer. I tried doing new LambdaTest but it did not work.

As already explained by jb-nizet:

To avoid the unboxing and the boxing, you would have to use DoubleBinaryOperator and IntBinaryOperator, which use primitive types. But then you would need two different interfaces.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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