7

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?

Community
  • 1
  • 1
Cagatay
  • 1,372
  • 1
  • 12
  • 16
  • 2
    It’s the same problem as described in [this question](http://stackoverflow.com/q/26258412/2711488), however, there is no solution yet. Well, besides using a lambda expression instead of the method reference… – Holger May 20 '15 at 08:10

1 Answers1

4

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.)

Brett Kail
  • 33,593
  • 2
  • 85
  • 90
  • Right; for the example's sake I've just used a simple local class. In my actual case, I have a more involved one that does require access to the method parameters, and I'd rather have avoided the indirection in code required for the workaround. – Cagatay May 19 '15 at 19:44
  • 1
    Given the bug, I don't think there's any way to avoid that, so I think your best option is to submit a bug report and apply the workaround here. – Brett Kail May 19 '15 at 20:34