编译器警告的奇怪设计选择

时间:2015-08-19 11:29:16

标签: java javac compiler-warnings

如果你在一个函数中创建一个匿名类,并尝试在静态类中使用一个参数,那么javac /你的IDE会抛出一个错误,说你不能在匿名类中使用一个变量,除非它被声明为final。 ......但由于java的值语义传递引用,所有参数都是有效的。那么为什么不要'相同的语义适用于这里?

E.g。错误:

public static Optional<Node> getLayerByName(String name) {
    Optional<Node> optional = FluentIterable.from(someCollection).firstMatch(new Predicate<Node>() {
        @Override
        public boolean apply(Node node) {
            return name.equals(node.getProperty(LayerProperties.NAME.name()));
        }
    });
}

但这很好:

public static Optional<Node> getLayerByName(final String name) {
    Optional<Node> optional = FluentIterable.from(someCollection).firstMatch(new Predicate<Node>() {
        @Override
        public boolean apply(Node node) {
            return name.equals(node.getProperty(LayerProperties.NAME.name()));
        }
    });
}

据我所知,这是Javac唯一强制你将函数参数声明为final的时间。由于java的范围规则,由于它们实际上是最终的,所以关于它是否有权将论证宣布为最终,有很多问题。并且通常认为在函数范围内更改参数引用是不好的做法。

我只是好奇为什么设计选择迫使你声明它是最终的,就好像在这个实例中混淆在匿名类使用函数参数时更改函数范围内的引用一样,但它不被考虑令人困惑,自动将所有函数引用声明为final。

编辑 - 这是关于Java选择语义的问题,而不是关于为什么变量具有最终的问题。

E.g。很多来自非Java背景的程序员可能会期待

public static changeReference(String thing){
    thing = "changed";
}

public static void main(String[] args) {
    String thing = "orgininal";
    changeReference(thing)
    System.out.println(thing); //prints original
}

实际打印&#34;更改&#34;。在这个意义上,参数中的所有引用都是final,因为没有任何改变函数内部引用的内容会影响函数外部的引用。如果我们总是把最后一个放进去,它似乎会提高清晰度,那么很明显changeReference函数没有做任何事情,因为编译器会告诉你。但是如果你要允许局部重新引用参数变量,这实际上是否比以下更令人困惑:

public static Optional<Node> getLayerByName(String name, Collection<Node> someCollection) {
    name = "changed"
    Optional<Node> optional = FluentIterable.from(someCollection).firstMatch(new Predicate<Node>() {
        @Override
        public boolean apply(Node node) {
            System.out.println(name);
            return name.equals(node.getProperty(LayerProperties.NAME.name()));
        }
    });
}

public static void main(Collection<Node> someCollection){
    getLayerByName("original", someCollection); // prints "original" never "changed"
}

允许打印&#34;原始&#34;而不是改变?为什么设计师认为这些行为中的一个比另一个更令人困惑,并强迫你处理它的额外语义,或者为什么他们在这两个几乎完全相同的情况下没有强制执行相同的语义?

2 个答案:

答案 0 :(得分:0)

原因: 这样的变量以相同的名称​​复制到匿名类中。那是因为局部变量的生命周期仅限于函数调用。它是堆栈中的一个变量槽,其中放置了值(此处为String对象引用)。内部类实例甚至可以存在于另一个线程中。

所以实际上有两个同名的变量。现在,如果你可以分配给一个变量,那么另一个变量需要一些同步(它仍然存在,即!)。这将不再是一个简单的机器级别的事情,因此决定“变量”必须是有效的最终,以防止进一步的分配。首先是真正的决赛,后来在Java 8中使用lambdas,实际上是最终的。

答案 1 :(得分:0)

在你的评论中,你问,

  

问题是,为什么他们强迫你在这里宣布它,当所有的方法参数无论如何都是最终的。

因此,如果我理解正确,那么你说所有方法参数都是有效的,因为它们只存在于该方法的上下文中,所以你要问为什么在上面提供的例子中,Java会抛出错误,如果你不要将变量声明为最终变量。

我理解的答案是,这种选择是由于在这种情况下使用了内部类或匿名内部类。让内部类在其封闭方法中访问变量使得它看起来好像内部类正在使用相同的变量。实际上,内部类只使用该变量的副本,并且内部类对所述变量所做的任何更改都不会在封闭方法中持久存在。 Java只是想确保开发人员不希望对内部类中的变量进行任何更改以影响内部类之外的变量,并且对于任何毫无戒心的开发人员来说,这正是看起来会发生的事情。

(如果你想多读一点,我最近写了一篇short article。)