關於如何正確地定義Java內部類方法詳解
一. 內部類簡介
1. 概念
在Java中,我們通常是把不同的類創建在不同的包裡面,對於同一個包裡的類來說,它們都是同一層次的。但其實還有另一種情況,有些類可以被定義在另一個類的內部,我們把在一個類裡面定義的類稱為內部類(InnerClass)或嵌套類,把外面定義的類稱為外部類(OutClass)或宿主類。 也就是說,在類的內部既可以定義成員變量和方法,也可以定義其他的類。定義內部類的常見格式如下:
class Outer {//外部類 class Inner {//內部類 //方法和屬性 } }
上面的代碼中,Outer是普通的外部類,Inner就是內部類。它與普通外部類最大的不同,在於其實例對象不能單獨存在,必須依附於一個外部類的實例對象。
內部類可以很好地實現隱藏,一般的非內部類是不允許有private 與 protected權限的,但內部類卻可以,而且內部類還擁有外部類中所有元素的訪問權限。總之,對內部類的很多訪問規則都可以參考變量和方法。
但是要註意,雖然我們使用內部類可以使程序結構變得更加緊湊,但卻在一定程度上破壞瞭面向對象的思想。
2. 優點
內部類的存在,具有如下優點:
- 內部類使得多繼承的解決方案變得更完整:每個內部類都能獨立的實現接口,無論外部類是否已經實現瞭接口或繼承瞭父類,對於內部類都沒有影響;
- 既可以方便地將存在一定邏輯關系的類組織在一起,又可以對外界隱藏;
- 方便各類編寫事件驅動程序;
- 方便編寫線程代碼。
3. 分類
Java中的內部類可以分為如下幾種類型:
- 成員內部類
- 靜態內部類
- 局部內部類
- 匿名內部類
雖然大多數時候,內部類用得並不多,但我們也有必要瞭解它們是如何具體使用的。
4. 內部類的特點
內部類相比外部類,具有如下特點:
- 內部類可以訪問外部類的私有成員,且不破壞封裝性;
- 內部類仍是一個獨立的類,在編譯之後內部類會被編譯成獨立的 .class 文件,但前面會冠以外部類的類名和 **∗∗∗∗符號,∗∗∗∗該文件名的格式是∗∗‘外部類名** **符號,** **該文件名的格式是**`外部類名∗∗∗∗符號,∗∗∗∗該文件名的格式是∗∗‘外部類名內部類名.class` ;
- 因為內部類是外部類的一個成員,所以內部類不能用普通的方式訪問,但內部類可以自由地訪問外部類裡的成員變量,無論是否被private修飾;
- 如果是靜態內部類,我們不能隨便訪問外部類的成員變量,隻能訪問外部類的靜態成員變量。
5. Java類的創建要求
我們在創建定義Java類時,應該遵循如下要求:
- 一個java文件中可以編寫多個類,但隻能有一個類使用public關鍵詞進行修飾,這稱之為主類;
- 主類名必須與文件名一致,在開發中,應盡量隻在一個java文件中編寫一個類;
- 外部類隻有兩種訪問級別:public 和默認;內部類則有 4 種訪問級別:public、protected、 private 和默認;
- 在外部類中,可以直接通過內部類的類名來訪問內部類;
- 在外部類以外的其他類中,需要通過內部類的完整類名來訪問內部類;
- 內部類與外部類不能重名。
接下來就針對上面提到的幾種內部類,分別給大傢講解這幾種內部類的用法。
二. 成員內部類
1. 概念
成員內部類就是指沒有被static修飾的內部類,也可以稱為非靜態內部類。
2. 特點
成員內部類具有如下特點:
- 在早期的jdk版本中,成員內部類中隻能定義非靜態的屬性和方法,除非同時使用final和static進行修飾;
- 在新版的jdk中,成員內部類中也可以定義靜態的屬性和方法;
- 成員內部類可以訪問外部類的所有成員,包括私有和靜態的成員,即使是多層嵌套時也如此;
- 外部類不能直接訪問內部類的成員,必須通過內部類的實例對象訪問;
- 在外部類的靜態方法和外部類以外的其他類中,必須通過外部類的實例創建內部類的實例對象;
- 外部類的實例與內部類實例是一對多的關系,即一個內部類實例隻對應一個外部類實例,但一個外部類實例則可以對應多個內部類實例。
3. 語法
如果是在外部類中,創建成員內部類對象的基本語法格式如下:
內部類 對象名 = new 內部類();
如果是在外部的其他類中,或者是在外部類的靜態方法中,創建成員內部類對象的基本語法格式如下:
內部類 對象名 = new 外部類().new 內部類();
4. 案例
4.1 定義成員內部類
/** * @author 一一哥Sun * 千鋒教育 * 成員內部類 */ public class OuterClass { // 外部類的非靜態成員 String name = "一一哥"; private String hobby = "擼碼"; static int age = 30; // 非靜態方法 public void show() { //這裡的this是指OuterClass對象 System.out.println("show方法...name="+this.name); //如果是在外部類裡面創建內部類的對象,就不需要創建外部類實例,可以直接new 內部類() //InnerClass inner = new InnerClass(); } // 定義一個成員內部類 public class InnerClass { // 也可以定義私有屬性 private int a = 10; //在早期的JDK中,成員內部類中不能定義靜態變量;但在新版JDK中,成員內部類中可以定義靜態變量 static int b = 20; // 非靜態方法 public void m1() { // 這裡的this對象是InnerClass內部類對象 System.out.println("成員內部類的成員變量:" + this.a); //外部類.this.屬性或方法,這個this是外部類對象 System.out.println("外部類的成員變量:" + OuterClass.this.name); //內部類中可以訪問外部類的私有成員和靜態成員 System.out.println("外部類的私有成員變量:" + hobby); System.out.println("外部類的靜態變量:" + age); } //在早期的JDK中,成員內部類中不能定義靜態方法;但在新版JDK中,成員內部類中可以定義靜態方法 public static void m2() { System.out.println("調用成員內部類的靜態變量:" + b); System.out.println("調用外部類的靜態變量:" + age); //在靜態方法中創建內部類對象,也要通過內部類 對象名 = new 外部類().new 內部類();的格式 //InnerClass innerClass = new OuterClass().new InnerClass(); } } }
我們要註意,在早期的JDK中,成員內部類中不能定義靜態屬性和方法;但在新版JDK中,成員內部類中可以定義靜態的屬性和方法。並且我們要搞清楚在不同的位置上,創建內部類對象的方式,以及this的具體含義。
4.2 定義測試類
我們在外部的其他類中,要想創建出一個成員內部類的對象,需要通過如下形式:
內部類 對象名 = new 外部類().new 內部類();
/** * @author 一一哥Sun * 千鋒教育 */ public class InnerClassTest { public static void main(String[] args) { //在外部的其他類中,不能直接創建內部類對象,否則: //No enclosing instance of type OuterClass is accessible. //Must qualify the allocation with an enclosing instance of type OuterClass //(e.g. x.new A() where x is an instance of OuterClass). //InnerClass inner=new InnerClass(); //在外部的其他類中創建內部類對象,需要通過如下格式: //內部類 對象名 = new 外部類().new 內部類(); //InnerClass inner=new OuterClass().new InnerClass(); //也可以拆分成如下格式: OuterClass outer=new OuterClass(); InnerClass inner=outer.new InnerClass(); inner.m1(); InnerClass.m2(); } }
5. 訪問方式小結
- 成員內部類 訪問 外部類的成員(屬性、方法),可以【 直接訪問使用 】;
- 外部類 訪問 成員內部類,需要【 直接創建內部類對象後再訪問 】,即
new InnerClass()
; - 外部的其他類 訪問 成員內部類,需要【 創建外部類對象,再創建內部類對象後訪問 】,即
InnerClass inner=new OuterClass().new InnerClass()
;
6. 關於this的註意事項
在內部類中,關於this,我們需要註意以下兩點:
- 如果同時存在外部類和內部類,那麼this在哪個類中使用,this就代表哪個類的對象;
- 如果內部類想要通過this來調用外部類的屬性和方法,需要使用外部類名.this.屬性或者方法名。
三. 局部內部類
1. 概念
局部內部類是指在方法中定義的內部類。
2. 特點
局部內部類具有如下特點:
- 局部內部類隻能在方法中定義和創建對象,也隻在當前方法中有效;
- 局部內部類中 可以訪問外部類的所有成員 ;
- 局部內部類與局部變量一樣,不能使用訪問控制修飾符(public、private和protected)和static修飾符;
- 在jdk 7版本中,如果局部變量是在局部內部類中使用,必須顯式地加上final關鍵字;在jdk 8版本中,會默認添加final關鍵字;
- 局部內部類隻能訪問當前方法中final類型的參數與變量。如果方法中的成員與外部類的成員同名,可以使用 .this. 的形式訪問外部類成員;
- 局部內部類中還可以包含內部類,但這些內部類也不能使用訪問控制修飾符(public、private 和 protected) 和 static修飾符;
- 局部變量在方法執行結束後會被銷毀,而局部內部類的對象會等到內存回收機制進行銷毀。如果是局部內部類裡的常量,該常量會被存放在常量池中。
3. 語法
創建局部內部類對象的基本語法格式如下:
public class PartClass { public void method() { //在方法中定義的內部類,就是局部內部類 class Inner { //屬性 //方法 } } }
4. 案例
4.1 定義局部內部類
我們來定義一個局部內部類的案例代碼。
/** * @author 一一哥Sun * 千鋒教育 * * 局部內部類---定義在方法中的內部類 */ public class PartOuterClass { //類的成員變量 String name="一一哥"; private int age=30; static String hobby="java"; public void show() { //局部變量 //JDK 7之前,匿名內部類和局部內部類中訪問外部的局部變量時,該變量需要明確地帶有final修飾符 //final int num = 10; //Effectively final特性 int num = 10; //局部內部類,類似於是方法中的局部對象 class PartInnerClass{ //內部可以正常定義方法 public void m1() { //訪問外部類的非靜態成員,可以使用OuterClass.this.成員的格式,也可以直接訪問 //System.out.println("外部類的成員變量"+name); System.out.println("外部類的成員變量"+PartOuterClass.this.name); System.out.println("外部類私有的成員變量"+age); System.out.println("外部類的靜態變量"+hobby); //局部內部類,可以直接訪問方法中的局部變量 System.out.println("訪問局部變量"+num); } //在新版的jdk中,也可以定義靜態的屬性和方法,老版的jdk則不行 static int b=10; public static void m2() { System.out.println("外部類的靜態變量,hobby="+hobby+",b="+b); } } //創建局部內部類對象 PartInnerClass inner = new PartInnerClass(); inner.m1(); //在當前類中,局部內部類可以直接訪問靜態成員 PartInnerClass.m2(); } }
在JDK 7之前,匿名內部類和局部內部類中訪問外部的局部變量時,該變量需要明確地帶有final修飾符。但從JDK 8之後,我們可以不帶final修飾符,而是由系統默認添加。
4.2 定義測試類
接下來我們對上面的案例進行測試。
/** * @author 一一哥Sun * 千鋒教育 */ public class PartInnerClassTest { public static void main(String[] args) { //創建外部類對象,調用方法,執行局部內部類 PartOuterClass outer=new PartOuterClass(); outer.show(); } }
4.3 Effectively final特性
一般情況下,Java中的局部內部類和匿名內部類訪問局部變量時,該變量必須由 final修飾,以保證內部類和外部類的數據一致性。但從 Java 8開始,我們可以不加 final修飾符,而是由系統默認添加,當然這在 Java 8以前是不允許的。Java將這個新的特性稱為 Effectively(有效的、實際的) final 功能。
另外在 Lambda表達式中,使用局部變量時也要求該變量必須是 final 修飾的,所以 effectively final特性在 Lambda表達式的上下文中非常有用。
其實effectively final特性,隻是讓我們不用顯式地把變量聲明為final修飾的,它給我們自動添加瞭final修飾詞,但並沒有取消final,主要是減少瞭一點不必要的操作,給開發節省瞭點時間。
四. 匿名內部類
1. 概念
匿名內部類就是指沒有類名的內部類,必須在創建時使用 new 語句來聲明。匿名內部類不能在Outer Class外部類中定義,而是要在某個方法的內部,通過匿名類(Anonymous Class)的形式來定義。 匿名內部類本身就是一個對象。
通常情況下,如果一個方法的參數是接口類型,且該接口隻需要實現一次,那麼我們就可以通過匿名內部類的形式來進行定義。另外如果該接口的實現每次都不同,也可以使用匿名內部類的形式進行定義。我們也可以把這種定義形式叫做 “接口回調” 。匿名內部類的代碼格式使得代碼更加簡潔、緊湊,模塊化程度也更高。
2. 特點
匿名內部類具有如下特點:
- 匿名內部類本身就是一個對象;
- 一般在匿名內部類中不會定義屬性和方法,因為沒有意義;
- 匿名內部類的父類一般都是抽象類或者是接口;
- 匿名內部類和局部內部類一樣,可以訪問外部類的所有成員;
- 如果匿名內部類位於方法中,則該類隻能訪問方法中 final 類型的局部變量和參數;
- 匿名內部類中允許使用非靜態代碼塊對成員進行初始化操作;
- 匿名內部類的非靜態代碼塊會在父類的構造方法之後被執行。
3. 語法
通常匿名內部類有兩種實現方式:
- 繼承一個類,重寫其方法;
- 實現一個或多個接口,並實現其方法。
創建匿名內部類對象的基本語法格式如下:
new <類或接口> (){ 重寫類或接口的方法 }
4. 案例
當我們進行安卓等設備開發時,面板上有個按鈕,點擊該按鈕,如何監聽點擊事件?在Android系統中提供瞭各種對應的按鈕點擊監聽事件。所以這裡就通過實現接口的形式來定義匿名內部類,模擬一個單擊事件。
4.1 定義接口
首先我們需要定義一個接口,表示單擊監聽,內部有個點擊事件。
/** * @author 一一哥Sun * 千鋒教育 * * 點擊監聽事件 */ public interface OnClickListener { //點擊事件 void onClick(); }
4.2 定義Button按鈕類
然後定義一個Button按鈕類,給Button按鈕安排一個點擊監聽方法。
/** * @author 一一哥Sun * 千鋒教育 * * 局部內部類---定義在方法中的內部類 */ public class Button { //處理案例點擊的監聽事件 public void setOnClickListener(OnClickListener listener) { listener.onClick(); } }
4.3 定義測試類
接下來我們就測試運行上面的代碼。
/** * @author 一一哥Sun * 千鋒教育 * * 匿名內部類測試 */ public class AnonyInnerClassTest { public static void main(String[] args) { //外部變量 int num=20; //測試匿名內部類 Button btn=new Button(); //模擬處理按鈕的點擊事件 btn.setOnClickListener(new OnClickListener() {//這裡就是一個匿名內部類 //在匿名內部類中,可以允許使用非靜態代碼塊進行成員初始化操作。 int i; { // 非靜態代碼塊,在構造方法之後執行 i = 100; //成員初始化 } @Override public void onClick() { System.out.println("按鈕被點擊啦...i="+i+",num="+num); } }); } }
根據上面的案例可知:
- 在匿名內部類中,可以允許使用非靜態代碼塊進行成員初始化操作;
- 匿名內部類的非靜態代碼塊,會在構造方法之後執行;
- 匿名內部類也可以直接使用外部類的成員。
五. 靜態內部類
1. 概念
靜態內部類和成員內部類的定義類似,但要使用static修飾,所以稱為靜態內部類(Static Nested Class)。
靜態內部類和成員內部類有很大的不同,它不再依附於Outer的實例,而是一個完全獨立的類,因此無法引用Outer.this的方式調用。但它可以訪問Outer類的private靜態字段和靜態方法,如果我們把靜態內部類移到Outer類之外,就失去瞭訪問private的權限。
2. 特點
- 靜態內部類中可以定義非靜態的屬性和方法,也可以定義靜態的屬性和方法;
- 靜態內部類中隻能訪問靜態外部類的靜態屬性和方法。
3. 語法
創建靜態內部類對象的基本語法格式如下:
內部類 對象名 = new 外部類.內部類();
4. 案例
4.1 定義靜態內部類
這裡先簡單定義一個靜態內部類,後面我們在學習內部類時再專門講解。在這個靜態內部類中,定義瞭一個方法,來訪問外部類中的普通屬性和靜態屬性。我們要記住以下幾點:
- 靜態內部類訪問外部類的成員變量時,需要先創建外部類對象;
- 非靜態內部類可以直接訪問使用外部類的成員變量,如同使用本類中的變量;
- 所有的內部類訪問外部類的靜態變量時,可以直接通過"外部類.靜態變量"的形式訪問。
/** * @author 一一哥Sun * 千鋒教育 * * 外部類和內部類 */ public class OuterClass { //普通屬性,屬於外部類 static int outerNum=10; //定義一個靜態的內部類,如果不帶static,就是一個普通的內部類。 //內部類的使用,和普通類一樣,裡面可以正常定義屬性、方法、構造方法等。 //static前面可以帶public等任意訪問修飾符,也可以不帶! static class InnerClass{ //私有屬性無法在類的外部直接訪問 //private int innerNum=20; int innerNum=20; public void printNum() { //定義外部類對象 OuterClass outer=new OuterClass(); //這裡的this是指InnerClass內部類對象! System.out.println("innerNum="+this.innerNum+",outerAge="+outer.outerAge+",outerNum="+OuterClass.outerNum); } } }
對於靜態內部類而言,static前面可以帶public等任意訪問修飾符,也可以不帶!
4.2 定義測試類
我們再定義一個測試類,看看內部類對象是怎麼調用的。
/** * @author 一一哥Sun * 千鋒教育 * * 測試訪問內部類 */ public class InnerClassTest { public static void main(String[] args) { //創建內部類對象,格式為“外部類.內部類 對象名 = new 外部類.內部類的構造方法” OuterClass.InnerClass inner = new OuterClass.InnerClass(); //調用內部類的方法 inner.printNum(); //訪問外部類屬性 System.out.println("outerNum="+OuterClass.outerNum); //訪問內部類屬性 System.out.println("innerNum="+inner.innerNum); } }
5. 訪問方式小結
對於靜態內部類的訪問要求,給大傢總結如下:
- 靜態內部類中可以直接訪問外部類的所有靜態方法,包含私有的,但不能直接訪問非靜態成員;
- 靜態內部類可以添加任意訪問修飾符(public、protected、默認、private),因為它的地位就是一個成員;
- 如果靜態內部類 訪問 外部類 的靜態屬性、靜態方法等,訪問方式是【直接訪問】;
- 如果外部類或外部的其他類來 訪問 靜態內部類,訪問方式是【 外部類.內部類 對象名 = new 外部類.內部類的構造方法 】,創建出內部類對象後再訪問。
六. 結語
現在你學會瞭嗎?我們來總結一下內部類的重點內容吧:
- 內部類分為成員內部類、局部內部類、匿名內部類和靜態內部類;
- 成員內部類和匿名內部類在本質上是相同的,都必須依附於外部類的實例,會隱含地持有 Outer.this 實例,並擁有外部類的 private 訪問權限;
- 靜態內部類是獨立類,但擁有外部類的 private 訪問權限;
- 如果外部類和內部類中的成員重名時,內部類訪問時默認會遵循就近原則;如果想訪問外部類的成員,則可以用【外部類名.成員】的形式來訪問。
以上就是關於如何正確地定義Java內部類方法詳解的詳細內容,更多關於Java定義內部類的資料請關註WalkonNet其它相關文章!