概述
final是java中一个保留的关键字,也被视为一个修饰符(Modifier),可以修饰一个成员变量、方法参数、方法甚至一个类。
final变量(域)
凡是用final关键字修饰的成员变量或本地变量都可以称为final变量,即常量。被final修饰后的变量是只读的,即一旦初始化后就不允许重新赋值。但如果final修饰的是一个对象的引用,则对象本身的内容是可以改变的,不可改变的是final对象不允许再指向其他对象。 在修饰变量时,final经常和其好基友final一起出现,如声明一个日志对象时:
private final static Logger LOG = LoggerFactory.getLogger(Abc.class);
LOG被定义为static,使得这个变量与当前类Abc绑定,避免每次都new一个新对象,造成资源浪费;同时LOG被定义成final,可以有效防止无意中将LOG变量重新赋值。
在包括Math在内的工具类中,一般都包含很多final域,如:
public final class Math {
...
public static final double E = 2.7182818284590452354;
public static final double PI = 3.14159265358979323846;
...
}
常量分类
- 全局常量:类的公开静态常量,使用
public static final修饰 - 类内常量:类的私有静态属性,使用
private static final修饰 - 局部常量:包括方法常量和参数常量,前者包括方法体内和代码块内定义的常量。
命名规约:
- 全局常量和类内常量使用字母全部大写、单词之间加下划线的方式
- 局部变量采用小驼峰形式
final入参
首先需要明确一点java中只有按值传递,没有按引用传递。
基本类型参数
对于基本数据类型,按值传递调用函数时并不会改变在原函数中的值。所以使用final修饰的目的不是为了防止调用函数对原数值造成副作用,而仅仅是为了防止用户无意间对入参重新赋值,而可能引发的一些难以发现的BUG。
引用类型参数
与基本类型参数相似,使用final修饰引用类型入参时,如果在方法内对该变量重新赋值,会导致编译失败。
public static void changeMe(int i){
i = 123; // OK,但不会更改实参的值
}
public static void changeMe(final int i) {
i = 123; // 编译失败
}
public static void changeMe(BookBean book) {
book = new BookBean(); // OK,但是一般不会这样使用,没啥意义。最好为新new的对象启用一个新的变量名
}
public static void changeMe(final BookBean book){
book = new BookBean(); // 编译失败
}
final方法
使用final修饰的方法,不能被子类override。意味着你认为这个方法已经很nice了,子类没有必要再去修改了。从性能角度考虑,final方法相比非final方法要更快,因为前者是在编译阶段就已经被静态绑定了,不需要在运行期间再去动态绑定。java.lang.Object类中大部分方法都是final修饰的,如:
public final native Class<?> getClass();
public final native void notify();
final类
final方法是不能被子类重写,相似地,final类则表明该类不能被子类继承。java中有许多类都是被final修饰的,如String类、包装类、Objects。这些类都属于不可变类,即只能创建只读对象 。如果我们尝试去extends一个String类,编译器就不会同意。
// String类定义
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence { ... };
// Integer类定义
public final class Integer
extends Number implements Comparable<Integer> { ... }
// Objects类定义
public final clas Objects { ... }
效率与安全
- 当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。也就是说在用到该final变量的地方,相当于直接访问的这个常量,不需要在运行时确定。
- 不可变类可以提供一些静态方法,他们把频繁被请求的实例缓存起来,从而当现有实现可以符合请求的时候,就不必创建新的实例。所有基本类型的包装类型和BigInteger都有这样的静态工厂。使用这样的静态工厂也使得客户端之间可以共享现有的实例,从而降低内存占用率和GC的回收成本。如Integer的内部类IntegerCache就提供了实例的缓存机制。详见Integer的内部类IntegerCache。
- final修饰的不可变类创建的不可变对象是线程安全的,它们不要求同步。当多个线程并发访问这样的对象时它们不会被破坏,这无疑是获得线程安全最容易的办法,即不可变对象可以被自由地共享。--《Effective Java》
20190119更新:常量的分类及命名规约