文章目錄
9、內部類與匿名內部類
在類內部可定義成員變量和方法,且在類內部也可以定義另一個類。如果在類 Outer 的內部再定義一個類 Inner,此時類 Inner 就稱爲內部類(或稱爲嵌套類),而類 Outer 則稱爲外部類(或稱爲宿主類)。
內部類可以很好地實現隱藏,一般的非內部類是不允許有 private 與 protected 權限的(只有public與默認),但內部類可以。內部類擁有外部類的所有元素的訪問權限。
9.1 內部類的分類
在類 A 中定義類 B,那麼類 B 就是內部類,也稱爲嵌套類,相對而言,類 A 就是外部類。如果有多層嵌套,例如類 A 中有內部類 B,而類 B 中還有內部類 C,那麼通常將最外層的類稱爲頂層類(或者頂級類)。
內部類也可以分爲多種形式,與變量非常類似,如圖所示。
內部類的特點如下:
-
內部類仍然是一個獨立的類,在編譯之後內部類會被編譯成獨立的.class文件,但是前面冠以外部類的類名和$符號。
-
內部類不能用普通的方式訪問。內部類是外部類的一個成員,因此內部類可以自由地訪問外部類的成員變量,無論是否爲 private 的。
-
內部類聲明成靜態的,就不能隨便訪問外部類的成員變量,仍然是隻能訪問外部類的靜態成員變量。
-
外部類只有兩種訪問級別:public 和默認;內部類則有 4 種訪問級別:public、protected、 private 和默認。
-
在外部類中可以直接通過內部類的類名訪問內部類。
InnerClass ic = new InnerClass(); // InnerClass爲內部類的類名
在外部類以外的其他類中則需要通過內部類的完整類名訪問內部類。
Test.InnerClass ti = newTest().new InnerClass(); // Test.innerClass是內部類的完整類名
- 內部類與外部類不能重名。
提示:使用內部類可以使程序結構變得緊湊,但是卻在一定程度上破壞了 Java 面向對象的思想。
9.1.1 實例內部類(不用static修飾)
概念
實例內部類是指沒有用 static 修飾的內部類,有的地方也稱爲非靜態內部類。在實例內部類中不能定義 static 成員,除非同時使用 final 和 static 修飾。
Demo
package 內部類的三種類型;
/*非靜態內部類不能有靜態成員,實例內部類、
*/
public class Practice_Not_Static {
class Inner1 {//非靜態成員類,實例內部類
}
Inner1 i = new Inner1(); // 使用時不需要創建外部類實例
public void method1() {
Inner1 i = new Inner1(); // 不需要創建外部類實例
}
//靜態方法需要創建外部類實例
public static void method2() {
Inner1 i = new Practice_Not_Static().new Inner1();
}
class Inner2 {
Inner1 i = new Inner1(); // 不需要創建外部類實例
}
}
//在實例內部類中,可以訪問外部類的所有成員。
class OtherClass {
public int a = 100;
static int b = 100;
final int c = 100;
private int d = 100;
public String method1() {
return "實例方法1";
}
public static String method2() {
return "靜態方法2";
}
class Inner {
int a2 = a + 1; // 訪問public的a
int b2 = b + 1; // 訪問static的b
int c2 = c + 1; // 訪問final的c
int d2 = d + 1; // 訪問private的d
String str1 = method1(); // 訪問實例方法method1
String str2 = method2(); // 訪問靜態方法method2
}
public static void main(String[] args) {
Inner i = new OtherClass().new Inner(); // 創建內部類實例
System.out.println(i.a2); // 輸出101
System.out.println(i.b2); // 輸出101
System.out.println(i.c2); // 輸出101
System.out.println(i.d2); // 輸出101
System.out.println(i.str1); // 輸出實例方法1
System.out.println(i.str2); // 輸出靜態方法2
Practice_Not_Static.Inner2 l = new Practice_Not_Static().new Inner2(); // 需要創建外部類實例
//打印hashcode
System.out.println(l.hashCode());
//打印類的包,$加類名加哈希碼
System.out.print(l);
}
}
/*
在外部類中不能直接訪問內部類的成員,而必須通過內部類的實例去訪問。
如果類 A 包含內部類 B,類 B 中包含內部類 C,則在類 A 中不能直接訪問類 C,
而應該通過類 B 的實例去訪問類 C。
外部類實例與內部類實例是一對多的關係,也就是說一個內部類實例只對應一個外部類實例,
而一個外部類實例則可以對應多個內部類實例。
如果實例內部類 B 與外部類 A 包含有同名的成員 t,
則在類 B 中 t 和 this.t 都表示 B 中的成員 t,而 A.this.t 表示 A 中的成員 t。
*/
編譯結果:
運行結果:
9.1.2 靜態內部類(使用static修飾)
概念
靜態內部類是指使用 static 修飾的內部類。
Demo
package 內部類的三種類型;
public class Static_Ineer {
static class Inner {//靜態內部類
int a = 0; // 實例變量a
static int b = 3; // 靜態變量 b
}
class OtherClass {//實例內部類,非靜態內部類
Static_Ineer.Inner oi = new Static_Ineer.Inner();//不用創建外部類實例,通過類名創建內部類
int a2 = oi.a; // 訪問實例成員
int b2 = Static_Ineer.Inner.b; // 訪問靜態成員
int b3 = oi.b;
}
}
/*
在創建靜態內部類的實例時,不需要創建外部類的實例。
靜態內部類可以直接訪問外部類的靜態成員,如果要訪問外部類的實例成員,則需要通過外部類的實例去訪問。
靜態內部類中可以定義靜態成員和實例成員。外部類以外的其他類需要通過完整的類名訪問靜態內部類中的靜態成員,
如果要訪問靜態內部類中的實例成員,則需要通過靜態內部類的實例。
*/
9.1.3 局部內部類(在類方法內定義的類)
概念
局部內部類是指在一個方法中定義的內部類
Demo1
package 內部類的三種類型;
public class Local_Class {
//局部內部類是指在一個方法中定義的內部類。
public void sayHello(){
//局部內部類只在當前方法中有效。
class Local_inner{//局部內部類與局部變量一樣,不能使用訪問控制修飾符(public、private 和 protected)和 static 修飾符修飾。
//局部內部類中不能定義 static 成員。既然是局部變量自然不能聲明static成員
public void sayHello(){
System.out.println("我是局部內部類,在方法裏邊定義的類!");
}
}
System.out.println("我是包含該局部內部類的方法,局部內部類我兒子!");
new Local_inner().sayHello();//在該方法裏面調用局部內部類的方法,使用匿名內部類一次
}
public static void main(String[]args){
Local_Class local = new Local_Class();
local.sayHello();
}
}
Demo2演示局部內部類只能訪問方法中的final成員
package 內部類的三種類型;
/*
在局部內部類中可以訪問外部類的所有成員。
在局部內部類中只可以訪問當前方法中 final 類型的參數與變量。如果方法中的成員與外部類中的成員同名,
則可以使用 <OuterClassName>.this.<MemberName> 的形式訪問外部類中的成員。
*/
public class Local_Class_2 {
int a = 0;
int d = 0;
public void method() {
int b = 0;//不用final定義的局部變量,默認使用final,java8 裏邊叫做 Effectively final 功能
//b=2;//一旦重寫賦值就會改變b的屬性不是final則報錯
/*
如果在1.8的環境下,會很神奇的發現我們最開始的java文件編譯和運行是完全沒有問題的,
也就是說內部類使用的局部變量是可以不聲明爲final?!
也就是說規則沒有改變,只是java1.8的編譯變得更加智能了而已,
在局部變量沒有重新賦值的情況下,它默認局部變量爲final型,
認爲你只是忘記加final聲明瞭而已。如果你重新給局部變量改變了值或引用,
那就無法默認爲final了,所以報錯。
*/
final int c = 0;
final int d = 10;
class Inner {
int a2 = a; // 訪問外部類中的成員
int b2 = b; //訪問方法中的成員,沒有編譯錯誤?
int c2 = c; // 訪問方法中的成員
int d2 = d; // 訪問方法中的成員
int d3 = Local_Class_2.this.d; //訪問外部類中的成員
}
Inner i = new Inner();//創建局部內部類
System.out.println(i.d2); // 輸出10
System.out.println(i.d3); // 輸出0
System.out.print(i.b2);
}
public static void main(String[] args) {
Local_Class_2 t = new Local_Class_2();
t.method();
}
}
運行結果:
9.2 匿名內部類及Demo
9.2.1 概念
匿名類是指沒有類名的內部類,必須在創建時使用 new 語句來聲明類。
格式:
new <類或接口>() {
// 類的主體
};
9.2.2 實現形式
匿名類有兩種實現方式:
- 繼承一個類,重寫其方法。
- 實現一個接口(可以是多個),實現其方法。
例子1,通過重寫類來實現
public class Out
{
void show()
{
System.out.println("調用 Out 類的 show() 方法");
}
}
public class TestAnonymousInterClass
{
//在這個方法中構造一個匿名內部類
private void show()
{
Out anonyInter=new Out()//其實這裏算是有名字了。。。重寫Out的方法void
{
//獲取匿名內部類的實例
void show()
{
System.out.println("調用匿名類中的 show() 方法");
}
};
anonyInter.show();
}
public static void main(String[] args)
{
TestAnonymousInterClass test=new TestAnonymousInterClass();
test.show();
}
}
可以將其改爲:
//在這個方法中構造一個匿名內部類
private void show()
{
new Out()//這個時候就沒有名字了
{
//獲取匿名內部類的實例
void show()
{
System.out.println("調用匿名類中的 show() 方法");
}
}.show();//直接使用,只使用一次
}
程序的輸出結果如下:
從輸出結果可以看出,匿名內部類有自己的實現。
匿名內部類實現一個接口的方式與實現一個類的方式相同
例子2通過實現一個接口
public interface Caculation{
int caculationInt(int x,int y);//這是默認的抽象方法,如果不加聲明
}
package Lambda表達式;
public class Practice1 {//我們是通過匿名內部類來實現接口
public static Caculation caculation(char op) {
Caculation result;
if (op == '+') {
result = new Caculation() {//使用匿名內部類來實現該接口
@Override
public int caculationInt(int x, int y) {//重寫接口方法
return x + y;
}
};
} else{
result = new Caculation() {
@Override
public int caculationInt(int x, int y) {
return x - y;
}
};
}
return result;
}
public static void main(String[]args){
//創建類
Practice1 pr = new Practice1();
//使用類的方法caculation()調用接口方法
System.out.println(pr.caculation('+').caculationInt(3,4));
}
}
運行結果:
9.2.3 與局部內部類的關係
(1) 匿名類和局部內部類一樣,可以訪問外部類的所有成員。如果匿名類位於一個方法中,則匿名類只能訪問方法中 final 類型的局部變量和參數。
(2) 匿名類中允許使用非靜態代碼塊進行成員初始化操作。
Out anonyInter=new Out()
{
int i;
{ //非靜態代碼塊
i=10; //成員初始化
}
public void show()
{
System.out.println("調用了匿名類的 show() 方法"+i);
}
};
(3) 匿名類的非靜態代碼塊會在父類的構造方法之後被執行。
9.2.4 匿名內部類的應用
1.創建的類對象是匿名的
- 當我們只需要一次調用類的對象的時候,我們就可以考慮使用匿名類的創建方式創建匿名類對象。
- 創建的匿名類對象只能夠調用一次!
2.匿名類只能夠調用一次的原因
- 創建的時候只在jvm的堆空間新建了對象,並進行初始化,但棧空間沒有一個變量名指向匿名對象。
- jvm垃圾回收機制在發現堆空間的對象,沒有一個引用指向他,就給回收內存了。
如:
//輸出的時候,直接new一個Date();
System.out.println(new Date());
9.3 內部類實現多繼承
多重繼承指的是一個類可以同時從多於一個的父類那裏繼承行爲和特徵,然而我們知道 Java 爲了保證數據安全,只允許單繼承。有些時候我們會認爲如果系統中需要使用多重繼承,那往往都是糟糕的設想,這時開發人員往往需要思考的不是怎麼使用多重繼承,而是他的設計是否存在問題。但是,有時候開發人員確實需要實現多重繼承,而且現實生活中真正地存在這樣的情況
,例如遺傳,我們既繼承了父親的行爲和特徵,也繼承了母親的行爲和特徵。
Java 提供的兩種方法讓我們實現多重繼承:接口和內部類。
Demo
1)Father class
public class Father {
public int strong(){//帥氣指數
return 9;
}
}
2)Mother class
public class Mother {
public int kind(){//友好指數
return 8;
}
}
3)Son class
package 匿名內部類;
public class Son {
public class Father_1 extends Father{//內部類,而且是實例內部類,非靜態內部類
public int strong(){
return super.strong()+1;//使用super調用父類方法
}
}
public class Mother_1 extends Mother{////內部類,而且是實例內部類,非靜態內部類
public int kind(){
return super.kind()+1;
}
}
public int getStrong(){
//使用匿名內部類
return new Father_1().strong();//只用到了一次
}
public int getKind(){
//使用匿名內部類
return new Mother_1().kind();
}
public static void main(String[]args){
Son son = new Son();
System.out.println("強壯指數"+son.getKind());
System.out.println("和藹指數"+son.getStrong());
}
}
運行結果: