final 关键字示意的不可变的。上面探讨 final 关键字应用的三种场合:数据、办法以及类。
final 数据
1、final 属性
程序中常常须要用到一些“常数”。常数次要利用于两个方面:
编译期常数,永远不会扭转
在运行期初始化一个值,不心愿它产生扭转。
对于编译期的常数,计算能够在编译期间提前执行,能够将常数值间接用于程序中。Java 中,这种常数必须是根本数据类型。前置关键字 final 申明。定义时必须提供一个值。
class Person {
final String name; // name 未初始化,编译出错
}
如果对对象句柄应用 final,final 会将句柄变成一个常数。进行申明时,必须将句柄初始化到一个具体的对象,而且不能将句柄指向另一个对象。
复制代码
class Person {
String name = "张三";
}
public class FinalDemo {
public static void main(String[] args) {final Person p = new Person();
p = new Person(); // Error: 无奈为最终变量 p 调配值}
}
复制代码
然而,对象自身是能够批改的。
复制代码
class Person {
String name = "张三";
}
public class FinalDemo {
public static void main(String[] args) {final Person p = new Person();
p.name = "萧萧弈寒";
}
}
复制代码
一个可能的后果:
name = fd1,i1 = 0, i2 = 6
name = fd2,i1 = 8, i2 = 6
i1,i2 是在运行期间随机产生的数据。
2、空白 final
Java1.1 容许创立“空白 final”, 它们属于非凡字段。只管被申明为 final,然而却未失去一个初始值。即便如此,空白 final 还是必须在应用之前失去初始化。示例:
复制代码
class Person {}
public class FinalDemo {
final int i;
final Person p;
FinalDemo() {
i = 1;
p = new Person();}
FinalDemo(int x) {
i = x;
p = new Person();}
public static void main(String[] args) {FinalDemo fd = new FinalDemo();
}
}
复制代码
当初强行要求对 final 进行赋值解决,要么在定义字段时应用一个表达式,要么在每个构建器中。
3、用 final 润饰参数
查了一些材料,很多人都说用 final 润饰办法参数是避免参数在调用时被批改。集体认为这种说法其实有两种了解:一种是变量的理论值不会被批改,另一种是在办法外部不能被批改。无论是基本参数类型还是援用类型,前一种说法都是谬误的。因为 Java 是值传递。
复制代码
public class FinalDemo {
static void f(final int i) {i++; // 无奈为 final 变量赋值,编译谬误}
public static void main(String[] args) {
int x = 10;
f(x); // ①
}
}
复制代码
①处调用的 f 办法只是将 x 的值赋给了 i,实际上 i 和 x 是两个变量。
再看上面的例子:
复制代码
class Person {
String name = "张三";
}
public class FinalDemo {
public static void main(String[] args) {final Person p = new Person();
changeName(p);
System.out.println(p.name);
}
static void changeName(final Person p) {p.name = "萧萧弈寒";}
}
复制代码
【运行后果】:
萧萧弈寒
由此阐明,final 并不能阻止 changeName()办法扭转 p 的内容。接下来,咱们删除润饰参数的 final,而后在 changeName 办法体内扭转 p 指向的实例:
复制代码
class Person {
String name = "张三";
}
public class FinalDemo {
public static void main(String[] args) {final Person p = new Person();
p.name = "萧萧弈寒";
changeName(p);
System.out.println(p.name);
}
static void changeName(Person p) {p = new Person();
}
}
复制代码
【运行后果】:
changeName 中的 name: 张三
萧萧弈寒
咱们能够看出, 尽管办法体内的 p 指向了其余对象,然而对于 main 办法中的 p 并没有影响。起因还是 Java 是值传递的。具体的请参考 Java 值传递还是援用传递?
final 办法
final 办法次要有两个方面的作用:一种是避免任何继承类笼罩办法。若心愿一个办法的行为在继承期间放弃不变,不可被笼罩和改写,就能够采取这种做法。另一种是进步程序执行的效率。将一个办法设成 final 后,编译器就会疏忽为执行办法调用机制而采取的惯例代码插入方法(将自变量压入堆栈;跳至办法代码并执行它;跳回来;革除堆栈自变量;最初对返回值进行解决)。它会用办法主体内理论代码的一个副原本替换办法调用。这样能够防止办法调用时的零碎开销。若办法体太大,可能效率也得不到晋升。
复制代码
class Human {
public final void show() {//...}
}
public class Man extends Human{
public void show() {} //Cannot override the final method from Human
}
复制代码
类内所有的 private 办法都主动成为 final。因为不能拜访一个 private 办法,所以它相对不会被笼罩。
final 类
如果整个类都是 final,就表明这个类不容许被继承。或者出于平安方面的理由,不心愿进行子类化。除此之外,或者还思考执行效率的问题,确保波及这个类各对象的所有口头都要尽可能地无效。
final class Human {
}
public class Man extends Human{// The type Man cannot subclass the final class Human
}
留神:数据成员既能够是 final,也能够不是。无论类是否被定义成 final,利用于 final 的规定同样实用于数据成员。
将类定义成 final 后,后果只是禁止被继承。因为禁止了继承,所以一个 final 类中的所有办法都默认为 final。