一、方法
1、概述
方法,也可以稱之為函式,在其他語言中可能方法和函式的概念和語法是不同的,比如Python中的函式是可以在檔案中獨立定義并存在的,而方法則是在類之中定義的函式,但是在Java中,方法和函式都指的是同一個語法,都是一樣的,既可以稱它為方法,也可以稱它為函式,需要注意以下幾點:
- 方法是定義在類體之中的,
- 類體之中的多個方法之間是沒有順序關系的,
- 方法體之中不能再定義方法,
2、定義方法
語法如下:
[修飾符串列] 回傳值型別 方法名(形式引數串列){
方法體;
}
修飾符串列:這是可選項,不是必須的,如果不寫,則使用默認選項,對于訪問控制符,如public、private等,預設的訪問控制權限為包范圍內,
回傳值型別:使用“return 值”回傳一個值,且這個值的型別必須和指定的回傳值型別一致,指定的回傳值型別可以是Java中的任何資料型別,包括基本資料型別和所有參考型別,如果此方法不回傳任何值,就必須將其指定為void,表示此方法不回傳任何值,即方法體中不能有“return 值”這樣的陳述句,但是可以寫“return;”表示回傳void,
方法名:遵循識別符號的定義規則,但通常應該注意以下幾點:
- 最好見名知意,不要為了方便省時,就寫func1、func2等這種看不出方法大概功能的方法名,
- 最好是動詞,
- 首字母小寫,其后的每個單詞的首字母大寫,即遵循駝峰命名法,
形式引數串列:
- 形參是區域變數,即它們的作用范圍只在方法體之內,
- 多個形參之間使用逗號隔開,
- 形參中起決定性作用的是其資料型別,形參的名字就是區域變數的名字,
- 在呼叫方法時給方法傳遞的真實資料被稱為“實際引數”,即實參,
- 對于實參串列和形參串列的使用,需要注意,它們的數量必須相同,而且對應的資料型別也必須一致,
方法呼叫:使用點號“.”進行呼叫,但是注意,方法體中的程式只有在呼叫時才會去執行,定義或編譯時都不會執行,
3、static方法呼叫
如果修飾符串列中有static的話,則稱之為靜態方法,呼叫此方法的語法格式為“類名.方法名(實際引數串列)”,但如果是呼叫本類中的static方法,則可以省略類名,直接使用方法名進行呼叫,
4、引數值的傳遞
在呼叫方法時,給方法傳遞的引數為變數的值(即值傳遞),而不是變數本身,因為如果傳遞的是變數本身,那豈不是就可以在呼叫的方法中使用這個變數了,但實際情況卻是,呼叫的方法只能使用自己這個作用域中的區域變數,而不能使用它的呼叫者所在的作用域中的變數,所以傳遞的就是變數的值,從而使得方法的形參有了自己的值,
5、可變長引數
方法的可變長引數指的是定義方法時只需要定義引數的型別,而不用寫死這種型別的引數個數,使用時也可以根據需要傳入不同的引數個數,
public class Test{ public static void main(String[] args){ // 使用時,可傳入0-n個引數 func(); func(10); func(10, 20, 30); } // 語法格式:定義方法的引數時,使用形如“型別... 引數名稱”的格式,注意,型別名稱之后一定是三個點 // 在方法中使用這個引數時,可以將這個可變長引數當做陣列來處理 public static void func(int... args){ // code... } // 可變長引數的定義必須也只能是引數串列的最后一個位置,且一個方法只能有一個可變長引數 public static void func(String s, int... args){ // code... } }
6、方法多載(overload)
多載原則:多載的方法功能相似的時候可以考慮使用方法多載,但是功能不同的時候盡量使用方法名稱不同的方法來定義,
多載機制:
- 多載的方法必須在同一個類中,
- 多載的方法它們的方法名必須相同,
- 引數串列不同:包括引數數量不同,以及數量相同但順序上型別不同兩種情況,
- 注意:方法多載只和方法名與引數串列有關,與回傳值與修飾符串列無關,即只要方法名和引數串列滿足多載機制,就算作方法的多載,
7、JVM記憶體分配
如果只是定義方法,不去呼叫,則不會被執行,在JVM中也不會給該方法分配運行所需的記憶體,只有在呼叫這個方法的時候才會動態的分配該方法所需的記憶體空間,
JVM的記憶體劃分中主要有三大塊的記憶體空間(當然也還有其他的記憶體空間):
- 方法區記憶體:專門用于代碼的存放,當然方法的代碼片段也是存放在這里的,方法的代碼片段屬于“.class”檔案的一部分,在JVM加載“.class”位元組碼檔案的時候就會將對應的方法加載到方法區記憶體中,所以這三塊記憶體中最先有資料的就是方法區記憶體,并且方法代碼片段在記憶體中只有一份,但是可以重復呼叫,
- 堆記憶體(heap):主要用于存放創建的實體物件,包括物件自身的資料,如屬性值等,因為物件的資料是存放在物件內部的,Java的垃圾回識訓制主要針對的也是這塊記憶體區域,只要堆記憶體中的物件沒有其他地方對它進行參考的話就會被回收掉,
- 堆疊記憶體:在方法呼叫執行的時候,需要給該方法分配獨立的記憶體空間,這個記憶體空間就是在堆疊記憶體空間中進行分配的(方法在呼叫的瞬間,會在堆疊中給該方法分配獨立的記憶體空間,此時,會在堆疊中發生壓堆疊動作,方法執行結束之后,給該方法分配的記憶體空間就會被全部釋放,此時,會在堆疊中發生彈堆疊動作),方法中的區域變數因為是在方法體中宣告定義,所以區域變數的記憶體空間也是在堆疊中分配的(即堆疊記憶體中主要存盤的是區域變數),
- 關于堆疊(stack):堆疊是一種資料結構(資料結構反應的是資料的存盤形態,資料結構這個詞和概念是獨立的,并不只是在Java中有),堆疊中包括堆疊底元素、堆疊中元素、堆疊頂元素和堆疊幀,堆疊幀永遠指向堆疊頂元素,并且只有堆疊頂元素處于活躍狀態,其他元素則處于靜止狀態,
- 壓堆疊/入堆疊/push:將一個元素加入堆疊的堆疊頂,
- 彈堆疊/出堆疊/pop:將一個堆疊頂元素彈出堆疊,
- 堆疊資料結構的特點:先進后出,后進先出,
- 注意:堆記憶體和方法區記憶體都只是各有一個,但是堆疊記憶體是一個執行緒有一個堆疊記憶體,
8、方法遞回
方法遞回是指方法在方法體中呼叫自身,使用方法遞回時應注意以下幾點:
- 方法遞回非常的耗費堆疊記憶體,所以能不用遞回就盡量不用遞回,
- 如果遞回沒有設定結束標識或者遞回太深就會發生堆疊記憶體溢位的錯誤,導致JVM停止作業,因為每一次呼叫一個方法,哪怕是呼叫自身,都會在堆疊中開辟一塊新的記憶體來創建這個方法的運行環境(壓堆疊),所以遞回呼叫會導致不斷的記憶體開辟,即不斷的壓堆疊,最終記憶體不夠用時還在進行記憶體的開辟和壓堆疊操作,肯定就會出錯了,
二、類和物件
1、封裝和類定義
類主要描述的物件的狀態(屬性)和動作(方法),
類也是面向物件編程中“封裝”特性在語法上的體現,封裝特性的優點通常有以下幾點:
- 將程式的實作原理的復雜性進行封裝,只對外提供簡單的操作入口,
- 封裝之后才能形成真正的“物件”和“獨立體”的概念,
- 封裝意味著程式可以重復使用,
- 封裝之后,對于物件本身,提高了安全性,
一個普通的類的定義語法如下:
[修飾符串列] class 類名{ 屬性; 方法; }
成員變數:在類體之中、方法之外的變數稱之為成員變數,成員變數如果沒有手動賦值的話,系統會自動賦予默認值(一切向“0”看齊),成員變數又分為:
- 實體變數(沒有static修飾符)
- 靜態變數(有static修飾符)
注意:類也是一種資料型別,屬于參考資料型別,它的型別名稱就是對應的類名,
2、物件創建和記憶體分配
物件就是類實體化之后的具體個體,類到物件的程序稱之為實體化,反過來,物件到類的程序則稱之為抽象,
new關鍵字:Java中使用new關鍵字來創建一個物件,new關鍵字也是Java中的一個運算子,
記憶體分配:當在方法區記憶體中的代碼執行時,會在堆疊記憶體中開辟一塊該方法對應的記憶體空間,而在方法執行程序中使用new關鍵字創建一個物件時,則會在堆記憶體中開辟一塊該物件對應的記憶體空間,所以方法中定義的區域變數是在堆疊中的,而創建的物件則是在堆記憶體中的,實體物件每一個物件都會有自己的一塊記憶體空間,即100個物件就會分配100個記憶體空間,
指標屏蔽:Java中想要訪問堆記憶體中的資料,必須通過參考,而不能直接操作堆記憶體,因為Java中屏蔽了指標的概念,不能通過指標的方式直接訪問或操作記憶體中的資料,
訪問屬性:對于實體變數屬性的讀取和修改,使用語法格式“參考.變數名”進行讀取,使用語法格式“參考.變數名=值”對屬性進行修改,注意,實體變數存盤在堆記憶體中對應的實體物件內部,且不能通過類名的方式來訪問,
3、空指標例外NullPointerException
當一個參考型別的變數的值不再是指向某個物件的記憶體地址,而是null,此時再去訪問物件的相關屬性或方法就會發生空指標例外,因為此時的變數不再指向該物件,而是值為null了,無法去訪問該物件了,更不要說訪問物件中的屬性和方法了,空參考訪問實體相關資料就一定會出現空指標例外,
4、get方法和set方法
屬性私有化:在封裝特性中,類中的所有屬性都應該使用private修飾符進行修飾,private表示私有的,表示此屬性只有在本類中才能訪問,在類的外部不能訪問,但是在類中應該為外部訪問這些屬性提供一些簡單的公開的(public)操作入口,如對應的get方法和set方法,
get方法和set方法的寫法如下:
// get方法 public 回傳值型別 get+屬性名首字母大寫(){ return 屬性名; } // set方法 // 注意,形參的名字如果和屬性名相同了,那么屬性名前面應該加一個this關鍵字 // 因為不加this關鍵字的話,由于名稱是相同的,Java的就近原則會認為它倆都是同一個區域變數,即形參 public void set+屬性名首字母大寫(形參串列){ 屬性名=形參值; ... }
示例:
public class A{ private int age; public int getAge(){ // 這里可以使用this.age,也可以不用this // return this.age return age; } public void setAge(int age){ // 這里因為形參和屬性名相同了,所以必須用this加以區分 this.age = age; } }
注意:get和set方法是沒有static修飾符的,使用的是public修飾符,沒有static修飾符的方法的訪問方式為“參考.方法名(實參)”,
5、參考引數的傳遞
物件變數通常也稱之為參考,因為在堆疊中這個變數只是個區域變數,而物件變數的值是該物件在堆記憶體中的記憶體地址,當然,這個記憶體地址則指向堆記憶體中的該物件實體,所以對于基本資料型別,值的傳遞不會影響到原本變數的值,但是對于類的實體,因為傳遞的值是記憶體地址,所以它雖然不會影響原本區域變數的值(即記憶體地址),但是如果對記憶體地址中的物件實體進行修改則會影響到記憶體地址指向的實體物件,即原本的區域變數指向的實體物件會被修改,
6、構造方法(constructor)
語法如下:
// 構造方法,也稱為構造器(constructor), // 構造方法是不用也不能指定回傳值型別的, // 注意,構造方法名必須和類名相同,所以這里的語法就直接寫類名了, [修飾符串列] 類名(形式引數串列){ 構造方法體; }
示例:
public class A{ private int i; // 下面的兩個構造方法使用了方法的多載機制 public A(){ System.out.println("類A的無參構造方法!"); } public A(int i){ // 使用this關鍵字區分實體變數和方法的區域變數 this.i = i; System.out.println("類A的有參構造方法!"); } }
構造方法的呼叫:構造方法的作用是通過呼叫構造方法來創建物件并初始化實體變數的值,而構造方法的呼叫使用new關鍵字“new 構造方法名(實參串列)”,注意new之后呼叫的其實是構造方法名而不是類名,但因為兩者是相同的,所以可能會讓人誤以為呼叫的是類名,
構造方法回傳值:雖然沒有指定回傳值型別,但是構造方法的回傳值型別就是其所在類的型別,回傳值就是新創建的物件的參考,但是注意的是這個回傳值是不需要開發人員手動撰寫的,即構造方法的定義中,回傳值型別和回傳值都不需要人為的去定義,
默認構造方法:當類中沒有定義構造方法時,系統會給該類提供一個無引數的默認構造器,需要特別注意的是,如果類中提供了構造方法,那么系統就不再為這個類提供默認的無引數構造方法了,所以,如果在類中提供了自己的構造方法,那么推薦手動將無參的構造方法加上,因為這個構造方法太常用了,
關于構造方法,還應該注意以下幾點:
- 構造方法支持多載機制,
- 因為實體變數是屬于實體的,所以構造方法是先創建物件再初始化實體變數,
7、this關鍵字
其實每一個實體物件中都有一個this變數,this中保存的是自身所在實體物件的記憶體地址,即this是指向實體物件本身的一個參考型別的變數,可以換一種方式理解,this可以出現在實體方法中,而方法中的this代表當前正在執行這個方法動作的實體物件,
在實體方法中對實體變數的訪問,由于它是實體變數,所以不使用this關鍵字也是可以訪問的,所以this在多數情況下是可以不寫的,this主要用于區分實體變數和區域變數,比如setter方法和構造方法中就比較常用,
當然,this不能在含有static修飾符的方法中使用,
this關鍵字除了使用“this.xxx”的方式表示實體物件的使用之外,還可以在構造方法中以“this(實參串列)”形式表示呼叫本類的另一個構造方法,但是注意,使用這種用法時這個陳述句只能出現在構造方法的第一行(當然這個陳述句之后可以添加其他的陳述句,但前面就不能有其他任何陳述句了),如:
public class User{ private int age; public User(int age){ this.age = age; } public User(){ // 此處表示呼叫另一個構造方法 // 但是注意,這個陳述句只能是此構造方法的第一個陳述句 this(18); // 之后可以加別的陳述句 System.out.println("my age is " + this.age); } }
8、super關鍵字
super關鍵字和this關鍵字在用法上有許多相似的地方,但是this代表的是當前實體物件,而super代表的是當前子類的父類的特征(包括屬性和方法),通常用于訪問父類的某些屬性和方法,和this對比著看,它們的相似之處如下:
- super用法也有兩種:”super.“和”super()“,
- super也是只能出現在實體方法和構造方法中,不能在靜態方法中使用,
- super在大多情況下也是可以省略的,除非特定指明需要使用父類中的屬性或方法的時候才使用,
- super()這種用法也是只能出現在構造方法的第一行,通過當前的構造方法去呼叫父類的構造方法,super的一個作用是代碼復用,另一個作用是在創建子類物件的時候先初始化父類的特征(屬性等),父類的特征如屬性因為大多是private,并不能通過直接賦值來初始化,此時就需要使用super來呼叫父類的構造方法來進行初始化了,
對于super()這種用法,當一個子類的構造方法的第一行既沒有this(),也沒有super(),那么默認會有一個super()執行,表示在子類的構造方法中呼叫父類的無參構造方法,此時必須保證父類必須有一個無參構造方法,推薦在類的定義中都手動寫好一個無參的構造方法,當然,要是你自己手動呼叫了this(實參串列)或者super(實參串列),程式就會按照你寫進行呼叫了,示例如下:
public class TestSuper{ public static void main(String[] args){ // 執行結果: // 類A的無參構造方法! // 類B的無參構造方法! new B(); } } class A{ public A(){ System.out.println("類A的無參構造方法!"); } } class B extends A{ public B(){ // 由main方法的輸出可以看出類A的無參構造方法也是被執行的, // 其實如果沒有手動呼叫super(),此處會默認執行一個super() // super(); System.out.println("類B的無參構造方法!"); } }
關于super的使用,注意以下幾點:
- Java中允許在子類中出現和父類一樣的同名變數或同名屬性,此時,如果想要在子類中訪問父類的這個同名的屬性,就需要使用“super.xxx”的形式去訪問了,
- super不是參考,保存的也不是記憶體地址,也不指向任何物件,只是代表當前物件內部的父類特征,
9、繼承
繼承特性優點:繼承最基本的作用是代碼復用,但是最重要的作用卻是有了繼承才有了方法的覆寫和多型機制,
單繼承:Java中的繼承機制只支持單繼承,一個類不能同時繼承多個類,只能繼承一個類,語法如下:
// 繼承使用extends關鍵字 [修飾符串列] class 類名 extends 父類名{ 類體; }
可以繼承的資料:
- private私有的不支持繼承,
- 構造方法不支持繼承,
- 其他資料可以被繼承,
多繼承:Java中雖然只支持單繼承,但是可以間接實作多繼承:
C extends B{ } B extends A{ } A extends T{ } // 這樣C直接繼承B,但間接繼承了T和A類
默認基類:Java中一個類如果沒有顯式繼承任何類,那么該類默認繼承javaSE庫中提供的java.lang.Object類,
需要注意一個概念,當一個子類在繼承某個父類時,在運行時,不是說在子類中查找對應方法或屬性,子類中沒有再到父類中查找,而是在定義時,如果繼承了某個父類,那么這個類的定義中就包含了父類繼承過來的某些方法和屬性,即子類物件執行的方法和屬性總是自己的屬性和方法,
10、方法的覆寫/重寫(override)
方法的覆寫也稱為方法的重寫,子類將父類繼承過來的方法進行重新撰寫被稱為方法的重寫,方法重寫時需要注意:
- 方法重寫發生在具有繼承關系的父子類之間,且是可以繼承的方法上(私有的以及構造方法不能繼承,也就不能進行重寫了),
- 重寫時必須遵守:回傳值型別相同,方法名相同,形參串列相同,
- 訪問權限不能更低,但是可以更高,private最低,public最高,
- 拋出例外不能更多,但是可以更少,
- 靜態方法不存在重寫,
- 覆寫只談方法,不談屬性,
11、多型
向上轉型(upcasting):子型別 --> 父型別,可以理解為自動型別轉換,
向下轉型(downcasting):父型別 --> 子型別,可以理解為強制型別轉換,
在類和類之間,無論是向上轉型還是向下轉型,都必須具有繼承關系,不然編譯不通過,
多型語法機制:父型別的參考指向子型別物件這種機制導致程式在編譯階段和運行階段出現了兩種不同的形態或狀態,這種機制可以稱為一種多型語法機制,
多型的作用:降低程式的耦合度,提高程式的擴展力,能使用多型就多使用多型,即父型別參考指向子型別物件,
多型的核心思想:面向抽象編程,盡量不要面向具體編程,
示例:重點在注釋哦
public class Animal{ public void run(){ System.out.println("動物在移動!"); } } public class Cat extends Animal{ public void run(){ System.out.println("貓在散步!"); } public void catchMouse(){ System.out.println("貓在抓老鼠!"); } } public class Bird extends Animal{ public void run(){ System.out.println("鳥兒在飛翔!"); } } public class Test{ public static void main(String[] args){ // 此處為向上轉型,從Cat型別自動轉換為Animal型別 Animal cat1 = new Cat(); // 向上轉型之后,可以訪問父型別中的方法,但是如果這個方法被子型別中重寫了 // 那么執行的就是子型別中的方法了,并且型別轉化之后不能再執行子型別中特有的方法了 // 比如catchMouse方法,但是需要注意的是,雖然型別轉換了,但是參考指向的堆記憶體中的 // 物件依然是最開始創建的Cat型別的源物件cat1,所以執行方法時原則就是子型別中沒有就執行繼承自父型別的方法,如果子型別中有這個方法時就執行子型別中的方法,但是不能執行子型別中特有的方法, // 在編譯階段會將符合語法的該物件的方法系結,這個程序稱之為靜態系結,只有靜態系結成功之后才能運行程式,這個例子中,靜態系結是將Animal的run方法系結到cat1物件,因為cat1是宣告為Animal型別的,而Animal類是有run方法的,所以能系結成功, // 在運行階段則會將實際運行的方法系結到該物件上,這個程序稱之為動態系結,這個例子中,動態系結是,在運行時,由于是先在記憶體中生成的物件是new出來的Cat型別的物件,雖然在等號賦值運算時型別被轉換為Animal型別了,但是記憶體中其實還是那個被創建好的Cat型別的cat1物件,所以會執行Cat類中的run方法, cat1.run(); // 輸出為:貓在散步! // 此處會編譯不通過,雖然cat物件有catchMouse方法,但是型別轉換后,因為Animal型別中沒有catchMouse方法,所以編譯不通過,即靜態系結失敗,當然,也就不可能繼續運行了, cat1.catchMouse(); // 向下轉型,這里不僅能編譯通過,還能正確執行catchMouse方法,因為cat1其本質就是最初在記憶體中創建的Cat型別物件,而Cat類是由這個方法的 Cat cat2 = (Cat)cat1; cat2.catchMouse(); // 此處的向下轉型編譯能能通過,但是運行會報錯java.lang.ClassCastException(除了空指標例外之外另一個著名的例外),即型別轉換例外,而且只有在向下轉型的時候會發生, // 因為第一個陳述句向上轉型后,其實際還是個Bird型別物件,在第二個陳述句的向下轉型,因為 // Animal型別和Cat型別之間具有繼承關系,所以可以編譯通過,但是運行時由于它本質是Bird型別 // 物件,不能轉換成Cat型別物件,因為Bird和Cat之間沒有繼承關系,所以會報錯, Animal bird1 = new Bird(); Cat cat3 = (Cat)bird1; } }
12、instanceof運算子
語法:“參考 instanceof 資料型別名”,回傳值為true/false,true表示這個參考指向的記憶體真實物件就是該資料型別的物件,false則表示這個參考指向的記憶體真實物件不是該資料型別的物件,如上例中“Animal bird1 = new Bird();”的bird1雖然轉換成了Animal型別,但其真實記憶體物件其實是Bird型別的,所以如果執行“bird1 isinstanceof Bird”就會回傳true,
Java編程規范中,在進行強制型別轉換時,建議先使用instanceof運算子判斷參考的型別再進行轉換,
13、抽象類
抽象類使用abstract關鍵字修飾,是類和類之間共同特征的提取而形成的類,通常抽象類中含有抽象方法,但也不是說抽象類中就一定需要定義抽象方法,對于抽象方法的定義,需要注意,它同樣需要abstract關鍵字修飾,同時不能有大括號,還需要以分號結尾,
抽象類也屬于一種參考型別,使用抽象類來定義一個子類的物件,這種語法正是多型的應用,即向上轉型,父型別的參考指向子型別的物件,
// 語法 [修飾符串列] abstract class 類名{ // 通常含有抽象方法,但也不是必須的 類體; }
示例:
// 抽象類使用abstract關鍵字修飾 abstract class Animal{ // 抽象方法也使用abstract關鍵字修飾 // 并且抽象方法定義時不能有大括號 public abstract void run(); } class Dog extends Animal{ // 如果子類繼承自抽象類,但自身又不是抽象類 // 那么子類就必須重寫/覆寫/實作抽象類中的所有抽象方法 public void run(){ System.out.println("小狗在奔跑!!!"); } }
使用抽象類時,應注意以下幾點:
- 抽象類無法實體化,無法創建物件,只能是用于子類來繼承,所以也由此引出另一個點,final關鍵字和abstract關鍵字是不能聯合使用的,因為final修飾的類是無法被繼承的,
- 抽象類的子類也可以是抽象類,
- 抽象類是可以有構造方法的,但是這個構造方法是給子類用的,因為抽象類是無法實體化的,
- 抽象類中可以沒有抽象方法,但是有抽象方法的類必須是抽象類,
- 如果子類繼承自抽象類,且子類不是抽象類,那么子類就必須實作(其實就是方法重寫/覆寫)抽象類中的所有抽象方法(如果有),當然,如果子類也是抽象的,那么抽象方法的實作就不是必須的,抽象方法的實作注意兩點:去掉abstract關鍵字,以及加上具體實作的方法體,
14、介面
介面在學習時候雖然可以將它當做是類來理解,但是注意,介面并不是類,定義使用的關鍵字是interface而不是class,但是編譯之后也是一個class位元組碼檔案,
同時,和抽象類一樣,介面也是一種參考型別,使用介面的時候,可以使用多型,或者說介面的使用離不開多型,因為介面本身無法直接創建物件,一旦創建物件就必然是介面的“子類”,即向上轉型,父型別的參考指向子型別的物件,
// 語法 [修飾符串列] interface 介面名{ 常量或抽象方法; }
示例:
public class HelloWorld{ public static void main(String[] args){ // 這里是向上轉型 A a = new C(); a.func1(); // 這里是向下轉型,介面的向下轉型編譯不會報錯,但是運行的時候可能報ClassCastException例外 // 所以無論是類之間的向下轉型還是介面之間的向下轉型,轉型之前,都應該使用instanceof運算子判斷下 // 當然,這里是不會報錯的,因為a物件本質上是個C物件,而C中有實作了B,所以轉成B是沒有問題的, B b = (B)a; b.func2(); // 輸出結果: // func1... // func2... } } // 介面中只能定義常量和抽象方法,并且都只能是public interface A{ // 介面中的常量都只能是public static final修飾的,這三個關鍵字也是可以省略不寫的,編譯的時候會自動加上的, int i = 10; // 介面中的抽象方法都只能是public abstract修飾的,這兩個關鍵字是可以省略不寫的,編譯的時候會自動加上的, void func1(); } interface B{ void func2(); } // 類實作介面使用implements,而不是extends // 一個類可同時實作多個介面,類對介面的多實作相當于多繼承,這其實彌補了Java的類和類之間只能單繼承的缺點, // 如果實作介面的類不是抽象類,那么就必須實作介面中的所有方法 class C implements A, B{ public void func1(){ System.out.println("func1..."); } public void func2(){ System.out.println("func2..."); } }
使用介面的使用,需要注意以下幾點:
- 介面中只允許定義常量和抽象方法,介面中的常量都只能是public static final修飾的,這三個關鍵字是可以省略不寫的,編譯的時候會自動加上的,介面中的抽象方法都只能是public abstract修飾的,同樣,這兩個關鍵字也是可以省略不寫的,編譯的時候會自動加上的,
- 需要注意的是,重寫介面中的方法時,修飾符public不能省略(介面中定義時是可以省略的),
- 介面和介面之間也可以繼承,并且介面支持多繼承,繼承的介面之間使用逗號隔開即可,
- 一個類可同時實作多個介面,類對介面的多實作相當于多繼承,這其實彌補了Java的類和類之間只能單繼承的缺點,
- 類和類之間的繼承使用關鍵字extends,而類和介面之間的實作使用關鍵字implements關鍵字,但是注意,實作介面的類也可以是抽象類,只要加上關鍵字abstract就行,因為介面中的方法全是抽象的,而抽象類中是可以包含抽象方法的,所以一個非抽象的類實作一個介面的話必須重寫介面中的所有抽象方法,
- 在進行強制型別轉換時,兩個介面型別之間沒有繼承關系也可以進行強轉,編譯器不會報錯,但是運行時可能發生ClassCastException例外,這和類之間的強轉不同,類的強轉在編碼的時候就不允許,見上面的示例,
- 一個類中如果同時有extends和implements,編碼時應該extends關鍵字在前,implements關鍵字在后,其實,由此可以看出,介面的作用是提取某些動作,類實作介面就是在給自身添加某些動作,當不想要這個動作時,刪掉這個介面和對應的方法即可,
15、抽象類和介面的區別
其實,在實際使用中還是介面使用的多,抽象類使用的少,抽象類和介面看著有許多相似的地方,但它們之間的區別還是有很多的,如下:
- 抽象類是半抽象的,介面是完全抽象的,
- 抽象類中有構造方法,介面中沒有構造方法,
- 抽象類屬于類,類和類之間只能單繼承,而介面和介面之間支持多繼承,
- 一個類可以同時實作多個介面,而一個抽象類只能繼承一個類(單繼承),
三、修飾符
1、static
靜態變數:帶有static關鍵字的變數,稱之為靜態變數,并且,在類加載的時候就靜態變數開始初始化了,不需要創建物件它的記憶體就已經開辟了,并且是存盤在方法區中的,
靜態方法:帶有tatic修飾符的方法稱為靜態方法,在靜態方法中不能訪問實體變數和實體方法,當然,也包括this關鍵字,而是只能訪問同樣帶有static修飾符的變數(靜態變數),
使用原則:當一個方法或變數它的執行與具體的物件無關,或者說所有物件都會用到這個方法或變數,并且還不會因為物件的不同而發生變化,此時就應該將它定義為static型別,而當一個行為或動作執行的程序中需要物件參與,或者說不同物件執行這個動作的結果可能會不同,那么這個方法就應該定義為實體方法,不應該加static關鍵字,同理,當一個屬性在不同的物件中可能會不同時,那這個屬性就應該定義為實體變數,也不應該加static關鍵字,
訪問static變數和方法:帶有static的方法和變數,可以使用類名的方式去訪問,也可以使用參考的方式去訪問,但使用參考的方式去訪問,其實本質上也是使用類名的方式去訪問的,因為你會發現當這個參考為null時也能去訪問static的方法和變數,而不會報空指標例外,所以不推薦使用參考的方式去使用static方法,
static另一種語法的使用:靜態代碼塊,在類加載的時候就會去執行這個方法,定義和使用示例如下:
// 語法 static{ java陳述句; } // 靜態代碼塊在一個類中可以撰寫多個
public class StaticTest{ static{ System.out.println("--->1"); } static{ System.out.println("--->2"); } public static void main(String[] args){ System.out.println("main method!!!"); } } // 運行結果為:可以看到靜態代碼塊在main方法之前運行了, // --->1 // --->2 // main method!!!
2、final
final關鍵字的使用,需要注意以下幾點:
- final是一個關鍵字,表示最終的、不可變的,
- final修飾的類無法被繼承,
- final修飾的方法無法被覆寫,
- final修飾的變數一旦賦值之后,不可重新賦值,
實體變數如果宣告為final變數,那么在宣告的同時就需要給它賦值,不然就會報錯,因為類的實體變數在呼叫構造方法之后還沒有被賦值的話就會被系統賦予默認值,而final變數是不能重新賦值的,所以如果允許final實體變數宣告的時候不賦值,那么這個變數將永遠是系統默認的值,這樣肯定是不行的,所以語法上就要求final的實體變數必須在宣告的同時必須手動賦值或者在構造方法中給它賦值,示例如下:
public class A{ // 第一種方式:宣告的同時賦值 final int a = 10; // 第二種方式:先宣告,然后在構造方法中賦值 final int b; public A(){ this.b = 20; } // 注:其實這兩種方式都是一種方式,都是在構造方法執行程序中給實體變數賦值的, }
注意,final修飾的參考雖然不能再指向其他物件,但是所指向的物件內部的記憶體是可以被修改的,
final修飾的實體變數通常會和static聯合使用,被稱為常量,
3、常量
語法格式:“public static final 型別 常量名 = 值”,final表示不可被修改,static表示無論實體化多少物件都只會保存一份資料在方法區記憶體中,此時,就算被宣告為public也不用擔心被別人修改,因為它本身就是final不可被修改的,
Java規范中,所有常量必須全部使用大寫,單詞之間使用下劃線連接,
4、訪問控制權限修飾符
對于屬性和方法,以下4種都可以使用,但是對于類,只可以使用public和預設的方式定義,但是無論是類還是屬性和方法,這4中的作用范圍都是相同的:
- public:表示公開的,在任何位置都可以訪問,
- protected:在同一個包下,或者在其子類中,那就可以訪問,
- 預設:只允許在同一個包下的類訪問,
- private:表示私有的,只能在本類中可以訪問,
- 修飾符的作用范圍:private < 預設 < protected < public,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/191464.html
標籤:Java
