学习了一些Android类,我意识到方法的大多数变量
都声明为final。

示例代码取自android.widget.ListView类:

/**
* @return Whether the list needs to show the top fading edge
*/
private boolean showingTopFadingEdge() {
    final int listTop = mScrollY + mListPadding.top;
    return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
}

/**
 * @return Whether the list needs to show the bottom fading edge
 */
private boolean showingBottomFadingEdge() {
    final int childCount = getChildCount();
    final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
    final int lastVisiblePosition = mFirstPosition + childCount - 1;
    final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
    return (lastVisiblePosition < mItemCount - 1)
                     || (bottomOfBottomChild < listBottom);
}


在这种情况下使用final关键字的意图是什么?

评论

有人会问:为什么在方法内部声明非最终一次性变量?除非绝对没有必要,否则他们可能将其定为最终决定。我想说这不是一个坏习惯-您更有可能避免几个错误,为某些读者节省几秒钟的时间。另外,经验丰富的C ++黑客使用final可能会产生不冷不热的感觉,尽管感觉并不温暖,就像const。这样做与我所谓的“断言驱动的开发”具有相同的好处。既然会出错的地方也会出错,为什么不为此花便宜的钱呢?国家修改罪魁祸首。

另请参阅developers.stackexchange.com/questions/48413/…

大概是他们可以通过静态代码分析工具,例如PMD。

#1 楼

我想说这是由于习惯造成的。编写此代码的程序员在编写代码时就知道,最终变量的值在赋值后绝不可更改,因此将其定为最终值。任何在分配后将新值分配给最终变量的尝试都将导致编译器错误。

随着习惯的发展,养成习惯也不错。至少使变量为final可以指定程序员在编写本文时的意图。这很重要,因为它可能会使随后编辑代码的程序员在开始更改该变量的使用方式之前先停下来思考一下。

评论


我会添加(并且确实在回答中添加了)默认情况下声明变量final也是一个好习惯,因为它鼓励您也将字段默认设置为final。

–乔·卡纳汉(Joe Carnahan)
11-10-22在11:42

同意与许多其他人相处是一个好习惯。例如:“除非另有要求,否则将所有内容默认设置为最终状态”,“除非另有要求,否则将所有内容默认设置为私有状态”。

–乔恩
2011年12月3日上午10:46

好吧,但是这个习惯背后有很多很好的理由

– sea-rob
2014年2月21日在15:33

国际海事组织称其为“习惯”有负面含义。这是一个好习惯:声明变量的用途。我意识到您同意,只是以为我会把它扔出去。

–乔什·M。
18年6月21日在19:07

#2 楼

作为一名Java开发人员,他默认设置所有变量final(并且赞赏Eclipse可以自动执行此操作),我发现,如果变量被初始化一次且再也没有更改,则更容易对我的程序进行推理。一方面,未初始化的变量不再受关注,因为尝试在初始化之前使用final变量将导致编译错误。这对于嵌套条件逻辑特别有用,在该条件下,我想确保覆盖所有情况:

(提示:否。)当然,此代码甚至不会编译。

还有一点:通常,任何时候只要您知道某个变量始终是正确的,就应该尝试获取您要执行的语言。当然,我们每天都会在指定变量的类型时执行此操作:语言将确保非该类型的值不能存储在该变量中。同样,如果您知道一个变量不应该被重新分配,因为它已经具有整个方法应保留的值,那么可以通过声明final来获得强制执行该限制的语言。

最后,有习惯的问题。其他人提到这是一种习惯(为此,乔恩向+1),但是让我说说你为什么想要这种习惯。如果要在类中声明字段,而不是在方法中声明局部变量,则可能有多个线程同时访问这些字段。有一些晦涩的例外,但是总的来说,如果字段是final,那么使用您的类的每个线程将看到相同的变量值。相反,如果字段不是final且有多个线程正在使用您的类,则您将需要担心使用synchronized块和/或java.util.concurrent中的类进行显式同步。可以进行同步,但是编程已经足够困难。 ;-)因此,如果您只是习惯性地声明了所有内容,那么您的许多字段将是最终字段,并且您将花费最少的时间来担心同步和与并发相关的错误。有关此习惯的更多信息,请查看Joshua Bloch的Effective Java中的“最小化可变性”技巧。

编辑:@Peter Taylor指出,如果删除final关键字,则上面的示例也不会编译,这是完全正确的。当我建议保留所有局部变量为最终变量时,是因为我想使以下示例无法实现:

