網易希望遊戲50%收入來自海外 將涉足主機和雲遊戲領域
1、簡單概述
java中的三大特性:封裝、繼承、多型
在封裝特性中,程式設計師在對屬性的操作的時候,儘量要通過方法來操作屬性。
當然並非是所有的都需要這麼來進行操作,比如說static關鍵字修飾的屬性,就可以直接通過型別.屬性名來進行操作。但是通常來說,我們不涉及到static修飾的時候,我們都會通過public關鍵字修飾的方法來進行呼叫private關鍵字修飾的,因為public關鍵字修飾的更多的是要暴露給外部來進行使用。
繼承做為承上啟下的關鍵的一點,所以繼承這一塊要學好。因為承接了封裝,也是多型的基石 。
繼承是類和類之間的關係;繼承是通過已經存在的類作為基礎,從而建立起來新的類;所以繼承中的知識就是圍繞著繼承來進行展開的。
首先需要進行宣告的是:如果java中的一個類沒有顯示的有繼承關係,也就是沒有extends關鍵字,那麼預設的繼承的是Object類。
在之前的java封裝中寫過一個標準的javabean類,但是還是有些細節沒有描述清楚
public class Person { private Integer id; private String username; public Person() { } public Person(Integer id, String username) { this.id = id; this.username = username; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String toString() { return "Person{" + "id=" + id + ", username='" + username + '\'' + '}'; } }
可以從上面看到類上沒有extends關鍵字,在java中預設給省略了,其實是繼承了Object類的。關鍵沒有顯示的寫,但是最終依然會使用到其中的方法
public class Object { private static native void registerNatives(); static { registerNatives(); } // 這個也是經常使用到的方法 public final native Class<?> getClass(); // 下面兩個方法是常用的方法 public native int hashCode(); public boolean equals(Object obj) { return (this == obj); } protected native Object clone() throws CloneNotSupportedException; // 最常用的方法。但是這個方法通常都是要被重寫的,因為這個原始方法沒有太大的參考意義 public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); } // 執行緒中的方法 public final native void notify(); public final native void wait(long timeout) throws InterruptedException; public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); } public final void wait() throws InterruptedException { wait(0); } // 垃圾回收呼叫的方法 protected void finalize() throws Throwable { } }
2、繼承體系
對於繼承來說,我更感覺每個子類例項物件都擁有了父類的一塊空間。因為對於一個子類來說,父類是一個通用的模板。每個子類都有著父類的空間。
我在這裡畫出來自己理解的一塊記憶體圖:
這裡模擬三個類之間繼承的關係圖,有些不太準備,應該有方法區的引用資訊,這裡沒有畫出:
對於當前類來說,物件例項在堆記憶體有一塊空間,但是這塊記憶體並非只包含了當前類的屬性和方法的空間,還包含了父類的一些資訊,還有父類的父類的一些資訊。
針對於上面的Person類,那麼對應的關係圖就應該是:
那麼接下來就需要詳細的講解下一個物件中的記憶體圖的初始化過程。
3、初始化過程
類似之前C語言中提到過的變數為什麼需要進行初始化,創建出來一個物件,也需要經過分配記憶體和初始化操作等。
對於建立一個物件來說:
Person person = new Person();
這段程式碼的實現做了三件事情:
1、new關鍵字負責向作業系統申請分配記憶體;
2、呼叫了Person類的建構函式;
3、Person將會去初始化父類空間。
在第一步中我們無需關注,更多的是需要關注第二步和第三步。
從第二步中首先會呼叫Person類的建構函式,如下所示:
public Person() {
}
public Person(Integer id, String username) {
this.id = id;
this.username = username;
}
首先,從建構函式入手。其實這裡也省略了一些程式碼,我顯示的寫出來:
public Person() {
super();
}
public Person(Integer id, String username) {
super();
this.id = id;
this.username = username;
}
通過這個super,這結合著上面畫的體系結構圖,可以判斷出這個super()使用來初始化父類空間的。但是因為沒有new關鍵字,是不會建立父類物件的,只是用來做初始化的,初始化父類中的屬性等。開篇的時候提到過,繼承是通過已經存在的類創建出來一個新的類。
通過構造方法可以達到對父類空間的初始化,但是一般來說,都是隱式的,除非自己有特定的要求。
那麼初始化父類空間做了一些什麼事情?
首先會介紹一些概念,可能會引起各位看官的極度不適,非戰鬥人員請及時撤離:
參考部落格:https://www.cnblogs.com/ysocean/p/8194428.html#_label0_2
靜態程式碼塊:靜態程式碼塊在類被載入的時候就運行了,而且只執行一次,並且優先於各種程式碼塊以及建構函式。如果一個類中有多個靜態程式碼塊,會按照書寫順序依次執行
構造程式碼塊:構造程式碼塊在建立物件時被呼叫,每次建立物件都會呼叫一次,但是優先於建構函式執行。需要注意的是,如果不例項化物件,構造程式碼塊是不會執行的。
建構函式:在類的物件建立時定義初始化的狀態
普通程式碼塊:普通程式碼塊就是方法中的{}
下面來進行驗證:
public class CodeblockDemo1 {
static {
System.out.println("hello,static code1");
}
static {
System.out.println("hello,static code2");
}
static {
System.out.println("hello,static code3");
}
}
測試程式碼:
public class Test {
public static void main(String[] args) {
try {
Class.forName("com.guang.javaextends.CodeblockDemo1");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
檢視控制檯輸出,可以看到輸出了CodeblockDemo1類中的靜態程式碼塊,驗證了上面說過的,在類載入的時候就執行,而且是按照靜態程式碼塊的順序執行的。
但是這裡並沒有證實只會執行一次,這個馬上證明。
public class CodeblockDemo1 {
static {
System.out.println("hello,static code1");
}
static {
System.out.println("hello,static code2");
}
static {
System.out.println("hello,static code3");
}
{
System.out.println("構造程式碼塊....");
}
public CodeblockDemo1() {
System.out.println("構造方法.......");
}
}
測試類驗證:
public class Test {
public static void main(String[] args) {
try {
Class.forName("com.guang.javaextends.CodeblockDemo1");
CodeblockDemo1 demo1 = new CodeblockDemo1();
CodeblockDemo1 demo2 = new CodeblockDemo1();
CodeblockDemo1 demo3 = new CodeblockDemo1();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
檢視控制檯輸出:
hello,static code1
hello,static code2
hello,static code3
構造程式碼塊....
構造方法.......
構造程式碼塊....
構造方法.......
構造程式碼塊....
構造方法.......
通過上面的Demo可以看到,靜態程式碼塊是隻會在載入的時候執行一次,而構造程式碼塊會結合著建構函式一同使用的。但是構造程式碼塊一般來說使用的比較少。
下面再來證明一下上面將的順序問題,再寫一個類來繼承CodeblockDemo1
public class CodeblockDemo2 extends CodeblockDemo1 {
static {
System.out.println("hello,static code11");
}
static {
System.out.println("hello,static code22");
}
static {
System.out.println("hello,static code33");
}
{
System.out.println("構造程式碼塊....");
}
public CodeblockDemo2() {
System.out.println("構造方法.......");
}
}
測試類:
public class Test {
public static void main(String[] args) {
try {
Class.forName("com.guang.javaextends.CodeblockDemo2");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
檢視控制檯輸出:
hello,static code1
hello,static code2
hello,static code3
hello,static code11
hello,static code22
hello,static code33
可以看到,先訪問父類中的靜態方法,然後再訪問子類的靜態方法。
在接著看,建構函式和靜態方法的結合:
public class CodeblockDemo1 {
static {
System.out.println("hello,static code1");
}
static {
System.out.println("hello,static code2");
}
static {
System.out.println("hello,static code3");
}
{
System.out.println("構造程式碼塊1....");
}
public CodeblockDemo1() {
System.out.println("構造方法11.......");
}
}
public class CodeblockDemo2 extends CodeblockDemo1 {
static {
System.out.println("hello,static code11");
}
static {
System.out.println("hello,static code22");
}
static {
System.out.println("hello,static code33");
}
{
System.out.println("構造程式碼塊11....");
}
public CodeblockDemo2() {
System.out.println("構造方法11.......");
}
}
在測試類中測試:
public class Test {
public static void main(String[] args) {
try {
Class.forName("com.guang.javaextends.CodeblockDemo2");
CodeblockDemo2 demo1 = new CodeblockDemo2();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
檢視控制檯輸出:
hello,static code1
hello,static code2
hello,static code3
hello,static code11
hello,static code22
hello,static code33
構造程式碼塊1....
構造方法11.......
構造程式碼塊11....
構造方法11.......
可以看到,先載入父類中的靜態方法,然後再載入子類中的靜態方法。然後訪問建構函式中,先訪問父類的構造程式碼塊和建構函式,然後再去訪問子類的。
其實這裡也很好理解,因為對於類來說,不一定要建立物件,所以先將裡面的靜態方法載入完,然後再根據需要載入非靜態的。
同時也可以看到,呼叫了構造程式碼塊和構造方法,這裡只是做一些初始化工作。
4、this和super
這兩個關鍵字就非常簡單了,可以理解成this就是當前類的物件,super代表的是當前類的父類引用。
唯一一點使用的時候在於建構函式中,不能夠同時出現this()和super()等,不管是有參還是無參。
5、總結
用上面的一張記憶體圖來進行解釋十分合理。
對於父子類:如果不需要建立物件,首先載入父類中的靜態函式,然後載入子類中的靜態函式;
對於建立一個物件來說,首先經過記憶體分配,然後對成員進行初始化。首先也需要經過上面的一步。然後需要經過父類的構造程式碼塊和建構函式,再經過子類的構造程式碼塊和建構函式。
千里之行,始於足下。不積跬步,無以至千里