国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Java中的final

2019-11-06 07:49:20
字體:
來源:轉載
供稿:網友

這幾天,在網上找了一些關于final的知識,當然并不全面,有的一時也沒有很好的理解,先收集起來,理理思路,把不懂的畫出來,以便更好地學習……

java中的final關鍵字通常的指的是“這是無法改變的”。它可能被做為三種的修飾詞.------數據(基本類型,對象或者數組),方法(類方法、實例方法),類。

<1>final應用于類

如果類被聲明為final,則表示類不能被繼承,也就是說不能有子類。因為不能有子類,所以final類不能被聲明為abstract抽象類。所以final關鍵字和abstract關鍵字不能同時使用。

一個final類中的所有方法都隱式地指定為final

<2>final應用于類方法

使用final使用的原因有兩個:一. 把方法鎖定,使得在子類(導出類)中不能修改它的含義。二.效率,一個final方法被調用時會轉為內嵌調用,不會使用常規的壓棧方式,使得運行效率較高,尤其是在方法體較簡單的 情 況下,但也并不絕對。(與C++中的inline關鍵字類似)

類的方法分為“類方法”(類中static方法)和“實例方法”(類中非static方法)。不管是類方法還是實例方法,只要被聲明為final,就表示不能被子類“覆蓋”或“隱藏”,也就是不能在子類中聲明為“一模一樣”的方法。

特別的:類中的所有PRivate方法都隱式地指定為是final,所以在繼承關系中不存在覆蓋問題。

<3>final應用于類屬性

類中的屬性,也分為“類屬性”(靜態屬性)和“實例屬性”(非靜態屬性)。不管是靜態屬性還是非靜態屬性,只要被聲明為final,則屬性的值只能被指定一次(也就是說初始化后就不能再給屬性賦值),而且必須進行“顯示初始化”。

對于類的靜態屬性,可以完成顯示初始化的地方有兩個:一個是在聲明靜態屬性的時候就進行初始化;另一個,也可以在“靜態初始化塊”中進行初始化。

對于類的非靜態屬性,可以完成顯示初始化的地方有三個:一個是在聲明屬性的時候就進行初始化;另一個,也可以在“初始化塊”中進行初始化;還可以在構造方法中進行初始化。

需要注意的是,在“靜態初始化塊”中不能訪問“實例屬性”,那當然就不能完成“實例屬性”的“顯示初始化”任務了;在“初始化塊”中能訪問“類屬性”,但是不能完成“類屬性”的“顯示初始化”任務,類屬性的顯示初始化應該在“靜態初始化塊”中。

如果final屬性在聲明的時候沒有進行初始化,我們管這種final叫做“空白final”。但必須確保空白final在使用前被初始化,一般在構造方法完成。

注:在類中,如果類的屬性(包括類屬性和實例屬性)沒有被final所修飾,那么即使我們沒有進行“顯示初始化”,那么編譯器也會給他們進行默認初始化。而對于final屬性我們必須進行“顯示初始化”。

<4>final應用于方法參數或變量

final也可以修飾方法參數或變量。

final變量可以在聲明的時候不進行“顯示初始化”,(而且只要不對它進行使用,就不會報編譯錯誤,但是既然不使用,這個變量也就沒什么用,顯然是多余的),只要在使用之前進行初始化就行了,這個特性和普通的變量是一樣的。

方法參數被聲明為final,表示它是只讀的。

注意:方法中的變量可以聲明為final,但是不能聲明為static,不管這個方法是靜態的還是非靜態的。

在談final之前我們現看一個最簡單的程序:

Class Main{

public String a=Test.aa;

public String b=Test.bb;

public static void main(String args[]){      ....

}

}

Class Test{

public final static String aa="HelloA";

public final static String bb=new String("HelloB");

}

大家肯定要問了,Main中的a 和 b 到底有什么區別?

我們先什么都不說,看一下反編譯的結果。

