我发现了一些建议使用的参考文献(例如)final
尽可能多地使用,我想知道它有多重要.这主要是在方法参数和局部变量的上下文中,而不是最终方法或类.对于常数,它显然是有道理的.
一方面,编译器可以进行一些优化,这使得程序员的意图更加清晰.另一方面,它增加了详细程度,优化可能是微不足道的.
这是我应该努力记住的事情吗?
痴迷于:
最终字段 - 将字段标记为最终强制它们在构造结束时设置,使该字段引用不可变.这样可以安全地发布字段,并且可以避免在以后的读取时进行同步.(请注意,对于对象引用,只有字段引用是不可变的 - 对象引用引用的内容仍然可以更改并影响不变性.)
最终的静态字段 - 虽然我现在使用枚举用于我曾经使用静态最终字段的许多情况.
考虑但明智地使用:
最终类 - 框架/ API设计是我认为的唯一案例.
最终方法 - 与最终类基本相同.如果你正在使用模板方法模式,如疯狂和标记最终的东西,你可能过分依赖继承而不是委托.
忽略除非感觉肛门:
方法参数和局部变量 - 我很可能这样做很大程度上是因为我很懒,我发现它使代码混乱.我将完全承认,我不打算修改的标记参数和局部变量是"更严格".我希望这是默认的.但事实并非如此,而且我发现整个决赛的代码更难以理解.如果我在别人的代码中,我不会把它们拉出去,但是如果我正在编写新的代码,我就不会把它们放进去.一个例外是你需要标记最终的东西以便你可以访问它来自一个匿名的内部类.
这是我应该努力记住的事情吗?
不,如果您正在使用Eclipse,因为您可以配置"保存操作"以自动为您添加这些最终修饰符.然后,您可以轻松获得收益.
"最终"的开发时间优势至少与运行时间优势一样重要.它告诉未来的编辑代码有关你的意图.
将类标记为"final"表示您在设计或实现类时没有努力优雅地处理扩展.如果读者可以对课程进行更改,并希望删除"最终"修饰符,则他们可以自行承担风险.这取决于他们确保课程能够很好地处理扩展.
标记变量"final"(并在构造函数中赋值)对依赖项注入很有用.它表示变量的"协作者"性质.
标记方法"final"在抽象类中很有用.它清楚地描绘了扩展点的位置.
我发现标记方法参数和局部作为final
重构辅助工具,当有问题的方法是一个难以理解的混乱几页长.final
自由地撒上,看看编译器(或你的IDE)抛出的"无法分配给最终变量"的错误,你可能会发现为什么名为"data"的变量最终为null,即使几个(过时的)注释发誓可以不会发生.
然后,您可以通过将重用的变量替换为更接近使用点的新变量来修复一些错误.然后你会发现你可以将方法的整个部分包装在范围大括号中,突然间你只需要一个远离"提取方法"的IDE键盘,你的怪物就会更容易理解.
如果你的方法不是一个不可维护的残骸,我想最终可能会有一些东西可以阻止人们把它变成残骸.但如果它是一个简短的方法(参见:不是不可维护的),那么你冒着增加大量冗长的风险.特别是,Java函数签名非常难以适应80个字符,而不是每个参数增加6个字符!
我一直用final
它来使Java更基于表达式.请参阅Java的条件(if,else,switch
)不是基于表达式的,我一直很讨厌,特别是如果您习惯于函数式编程(即ML,Scala或Lisp).
因此,在使用条件时,您应该尝试始终(IMHO)使用最终变量.
让我给你举个例子:
final String name; switch(pluginType) { case CANDIDATE_EXPORT: name = "Candidate Stuff"; break; case JOB_POSTING_IMPORT: name = "Blah"; break; default: throw new IllegalStateException(); }
现在如果添加另一个case
语句而不设置name
编译器将失败.如果你不打破每个案例(你设置变量),编译器也会失败.这使得Java可以使Java与Lisp的let
表达式非常相似,并使得代码不会大量缩进(因为词法范围变量).
正如@Recurse所指出的那样(但显然是-1我)你可以完成前面的工作String name
final
来获取编译器错误(我从来没有说过你不能)但你可以很容易地让编译错误消失后设置名称抛出表达语义的语句或更糟糕的忘记break
你不能导致错误(尽管@Recurse说的话)不使用final
:
String name; switch(pluginType) { case CANDIDATE_EXPORT: name = "Candidate Stuff"; //break; whoops forgot break.. //this will cause a compile error for final ;P @Recurse case JOB_POSTING_IMPORT: name = "Blah"; break; } // code, code, code // Below is not possible with final name = "Whoops bug";
由于bug设置名称(除了忘记break
还有另一个bug)我现在可以意外地执行此操作:
String name; switch(pluginType) { case CANDIDATE_EXPORT: name = "Candidate Stuff"; break; //should have handled all the cases for pluginType } // code, code, code // Below is not possible with final name = "Whoops bug";
最终变量强制单一评估应该是什么名称.类似于具有返回值的函数必须始终返回一个值(忽略异常),名称切换块必须解析名称,从而绑定到该切换块,这使得重构代码块变得更容易(即Eclipe重构:提取方法) .
OCaml中的上述内容:
type plugin = CandidateExport | JobPostingImport let p = CandidateExport let name = match p with | CandidateExport -> "Candidate Stuff" | JobPostingImport -> "Blah" ;;
的match ... with ...
像的功能,即表达式的计算结果.注意它看起来像我们的switch语句.
这是Scheme(球拍或鸡肉)中的一个例子:
(define name (match b ['CandidateExport "Candidate Stuff"] ['JobPostingImport "Blah"]))
好吧,这一切都取决于你的风格...如果你想看到最后你不会修改变量,那么使用它.如果你不喜欢看到它...然后把它留下来.
我个人喜欢尽可能少的冗长,所以我倾向于避免使用非必要的额外关键字.
我喜欢动态语言,所以我喜欢避免冗长也就不足为奇了.
所以,我会说只是选择你倾向的方向,然后继续使用它(无论如何,尽量保持一致).
作为旁注,我参与了使用和不使用这种模式的项目,我发现错误或错误的数量没有差别...我不认为这是一个巨大的模式改进你的错误计数或任何东西,但同样它是风格,如果你喜欢表达你不会修改它的意图,那么继续使用它.
在参数中有用是为了避免意外更改参数值并引入一个微妙的错误.我用了忽略这个建议,但花了4个小时后.在一个可怕的方法(有数百行代码和多个fors,嵌套ifs和各种不良做法)我建议你这样做.
public int processSomethingCritical( final int x, final int y ){ // hundreds of lines here // for loop here... int x2 = 0; x++; // bug aarrgg... // hundreds of lines there // if( x == 0 ) { ... }
当然,在一个完美的世界中,这不会发生,但是......好吧..有时你必须支持其他代码.:(
如果你正在编写一个应用程序,有人必须在1年之后读取代码,那么是的,使用final不应该一直修改的变量.通过这样做,您的代码将更加"自我记录",并且您还可以减少其他开发人员使用本地常量作为本地临时变量等愚蠢行为的机会.
如果你正在编写一些一次性代码,那么,不,不要费心去识别所有的常量并使它们成为最终的.