final int result;
if (/* something */) {
  if (/* something else */) {
    result = 1;
  }
  else if (/* some other thing */) {
    result = 2;
  }
}
else {
  result = 3;
}
System.out.println(result);


使用新变量而不是重用旧的是我如何告诉编译器试图覆盖所有可能性,并且使用final变量迫使我使用新变量而不是回收旧变量。

关于此示例的另一个有效抱怨是,您首先应该避免使用复杂的嵌套条件逻辑。当然,这是正确的,正是因为很难确保按照您的意图涵盖所有案例。但是,有时无法避免复杂的逻辑。当我的逻辑很复杂时,我希望我的变量尽可能简单地进行推理,这可以通过确保变量的值在初始化之后不改变来实现。

评论


一方面,未初始化的变量不再受关注,因为尝试在最终变量初始化之前使用最终变量将导致编译错误。如果删除final关键字使示例得以编译,则说明您使用的是有缺陷的编译器。换句话说,您已经将帖子的一半专用于误导性陈述。 (如果您是根据字段而不是变量来编写的,那么在这种情况下,final会有所作为,但与问题无关紧要,就像您的第三点已经说过一样)。

– Peter Taylor
2011-10-22 14:16

我想到的代码片段的替代方法不仅仅是删除了“ final”一词的相同代码片段。我将编辑我的答案以使备选方案明确。

–乔·卡纳汉(Joe Carnahan)
11-10-23在16:19



好答案。比我的要全面得多。我想推荐有关“有效Java”的建议,特别是关于不可变对象的部分,该部分与讨论有关。

–乔恩
2011年12月3日,10:44

“程序员的才智和逻辑不能衡量他们,而要通过案例分析的完整性来衡量。” -艾伦·珀利斯(Alan Perlis),《编程中的插图》,转载于:cs.yale.edu/quotes.html

–minopret
13年2月12日在20:49

就个人而言,我会将示例写为int result = someFunctionWithComplexLogic(),然后允许编译器确保函数始终返回值。这通常也简化了复杂的逻辑-如果(!/ *某些* /)返回4,则您的示例变为;否则,如果(/ *其他* /)返回1;否则if(/ *其他事情* /)返回2 ;,这比您的原始示例大大简化了。仅通过检查(尽管编译器也会为您验证)来查看所有情况都没有得到处理也很简单。

–crazy2be
13年8月9日在21:54

#3 楼

我们无法确定答案(除非我们询问原始开发人员),但是我的猜测是:


这些方法的程序员可能会觉得添加“ final”更好地表达了这些变量的目的。
程序员可能正在使用一种工具,该工具会在可能的地方自动添加“最终”。我自己,但有兴趣知道这两种方式。


#4 楼

另一个原因是能够在内部匿名类中使用它们:

public interface MyInterface {
    void foo();
} 

void myMethod() {
    final String asdf = "tada";//if not final cannot be used in inner classes
    new MyInterface() {
        @Override
        public void foo() {
             String myNewString = asdf;//does not compile only if asdf is final
        }
    }
}


评论


从Java 8开始,这不再是一个正当的理由:编译器将任何未修改的变量视为有效的final,因此在建议的示例中无需添加final关键字。另请注意,缺少“;”在最后一个“}”之前

–朱利恩·克朗格(Julien Kronegg)
18年5月6日在22:36

@JulienKronegg评论来自5年前,当时还没有发布Java 8。

–随机42
18年5月8日在9:25

#5 楼

减少经常犯的愚蠢错误。 final确保变量始终指向其第一个分配的对象,并且任何尝试的更改都将计为编译时错误。

您还可以问“为什么显式声明变量的类型? ”或“为什么在C ++中将方法声明为常量?”。同样的原因。

#6 楼

原因如下:避免重新分配给局部变量/参数。
这将提高可读性并避免一些愚蠢的错误。

http://sourcemaking.com/refactoring/remove-assignments-to -parameters

摘录:


Java 1.1和更高版本允许您将参数标记为final;
这样可以防止分配给变量。它仍然允许您
修改变量引用的对象。我总是将参数作为最终参数,但我承认我很少在参数列表中对它们进行标记。


#7 楼

老实说,在这种情况下,我认为没有真正的理由做出可变的final。我想他们要么复制并粘贴代码,要么他们真的想阻止向该方法添加更多代码的任何人重新分配这些变量。