JAVA中的final關(guān)鍵字用法實(shí)例詳解
本文實(shí)例講述了JAVA中的final關(guān)鍵字用法。分享給大家供大家參考,具體如下:
根據(jù)上下文環(huán)境,java的關(guān)鍵字final也存在著細(xì)微的區(qū)別,但通常指的是“這是無(wú)法改變的?!辈幌敫淖兊睦碛捎袃煞N:一種是效率,另一種是設(shè)計(jì)。由于兩個(gè)原因相差很遠(yuǎn),所以關(guān)鍵子final可能被誤用。
接下來(lái)介紹一下使用到final的三中情況:數(shù)據(jù),方法,類
final數(shù)據(jù)
許多編程語(yǔ)言都有某種方法,來(lái)向編譯器告知一塊數(shù)據(jù)是恒定不變的。有時(shí)數(shù)據(jù)的恒定不變是很有用的,例如:
1. 一個(gè)編譯時(shí)恒定不變的常量
2. 一個(gè)在運(yùn)行時(shí)初始化,而你不希望它被改變。
對(duì)于編譯期常量的這種情況,編譯器可以將該常量值代入任何可能用到它的計(jì)算式中,也就是說(shuō),可以在編譯期就執(zhí)行計(jì)算式,這減輕了一些運(yùn)行時(shí)的負(fù)擔(dān)。在java中,這類常量必須是基本類型,并且以final表示。在對(duì)這個(gè)常量定義時(shí),必須進(jìn)行賦值。
一個(gè)即是static又是final的域只占一段不能改變的存儲(chǔ)空間。
當(dāng)final應(yīng)用于對(duì)象引用時(shí),而不是基本類型時(shí),其含義有些讓人疑惑。對(duì)基本類型使用final不能改變的是他的數(shù)值。而對(duì)于對(duì)象引用,不能改變的是他的引用,而對(duì)象本身是可以修改的。一旦一個(gè)final引用被初始化指向一個(gè)對(duì)象,這個(gè)引用將不能在指向其他對(duì)象。java并未提供對(duì)任何對(duì)象恒定不變的支持。這一限制也通用適用于數(shù)組,它也是對(duì)象。例如:
package finalPackage;
import java.util.*;
class Value {
int i;
public Value(int i) {
this.i = i;
}
}
/**
* final數(shù)據(jù)常量
* @author Administrator
* 對(duì)基本類型使用final不能改變的是它的數(shù)值。
* 而對(duì)于對(duì)象引用,不能改變的是他的引用,而對(duì)象本身是可以修改的。
* 一旦一個(gè)final引用被初始化指向一個(gè)對(duì)象,這個(gè)引用將不能在指向其他對(duì)象。
* 注意,根據(jù)慣例,即是static又是final的域(即編譯器常量)將用大寫表示,并用下劃分割個(gè)單詞。
*/
public class FinalData {
private static Random rand = new Random(47);
private String id;
public FinalData(String id) {
this.id = id;
}
// 編譯時(shí)常量 Can be compile-time constants:
private final int valueOne = 9;
private static final int VALUE_TWO = 99;
// 典型的公共常量 Typical public constant:
public static final int VALUE_THREE = 39;
// 運(yùn)行時(shí)常量 Cannot be compile-time constants:
private final int i4 = rand.nextInt(20);
static final int INT_5 = rand.nextInt(20);
private Value v1 = new Value(11);
private final Value v2 = new Value(22);
private static final Value VAL_3 = new Value(33);
// 數(shù)組 Arrays:
private final int[] a = { 1, 2, 3, 4, 5, 6 };
public String toString() {
return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5;
}
public static void main(String[] args) {
FinalData fd1 = new FinalData("fd1");
// ! fd1.valueOne++; // Error: can't change value
fd1.v2.i++; // Object isn't constant!
fd1.v1 = new Value(9); // OK -- not final
for (int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Object isn't constant!
// ! fd1.v2 = new Value(0); // Error: Can't
// ! fd1.VAL_3 = new Value(1); // change reference
// ! fd1.a = new int[3];
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 = 15, INT_5 = 18
* Creating new FinalData
* fd1: i4 = 15, INT_5 = 18
* fd2: i4 = 13, INT_5 = 18
*/
}
由于valueOne和VALUE_TWO都是帶有編譯時(shí)數(shù)值的final基本類型,所以它們二者均可以用作編譯期常量,并且沒有重大區(qū)別。VALUE_THREE是一種更加典型的對(duì)常量進(jìn)行定義的方式:定義為public,可以被任何人訪問(wèn);定義為static,則強(qiáng)調(diào)只有一份;定義為final,這說(shuō)明它是個(gè)常量。請(qǐng)注意帶有恒定初始值的final static基本類型全用大寫字母命名,并且字母與字母之間用下劃線隔開。
我們不能因?yàn)槟承?shù)據(jù)是final的就認(rèn)為在編譯時(shí)可以知道它的值。在運(yùn)行時(shí)使用隨機(jī)數(shù)來(lái)初始化i4和INT_5的值說(shuō)明了這一點(diǎn)。實(shí)例中fd1和fd2中i4的值是唯一的,每次都會(huì)被初始化為15,13。INT_5的值是不可以通過(guò)創(chuàng)建第二個(gè)FinalData對(duì)象加以改變的。這是因?yàn)樗莝tatic的,在裝載類時(shí)(也就是第一次創(chuàng)建這個(gè)類對(duì)象時(shí))已經(jīng)被初始化,而不是每次創(chuàng)建都初始化。
java也許生成"空白final",所謂空白final是指被聲明為final但又未給初值的域。無(wú)論什么情況下編譯器都會(huì)保證final域在使用前初始化。但空白final在final的使用上提供了很大的靈活性,為此,一個(gè)final域可以根據(jù)某些對(duì)象有所不同,卻又保持恒定不變的特性。下面的事例說(shuō)明了一點(diǎn):
package finalPackage;
class Poppet {
private int i;
Poppet(int ii) {
i = ii;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
/**
* 空白final
* @author Administrator
* 所謂空白final是指被聲明為final但又未給初值的域。無(wú)論什么情況下編譯器都會(huì)保證final域在使用前初始化。
*/
public class BlankFinal {
private final int i = 0; // Initialized final
private final int j; // Blank final
private final Poppet p; // Blank final reference
// Blank finals MUST be initialized in the constructor:
public BlankFinal() {
j = 1; // Initialize blank final
p = new Poppet(1); // Initialize blank final reference
}
public BlankFinal(int x) {
j = x; // Initialize blank final
p = new Poppet(x); // Initialize blank final reference
}
public static void main(String[] args) {
BlankFinal b1=new BlankFinal();
BlankFinal b2=new BlankFinal(47);
System.out.println("b1.j="+b1.j+"\t\t b1.p.i="+b1.p.getI());
System.out.println("b2.j="+b2.j+"\t\t b2.p.i="+b2.p.getI());
}
/**
* 輸出結(jié)果:
* b1.j=1 b1.p.i=1
* b2.j=47 b2.p.i=47
*/
}
final參數(shù)
java中也許將參數(shù)列表中的參數(shù)以聲明的方式聲指明為final。這意味著你無(wú)發(fā)改變參數(shù)所指向的對(duì)象。例如:
package finalPackage;
class Gizmo {
public void spin(String temp) {
System.out.println(temp+" Method call Gizmo.spin()");
}
}
/**
* final參數(shù)
* @author Administrator
* 如果將參數(shù)列表中的參數(shù)指明為final,這意味著你無(wú)發(fā)改變參數(shù)所指向的對(duì)象的引用。
*/
public class FinalArguments {
void with(final Gizmo g) {
// ! g = new Gizmo(); // Illegal -- g is final
}
void without(Gizmo g) {
g = new Gizmo(); // OK -- g not final
g.spin("without");
}
// void f(final int i) { i++; } // Can't change
// You can only read from a final primitive:
int g(final int i) {
return i + 1;
}
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
System.out.println("bf.g(10)="+bf.g(10));
}
/**
* 輸出結(jié)果:
* withoutMethod call Gizmo.spin()
* bf.g(10)=11
*/
}
使用final方法有兩個(gè)原因。第一個(gè)原因是把方法鎖定,以防止任何繼承它的類修改它的含義。這是出于設(shè)計(jì)的考慮:想要確保在繼承中使用的方法保持不變,并且不會(huì)被覆蓋。
過(guò)去建議使用final方法的第二個(gè)原因是效率。在java的早期實(shí)現(xiàn)中,如果將一個(gè)方法指明為final,就是同意編譯器將針對(duì)該方法的所有調(diào)用都轉(zhuǎn)為內(nèi)嵌調(diào)用。當(dāng)編譯器發(fā)現(xiàn)一個(gè)final方法調(diào)用命令時(shí),它會(huì)根據(jù)自己的謹(jǐn)慎判斷,跳過(guò)插入程序代碼這種正常的調(diào)用方式而執(zhí)行方法調(diào)用機(jī)制(將參數(shù)壓入棧,跳至方法代碼處執(zhí)行,然后跳回并清理?xiàng)V械膮?shù),處理返回值),并且以方法體中的實(shí)際代碼的副本來(lái)代替方法調(diào)用。這將消除方法調(diào)用的開銷。當(dāng)然,如果一個(gè)方法很大,你的程序代碼會(huì)膨脹,因而可能看不到內(nèi)嵌所帶來(lái)的性能上的提高,因?yàn)樗鶐?lái)的性能會(huì)花費(fèi)于方法內(nèi)的時(shí)間量而被縮減。
在最近的java版本中,虛擬機(jī)(特別是hotspot技術(shù))可以探測(cè)到這些情況,并優(yōu)化去掉這些效率反而降低的額外的內(nèi)嵌調(diào)用,因此不再需要使用final方法來(lái)進(jìn)行優(yōu)化了。事實(shí)上,這種做法正逐漸受到勸阻。在使用java se5/6時(shí),應(yīng)該讓編譯器和JVM去處理效率問(wèn)題,只有在想明確禁止覆蓋式,才將方法設(shè)置為final的。
final和private關(guān)鍵字
類中的所有private方法都是隱式的制定為final的。由于你無(wú)法訪問(wèn)private方法你也就無(wú)法覆蓋它??梢詫?duì)private方法添加final修飾詞,但這毫無(wú)意義。例如:
package finalPackage;
/**
* final和private關(guān)鍵字
*
* 類中的所有private方法都是隱式的制定為final的。
* 由于你無(wú)法訪問(wèn)private方法,所以你也就無(wú)法覆蓋它。
* 可以對(duì)private方法添加final修飾詞,但這毫無(wú)意義。
*/
class WithFinals {
// Identical to "private" alone:
private final void f() {
System.out.println("WithFinals.f()");
}
// Also automatically "final":
private void g() {
System.out.println("WithFinals.g()");
}
}
class OverridingPrivate extends WithFinals {
private final void f() {
System.out.println("OverridingPrivate.f()");
}
private void g() {
System.out.println("OverridingPrivate.g()");
}
}
class OverridingPrivate2 extends OverridingPrivate {
public final void f() {
System.out.println("OverridingPrivate2.f()");
}
public void g() {
System.out.println("OverridingPrivate2.g()");
}
}
public class OverideFinal {
public static void main(String[] args) {
WithFinals w1 = new WithFinals();
// ! w1.f(); //Error,無(wú)法訪問(wèn)私有方法
// ! w1.g(); //Error,無(wú)法訪問(wèn)私有方法
OverridingPrivate w2 = new OverridingPrivate();
// ! w2.f(); //Error,無(wú)法訪問(wèn)私有方法
// ! w2.g(); //Error,無(wú)法訪問(wèn)私有方法
OverridingPrivate2 w3 = new OverridingPrivate2();
w3.f();
w3.g();
}
/**
* 輸出結(jié)果:
* OverridingPrivate2.f()
* OverridingPrivate2.g()
*/
}
"覆蓋"只有在某方法是基類接口的一部分時(shí)才會(huì)發(fā)生。即,必須將一個(gè)對(duì)象向上轉(zhuǎn)型為它的基類并調(diào)用相同的方法。如果某方法是private的,它就不是基類接口的一部分。它僅是一些隱藏于類中的程序代碼,如果一個(gè)基類中存在某個(gè)private方法,在派生類中以相同的名稱創(chuàng)建一個(gè)public,protected或包訪問(wèn)權(quán)限方法的話,該方法只不過(guò)是與基類中的方法有相同的名稱而已,并沒有覆蓋基類方法。由于private方法無(wú)法觸及而且能有效隱藏,所以除了把它看成是因?yàn)樗鶜w屬的類的組織結(jié)構(gòu)的原因而存在外,其他任何事物都不需要考慮它。
final 類
當(dāng)將類定義為final時(shí),就表明了你不打算繼承該類,而且也不許別人這樣做。換句話說(shuō),出于某種考慮,你對(duì)該類的設(shè)計(jì)永不需要做任何變動(dòng),或者出于安全的考慮,你不希望他有子類。例如:
package finalPackage;
class SmallBrain {
}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f() {
System.out.println("Dinosaur.f()");
}
}
// ! class Further extends Dinosaur {}
// error: Cannot extend final class 'Dinosaur'
/**
* final 類
*
* final類中的屬性可以選擇是否定義為final
* final類中的方法都隱式的制定為final方法,因此你無(wú)法覆蓋他們
*/
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++;
System.out.println("n.i="+n.i);
System.out.println("n.j="+n.j);
}
/**
* 輸出結(jié)果為:
* Dinosaur.f()
* n.i=40
* n.j=2
*/
}
請(qǐng)注意,final類的域可以根據(jù)個(gè)人的意愿選擇是或不是final。不論類是否被定義為final,相同的規(guī)則同樣適用于定義為final的域。然而,由于final是無(wú)法繼承的,所以被final修飾的類中的方法都隱式的制定為fianl,因?yàn)槟銦o(wú)法覆蓋他們。在fianl類中可以給方法添加final,但這不會(huì)產(chǎn)生任何意義。
結(jié)論:
根據(jù)程序上下文環(huán)境,Java關(guān)鍵字final有“這是無(wú)法改變的”或者“終態(tài)的”含義,它可以修飾非抽象類、非抽象類成員方法和變量。你可能出于兩種理解而需要阻止改變:設(shè)計(jì)或效率。
final類不能被繼承,沒有子類,final類中的方法默認(rèn)是final的。
final方法不能被子類的方法覆蓋,但可以被繼承。
final成員變量表示常量,只能被賦值一次,賦值后值不再改變。
final不能用于修飾構(gòu)造方法。
注意:父類的private成員方法是不能被子類方法覆蓋的,因此private類型的方法默認(rèn)是final類型的。
希望本文所述對(duì)大家Java程序設(shè)計(jì)有所幫助。
相關(guān)文章
解決spring cloud gateway 獲取body內(nèi)容并修改的問(wèn)題
這篇文章主要介紹了解決spring cloud gateway 獲取body內(nèi)容并修改的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
SpringBoot如何優(yōu)雅的處理重復(fù)請(qǐng)求
對(duì)于一些用戶請(qǐng)求,在某些情況下是可能重復(fù)發(fā)送的,如果是查詢類操作并無(wú)大礙,但其中有些是涉及寫入操作的,一旦重復(fù)了,可能會(huì)導(dǎo)致很嚴(yán)重的后果,所以本文給大家介紹了SpringBoot優(yōu)雅的處理重復(fù)請(qǐng)求的方法,需要的朋友可以參考下2023-12-12
Java讀取項(xiàng)目json文件并轉(zhuǎn)為JSON對(duì)象的操作
這篇文章主要介紹了Java讀取項(xiàng)目json文件并轉(zhuǎn)為JSON對(duì)象的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
Java時(shí)間轉(zhuǎn)換成unix時(shí)間戳的方法
這篇文章主要為大家詳細(xì)介紹了Java時(shí)間轉(zhuǎn)換成unix時(shí)間戳的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
gateway與spring-boot-starter-web沖突問(wèn)題的解決
這篇文章主要介紹了gateway與spring-boot-starter-web沖突問(wèn)題的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
Spring Cloud Feign性能優(yōu)化代碼實(shí)例
這篇文章主要介紹了Spring Cloud Feign性能優(yōu)化代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03

