一般咱们对Java中final关键字的理解是“用final修饰的变量是不可变的”,若是尝试对final变量屡次赋值,编译器将报错。彷佛final的做用就是保证变量不可变,这没有错,可是若是咱们在Java中灵活应用final的被修饰目标不可变的特性,每每能发掘出不少使人意想不到的效果,而非仅仅保证变量不可变这么粗浅而已。下面咱们来讲说final关键字的多重用法java
用final修饰普通变量一般分为两种状况,修饰普通基本类变量和修饰引用类型变量,也就是对象类型变量。程序员
修饰普通基本类型变量最能清楚直白的表现出final的做用,它能使变量的值没法改变,由于变量不能再次被赋值。编程
final int myInt = 1; myInt = 2;
运行此代码,编译器会报错:安全
Error:(20, 9) java: 没法为最终变量myInt分配值
但咱们使用final修饰引用类型变量时,咱们能够保证变量不能被再次赋值, 但咱们没法保证对象值的改变。多线程
final StringBuilder sb = new StringBuilder("Java"); sb.append("Script"); System.out.println(sb); //resultJavaScript
如上代码所示, 虽然咱们用final修饰变量,但仍旧没法阻止变量内在值的改变。 使用final能保证变量不能改变引用的目标,却不能保证变量所引用的目标自己的变化。由于对于基本类型,咱们能够把变量看做是变量值的自己;而对于引用类型变量,变量和变量的值须要区分看待,它们只是以某种方式被关联起来了而已,事实上它们是不一样的东西,因此final没法同时做用于二者身上。app
2.ide
Java不支持原生常量,在Java种也没有定义常量的const关键字。然而, 咱们可使用final关键字间接的实现常量。函数式编程
public static final int CONST_ONE = 1; public static final int CONST_TWO = 2;
常量是全局的、不可变的,所以咱们同时使用static和final来修饰变量,就能达到定义常量的效果。 常量名一般全由大写字母组成。函数
3.性能
final能够保证明例变量必须被初始化,这点特性能减小代码出错概率,如令全部Java程序员头疼的NPE。
public class Main { private String name ; @Override public String toString() { return name; } public static void main(String[] arg)throws Exception { Main main = new Main(); System.out.println(main.toString().toLowerCase()); } }
以上代码由于没给name赋值,代码在运行起将报NPE异常。假如咱们使用final修饰name变量,代码将没法经过编译,由于Java语法规定,final变量在使用前必须被初始化,所以咱们必须在构造函数中初始化name变量,这样能百分百保证咱们使用的name变量不会是null。
public class Main { private final String name; public Main(String name) { this.name = name; } @Override public String toString() { return name; } public static void main(String[] arg)throws Exception { Main main = new Main("Java"); System.out.println(main.toString().toLowerCase()); } } }
4.
final不只能够修饰变量,还能够修饰方法和类。
若是咱们用final修饰方法,假如方法所属的类被继承,方法将不能在子类中被重写。
class SuperClass{ protected final String getName() { return “supper class”; } @Override public String toString() { return getName(); } } classSubClass extends SuperClass{ protected String getName() { return “sub class”; } }
以上代码没法经过编译,编译器报错
Error:(30,22) java: SubClass中的getName()没法覆盖SuperClass中的getName() 被覆盖的方法为final
由于SuperClass的getName方法被修饰为final,所以在子类中没法被重写。
一般,咱们不但愿方法在被继承时重写,能够用private修饰,由于这样方法的可见性被限制于方法所在的类中。可是,有时候咱们须要公开方法,却又不想方法被重写,此时用final修饰方法就有用武之地了。
然而,这时又引出了另一个问题,假如咱们使用final修饰private方法,是否有实际意义。 事实上,在现代的jdk中,这么作是没有任何意义的,由于private没法被继承,天然也不存在继承时被修改的问题。 可是在早期的Java版本中,final修饰private方法的做用是告知编译器,这个方法在编译时须要内联处理。这个特性在现代jdk中已经被抛弃。
当用final修饰类时, 表示此类是密封的, 没法被继承。从Java源码中可知,咱们最经常使用的String类即是一个final类。
5.
在haskell、F#之类的函数时语言中,变量值默认就是不可变的,仿佛如Java变量默认就是final同样, 这种特性能极大的减小代码出错的概率。许多极难排查的代码错误,都是因为状态改变引发的,即变量值的改变引发的。 若是从源头杜绝, 就能够从根本上消灭全部这类错误,函数式语言也是基于此考虑才把变量不可变做为语言的默认特性,因此函数式编程是无状态的, 这是被证实优势多余缺点的一种特性。
此外, 变量的值一旦不可变,在多线程编程的环境下能保证线程安全,由于变量值不可变,也就不存在多个线程同时竞争资源的问题,代码天然是线程安全的。如String类, 就是以这种模式实现的, 当咱们看到某个字符串被改变, 其实只是生成一个新的字符串而已,旧的字符串并无被修改。固然,这样作会形成必定的性能问题, 二者间如何权衡,须要开发者根据实际状况考虑。
根据现代编程的指导原则, 在Java种定义的任何变量,默认都要加上final关键字, 这么作虽然反直觉,却有好处。退一万步说,至少能让代码的阅读者了解,变量是不可变的, 咱们不用担忧它会产生反作用。