29

Given the following code:

package com.gmail.oksandum.test;

import java.util.ArrayList;
import java.util.List;

public class Test {

    public static void main(String[] args) {
    }

    public void foo() {
        class LocalFoo {

            LocalFoo(String in) {
                //Some logic
            }

        }

        List<String> ls = new ArrayList<>();
        ls.stream().map(LocalFoo::new); //Line 21
    }

}

my IDE gives me no errors. That is, until I try to build the project and run it. When I do that it gives me a compiler error that looks like this:

Error:(21, 24) java: incompatible types: cannot infer type-variable(s) R
    (argument mismatch; invalid constructor reference
      cannot access constructor LocalFoo(java.lang.String)
        an enclosing instance of type com.gmail.oksandum.test.Test is not in scope)

Now, I figured, given the error message, that this wouldn't happen if foo() were static. And quite right, this only happens if foo() is an instance method. And it only happens if LocalFoo is a local class in the instance method, and only if a constructor reference is used (i.e never a regular method reference).

What's more, if I change line 21 into

ls.stream().map(str -> new LocalFoo(str));

the compiler suddenly gives no error.

So to recap. If I try to use a constructor reference on a local class declared within an instance method, the compiler complains about not being able to access the constructor, about which I am confused.

If someone could shed some light on why this happens it would be appreciated. Thanks.

Rocoty
  • 406
  • 5
  • 11
  • I guess this has to do with the two facts that the Java specifications consider local classes not to have an outer class and that the local class of an instance method still requires an enclosing instance for construction. – Holger Oct 08 '14 at 14:43
  • 2
    [This JDK bug](https://bugs.openjdk.java.net/browse/JDK-8011591) implies constructor references to local classes should compile (that bug is a runtime error related to a local class using inheritance). Local classes seem messy and underspecified in general. – Jeffrey Bosboom Oct 08 '14 at 14:52
  • @Holger But the problem isn't constructing an object of the local class. The problem is using a constructor reference to refer to a constructor of the local class. Or did I misinterpret your comment? Please elaborate. – Rocoty Oct 08 '14 at 15:42
  • 3
    @Rocoty: What is a constructor reference used for? To construct an object. To construct an instance of such a local class, an instance of the outer class is needed, therefore, the constructor has an additional, invisible parameter of the type of the outer class. It’s up to the compiler to fill that parameter with the current outer class instance which works when using ordinary `new` expressions but fails with method references. And I guess, it’s because of that confusing [A local class … is not a member of any class](http://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.3) rule. – Holger Oct 08 '14 at 15:52
  • @Holger Oh. That actually makes a lot of sense. But surely this isn't optimal behaviour from the compiler's side? Wouldn't it be better if the compiler managed to fill that "invisible" parameter for constructor references as well? My point being, is this a bug? – Rocoty Oct 08 '14 at 18:35
  • 1
    @Rocoty: that’s the big question. After reading lots of specifications, I’m still not sure whether it’s possible to say whether it violates the specification. But I think, it should work or a clear statement about it “not working intentionally” should be added to the spec. After all, I don’t see a reason to deny that feature. So the best thing to do is to file a bug report, even when there’s no part of the spec to cite. If that report is closed with “not a bug”, well, then at least we have an answer. – Holger Oct 08 '14 at 18:43
  • 1
    The same code compiles and run perfectly on my PC (ubuntu 14.04, Eclipse Luna and jdk1.8.0_20) wit no error – Morteza Adi Oct 14 '14 at 17:05
  • Do you still get the error if the LocalFoo constructor is declared public? – Code-Apprentice Oct 14 '14 at 21:50
  • 7
    The LocalFoo class *is* a non-static class, and needs an enclosing instance. (The error you get from the JDK compiler is more clear about that than the Eclipse error.) The bug is that the compiler doesn't find the instance, as it would with an ordinary non-static inner class; filed https://bugs.openjdk.java.net/browse/JDK-8144673. – Brian Goetz Dec 04 '15 at 14:42

2 Answers2

7

It looks like LocalFoo is treated somehow like a non-static class. That's why it claims no instance of Test is in scope.

From the tutorial:

Local classes are non-static because they have access to instance members of the enclosing block. Consequently, they cannot contain most kinds of static declarations.

https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html

The method foo() or the class LocalFoo must be static for this to work. But a class inside a method can't be declared as static. So you'd have to move it out of the method if foo() should remain nonstatic (as an inner, static class). Another option is to just use this:
ls.stream().map(s -> new LocalFoo(s));

There should be a way to just say Test.this.LocalFoo, but that doesn't work. And if it did the compiler should also just accept LocalFoo::new.

There is a bug report now: https://bugs.openjdk.java.net/browse/JDK-8144673
(See comment by Brian Goetz)

Community
  • 1
  • 1
Claude Martin
  • 745
  • 6
  • 21
  • 1
    Well, I've also seen Eclipse accept code that it shouldn't. You could improve this answer by trying the OpenJDK command-line compiler, or finding an Eclipse bug report confirming it's an Eclipse bug. – Jeffrey Bosboom Nov 29 '15 at 21:40
  • Yes, it turns out javac 1.8.0_60 doesn't compile it. I have completely rewritten the answer. – Claude Martin Dec 01 '15 at 11:27
  • 1
    I just ran some more tests. The problem seems to be specific to local classes in instance methods only. The fact that it's a non-static class is irrelevant; I ran a test where I declared LocalFoo to be a non-static inner class, and I had no complaints from the compiler. It seems the compiler cannot infer the "hidden" argument of the outer type if the inner class is local to a method, even though it can infer the argument if the inner class isn't local. That's why I think this is a bug in the java compiler and not in any IDE – Rocoty Dec 04 '15 at 13:12
  • So the question is: is this a known bug and is there a bug report? I couldn't find anything. – Claude Martin Dec 05 '15 at 09:21
  • 2
    @СӏаџԁеМаятіи [it's a bug](http://stackoverflow.com/questions/26258412/invalid-constructor-reference-when-using-local-class#comment55933791_26258412http://stackoverflow.com/questions/26258412/invalid-constructor-reference-when-using-local-class#comment55933791_26258412) – assylias Dec 07 '15 at 10:47
-5

One problem is that you are trying to instantiate the Arraylist by specifying the generic type in the list, but not using the same type in the Arraylist.

Use : List ls = new ArrayList();

Also please provide a default constructor for LocalFoo

PipoTells
  • 511
  • 6
  • 11
  • 1
    The compiler infers the parameter type as being the same as specified in the reference type when provided with an empty set of brackets. I could of course try this without using a generic type, but I think you might be missing the essence of the question. – Rocoty Oct 15 '14 at 11:09
  • 2
    Using the raw type `List` just adds another error (`Object cannot be converted to String`). Adding a default constructor for LocalFoo does not resolve the error (because you can't use the default constructor as the `Function` argument to `map` due to arity mismatch). – Jeffrey Bosboom Oct 15 '14 at 19:07
  • 2
    Suggesting to use raw types is a bad idea because you circumvent the type safety of generics. I suggest you read about the diamond operator which was added in Java 7. – Code-Apprentice Oct 19 '14 at 23:52