一切都结束了

Posted

技术标签:

【中文标题】一切都结束了【英文标题】:Everything's Final 【发布时间】:2011-04-03 07:33:06 【问题描述】:

我一直在使用 PMD 来帮助发现我的 Java 代码中的潜在问题,并且我发现它的建议是在有用的、特殊的和“WTF?!”之间进行区分。

它一直告诉我要做的一件事是对我可以附加到的每个变量(包括输入参数)使用 final 关键字。对于实际的常量,这似乎是明智的,但对于其他东西,我觉得它很奇怪,甚至可能适得其反。

在每个变量声明上挂上final 是否有具体的优点/缺点?

【问题讨论】:

见***.com/questions/137868/… 【参考方案1】:

以下是为什么将几乎所有内容标记为 final

的一些原因

最终常数

 public static class CircleToolsBetter 
     public final static double PI = 3.141;
        public double getCircleArea(final double radius) 
          return (Math.pow(radius, 2) * PI);
        
    

这可以用于代码的其他部分或由其他类访问,这样,如果您要更改值,就不必一一更改。

最终变量

public static String someMethod(final String environmentKey) 
    final String key = "env." + environmentKey;
    System.out.println("Key is: " + key);
    return (System.getProperty(key));

  


在此类中,您构建了一个作用域 final 变量,该变量将前缀添加到参数 environmentKey。在这种情况下,最终变量仅在执行范围内是最终的,在方法的每次执行时都是不同的。每次输入方法时,都会重新构建最终的方法。一经构造,在方法执行范围内无法更改。这允许您在方法的持续时间内修复方法中的变量。见下文:

public class FinalVariables 


  public final static void main(final String[] args) 
    System.out.println("Note how the key variable is changed.");
    someMethod("JAVA_HOME");
    someMethod("ANT_HOME");
  

最终常数

public double equation2Better(final double inputValue) 
    final double K = 1.414;
    final double X = 45.0;

double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;         
if (result > 360) 
  powInputValue = X * Math.sin(result); 
 else 
  inputValue = K * Math.sin(result);   // <= Compiler error   

当您有很长的代码行时,这些特别有用,并且它会产生编译器错误,因此当有人意外更改不应更改的变量时,您不会遇到逻辑/业务错误。

最终合集

当我们谈论集合时的不同情况,您需要将它们设置为不可修改的。

 public final static Set VALID_COLORS; 
    static 
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    

否则,如果您不将其设置为不可修改:

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

Final ClassesFinal Methods 不能分别扩展或覆盖。

编辑:解决关于封装的最后一类问题:

有两种方法可以使类最终化。第一种是在类声明中使用关键字final:

public final class SomeClass 
  //  . . . Class contents

使类成为final的第二种方法是将其所有构造函数声明为私有:

public class SomeClass 
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) 
  

如果发现它实际上是一个final,那么将它标记为final可以为您省去麻烦,以演示查看这个Test类。乍一看似乎是公开的。

public class Test
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception
    //  . . . snip . . . 
  

不幸的是,由于该类的唯一构造函数是私有的,因此无法扩展该类。在 Test 类的情况下,没有理由认为该类应该是最终的。测试类是隐式 final 类如何导致问题的一个很好的例子。

因此,当您通过将其构造函数设为私有来隐式将类设为 final 时,应将其标记为 final。

【讨论】:

【参考方案2】:

“你可以做的每一个变量声明”听起来有点极端,但final 实际上在很多方面都是有益的。有时我希望final 是默认行为,并且不需要关键字,但真正的“变量”需要variable 修饰符。 Scala 通过 valvar 关键字采用了类似的方法——强烈建议使用 valfinal-like 关键字)。

仔细考虑每个成员变量是finalvolatile,还是两者都不是,这一点尤为重要,因为类的线程安全取决于是否正确。分配给finalvolatile 变量的值始终对其他线程可见,无需使用synchronized 块。

