Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do method references to local class constructors in Java 8 work?

Here's sample code that fails to compile in Java 8 (1.8.0_40) but compiles in Eclipse 4.4 JDT standalone compiler (Bundle-Version: 3.10.0.v20140604-1726) and runs successfully:

import java.util.Arrays;

import org.junit.Test;

import static org.junit.Assert.assertEquals;

/**
 * Test method references to local class constructors.
 */
public class LocalClassTest {

  public long sumOfLengths(String[] input) {
    class Sum {
      final long value;

      Sum(long value) {
        this.value = value;
      }

      Sum(Sum left, Sum right) {
        this.value = left.value + right.value;
      }

      Sum add(String s) {
        return new Sum(value + s.length());
      }
    }

    return Arrays.stream(input)
                 .reduce(new Sum(0), Sum::add, Sum::new)
               .value;
  }

  static String[] input =
      {
          "a", "ab", "abc"
      };

  @Test
  public void localClassConstructorMethodRefs() {
    assertEquals(sumOfLengths(input), 6);
  }
}

javac spits out the following error message:

Error:(32, 25) java: incompatible types: cannot infer type-variable(s) U
    (argument mismatch; invalid constructor reference
      cannot access constructor Sum(Sum,Sum)
        an enclosing instance of type LocalClassTest is not in scope)

Reading through JLS 15.9.2 (Determining Enclosing Instances) and 15.13.3 (Run-Time Evaluation of Method References), I can't see why this should fail to compile, and the error message seems outright false since I can manually construct a Sum instance in the same method just fine.

I've found a previous question Constructor reference for inner class fails with VerifyError at runtime that seems relevant, and the linked JDK issue there (https://bugs.openjdk.java.net/browse/JDK-8037404) indicate a series of compiler issues in this area; maybe this is another instance of a more general problem. Nevertheless, I'm not sure I'm reading the spec right; could this work in another way?

like image 759
Cagatay Avatar asked May 19 '15 18:05

Cagatay


People also ask

How does method reference work in Java 8?

Java provides a new feature called method reference in Java 8. Method reference is used to refer method of functional interface. It is compact and easy form of lambda expression. Each time when you are using lambda expression to just referring a method, you can replace your lambda expression with method reference.

What is method reference and constructor references in Java 8?

A method reference requires a target type similar to lambda expressions. But instead of providing an implementation of a method, they refer to a method of an existing class or object while a constructor reference provides a different name to different constructors within a class.

Is constructor reference allowed in Java 8?

A method reference can also be applicable to constructors in Java 8. A constructor reference can be created using the class name and a new keyword. The constructor reference can be assigned to any functional interface reference that defines a method compatible with the constructor.


1 Answers

This does look like a compiler bug dealing with local classes (class declaration within a method). If you change to an inner class (move the class declaration outside the method), then it works fine. You could change it to be a static class at the same time, which would marginally reduce overhead.

(Inner classes and local class both have an implicit reference to the containing class, which means the constructor has a "hidden" first parameter, which you can see with javap. I suspect the compiler knows to compensate for this hidden parameter when using Sum::new for an inner class, but the logic is just missing to do the same for a local class.)

like image 100
Brett Kail Avatar answered Oct 22 '22 11:10

Brett Kail