No.2 對象與記憶體控制(記憶體配置),no.2對象
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對象來訪問類變數
- 執行個體變數的初始化時機
- 類變數的初始化時機。只有兩種:定義類變數時指定;靜態初始化塊中。兩者的執行順序為按源碼順序執行。執行完之後再執行另一個
- 類變數初始化分為兩個階段;
- 第一階段;分配記憶體,並且此時有預設初始化值(在賦初始值前,執行個體變數是在構造方法之前,類變數在靜態代碼塊前)
- 第二階段:賦初始值(本質上是在靜態代碼塊中)
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修飾
- 吼吼吼