深入解析Java編程中final關(guān)鍵字的使用
在Java中聲明屬性、方法和類時(shí),可使用關(guān)鍵字final來修飾。final變量即為常量,只能賦值一次;final方法不能被子類重寫;final類不能被繼承。
1.final成員
聲明 final 字段有助于優(yōu)化器作出更好的優(yōu)化決定,因?yàn)槿绻幾g器知道字段的值不會(huì)更改,那么它能安全地在寄存器中高速緩存該值。final 字段還通過讓編譯器強(qiáng)制該字段為只讀來提供額外的安全級(jí)別。
1.1關(guān)于final成員賦值
1)在java中,普通變量可默認(rèn)初始化。但是final類型的變量必須顯式地初始化。
2)final 成員能且只能被初始化一次。
3)final成員必須在聲明時(shí)(在final變量定義時(shí)直接給其賦值)或者在構(gòu)造函數(shù)中被初始化,而不能在其它的地方被初始化。
示例1 Bat.java
public class Bat { final double PI = 3.14; // 在定義時(shí)賦值 final int i; // 因?yàn)橐跇?gòu)造函數(shù)中進(jìn)行初始化,所以此處便不可再賦值 final List<Bat> list; // 因?yàn)橐跇?gòu)造函數(shù)中進(jìn)行初始化,所以此處便不可再賦值 Bat() { i = 100; list = new LinkedList<Bat>(); } Bat(int ii, List<Bat> l) { i = ii; list = l; } public static void main(String[] args) { Bat b = new Bat(); b.list.add(new Bat()); // b.i=25; // b.list=new ArrayList<Bat>(); System.out.println("I=" + b.i + " List Type:" + b.list.getClass()); b = new Bat(23, new ArrayList<Bat>()); b.list.add(new Bat()); System.out.println("I=" + b.i + " List Type:" + b.list.getClass()); } }
結(jié)果:
I=100 List Type:class java.util.LinkedList I=23 List Type:class java.util.ArrayList
在main方法中有兩行語句注釋掉了,如果你去掉注釋,程序便無法通過編譯,這便是說,不論是i的值或是list的類型,一旦初始化,確實(shí)無法再更改。然而b可以通過重新初始化來指定i的值或list的類型。
1.2 final引用字段的無效初始化
要正確使用final字段有點(diǎn)麻煩,對(duì)于其構(gòu)造子能拋出異常的對(duì)象引用來說尤其如此。因?yàn)?final 字段在每個(gè)構(gòu)造器中必須只初始化一次,如果 final 對(duì)象引用的構(gòu)造器可能拋出異常,編譯器可能會(huì)報(bào)錯(cuò),說該字段沒有被初始化。編譯器一般比較智能化,足以發(fā)現(xiàn)在兩個(gè)互斥代碼分支(比如,if...else 塊)的每個(gè)分支中的初始化恰好只進(jìn)行了一次,但是它對(duì) try...catch 塊通常不會(huì)如此“寬容”。
下面這段代碼通常會(huì)出現(xiàn)問題。
class Thingie { public static Thingie getDefaultThingie() { return new Thingie(); } } public class Foo { private final Thingie thingie; public Foo() { try { thingie = new Thingie(); } catch (Exception e) { thingie = Thingie.getDefaultThingie();//Error:The final field thingie may already have been assigned } } }
你可以這樣修改。
public class Foo { private final Thingie thingie; public Foo() { Thingie tempThingie; try { tempThingie = new Thingie(); } catch (Exception e) { tempThingie = Thingie.getDefaultThingie(); } thingie = tempThingie; } }
1.3關(guān)于final成員使用
當(dāng)你在類中定義變量時(shí),在其前面加上final關(guān)鍵字,那便是說,這個(gè)變量一旦被初始化便不可改變,這里不可改變的意思對(duì)基本類型來說是其值不可變,而對(duì)于對(duì)象變量來說其引用不可再變。然而,對(duì)象其本身卻是可以被修改的,Java并未提供使任何對(duì)象恒定不變的途徑。這一限制同樣適合數(shù)組,它也是對(duì)象。
示例2
private final int VAL_ONE=9; private static final int VAL_TWO=99; public static final int VAL_THREE=999;
由于VAL_ONE 和VAL_TOW 是帶有編譯期數(shù)值的final 原始類型,所以它們二者均可以用作編譯期常量,并且沒有重大區(qū)別。VAL_THREE是一種更加典型的對(duì)常量進(jìn)行定義的方式:定義為 public,則可以被用于包之外;定義為 static 來強(qiáng)調(diào)只有一份;定義為 final 來說明它是一個(gè)常量。
final標(biāo)記的變量即成為常量,但這個(gè)“常量”也只能在這個(gè)類的內(nèi)部使用,不能在類的外部直接使用。但是當(dāng)我們用public static final 共同標(biāo)記常量時(shí),這個(gè)常量就成為全局的常量(一個(gè)既是static又是final的字段只占據(jù)一段不能改變的存儲(chǔ)空間)。而且這樣定義的常量只能在定義時(shí)賦值,其他地方都不行。
示例3
class Value { int i; public Value(int i) { this.i = i; } } public class FinalData { private static Random rand = new Random(); private String id; public FinalData(String id) { this.id = id; } private final int i4 = rand.nextInt(20); static final int i5 = rand.nextInt(20); public String toString() { return id + ":" + "i4:" + i4 + ", i5=" + i5; } public static void main(String[] args) { FinalData fd1 = new FinalData("fd1"); System.out.println(fd1); System.out.println("Creating new FinalData"); FinalData fd2 = new FinalData("fd2"); System.out.println(fd1); System.out.println(fd2); } }
結(jié)果
fd1:i4:6, i5=3 Creating new FinalData fd1:i4:6, i5=3 fd2:i4:17, i5=3
示例部分展示了將final 數(shù)值定義為static(i5) 和非static(i4) 的區(qū)別。此區(qū)別只有在數(shù)值在運(yùn)行期內(nèi)被初始化時(shí)才會(huì)顯現(xiàn),這是因?yàn)榫幾g器對(duì)編譯期數(shù)值一視同仁。(并且它們可能因優(yōu)化而消失。)當(dāng)你運(yùn)行程序時(shí),就會(huì)看到這個(gè)區(qū)別。請(qǐng)注意,在fd1 和fd2 中, i5 的值是不可以通過創(chuàng)建第二個(gè)FinalData 對(duì)象而加以改變的。這是因?yàn)樗?static,在裝載時(shí)已被初始化,而不是每次創(chuàng)建新對(duì)象時(shí)都初始化。
示例4
class Value { int i; public Value(int i) { this.i = i; } } public class … { private Value v1=new Value(11); private final Value v2=new Value(22); private static final Value v3=new Value(33); … } public static void main(String[] args) { … fd1.v2.i++;// OK--Object isn't constant! fd1.v1=new Value(9);//OK--not final fd1.v2=new Value(0);//Error:Can't change reference fd1.v3=new Value(1);//Error:Can't change reference … }
從v1 到v3 的變量說明了final 引用的意義。正如你在main( )中所看到的,不能因?yàn)関2 是final 的,就認(rèn)為你無法改變它的值。由于它是一個(gè)引用,final 意味著你無法將v2 再次指向另一個(gè)新的對(duì)象。
示例5
public class … { private final int[] a={1,2,3,4,5,6}; … } public static void main(String[] args) { … for(int i=0;i<fd1.a.length;i++) fd1.a[i]++;// OK--Object isn't constant! fd1.a=new int[3];//Error:Can't change reference … }
對(duì)數(shù)組具有同樣的意義(可以改變它的值,但不能指向一個(gè)新的對(duì)象),數(shù)組是另一種引用。
1.4解決final數(shù)組的局限性
盡管數(shù)組引用能被聲明成 final,但是該數(shù)組的元素卻不能。這意味著暴露 public final 數(shù)組字段的或者通過它們的方法將引用返回給這些字段的類都不是不可改變的。
// Not immutable -- the states array could be modified by a malicious // callerpublic class DangerousStates { private final String[] states = new String[] { "Alabama", "Alaska", "ect" }; public String[] getStates() { return states; } }
同樣,盡管對(duì)象引用可以被聲明成 final 字段,而它所引用的對(duì)象仍可能是可變的。如果想要使用 final 字段創(chuàng)建不變的對(duì)象,必須防止對(duì)數(shù)組或可變對(duì)象的引用“逃離”你的類。要不用重復(fù)克隆該數(shù)組做到這一點(diǎn),一個(gè)簡(jiǎn)單的方法是將數(shù)組轉(zhuǎn)變成 List。
// Immutable -- returns an unmodifiable List insteadpublic class SafeStates { private final String[] states = new String[] { "Alabama", "Alaska", "ect" }; private final List statesAsList = new AbstractList() { public Object get(int n) { return states[n]; } public int size() { return states.length; } }; public List getStates() { return statesAsList; } }
1.5關(guān)于final參數(shù)使用
還有一種用法是定義方法中的參數(shù)為final,對(duì)于基本類型的變量,這樣做并沒有什么實(shí)際意義,因?yàn)榛绢愋偷淖兞吭谡{(diào)用方法時(shí)是傳值的,也就是說你可以在方法中更改這個(gè)參數(shù)變量而不會(huì)影響到調(diào)用語句,然而對(duì)于對(duì)象變量,卻顯得很實(shí)用,因?yàn)閷?duì)象變量在傳遞時(shí)是傳遞其引用,這樣你在方法中對(duì)對(duì)象變量的修改也會(huì)影響到調(diào)用語句中的對(duì)象變量,當(dāng)你在方法中不需要改變作為參數(shù)的對(duì)象變量時(shí),明確使用final進(jìn)行聲明,會(huì)防止你無意的修改而影響到調(diào)用方法。
1.6關(guān)于內(nèi)部類中的參數(shù)變量
另外方法中的內(nèi)部類在用到方法中的參變量時(shí),此參數(shù)變量必須聲明為final才可使用。
示例6 INClass.java
public class INClass { void innerClass(final String str) { class IClass { IClass() { System.out.println(str); } } IClass ic = new IClass(); } public static void main(String[] args) { INClass inc = new INClass(); inc.innerClass("Hello"); } }
2.final方法
2.1final方法用途
1)為了確保某個(gè)函數(shù)的行為在繼承過程中保持不變,并且不能被覆蓋(overridding),可以使用final方法。
2)class中所有的private和static方法自然就是final。
2.2 final與private關(guān)鍵字
類中所有的private方法都隱式地指定是final的。由于無法取用private方法,所以也就無法覆蓋它。
“覆蓋”只有在某方法是基類的接口的一部分時(shí)才會(huì)出現(xiàn)。即,必須能將一個(gè)對(duì)象向上轉(zhuǎn)型為它的基本類型并調(diào)用相同的方法。如果某方法為private,它就不是基類的接口的一部分。它僅是一些隱藏于類中的代碼,只不過是具有相同的名稱而已。但如果在導(dǎo)出類以相同的方法生成一個(gè)public、protected或包訪問權(quán)限方法的話,該方法就不會(huì)產(chǎn)生在基類中出現(xiàn)的“僅具有相同名稱”的情況。此時(shí),你并沒有覆蓋該方法,僅是生成了一個(gè)新的方法。由于private方法無法觸及而且能有效隱藏,所以除了把它看成是因?yàn)樗鶜w屬的類的組織結(jié)構(gòu)的原因而存在外,其他任何事物都不需要考慮它。
3.final類
將某個(gè)類的整體定義為final 時(shí),該類無法被繼承。而且由于final類禁止繼承,所以final類中所有的方法都隱式指定為final的,因?yàn)闊o法覆蓋它們。
final 用于類或方法是為了防止方法間的鏈接被破壞。例如,假定類 X 的某個(gè)方法的實(shí)現(xiàn)假設(shè)了方法 M 將以某種方式工作。將 X 或 M 聲明成 final 將防止派生類以這種方式重新定義 M,從而導(dǎo)致 X 的工作不正常。盡管不用這些內(nèi)部相關(guān)性來實(shí)現(xiàn) X 可能會(huì)更好,但這不總是可行的,而且使用 final 可以防止今后這類不兼容的更改。
PS:final,finally和finallize的區(qū)別
- final用于申明屬性,方法和類,表示屬性不可變,方法不可以被覆蓋,類不可以被繼承。
- finally是異常處理語句結(jié)構(gòu)中,表示總是執(zhí)行的部分。
- finallize表示是object類一個(gè)方法,在垃圾回收機(jī)制中執(zhí)行的時(shí)候會(huì)被調(diào)用被回收對(duì)象的方法。
相關(guān)文章
Intellij Idea修改代碼方法參數(shù)自動(dòng)提示快捷鍵的操作
這篇文章主要介紹了Intellij Idea修改代碼方法參數(shù)自動(dòng)提示快捷鍵的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2021-01-01Java使用新浪微博API通過賬號(hào)密碼方式登陸微博的實(shí)例
這篇文章主要介紹了Java使用新浪微博API通過賬號(hào)密碼方式登陸微博的實(shí)例,一般來說第三方App都是采用OAuth授權(quán)認(rèn)證然后跳轉(zhuǎn)之類的方法,而本文所介紹的賬號(hào)方式則更具有自由度,需要的朋友可以參考下2016-02-02reactor-logback的AsyncAppender執(zhí)行流程源碼解讀
這篇文章主要為大家介紹了reactor-logback的AsyncAppender執(zhí)行流程源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12SpringBoot?自定義注解實(shí)現(xiàn)涉密字段脫敏
關(guān)于數(shù)據(jù)脫敏,網(wǎng)上的文章都是硬編碼規(guī)則,比如對(duì)身份證,手機(jī)號(hào),郵件地址等固定寫法脫敏。本文在此基礎(chǔ)上,拓展動(dòng)態(tài)從數(shù)據(jù)庫查出涉密關(guān)鍵字執(zhí)行脫敏操作。感興趣的同學(xué)可以參考閱讀2023-03-03Feign Client 超時(shí)時(shí)間配置不生效的解決
這篇文章主要介紹了Feign Client 超時(shí)時(shí)間配置不生效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09Spring Boot 基于注解的 Redis 緩存使用詳解
本篇文章主要介紹了Spring Boot 基于注解的 Redis 緩存使用詳解,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05Mybatis-plus操作json字段實(shí)戰(zhàn)教程
這篇文章主要介紹了Mybatis-plus操作json字段實(shí)戰(zhàn)教程,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-02-02Java實(shí)現(xiàn)斷點(diǎn)下載服務(wù)端與客戶端的示例代碼
這篇文章主要為大家介紹了如何實(shí)現(xiàn)服務(wù)端(Spring Boot)與客戶端(Android)的斷點(diǎn)下載與下載續(xù)傳功能,文中的示例代碼講解詳細(xì),需要的可以參考一下2022-08-08