final关键字可以理解为“这个东西不能改变”。之所以要禁止改变,可能是因为实际业务情况需要或者效率或者设计因素,比如说应用中的全局变量我们经常将其用static加final关键字声明保证不能改变且该类的所有对象只有一份。在声明为final的地方有成员变量、方法、类。
final成员变量:
往往我们在用final声明为是常数数据的时候,可以分为编译期的常数数据和运行期的常数数据。
什么叫编译期的常数数据?是指在程序在编译期间已经将数据通过过程的形式存在了class文件中了,这部分数据在不需要在运行期间去执行,相当于节省了一部分开销,这类数据必须是属于基本数据类型(boolean、byte、char、short、int、float、long、double)且必须赋予一个初始值这样一经初始化就在编译期间设定了不能再改变。这类变量的声明我们习惯性的用大写来命名。
public class FinalTest {
public static final int A = 5;
public static void main(String[] args) {
//FinalTest.A = 5; 编译不能通过 因为final声明的数据一旦指定不能改变
}
}
什么叫运行期初始化的常数?是指该变量的初始化数据必须在运行期间才能被指定,一旦指定后就不会改变,在这里包括基本数据类型的指定和对象句柄的指定。基本数据类型的指定后是不可改变的,对象句柄的指定后也是不可改变的,但他的不同点是该句柄始终指向到一个具体的对象,而且该句柄始终指向这一对象,但是这一对象本身是可以改变的。
class Student {
private int age;
public Student(int age){
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class FinalTest {
public static final int A = 5;
final int b = (int) (Math.random() * 4);
static final int staticb = (int) (Math.random() * 4);
final Student p = new Student(20);
public static void main(String[] args) {
FinalTest test = new FinalTest();
FinalTest test2 = new FinalTest();
System.out.println(test.b);//b在运行期被初始化,一旦初始化后就不会改变
System.out.println(test2.b);//b在运行期被初始化,一旦初始化后就不会改变
System.out.println(test.staticb);//staticb在运行期被初始化,一旦初始化后就不会改变
System.out.println(test2.staticb);//staticb在运行期被初始化,一旦初始化后就不会改变 与b不同的是加了static关键字所以test2.staticb值与test.b值一样
//test.p = new Student(15);//编译不通过 ,对象句柄的指定后也是不可改变的,该句柄始终指向到一个具体的对象(就是之前初始化的那个对象)
System.out.println(test.p.getAge());
test.p.setAge(21);//但是值是指向的对象本身是可以改变的
System.out.println(test.p.getAge());
}
}
在《thinking in Java》中的中文翻译中这句话个人觉得是有问题的:无论static还是final字段,都只能存储一个数据,而且不得改变。
英文中是:A field that is both static and final has only one piece of storage that cannot be changed .
我的理解是只有final声明的字段是只能存储一个数据而且不得改变,事实上static单独声明的字段是可以改变数据的。英文的正确翻译应该是“字段同时由static和final声明后都只能存储一个数据,而且不得改变”。
final方法:
之所以要使用final方法,可能是出于对两方面理由的考虑。第一个是为方法“上锁”,防止任何继承类改变它的本来含义。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。
class FinalFather {
public final void say(String str){
System.out.println(str);
}
}
public class FinalTest extends FinalFather{
public void say(String str){//编译不通过
System.out.println(str);
}
}
采用final方法的第二个理由是程序执行的效率。只要编译器发现一个final方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受到到不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。Java编译器能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个final方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。
final类:
如果说整个类都是final(在它的定义前冠以final关键字),就表明自己不希望从这个类继承,或者不允许其他任何人采取这种操作。换言之,出于这样或那样的原因,我们的类肯定不需要进行任何改变;或者出于安全方面的理由,我们不希望进行子类化(子类处理)。
将类定义成final后,结果只是禁止进行继承——没有更多的限制。然而,由于它禁止了继承,所以一个final类中的所有方法都默认为final,但是成员变量默认是没加的。因为此时再也无法覆盖它们。所以与我们将一个方法明确声明为final一样,编译器此时有相同的效率选择。
final class FinalFather {
//final int a = 5;
int a = 5;
public void say(String str){//默认也是带有final的
System.out.println(str);
}
}
//class FinalChild extends FinalFather{}//编译不通过
public class FinalTest{
public static void main(String[] args) {
FinalFather f = new FinalFather();
f.a=10;//如果把FinalFather中的a声明为final则编译不通过说明final类的成员变量是没有加自动加上fianl的只有方法自动加上了final限制
System.out.println(f.a);
}
}
设计一个类时,往往需要考虑是否将一个方法设为final。可能会觉得使用自己的类时执行效率非常重要,没有人能覆盖自己的方法。这种想法在某些时候是正确的。但要慎重作出自己的假定。通常,我们很难预测一个类以后会以什么样的形式再生或重复利用。常规用途的类尤其如此,若将一个方法定义成final,就杜绝了在其他程序员的项目中对自己的类进行继承的途径。