Java 老是採用按值調用。java
public class PrimitiveTransferTest {
public static void swap(int a, int b) {
int tmp = a;
a = b;
b = tmp;
System.out.println("swap 方法裏 a 的值爲: " + a + " b的值爲: " + b);
}
public static void main(String[] args) {
int a = 6;
int b = 9;
swap(a, b);
System.out.println("交換結束後 a 的值爲 " + a + " b的值爲 " + b);
}
}
/** 運行結果: swap 方法裏 a 的值爲: 9 b的值爲: 6 交換結束後 a 的值爲 6 b的值爲 9 */
複製代碼
分析圖:設計模式
java 程序老是從 main() 方法開始執行,main() 方法定義了 a、b 兩個局部變量,兩個變量在 main 棧區中。在 main() 方法中調用 swap() 方法時,main() 方法此時還未結束,所以系統爲 main 方法和 swap 方法分配了兩塊棧區,用於保存 main 方法和 swap 方法的局部變量。main 方法中的 a、b 變量做爲參數傳入 swap 方法,其實是在 swap 方法棧區中從新產生了兩個變量 a、b,並將 main 方法棧區中 a、b 變量的值分別賦給 swap 方法棧區中的 a、b 參數(這就是初始化)。此時系統內存中有兩個 a 變量、兩個 b 變量,只是存在於不一樣的方法棧區中而已。ide
public class ReferenceTransferTest {
public static void swap(DataWrap dw) {
int tmp = dw.a;
dw.a = dw.b;
dw.b = tmp;
System.out.println("swap 方法裏, a 成員變量的的值爲: " + dw.a + " b 成員變量的值爲: " + dw.b);
}
public static void main(String[] args) {
DataWrap dw = new DataWrap();
dw.a = 6;
dw.b = 9;
swap(dw);
System.out.println("交換結束後, a 成員變量的的值爲: " + dw.a + " b 成員變量的值爲: " + dw.b);
}
}
/** swap 方法裏, a 成員變量的的值爲: 9 b 成員變量的值爲: 6 交換結束後, a 成員變量的的值爲: 9 b 成員變量的值爲: 6 */
複製代碼
你可能會疑問,dw 對象的成員變量 a、b的值也被替換了,這跟前面基本類型的傳遞徹底不同。這很是容易讓人以爲,調用傳入 swap 方法的就是 dw 對象自己,而不是它的複製品。其實傳遞的依然是 dw 的值。函數
分析圖:this
系統同樣賦值了 dw 的副本,只是關鍵在於 dw 只是一個引用變量,它存儲的值只是一段內存地址,將該內存地址傳遞給 swap 棧區,此時 swap 棧區的 dw 和 main 棧區的 dw 的值也就是內存地址相同,該段內存地址指向堆內存中的 DataWrap 對象。對 swap 棧區的 dw 操做,也就是對 DataWrap 對象操做。spa
重載:同一個類中,方法名相同,參數列表不一樣。設計
當調用被重載的方法時,根據參數的個數和類型判斷應該調用哪一個重載方法,參數徹底匹配的方法將被執行。code
僅當類沒有定義任何構造器的時候,系統纔會提供一個默認的構造器。這個構造器將全部的實例域設置爲默認值。cdn
當類中有自定義構造器時,系統不會再提供默認的構造器對象
public static final double PI = 3.1415926
複製代碼
在類加載的時候就存在了,不依賴於任何類的任何實例。
建議經過類名調用,而不是經過實例對象調用,不然很容易混淆概念。
java 使用 extends 做爲繼承的關鍵字,有趣的是 extends 是擴展的意思,並非繼承。可是 extends 很好體現了子類和父類的關係,子類是對父類的擴展,子類是一種特殊的父類。擴展更加準確。ps:這個理解真的是流弊啊。
方法的重寫遵循 「兩同兩小一大」規則:
方法名相同、形參列表相同
子類方法返回的值類型應比父類方法返回值類型更小或相等,子類方法聲明拋出的異常類應該比父類方法聲明拋出的異常類更小或相等
子類方法訪問權限應該比父類方法的訪問權限更大或相等
子類不能繼承父類的構造器。在子類的構造器中,若是沒有顯式使用 super 調用父類的構造函數,那麼系統必定會在子類構造器執行以前,隱式的調用父類的無參構造器
在子類構造器中,可使用 super 顯式調用父類構造器,但 super 語句必須在第一行
Java 引用變量有兩個類型。若是編譯時類型和運行時類型不一致,就可能出現多態。
編譯時類型:由聲明該變量時使用的類型決定
運行時類型:由實際賦給該變量的對象決定
示例代碼:
public class BaseClass {
public int book = 6;
public void base() {
System.out.println("父類的普通方法");
}
public void test() {
System.out.println("父類的test方法");
}
}
public class SubClass extends BaseClass {
public String book = "輕量級 Java EE";
public void test() {
System.out.println("子類的test方法");
}
public void sub() {
System.out.println("子類的sub方法");
}
public static void main(String[] args) {
BaseClass ploymophicBc = new SubClass();
System.out.println(ploymophicBc.book);
ploymophicBc.base();
ploymophicBc.test();
// 由於 ploymophicBc 的編譯時類型是 BaseClass
// BaseClass 類沒有提供 sub 方法,因此下面代碼編譯時會出錯
// ploymophicBc.sub();
}
}
複製代碼
上面的例子中,引用變量 ploymophicBc 比較特殊,它的編譯時類型是 BaseClass,而運行時類型是 SubClass。
ploymophicBc.sub() 這行代碼會在編譯時報錯,由於 ploymophicBc 編譯時類型爲 BaseClass,而 BaseClass 中沒有定義 sub 方法,所以編譯時沒法經過。
可是注意,ploymophicBc.book 的值爲 6, 而不是 」輕量級 Java EE「。由於對象的實例變量不具有多態性,系統老是試圖訪問它編譯時類型所定義的成員變量,而非運行時。
子類實際上是一種特殊的父類,所以 java 容許把父類的引用指向子類對象,這被稱爲向上轉型(upcasting),向上轉型由系統自動完成。
能夠調用哪些方法,取決於引用類型(編譯時)。 具體調用哪一個方法,取決於引用指向的實例對象(運行時)。
問題:引用變量在代碼編譯過程當中,只能調用它編譯時類型具有的方法,而不能調用它運行時類型具有的方法
解決:強制轉換成運行時類型
方法:引用類型之間的轉換隻能在有繼承關係的兩個類型之間進行,不然編譯出錯。若是想把一個父類引用變量的編譯時類型轉換成子類類型,則這個引用變量的運行時類型得是子類類型,不然引起 ClassCastException
示例代碼:
//建立子類對象
Dog dog = new Dog();
// 向上類型轉換(類型自動提高),不存在風險
Animal animal = dog;
// 風險演示 animal 指向 Dog 類型對象,沒有辦法轉化成 Cat 對象,編譯階段不會報錯,可是運行會報錯
Cat cat = (Cat)animal; // 1.編譯時按 Cat 類型 2. 運行時 Dog 類型,類型不匹配,直接報錯
複製代碼
爲了解決強制類型轉換,可能引起的 ClassCastException 異常,引入 instanceof 運算符。
instanceof 運算符的含義:用於判斷左邊的對象(運行時類型或者叫實際類型)是不是右邊的類或者其子類、實現類的實例。若是是返回 true,不然返回 false。
在以前的代碼中,強制類型轉換前使用 instanceof 判斷:
if (anmial instanceof Cat) {
Cat cat = (Cat)animal;
}
複製代碼
不能被繼承
不可被子類覆蓋
特徵:變量一旦被初始化,便不可改變
初始化:定義時直接賦值、藉助構造函數
對於基本類型域而言,其值是不可變的。
對於引用類型變量而言,它保存的僅僅只是個引用。final 只保證這個變量所引用的地址不會改變,即一直引用同一個對象。但這個對象自身內容徹底能夠發生改變。
toString 用於輸出對象的自我描述信息。
Object 類提供的 toString 返回該對象實現類的 "類名 + @ + hashCode"。一般須要重寫該方法。
對於數值類型的基本變量,只要兩個變量的值相等(不須要數據類型徹底相同),就返回 true。
對於兩個引用類型的變量,只有它們指向同一個對象時,== 判斷纔會返回 true。
equals 方法是 Object 類提供的一個實例方法。對於引用變量,只有指向同一個對象時才返回 true。通常須要重寫 equals 方法。
重寫 equals 方法的示例:
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj !=null && obj.getClass() == Person.class) {
Person personObj = (Person)obj;
if (this.getIdStr().equals(personObj.getIdStr())) {
return true;
}
}
return false;
}
複製代碼
equals 爲 true,hashCode 就應該相等,這是一種約定俗稱的規範。即 equals 爲 true 是 hashCode 相等的充分非必要條件。
接口體現的是規範和實現分離的設計哲學,讓軟件系統的各組件之間面向接口耦合,是一種鬆耦合的設計
接口定義的是多個類共同的公共行爲規範,這些行爲是與外部交流的通道,意味着接口一般是定義一組公共方法
接口的修飾符,只能是 public 或者 default
因爲接口定義的是一種規範,因此接口裏不能包含構造器和初始化塊定義,只能包含靜態常量、方法(只能是抽象方法,類方法和默認方法)以及內部類、內部接口、內部枚舉
接口裏的常量只能是靜態常量,默認使用 public static final 修飾
接口裏的內部類、內部接口、內部枚舉,默認使用 public static 修飾
接口裏的抽象方法不能有方法體,但類方法和默認方法必須有方法體。
接口中定義抽象方法能夠省略 abstract 關鍵字和修飾符,默認修飾符爲 public。
Java 8 新增容許在接口中定義默認方法,使用 default 修飾。默認狀況下,系統使用 public 修飾默認方法。
Java 8 新增容許在接口中定義私有方法。
Java 8 新增容許在接口中定義靜態方法。靜態方法能夠被實現的接口的類繼承。
一個類能夠實現一個或多個接口。
一個類實現一個或多個接口,這個類必須重寫所實現的接口中的全部抽象方法。不然,該類必須被定義成抽象類,保留從父接口繼承到的抽象方法。
接口不能用來建立實例,可是能夠用於聲明引用類型的變量,該變量必須指向實現該接口的類的實例對象。
抽象類與普通類的區別,能夠歸納爲 「有得有失」。
得,是指抽象類多了一個能力,抽象類能夠包含抽象方法
失,是指抽象類失去了一個能力,抽象類不能用於建立實例
抽象類和普通類的區別:
抽象類使用 abstract 修飾
抽象類能夠和普通類同樣能夠包含成員變量、方法、構造器、初始化塊、內部類。但抽象類不能被實例化,抽象類的構造器主要用來被子類調用
抽象類能夠不包含抽象方法,可是含有抽象方法的類必須被定義爲抽象類
抽象類的設計思想:抽象類是模板模式的設計模式體現。抽象類是從多個具體類中抽象出來的父類,具備更高層次的抽象。從多個具備相同特徵的類中抽象出一個抽象類,以這個抽象類爲其子類的模板,避免子類設計的隨意性
public class Cow {
private double weight;
public Cow() {
}
public Cow(double weight) {
this.weight = weight;
}
// 定義一個非靜態內部類
private class CowLeg {
private double length;
private String color;
public CowLeg() {}
public CowLeg(double length, String color) {
this.length = length;
this.color = color;
}
public double getLength() {
return this.length;
}
public void setLength(double length) {
this.length = length;
}
public String getColor() {
return this.color;
}
public void setColor(String color) {
this.color = color;
}
public void info() {
System.out.println("當前牛腿的顏色是 " + this.color + ", 長 " + this.length);
// 直接訪問外部類的 private 修飾的成員變量
System.out.println("該牛腿所屬的奶牛重: " + weight);
}
}
public void test() {
CowLeg cl = new CowLeg(1.12, "黑白相間");
cl.info();
}
public static void main(String[] args) {
Cow cow = new Cow(378.9);
cow.test();
}
}
複製代碼
在非靜態內部類裏能夠直接訪問外部類的 private 成員,這是由於在非靜態內部類對象裏,保存了一個它所寄生的外部類對象的引用。以下圖:
若是外部類成員變量、內部類成員變量與內部類裏方法的局部變量名同名
直接訪問局部變量
this,訪問內部類實例的變量
外部類類名.this.varName 訪問外部類實例變量
外部類不能直接訪問非靜態內部類的成員,不管非靜態內部類的成員是什麼修飾符修飾的。只能顯示建立非靜態內部類對象來訪問其實例成員。
若是用 static 修飾一個內部類,則這個內部類就屬於外部類自己,而不屬於外部類的某個對象。所以也叫作類內部類。即靜態內部類是外部類的一個靜態成員。
靜態內部類能夠包含靜態成員,也能夠包含非靜態成員。
靜態內部類不能訪問外部類的實例成員,只能訪問外部類的類成員。
外部類依然不能直接訪問靜態內部類的成員,但可使用靜態內部類的類名做爲調用者來訪問靜態內部類的類成員,也可使用靜態內部類對象做爲調用者來訪問靜態內部類的實例成員。
在外部類之外的地方訪問內部類(包括靜態和非靜態兩種),則內部類不能使用 private 修飾,private 修飾的內部類只能在外部類內部使用。對於使用其餘訪問修飾符的內部類,按照訪問修飾符範圍訪問。
省略訪問控制符的內部類,只能被與外部類處於同一個包中的其餘類所訪問
使用 protected 修飾的內部類,可被與外部類處於同一個包中的其餘類和外部類的子類訪問
使用 public 修飾符的內部類,可在任何地方被訪問
因爲非靜態內部類的對象必須寄生在外部類的對象裏,所以在建立非靜態內部類對象以前,必須先建立其外部類對象。
示例代碼,以下:
public class Out {
// 使用默認訪問控制符,同一個包中的其餘類能夠訪問該內部類
class In {
public In(String msg) {
System.out.println(msg);
}
}
}
複製代碼
public class CreateInnerInstance {
public static void main(String[] args) {
Out.In in = new Out().new In("Test Msg");
/* 上面代碼能夠改成以下三行代碼 使用 OutterClass.InnerClass 的形式定義內部類變量 Out.In in; 建立外部類實例,非靜態內部類實例將寄生在該實例中 Out out = new Out(); 經過外部類實例和new來調用內部類構造器建立非靜態內部類實例 in = out.new In("Test Msg"); */
}
}
複製代碼
下面定義了一個子類繼承了 Out 類的非靜態內部類 In 類
public class SubClass extends Out.In{
// 顯示定義 SubClass 的構造器
public SubClass(Out out){
out.super("hello");
}
}
複製代碼
上面的代碼可能看起來很怪,其實很正常:非靜態內部類 In 類的構造器必須使用外部類對象來調用,代碼中 super 表明調用 In 類的構造器,而 out 則表明外部類對象。
若是須要建立 SubClass 對象時,必須建立一個 Out 對象。由於 SubClass 是非靜態內部類 In 的子類,非靜態內部類 In 對象裏必須有一個對 Out 對象的引用,其子類 SubClass 對象裏也應該持有對 Out 對象的引用。當建立 SubClass 對象時傳給該構造器的 Out 對象,就是 SubClass 對象裏 Out 對應引用所指向的對象。
結合上面兩段代碼,非靜態內部類 In 對象和 SubClass 對象都必須持有指向 Outer 對象的引用,區別是建立兩種對象時傳入 Out 對象的方式不一樣:當建立非靜態內部類 In 類的對象時,必須經過 Outer 對象來調用 new 關鍵字;當建立 SubClass 類的對象時,必須使用 Outer 對象做爲調用者來調用 In 類的構造器
由於靜態內部類是外部類類相關的,所以建立靜態內部類對象時無需建立外部類對象。
public class CreateStaticInnerInstance {
public static void main(String[] args) {
StaticOut.StaticIn in = new StaticOut.StaticIn();
/* 上面的代碼可改成以下兩行代碼 使用 OuterClass.InnerClass 的形式定義內部類變量 StaticOut.StaticIn in; 經過 new 調用內部類構造器建立靜態內部類實例 in = new StaticOut.StaticIn(); */
}
}
複製代碼
由於調用靜態內部類的構造器時不須要使用外部類對象,因此建立靜態內部類的子類也比較簡單。下面代碼爲靜態靜態內部類 StaticIn 定義了一個空的子類
public class StaticSubClass extends StaticOut.StaticIn {}
複製代碼
匿名內部類適合建立只須要一次使用的類,建立匿名內部類時會當即建立一個該類的實例,這個類定義當即消失,匿名類不能重複使用。
匿名類是用來建立接口或者抽象類的實例的。
匿名內部類不能定義構造器。由於匿名內部類沒有類名,全部沒法定義構造器。但匿名內部類能夠定義初始化塊,能夠經過實例初始化塊來完成構造器須要完成的事情。
定義匿名內部類格式以下:
new 實現接口 | 抽象父類構造器(實參列表)
{
匿名內部類的類體部分
}
複製代碼
最經常使用的建立匿名內部類的方式是須要建立某個接口類型的對象,以下
public interface ProductA {
public double getPrice();
public String getName();
}
複製代碼
public class AnonymousTest {
public void test(ProductA p) {
System.out.println("Buy a" + p.getName() + "Cost " + p.getPrice());
}
public static void main(String[] args) {
AnonymousTest ta = new AnonymousTest();
// 調用 test() 方法時,須要傳入一個 Product 參數
// 此處傳入其匿名實現類的實例
ta.test(new ProductA() {
@Override
public double getPrice() {
return 567.8;
}
@Override
public String getName() {
return "APG Card";
}
});
}
}
複製代碼
經過繼承抽象父類來建立匿名內部類時,匿名內部類將擁有和父類相同形參列表的構造器。看下面一段代碼
public abstract class Device {
private String name;
public abstract double getPrice();
public Device() {};
public Device(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
複製代碼
public class AnonymousInner {
public void test(Device d) {
System.out.println("Buy a" + d.getName()+ "Cost" + d.getPrice());
}
public static void main(String[] args) {
AnonymousInner ai = new AnonymousInner();
// 調用有參數的構造器建立 Device 匿名實現類的對象
ai.test(new Device("電子顯示器") {
@Override
public double getPrice() {
return 67.8;
}
});
// 調用無參數的構造器建立 Device 匿名實現類的對象
Device d = new Device() {
// 初始化塊
{
System.out.println("匿名內部類的初始化塊");
}
// 實現抽象方法
@Override
public double getPrice() {
return 56.2;
}
// 重寫父類的實例方法
public String getName() {
return "keyboard";
}
};
ai.test(d);
}
}
複製代碼
歡迎關注個人公衆號