我的部落格網站 http://www.caoyong.xin:8080/blogger
這幾天在把程式設計思想(初學者怎麼好不要過早看這本書。)重新拿出來看了一遍,看到Java内部類,結合該書和資料,總結了一下内部類的一些知識
1:什麼是内部類?
顧名思義就是在Java類中定義的類叫内部類,其實這是片面的,不僅在類的内部定義,還可以在方法中,代碼塊中(什麼代碼塊,就相當于if(){大括号裡面的都叫代碼塊}),是以這裡我們就給出了内部類定義的範圍
2:内部類有哪些形式?
普通内部類
局部内部類
匿名内部類
靜态内部類
普通内部類
就是我們所了解的定義在類的内部的類,看下面代碼
public class Out {
public class Inner {
public void innerMethod() {
System.out.println("我是内部類中的方法 ....innerMethod");
Out.this.outMethod();
}
}
public void outMethod() {
System.out.println("我是外部類的方法 ....outMethod");
}
public static void main(String[] args) {
Out d = new Out();
Out.Inner i = d.new Inner();
i.innerMethod();
}
}
這裡我定義一個外部類Out 定義一個内部類Inner 在外部類中定義一個outMethod方法,在内部類中定義一個innerMethod方法。
當内部類生成對象的時候,與外部類有一定的聯系,這時候就會想到,在外部類怎樣得到内部類的對象?在内部類裡面怎樣得到外部類的對象?
這個時候時候就要用到.new 和.this,看下面代碼
Out d = new Out();
Out.Inner i = d.new Inner();
i.innerMethod()
内部類對象必須通過外部類對象才能産生(靜态内部類除外,稍後再提)上面的d就是外部類對象,通過外部類名.内部類名 和 d.new就可以得到内部類對象i
如何在内部類中得到外部類對象,就需要用到.this,看上面代碼在内部類Inner中 Out.this.outMethod(); 外部類名.this就得到了外部類對象。
局部内部類
前面提到的都是處于外部類中的内部類,而局部内部類定義在一個方法裡面或者在任意的作用域中,這麼做有以下兩個理由:
1.如實作某接口的内部類,可以在方法中建立并傳回對其的引用
2.要解決一個複雜的問題,需要建立一個類來輔助,但不希望這個類是公共可用的
public class Test {
public double count = 100;
public Car getCar() {
class BaoMa implements Car {// 如實作某接口的内部類,可以在方法中建立并傳回對其的引用
@Override
public double money() {
return count * 20;
}
}
return new BaoMa();
}
public static void main(String[] args) {
Test t = new Test();
Car baoMa = t.getCar();
System.out.println(baoMa.money());
}
}
結果:2000.00
局部内部類不能有 private 等通路說明符,因為它不是外部類的一部分,但是它可以通路目前代碼塊内的常量以及外部類中的所有成員
匿名内部類
匿名内部類也就是沒有名字的内部類
從上面可以看出 Car baoMa = t.getCar(); 在getCar()方法裡面雖然定義了一個内部類,但是我們在調用gtCar()方法的時候,好像不怎麼關心内部類的名字,進而也就變成了匿名。
對于上面的代碼我們可以這樣來寫
public class Test {
public Car getCar(){
return new Car(){//這裡就實作了匿名 原本應該有一個内部類 繼承Car類并重寫price()方法,但是這裡内部類的名字卻沒有,變成了匿名
int count = 100;//這個變量在内部類裡面定義的。
public double price() {
return count * 20;
}
};
}
public static void main(String[] args) {
Test t = new Test();
Car car = t.getCar();
System.out.println(car.price());
}
}
在匿名内部類中使用一個在其外部定義的對象,那麼編譯器必須要求其參數引用是 final 類型,以上代碼在低于 Java 8 的版本編譯不會通過,但是在 Java 8 版本不用 final 修飾局部變量也可以編譯通過,隻不過不能修改值,隻能列印輸出或指派給其他變量。
public Car getCar(){
final int count = 100 ;
return new Car(){
public double price() {
return count * 20;
}
};
}
可能有人就會問了,沒有類名,怎麼使用構造函數初始化類了?
面對這個問題,我們可以在内部類裡面加上一段代碼塊來解決
public Car getCar(){
final int count = 100 ;
return new Car(){
{System.out.println("這裡初始化内部類");}
public double price() {
return count * 20;
}
};
}
結果:
這裡初始化内部類
2000.0
在上面,我們分别使用局部内部類和匿名内部類實作了這個功能,它們具有相同的行為和能力,既然局部内部類的名字在方法外是不可見的,那為什麼我們仍然使用局部内部類而不是匿名内部類呢?唯一的理由是:我們需要一個已命名的構造器,或者需要重載狗仔器,而匿名内部類隻能用于執行個體初始化,是以使用局部内部類而不使用匿名内部類的另一個理由就是:需要不止一個該内部類的對象。
靜态内部類(嵌套内部類)
在了解靜态内部了之前,要先了解static關鍵字,關于什麼是static可以檢視我的部落格關于static的介紹。
如果不需要内部類對象與其外部類對象之間有聯系,那麼可以将内部類聲明為 static,即靜态内部類,也稱嵌套類,靜态内部類和非靜态内部類的最大差別就是非靜态内部類對象隐士的儲存了一個外部類對象的引用,這意味着:
1.不需要外部類的對象就可以建立靜态内部類的對象
2.不能從靜态内部類的對象中通路非靜态的外部類對象
3.靜态内部類中可以定義靜态或者非靜态的成員,而非靜态内部類則不能有靜态成員。
public class Test {
static int count = 100;// 靜态
int noCount = 100;// 非靜态
static class getCar implements Car {
String str = "我是非靜态";
static String sstr = "我是靜态";
public double price() {
System.out.println(str);
System.out.println(sstr);
return count * 20;
// return noCount * 20;//編譯不通過 不能通路非靜态對象
}
}
public static void main(String[] args) {
Test.getCar car = new Test.getCar();//直接使用外部類類名建立内部類對象
System.out.println(car.price());
}
}
結果:
我是非靜态
我是靜态
2000.0
這個例子很好的對應了上面的三句話。
3:内部類的繼承
内部類,雖然是在類的内部定義的一個類,但是也可以去繼承這個内部類,怎麼繼承自一個内部類?内部類的構造必須依賴其外部類對象,是以在繼承内部類的時候,事情會變得複雜
看下面的例子 首先有一個基類
public class Fruit {
public class Apple{
void print() {}
}
}
接着有一個類去繼承内部類Apple
public class Test extends Fruit.Apple {
Test(Fruit fruit){
fruit.super();
}
@Override
void print() {
System.out.println("内部類繼承");
}
public static void main(String[] args) {
Fruit f = new Fruit();
Test t = new Test(f);
t.print();
}
}
前面提到,内部類對象的建立需要依賴外部類對象,是以我們要在導出類(Test類)的構造器中調用外部類對象。
4:内部類可以被覆寫嗎
如果建立了一個内部類,然後繼承其外部類并重新定義此内部類時,内部類可以被覆寫嗎?例如:
class Car {
Car() {
System.out.println("new Car()"); new Tyre();
} class Tyre { // 我會被覆寫嗎
Tyre() { System.out.println("new Tyre()"); }
}
}
public class BigCar extends Car {
class Tyre {
Tyre() {System.out.println("BigCar new Tyre()"); }
} public static void main(String[] args) { new BigCar();
}
}
在 Car 的構造器中建立的 Tyre 是 Car 中的 Tyre 還是 BigCar 中的 Tyre 呢?運作程式輸出:
new Car()
new Tyre()
BigCar 中定義的 Tyre 内部類并沒有覆寫 Car 中的 Tyre 内部類,實際上這兩個内部類是完全獨立的兩個實體,各自在自己的命名空間内,沒有誰覆寫誰之說。
5:為什麼需要内部類
也就是說,内部類存在的意義是什麼呢?為什麼 Sun 公司如此費心地增加這項語言特性呢?這裡将内部類的意義總結為以下四點:
A. 邏輯上被包含且對外隐藏
如果一個類在邏輯上被包含于另一個類,那麼将此類設定為另一個類的内部類,比如輪胎類可以寫作汽車類的内部類:
public class Car { public class Tyre{}
}123
上面代碼中隻存在被包含關系,也可通過組合方式實作,寫作内部類是沒有必要的,當想對外保密汽車使用何種輪胎時,寫作内部類才是有必要的:
public class Car { private class Tyre{}
}123
B. 實作多重繼承(這應該是内部類的意義所在)
每個内部類都能獨立的繼承一個接口,無論外部類是否已經繼承了某個接口的實作,對于内部類都沒有影響。内部類提供可以繼承多個抽象類或具體的類的能力,有效的實作了多重繼承,網上一個執行個體簡單直覺的展示了通過内部類實作多重繼承(兒子利用多重繼承來繼承父親和母親的優良基因):
public class Father { // 父親
public int strong() {
return 9;
}
}
public class Mother { // 母親
public int kind() {
return 8;
}
}
public class Son { // 兒子通過内部類實作多重繼承
class Father_1 extends Father {
public int strong() {
return super.strong() + 1;
}
}
class Mother_1 extends Mother {
public int kind() {
return super.kind() - 2;
}
}
public int getStrong() {
return new Father_1().strong();
}
public int getKind() {
return new Mother_1().kind();
}
}
C. 閉包與回調
閉包是一個可調用的對象,它記錄了一些資訊,這些資訊來自于建立它的作用域。通過這個定義,可以看出内部類是面向對象的閉包,因為它不僅包含外部類對象(建立内部類的作用域)的資訊,還自動擁有一個指向此外部類對象的引用,在此作用域内,内部類有權操作所有的成員,包括private成員。
通過内部類提供閉包功能比指針更靈活、更安全
例如:一個接口程式員和一個基類作家都有一個相同的方法work,相同的方法名,但是其含義完全不同,這時候就需要閉包。
class Writer { //作家基類
void work(){}
}interface programmer{ //程式員接口
void work();
}
閉包實作代碼如下:
public class WriterProgrammer extends Writer {
@Override public void work(){ //寫作
}
public void code() { // 寫代碼
}
class ProgrammerInner implements programmer {
@Override
public void work() {
code();
}
}
}
WriterProgrammer 繼承自 Writer , 直接實作父類作家的work()方法,然後使用内部類實作程式員的work()方法回調code()方法。如果WriterProgrammer 同時繼承自 Writer 且實作 programmer 接口,那麼就不能同時實作作家和程式員的意義不同的 work()方法:
class WriterProgrammer extends Writer implements programmer{
@Override public void work() { //programmer的 work
}
}
應用程式架構就是被設計用以解決某類特定問題的一個類或一組類,而控制架構就是一類特殊的應用程式架構,它用來解決響應事件的需求,主要用來響應事件的系統被稱作事件驅動系統。 Java Swing 庫就是一個控制架構,它優雅的解決了 GUI 的問題,并使用了大量的内部類
控制架構的完整實作是由單個類建立的,内部類用來表示解決問題所必須的各種不同的 action,另外内部類能夠很容易的通路外部類的任意成員,可以讓這種實作更輕松。
例如控制溫室的運作:控制燈光、水、溫度調節器的開關等,每個行為都是完全不同的,使用内部類可以在單一的類中産生對同一個基類 Event 的多種導出版本,對于溫室系統的每一種行為,都繼承一個新的 Event 内部類,并在要實作的 action() 中編寫控制代碼:
public class GreenhouseControls{
public class LightOn extends Event {
public void action() { //開燈...
}
} public class LightOff extends Event {
public void action() { //關燈...
}
} public class WaterOn extends Event {
public void action() { //開水閘...
}
} // 其他操作...}
6:内部類辨別符
每個類都會産生一個.class檔案,其中包含了如何建立該類型的對象的全部資訊,内部類也必須生成一個 .class 檔案,進而可以包含它自己的 Class 對象資訊,這些類檔案的命名有嚴格的規則:外部類名字+“$”+ 内部類名字,例如:
class Car {
class Tyre {}}123
生成的 .class 檔案包括:
Car.class
Car$Tyre.class