在使用本地类时出现无效的构造函数引用?

29

考虑下面的代码:

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
    }

}

我的IDE没有任何错误。也就是说,在我尝试构建和运行项目之前没有问题。但是当我这样做时,它会给我一个编译器错误,看起来像这样:

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)

鉴于错误信息,我想,如果foo()是静态方法,这种情况就不会发生。而且完全正确,只有当foo()是实例方法时才会发生这种情况。只有在实例方法中LocalFoo是本地类并且使用构造函数引用时才会发生(即从不是常规方法引用)。

更重要的是,如果我将第21行改为

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

编译器突然没有报错了。

简单概括一下,如果我在实例方法内声明一个局部类并尝试使用构造函数引用,编译器就会抱怨无法访问构造函数,这让我感到困惑。

如果有人能解释一下为什么会发生这种情况,将不胜感激。谢谢。


2
这个JDK的bug意味着对于本地类的构造函数引用应该编译(该bug是与使用继承的本地类相关的运行时错误)。总体而言,本地类似乎很混乱且规范不足。 - Jeffrey Bosboom
3
构造函数引用的作用是构造一个对象。为了构造这样一个局部类的实例,需要外部类的一个实例,因此构造函数有一个额外的、不可见的参数,类型为外部类的类型。编译器可以使用当前外部类实例来填充该参数,在使用普通的 new 表达式时可以正常工作,但在使用方法引用时会失败。我猜这是因为 局部类不属于任何类 这个令人困惑的规则导致的。 - Holger
1
@Rocoty:这是个大问题。阅读了很多规范后,我仍然不确定是否可以说它违反了规范。但我认为,它应该能够工作,或者规范中应该明确说明“有意不工作”。毕竟,我没有理由拒绝那个功能。所以最好的做法是提交一个错误报告,即使没有规范的部分可引用。如果该报告被关闭并标记为“非错误”,那么至少我们有了答案。 - Holger
1
相同的代码在我的电脑上(ubuntu 14.04,Eclipse Luna和jdk1.8.0_20)编译并运行得非常完美,没有任何错误。 - Morteza Adi
7
本地Foo类非静态类,需要一个封闭实例。(来自JDK编译器的错误比Eclipse错误更清晰。)错误在于编译器找不到实例,就像普通的非静态内部类一样;已经提交了https://bugs.openjdk.java.net/browse/JDK-8144673。 - Brian Goetz
显示剩余4条评论
2个回答

7
看起来LocalFoo被当作非静态类处理了。这就是为什么它声称Test的任何实例都不在范围内的原因。
来自教程:
局部类是非静态的,因为它们可以访问封闭块的实例成员。因此,它们不能包含大多数类型的静态声明。

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

要使这个方法起作用,foo() 方法或者 LocalFoo 类必须是静态的。但是方法内部的类无法声明为静态的。因此,如果 foo() 仍应保持为非静态 (作为内部静态类),则必须将其移出该方法。
另一个选择是只需使用这个:
ls.stream().map(s -> new LocalFoo(s)); 应该有一种方法可以简单地说 Test.this.LocalFoo,但那并不起作用。如果它起作用,编译器也应该接受 LocalFoo::new
现在有一个错误报告:https://bugs.openjdk.java.net/browse/JDK-8144673
(请参见 Brian Goetz 的评论)

1
嗯,我也见过 Eclipse 接受不应该接受的代码。你可以尝试使用 OpenJDK 命令行编译器来改进这个答案,或者找到一份 Eclipse 的错误报告来确认这是一个 Eclipse 的 bug。 - Jeffrey Bosboom
是的,结果javac 1.8.0_60无法编译它。我已经完全重写了答案。 - Claude Martin
1
我刚刚进行了更多的测试。问题似乎只出现在实例方法中的本地类中。它是一个非静态类并不重要;我进行了一项测试,其中声明LocalFoo为非静态内部类,并且编译器没有抱怨。似乎编译器无法推断出外部类型的“隐藏”参数,如果内部类是方法的本地类,即使它可以推断出内部类不是本地类的参数。这就是为什么我认为这是Java编译器中的错误,而不是任何IDE中的错误。 - Rocoty
所以问题是:这是一个已知的错误吗?是否有错误报告?我找不到任何信息。 - Claude Martin
2
@СӏаџԁеМаятіи 这是一个bug - assylias

-5
一个问题是您尝试通过在列表中指定泛型类型来实例化Arraylist,但是没有在Arraylist中使用相同的类型。
请使用: List ls = new ArrayList();
同时,请为LocalFoo提供默认构造函数。

1
编译器在提供了一组空括号时,会将参数类型推断为与引用类型指定的类型相同。当然,我也可以尝试不使用泛型类型来实现这个问题,但我认为你可能会错过问题的本质。 - Rocoty
2
使用原始类型 List 只会添加另一个错误 (Object cannot be converted to String). 为 LocalFoo 添加默认构造函数并不能解决这个问题 (因为你不能将默认构造函数作为 mapFunction 参数,由于它们的参数数量不匹配). - Jeffrey Bosboom
2
建议使用原始类型是一个坏主意,因为这样会绕过泛型的类型安全性。我建议你阅读一下Java 7中添加的钻石操作符。 - Code-Apprentice

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接