zheng@zheng-laptop:~/workspace/Test/src$ javap -c Main Compiled from "Main.java"public class Main extends java.lang.Object{public java.lang.String MainA;public java.lang.String MainB;public Main();Code:0: aload_01: invokespecial #1; //Method java/lang/Object."<init>":()V4: aload_05: ldc #2; //String HelloA7: putfield #3; //Field MainA:Ljava/lang/String;10: aload_011: getstatic #4; //Field Test.bb:Ljava/lang/String;14: putfield #5; //Field MainB:Ljava/lang/String;17: returnpublic static void main(java.lang.String[]);Code:0: return}

在第5行java進行了一次值賦值,所以就直接霸HelloA給Main.a了。 而在第11行,java則是傳給b一個Object,也就是將Test.bb給了Main.b;

這是為什么了,C++中說 const其中一個功能就是為了加快編譯速度,的確值賦值加快了編譯的速度(java應該也是這么做的)

final放在什么內存中等把JVM搞懂了,再來補上!

但是這可能會引發一個問題,如果修改aa的值,并且就編譯Test.java,Main中的a還是原來的”HelloA“,沒有改變,因為final在這種情況下是直接賦值的。

對與java中的final變量,java編譯器是進行了優化的。每個使用了final類型變量的地方都不會通過連接而進行訪問。比如說Test類中使用了Data類中一個final的int數字fNumber=77,這時候,java編譯器會將77這個常數編譯到Test類的指令碼或者常量池中。這樣,每次Test類用到fNumber的時候,不會通過引用連接到Data類中進行讀取,而是直接使用自己保存在類文件中的副本。用程序說話:Test.java:

public class Test{public static void main(String[] args){System.out.println(Data.fNumber);}}

Data.java:

public class Data{public static final int fNumber=77; }

執行命令和結果:

Microsoft Windows xp [版本 5.1.2600](C) 版權所有 1985-2001 Microsoft Corp.

C:/Documents and Settings/zangmeng>cd ..

C:/Documents and Settings>cd ..

C:/>javac Test.java

C:/>JavaTest77

C:/>

這時候,我們更改Data.java的內容:public class Data{public static final int fNumber=777; }

然后執行如下命令:

C:/>javac Data.java

C:/>java Test77

C:/>

這里我們看到,雖然Data.java中的fNumber已經更改為777,而且已經重新編譯了,但是因為編譯器把fNumber的副本保存Test類中,所以在重新編譯Test類的前,Test類一直把fNumber認為是77而不是777。下面我們變異Test.java,再執行,看看結果。

C:/>javac Test.java

C:/>java Test777

C:/>這時候,我們看到,重新編譯的Test類將新的777數值封裝到了自己類中。

整個過程如下:Microsoft Windows XP [版本 5.1.2600](C) 版權所有 1985-2001 Microsoft Corp.

C:/Documents and Settings/zangmeng>cd ..

C:/Documents and Settings>cd ..

C:/>javac Test.java

C:/>java Test77//在這里改變了Data.java的內容C:/>javac Data.java

C:/>java Test77

C:/>javac Test.java

C:/>java Test777

C:/>

這個是java編譯器的優化,具體的,大家可以繼續參考http://www.blogjava.net/aoxj/archive/2009/11/10/165536.html

另外java的final還有inline的功能,這個和C++有異曲同工之妙。簡單的來說就是內聯函數就是指函數在被調用的地方直接展開,編譯器在調用時不用像一般函數那樣,參數壓棧,返回時參數出棧以及資源釋放等,這樣提高了程序執行速度。

但 是網上有人說這樣并沒有加快速度,這是為什么呢?還不太清楚!!

final使得被修飾的變量"不變",但是由于對象型變量的本質是“引用”,使得“不變”也有了兩種含義:引用本身的不變,和引用指向的對象不變。

引用本身的不變:final StringBuffer a=new StringBuffer("immutable");final StringBuffer b=new StringBuffer("not immutable");a=b;//編譯期錯誤

引用指向的對象不變:final StringBuffer a=new StringBuffer("immutable");a.append(" broken!"); //編譯通過

可見,final只對引用的“值”(也即它所指向的那個對象的內存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會導致編譯期錯誤。至于它所指向的對象的變化,final是不負責的。這很類似==操作符:==操作符只負責引用的“值”相等,至于這個地址所指向的對象內容是否相等,==操作符是不管的。

Java的局部內部類以及final類型的參數和變量

本文是Thinking In Java中其中一段的閱讀總結。如果定義一個匿名內部類,并且希望它使用一個在其外部定的對象,那么編譯器會要求其參數引用是final 的。經研究,Java虛擬機的實現方式是,編譯器會探測局部內部類中是否有直接使用外部定義變量的情況,如果有訪問就會定義一個同類型的變量,然后在構造方法中用外部變量給自己定義的變量賦值。

Thinking In Java里面的說法(唯一正確的說法): 如果定義一個匿名內部類,并且希望它使用一個在其外部定的對象,那么編譯器會要求其參數引用是final 的。

public class Tester {

public static void main(String[] args) {

A a = new A();

C c = new C();

c.shoutc(a.shout(5));

} } ////////////////////////////////////////////////////////

class A {

public void shouta() {

System.out.println("Hello A");

}

public A shout(final int arg) {

class B extends A {

public void shouta() {

System.out.println("Hello B" + arg);

} }

return new B();

} } ////////////////////////////////////////////////////////

class C {

void shoutc(A a) {

a.shouta();

} } 

c.shoutc(a.shout(5)),在a.shout(5)得到返回值后,a的shout()方法棧被清空了,即arg不存在了,而c.shoutc()卻又調用了a.shouta()去執行System.out.println("Hello B" + arg)。

再來看Java虛擬機是怎么實現這個詭異的訪問的:有人認為這種訪問之所以能完成,是因為arg是final的,由于變量的生命周期,事實是這樣的嗎?方法棧都不存在了,變量即使存在,怎么可能還被訪問到?試想下:一個方法能訪問另一個方法的定義的final局部變量嗎(不通過返回值)?

研究一下這個詭異的訪問執行的原理,用反射探測一下局部內部類 。編譯器會探測局部內部類中是否有直接使用外部定義變量的情況,如果有訪問就會定義一個同類型的變量,然后在構造方法中用外部變量給自己定義的變量賦值,而后局部內部類所使用的變量都是自己定義的變量,所以就可以訪問了。見下:

class A$1$B {

A$1$B(A, int);

private final int var$arg;

private final A this$0;

} A$1$B類型的對象會使用自定義的var$arg變量,而不是shout()方法中的final int arg變量,當然就可以訪問了。

那么為什么外部變量要是final的呢?即使外部變量不是final,編譯器也可以如此處理:自己定義一個同類型的變量,然后在構造方法中賦值就行了。原因就是為了讓我們能夠挺合邏輯的直接使用外部變量,而且看起來是在始終使用 外部的arg變量(而不是賦值以后的自己的字段)。

考慮出現這種情況:在局部內部類中使用外部變量arg,如果編譯器允許arg不是final的,那么就可以對這個變量作變值操作(例如arg++),根據前面的分析,變值操作改變的是var$arg,而外部的變量arg并沒有變,仍然是5(var$arg才是6)。因此為了避免這樣如此不合邏輯的事情發生:你用了外部變量,又改變了變量的值,但那個變量卻沒有變化,自然的arg就被強行規定必須是final所修飾的,以確保讓兩個值永遠一樣,或所指向的對象永遠一樣(后者可能更重要)。

還有一點需要注意的是內部類與方法不是同時執行的,比如實現ActionListener,只有當事件發生的時候才會執行,而這時方法已經結束了。

也有人這樣說:匿名內部類要訪問局部變量,但是函數的局部變量在執行完后會立即退出,銷毀掉所有臨時變量。而產生的匿名內部類可能會保留。在java中方法不是對象,不存儲狀態,這時候匿名內部類已經沒有外部環境了。我猜想匿名內部類可能會把需要訪問的外部變量作為一個隱藏的字段,這樣只是得到了一個變量的引用拷貝,所以是只讀的,所以編譯器要求給要訪問的外部局部變量加final。

可以用一個包裝對象來突破這一限制。final Result result=new Result();sqlMaker.selectById(id).execute(getTransaction(),new Function(){public Object call(Object... args) {ResultSet rs=(ResultSet)args[0];Object obj=sqlMaker.getTable().readFirstObject(rs);result.setValue(obj);return null;}});T r= (T)result.getValue();

理解final問題有很重要的含義。許多程序漏洞都基于此----final只能保證引用永遠指向固定對象,不能保證那個對象的狀態不變。在多線程的操作中,一個對象會被多個線程共享或修改,一個線程對對象無意識的修改可能會導致另一個使用此對象的線程崩潰。一個錯誤的解決方法就是在此對象新建的時候把它聲明為final,意圖使得它“永遠不變”。其實那是徒勞的

請教大家個問題,想了好久也不明白,為什么在某方法內定義一個匿名內部類,并且希望它使用外部定義的對象,那么要求此方法的參數引用要聲明為final?(我只知道final的作用對于對象引用來説,此對象引用不能指向新的對象,對于基本類型就是不能改變它的值) 因為內部要copy一份自己使用,怕你在外邊改了造成一些不確定的問題。所以干脆final

http://forums.sun.com/thread.jspa?threadID=5325241&messageID=10392871這是一個編譯器設計的問題,如果你了解java的編譯原理的話很容易理解。 首先,內部類被編譯的時候會生成一個單獨的內部類的.class文件,這個文件并不與外部類在同一class文件中。 當外部類傳的參數被內部類調用時,從java程序的角度來看是直接的調用例如: public void dosome(final String a,final int b){ class Dosome{public void dosome(){System.out.println(a+b)}}; Dosome some=new Dosome(); some.dosome(); } 從代碼來看好像是那個內部類直接調用的a參數和b參數,但是實際上不是,在java編譯器編譯以后實際的操作代碼是class Outer$Dosome{ public Dosome(final String a,final int b){ this.Dosome$a=a; this.Dosome$b=b; } public void dosome(){ System.out.println(this.Dosome$a+this.Dosome$b); } }} 從以上代碼看來,內部類并不是直接調用方法傳進來的參數,而是內部類將傳進來的參數通過自己的構造器備份到了自己的內部,自己內部的方法調用的實際是自己的屬性而不是外部類方法的參數。 這樣理解就很容易得出為什么要用final了,因為兩者從外表看起來是同一個東西,實際上卻不是這樣,如果內部類改掉了這些參數的值也不可能影響到原參數,然而這樣卻失去了參數的一致性,因為從編程人員的角度來看他們是同一個東西,如果編程人員在程序設計的時候在內部類中改掉參數的值,但是外部調用的時候又發現值其實沒有被改掉,這就讓人非常的難以理解和接受,為了避免這種尷尬的問題存在,所以編譯器設計人員把內部類能夠使用的參數設定為必須是final來規避這種莫名其妙錯誤的存在。 實現的確是如此,不過final只是讓一個引用不能修改而已,照樣可以修改它指向的數據的內容。 

再 一次闡述 內部類,final

1)所謂“局部內部類”就是在對象的方法成員內部定義的類。而方法中的類,訪問同一個方法中的局部變量,是天經地義的。那么為什么要加上一個final呢? 2)原因是:編譯程序實現上的困難,難在何處:內部類對象的生命周期會超過局部變量的生命期。為什么?表現在:局部變量的生命期:當該方法被調用時,該方法中的局部變量在棧中被創建(誕生),當方法調用結束時(執行完畢),退棧,這些局部變量全部死亡。而:內部類對象生命期,與其它類一樣,當創建一個該局部類對象后,只有沒有其它人再引用它時,它才能死亡。完全可能:一個方法已調用結束(局部變量已死亡),但該局部類的對象仍然活著。即:局部類的對象生命期會超過局部變量。 3)退一萬步:局部類的對象生命期會超過局部變量又怎樣?問題的真正核心是:如果:局部內部類的對象訪問同一個方法中的局部變量,是天經地義的,那么:只要局部內部類對象還活著,則:棧中的那些它要訪問的局部變量就不能“死亡”(否則:它都死了,還訪問個什么呢?),這就是說:局部變量的生命期至少等于或大于局部內部類對象的生命期。而:正是這一點是不可能做到的4)但是從理論上:局部內部類的對象訪問同一個方法中的局部變量,是天經地義的。所以:經過努力,達到一個折中結果:即:局部內部類的對象可以訪問同一個方法中的局部變量,只要這個變量被定義為final.那么:為什么定義為final變可以呢?定義為final后,編譯程序就好實現了:具體實現方法是:將所有的局部內部類對象要訪問的final型局部變量,都成員該內部類對象中的一個數據成員。這樣,即使棧中局部變量(含final)已死亡,但由于它是final,其值永不變,因而局部內部類對象在變量死亡后,照樣可以訪問final型局部變量。

不管變量是不是final,他的生命周期都在于{}中。

不管對象是不是final,他的生命周期都是 new開始,垃圾回收結束。 類對象(class對象)與其它對象不同,類對象的生命周期 開始于類被加到內存中那一刻,結束于垃圾回收。 類變量(static)與類對象的生命周期相同。

解析就是對于編譯型常量使用直接的內存地址代替變量,如final static int a = 10;但是對于在編譯的時候不能得到具體值得變量不做變換,如final static int a = Math.random()。

final和abstract一樣,都是非訪問控制符,當然也不會改變作用域 protect,private,public才是訪問控制符


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 甘洛县| 赤水市| 潜江市| 阿勒泰市| 北川| 垣曲县| 长治市| 丰城市| 罗平县| 惠东县| 万山特区| 巴里| 自治县| 乳山市| 沂南县| 奈曼旗| 元朗区| 家居| 蕲春县| 苏尼特右旗| 东丰县| 阿鲁科尔沁旗| 平舆县| 江华| 甘德县| 渑池县| 来凤县| 马边| 门源| 南昌市| 龙陵县| 长宁区| 乌审旗| 乌海市| 辰溪县| 五大连池市| 青州市| 左贡县| 朝阳县| 微山县| 布拖县|