2015-08-19 18 views
0

如果你在一个函数内创建一个匿名类,并尝试在静态类中使用一个参数,那么javac/IDE会抛出一个错误,说你不能在匿名类中使用一个变量除非它被声明为最终的......但是由于Java的值传递引用了值语义,所有参数都是有效的。那么为什么不在这里适用相同的语义呢?编译器的奇怪设计选择警告

例如错误:

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命令将迫使你申报的函数参数作为决赛。关于它是否有权将争论宣布为最终的决定,考虑到由于java的范围规则而确实是最终结果,所以在这方面存在很多问题。无论如何,它在函数范围内改变参数引用一般被认为是不好的做法。

我只是很好奇为什么设计选择强制你声明它是最终的,就好像它会混淆在这个实例中改变引用内部函数范围而匿名类使用函数参数,但它不是认为混淆足以自动声明所有函数引用为最终。

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

例如对于很多非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 
} 

实际上打印“已更改”。从这个意义上说,参数中的所有引用都是最终的,因为函数内部引用的任何变化都不会影响函数外部的引用。看起来如果我们总是放入final,它会提高清晰度,那么很明显changeReference函数不会做任何事情,因为编译器会告诉你。但是,如果你允许参数变量的地方rescoping,这真的是比任何如果有更多或更少的困惑:

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" 
} 

被允许的版画“原始”,而不是改变了?为什么设计者认为这些行为中的一个比另一个更容易混淆,并且强加额外的语义来处理它,或者为什么他们在这两个几乎完全相同的情况下不执行相同的语义?

+1

方法参数不能作出最终的,因为它会破坏向后兼容性。不幸。 – Kayaman

+0

[为什么Java内部类需要“最终”外部实例变量?](http://stackoverflow.com/questions/3910324/why-java-inner-classes-require-final-outer-instance-variables) –

+0

我不认为这是重复的 - 我知道为什么参数*有*是最终的,因为方法参数在堆栈上,但对象变量在堆上,所以最终保留了只有一个变量的错觉。问题是,为什么他们强迫你在这里宣布它,当所有的方法论点都是有效的时候。为什么编译器不能默默使用方法参数。 –

回答

0

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

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

0

在您的评论你问,

The question is, why did they force you to declare it here, when all method arguments are effectively final anyway.

所以,如果我理解正确的话,你是说所有的方法参数都是有效的决赛,因为他们只在该方法的情况下存在,所以你会问,为什么在上面提供的示例中,如果您没有将变量声明为final,那么Java会抛出错误。

答案据我所知,这是因为在这种情况下使用内部类或匿名内部类。让内部类在其封闭方法中访问一个变量使得它看起来好像内部类正在使用同一个变量。实际上,内部类只使用该变量的副本,并且内部类对所述变量所做的任何更改都不会保留在封闭方法中。 Java只是希望确保开发人员不希望对内部类中的变量进行任何更改,以便在内部类以外的任何变量以及任何不知情的开发人员实现,这看起来会发生。

(我最近写了一short article关于这个,如果你想读进去多一点点。)