1.實例變數和類變數 成員變數 VS 局部變數 局部變數(存儲在方法的棧記憶體中) 形參:方法簽名中定義,由方法調用者賦值,隨方法結束而消亡 方法內局部變數:方法內定義,必須在方法內進行顯示初始化,初始化完成後開始生效,隨方法結束而消亡 代碼塊內局部變數:代碼塊內定義,必須代碼塊內進行顯示初始化,初始 ...
1.實例變數和類變數
成員變數 VS 局部變數
- 局部變數(存儲在方法的棧記憶體中)
- 形參:方法簽名中定義,由方法調用者賦值,隨方法結束而消亡
- 方法內局部變數:方法內定義,必須在方法內進行顯示初始化,初始化完成後開始生效,隨方法結束而消亡
- 代碼塊內局部變數:代碼塊內定義,必須代碼塊內進行顯示初始化,初始化完成後開始生效,隨代碼塊結束而消亡
- 成員變數(類體內定義的)
- 無static:非靜態變數/實例變數;有static:靜態變數/類變數
- static
- 從程式角度看,static的作用:將實例成員變為類成員。無static修飾的類的成員,成員屬於類的實例;有static修飾,成員屬於類本身。因此,static只能修飾類的成員
- 定義成員變數時 要 合法前向引用
- 類變數的初始化時機總是處於實例變數的初始化時機之前
-
1 public class RightDef{ 2 // 下麵代碼完全沒有問題 3 int num1 = num2 + 20; 4 static int num2 = 10; 5 }
public class RightDef{ 2 // 非法前向引用 3 int num1 = num2 + 20; 4 int num2 = 10; 5 }
public class RightDef{ 2 // 非法前向引用 3 static int num1 = num2 + 20; 4 static int num2 = 10; 5 }
- 同一個JVM內,每個類只有一個Class對象(可以通過反射獲取該Class對象),但是可以創建多個對象
- Java允許通過類的實例對象訪問類變數,儘管類變數是屬於類Class對象的(實例對象並不具有類變數);底層是:轉換為通過類Class對象來訪問類變數
- 實例變數的初始化時機
- 類中定義實例變數指定初始值;類中的非靜態代碼塊中指定初始值;構造器中指定初始值
-
class Cat{ String name; int age; // 構造器中指定 public Cat(String name, int age){ this.name = name; this.age = age; } // 非靜態代碼塊指定 { weight = 2.0; } // 定義變數時指定 double weight = 2.3; }
- 三者的執行順序:經過編譯器處理之後,三者的賦值語句都被合併到構造器中。合併過程中,定義變數語句轉換得到的賦值語句、初始化塊里的語句轉換得到的賦值語句,總是位於構造器的所有語句之前;而前兩種語句編譯合併後的順序與它們在源代碼中的順序相同。父類構造器方法最先調用,然後再合併。javap 命令查看
- 因此,賦值語句的執行時機為:定義變數時指定,非靜態代碼塊中指定(在創建對象實例時,非靜態代碼塊會執行) 先於 構造器中的賦值語句,至於前兩者的順序與它們的源碼順序相同,二者地位平等。
- 上例中Cat的weight屬性值為2.3
- 類變數的初始化時機。只有兩種:定義類變數時指定;靜態初始化塊中。兩者的執行順序為按源碼順序執行。執行完之後再執行另一個
- 類變數初始化分為兩個階段;
- 第一階段;分配記憶體,並且此時有預設初始化值(在賦初始值前,實例變數是在構造方法之前,類變數在靜態代碼塊前)
- 第二階段:賦初始值(本質上是在靜態代碼塊中)
- 類變數初始化分為兩個階段;
2.父類構造器
- 隱式調用和顯式調用
- 只要在程式創建java對象時,系統總是先調用最頂層父類的初始化操作(包括初始化塊和</先於> 構造器),然後依次向下調用所有父類的初始化操作,最終執行本類的初始化操作返回本類的對象實例。
- 至於先調用父類的哪個構造器?
- ①super顯示調用父類構造器
- ②this調用本類重載構造器,然後轉到情形①
- ③既沒有顯示super,也沒有顯示this。系統在執行子類構造器前, 隱式調用父類的無參構造器。
- 註意:super/this,用於調用構造器(mine:構造只需一次,並且應該先構造出來,才有其他)。只能用於構造器中,必須作為第一行代碼,只能有一個(不能同時出現),只能調用一次
- 訪問子類對象的實例變數
- 在執行構造器代碼之前,對象所占的記憶體已經被分配下來,此時記憶體里的值預設是空值。構造器只是賦初始值。
- 當變數的編譯類型和運行類型不同時,通過該變數訪問引用對象的實例變數時,實例變數的值由聲明該變數的類型決定(編譯類型);但是當訪問方法時,由實際所引用的對象來決定(運行類型)
- 訪問被子類重寫的方法
- 應該避免在父類的構造器中調用被子類重寫過的方法。否則實際運行過程中,構造器調用的將是子類重寫的方法而不是父類的方法
- 因為:如果調用了,那麼通過子類的構造器創建子類對象實例時,若調用到父類的這個構造器,那麼會導致子類的重寫方法在子類的構造器的所有代碼執行前執行,從而導致子類的重寫方法訪問不到子類的實例變數的值得情形
3.父子實例的記憶體控制
- 繼承成員變數與繼承方法的區別
- 對於一個引用類型的變數而言,當通過該變數訪問引用對象的實例變數時,該實例變數的值取決於聲明該變數時的類型;當通過該變數調用所引用對象的方法時,該方法行為取決於他所實際引用的對象的類型
- why:編譯器會將繼承的方法轉移到子類中,但是並不會將繼承的成員變數轉移到子類中;因此,重寫方法將完全覆蓋父類的方法,實例變數卻不可能覆蓋父類的成員變數,子類中允許與父類中同名的實例變數。
- 記憶體中子類實例
- 系統中並沒有父類對象實例,只有子類對象實例,但是子類對象實例中保存了它的所有父類所定義的全部實例變數,因此可以重名。
- 當程式創建一個子類對象時,系統不僅僅會為該類中定義的實例變數分配記憶體,也會為其父類中定義的所有實例變數分配記憶體,即使父子類中有重名出現(子類會隱藏父類中的同名實例變數,但是不會完全覆蓋)
- super關鍵字本身並沒有引用任何對象,甚至不能被當成一個真正的引用變數來使用(限定作用)
-
- 子類方法不能直接直接使用return super;卻可以直接使用return this放回調用對象
- 程式不允許直接把super當成變數使用,例如判斷 super == a;這條語句將引起編譯錯誤
- super語句的作用:為了訪問父類中定義的、被隱藏的實例變數/調用父類中定義的、被覆蓋的方法,可以通過super.作為限定來修飾這些實例變數和方法
- 父、子類的類變數
- super.作為限定來訪問父類中定義的類變數(也可直接 父類名.屬性值<推薦,可讀性佳>)
4.final修飾符
- final修飾的變數
- 普通實例變亮,有預設初始值,但是final修飾的必須顯示的賦初始化值
- 本質上final實例變數只能在構造器中顯示賦值
- 本質上final類變數只能在靜態初始化塊中進行賦值
- final修飾的局部變數,需要顯示賦值,不能修改
- 執行“巨集替換”的變數
- 對於一個final修飾的變數而言(不管是類變數、實例變數、還是局部變數),如果定義該final變數時就指定初始值,而且這個初始值可以再編譯時就確定下來(直接量,基本的算術表達式,字元串拼接<包括隱士類型轉換,但是顯式轉換則不可以><沒有訪問變數,沒有調用方法><編譯器很笨,不能有調用(變數/方法/...)>),那麼這個final變數本質上將不再是變數,而是相當於一個直接量。(可以通過 “==” 方法驗證)
- 即“巨集變數”,編譯器會 把程式中 用到該變數的方法直接替換為改變數的值。
- final變數只有在定義變數時指定初始值才會有“巨集變數”效果,在其他地方指定則不會有(與編譯器 比較 笨 脫不了干係)
- final方法不能被重寫
- 如果子類中並不能訪問到父類中的某方法(比如限定符),那麼子類中定義的同名方法並不屬於對父類中方法的重寫(@override驗證),只是一個普通的方法,那麼就自然沒有final一說了
- 內部類中的局部變數
- 局部內部類<方法體中/代碼塊中>(包括匿名內部類)訪問方法體內的局部變數(其他內部類<靜態/非靜態內部類>也訪問不到方法體內的局部變數),必須用final修飾
- why:局部內部類產生的隱式“閉包”將使局部變數脫離它所在的方法而繼續存在(擴大作用域)
- 內部類可能擴大局部變數的作用域(eg. 內部類中新建線程,在新線程中調用局部變數),為了避免局部變數所在方法執行完畢仍然能夠隨意更改局部變數值引起的極大混亂,編譯器要求所有被內部類訪問的局部變數都必須使用final修飾
- 吼吼吼