Java面向对象编程中final关键字的使用方法详解
在Java中通过final关键字来声明对象具有不变性(immutable),这里的对象包括变量,方法,类,与C++中的const关键字效果类似。
immutable指对象在创建之后,状态无法被改变
可以从三个角度考虑使用final关键字:
- 代码本身:不希望final描述的对象所表现的含义被改变
- 安全:final对象具有只读属性,是线程安全的
- 效率:无法修改final对象本身,对其引用的操作更为高效
final变量
定义finalObjecta,则a只能被初始化一次,一旦初始化,a的数据无法修改,若a为引用类型,则不能重新绑定其他对象。
未被初始化的final变量被称为blankfinal,若为成员变量,则必须被初始化或在构造器中赋值。
例子:
classCircle{ staticfinaldoublePI=3.1415926; finalintradius=5; finalintxPos; finalintyPos; publicCircle(intx,inty){ xPos=x; yPos=y; } }
final方法
定义finalmethod,则该方法无法被重载,方法设计者不希望由于对方法的重载导致其他相关功能出现异常。
例子:
classBaseClass{ publicfinalvoidmethod(){} } classDerivedClassextendsBaseClass{ publicfinalvoidmethod(){}//编译出错 }
需要注意的是,final方法的定义不一定能够产生inline的效果,因为方法是否inline取决于JVM的策略,而非final关键字,通过final的设计提高方法效率是不准确的。
final类
finalclassX定义的类X无法被继承。
在Java中,String类被设计成final,其定义如下
publicclassfinalStringextendsObject implementsSerializable,Comparable<String>,CharSequence为什么String被设计成final?
- 一个String类的实例被初始化后,其在堆上的内容无法被改变,String类提供的任何修改String对象的方法都只能够产生一个新的String对象,大大简化了对String的操作,是代码更易于阅读和理解;
- Stringfinal是实现Stringinterning(在内存中不同的string值只有一份)的必要条件,因为通常代码中存在大量的String对象,不同的引用会指向相同的字符串空间,若String不为final,则当一个字符串空间的内容改变时,所有的引用都需要知道这一情况,这一机制的实现是十分复杂的,无疑会影响效率。Stringinterning能够节省内存空间,同时也节省时间花销;
- String只读,则不必担心非常重要的内容被篡改。
内部类与final
在一个方法内定义匿名内部类时,内部类只能访问方法内的final类型变量,使得Java编译器能够提前捕获变量的值,并在内部类保存一份副本,当方法销毁时,内部类的内存空间依然完整。
例子:
publicclassWrapper{ publicstaticvoidmain(String[]args){ //Objectobj=null;//编译出错 finalObjectobj=null; newThread(newRunnable(){ publicvoidrun(){ obj="hello"; } }).start(); } }
PS:内部匿名类无法访问外面的非final的变量的问题
这个听起来有点拗口,其实我更多的是想说Java内部类的一些特性。
之所以会想起这个题目只要是最近在阅读JDK源码中关于HTTPkeepalive的代码时,其中一个源文件sun.net.www.protocol.http.HttpURLConnection.java无意中看到下面这段代码。
finalbooleanresult[]={false}; java.security.AccessController.doPrivileged(newjava.security.PrivilegedAction(){ publicObjectrun(){ try{ InetAddressa1=InetAddress.getByName(h1); InetAddressa2=InetAddress.getByName(h2); result[0]=a1.equals(a2); }catch(UnknownHostExceptione){ }catch(SecurityExceptione){ } returnnull; } }); returnresult[0];
Java的匿名内部类无法访问对应的函数的非final变量。要想访问外部的localvariable,这个variable又必须要先定义成fianl,但是一定义成final就不能在匿名内部类中修改这个变量的值,所以要想匿名内部类返回一些有用的值时不是那么的容易。这段代码使用了一个非常巧妙的方法,这里使用数组的方式绕过这个限制,虽然我们无法修改result这个变量的引用,但是我们可以修改result指向的那个数组的内容。
只是想记录一下内部匿名类修改外部变量的一个小技巧。不过既然已经到了这里,不妨继续的看看内部类都有哪些特性或者限制吧。
在继续本文前,我觉得非常有必要的明确下本文中涉及的一些Java术语,这些术语不太好翻译成中文,所以我们还是用英文来描述。
//Thisisclass publicclassJavaTerm{ //fieldormembervariable privateintfield; //constructor publicJavaTerm(){ } //method publicvoidmethod(){ //localvariable intlocalVariable=0; //localclass classLocalClass{ publicLocalClass(){ } } //anonymousclass newRunnable(){ publicvoidrun(){ } }; } }
我们今天更多的将关注于localclass和anonymousclass,它们都属于innerclass。
Java允许我们在一个class里面再定义一个class,称为嵌套类(nestedclass),nestedclass又可以分为两类,一类是staticnestedclass,另外一个是non-staticnestedclass,又称为innerclass。innerclass又可以分为localclass和anonymousclass。
anonymousclass的一些限制
- 一个anonymousclass可以访问包含它的类的类变量(field/membervariable)
- 一个anonymousclass不能访问包含它的作用于中的不是final的本地变量(localvariable)
- 和nestedclass一样,anonymousclass中定义的variable会覆盖包含这个内部类的作用域中的同名的variable
- 你不能定义静态的初始化方法
- 一个anonymousclass可以有静态的成员变量。这个成员变量必须是常量(用final修饰)。
- 一个anonymousclass不可以有构造函数