对于局部变量,它并不那么重要,但是使用final 可以帮助您更清楚地推理您的代码并避免一些错误。如果您不希望在方法中更改值,请使用final 说明,并让编译器发现未注意到的违反此期望的违规行为。我目前不知道有什么,但很容易想象 JIT 编译器也可以使用这个提示来提高性能。

在实践中,我不会尽可能声明局部变量final。我不喜欢视觉上的混乱,而且看起来很麻烦。但是,这并不意味着这不是我应该做的事情。

已提出将var 关键字添加到Java 以支持类型推断的建议。但作为该提案的一部分,已经有许多关于指定局部变量不变性的其他方法的建议。例如,一个建议是还添加关键字val 来声明具有推断类型的不可变变量。或者,有些人主张同时使用finalvar

【讨论】:

事后看来,这本来是最好的。但是,它会破坏 C 语义,而 C 语义是明确寻求为 C++ 程序员提供轻松过渡的。 JIT 编译器根本不需要在局部变量上使用final,而 从不 需要;它可以查看每个变量是否实际被修改。当然,它看到的和程序员看到的可能完全不同…… final 在不同步的情况下不保证线程安全。如果在其他线程迭代列表时更改列表的大小,最终列表可能会抛出 ConcurrentModificationException。复杂的对象也是如此,比如 DAO。即使 DAO 本身是 final 的,某些线程可能会在其他线程读取属性时弄乱属性,并产生不一致的状态,因此您需要一个线程安全的 DAO / List 来确保 final 保证线程安全 @DGoiko 我没有说是这样,但感谢您的澄清。 哦,对不起,英语不是我妈妈的语言,我可能听起来很粗鲁。我只是想做一个旁注【参考方案3】:

final 告诉读者,首先分配的值或引用在以后的任何时候都是相同的。

由于在这种情况下所有可以是最终的都是最终的,所以 缺少 最终会告诉读者值 稍后会发生变化,并考虑到这一点。

【讨论】:

这对于原语或像字符串这样的不可变对象是有意义的。但这会鼓励对可变对象产生虚假的安全感吗? 一个缺失的final只会告诉你它可以在以后改变,当然不会。假设是完全错误的。如果一个类未定义为 final,则它可能是子类,但这并不意味着它是。 final 对所有变量的使用是有争议的(经常在这里),因此它的缺失可以很容易地归因于编码器的偏好。 @Robin,请注意 OP 说“一切都结束了”。这就是我正在讨论的情况。请在否决答案之前正确阅读问题。 @BlairHippo,仅当您不了解引用本身和被引用对象之间的区别时。你得到保证的是你拥有的东西是一样的——东西里面是什么是另一回事(但在这种特殊情况下,很有可能还有很多决赛)【参考方案4】:

PMD 还有一些选项规则,你可以打开它来抱怨final;这是一个任意规则。

如果我正在执行一个项目,其中 API 被导出到另一个团队 - 或世界 - 保留 PMD 规则不变。如果您只是在开发将永远是封闭 API 的东西,请禁用该规则并节省一些时间。

【讨论】:

【参考方案5】:

这是 PMD 等工具的常见习语。例如,下面是 Checkstyle 中对应的规则。这真的是风格/偏好的问题,你可以为双方争论。

在我看来,对方法参数和局部变量(如果适用)使用 final 是一种很好的风格。 “为扩展而设计”成语值得商榷。

http://checkstyle.sourceforge.net/config_misc.html#FinalParameters http://checkstyle.sourceforge.net/config_design.html#DesignForExtension http://checkstyle.sourceforge.net/config_coding.html#FinalLocalVariable

【讨论】:

以上是关于一切都结束了的主要内容,如果未能解决你的问题,请参考以下文章

奔波真是辛苦啊,然而生命终将逝去,只希望当一切都结束的时候,能够没有遗憾吧。

奔波真是辛苦啊,然而生命终将逝去,只希望当一切都结束的时候,能够没有遗憾吧。

C:函数结束后丢失char**的内容[重复]

歌曲结束后如何让音频播放器播放下一首歌曲?

JavaScript 在结束行添加“·”字符

C++ Aborted core 在执行结束时转储