# 接口與內部類
[TOC]
## 接口
### 導學
在Java中,只能支持單繼承。那么,如果想要在一個類型中能夠兼容多種類型特征,代碼該如何編寫呢?如果多個不同的類型在不具有相同父類的情況下,仍然要具有相同的特征,那代碼又該如何編寫呢?
比如,我們可以使用一個案例來描述一下。
案例:要求描述手機的發展史?

實現:
~~~
/**
* 最原始的手機
* @author LiXinRong
*
*/
public class Telphone {
private String brand;
private int price;
public Telphone() {
}
public void call() {
System.out.println("手機可以打電話");
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
~~~
~~~
package com.dodoke.phone.model;
/**
* 二代手機
* @author LiXinRong
*
*/
public class SecondPhone extends Telphone{
public void message() {
System.out.println("手機可以發短信");
}
}
~~~
~~~
package com.dodoke.phone.model;
/**
* 三代手機
* @author LiXinRong
*
*/
public class ThirdPhone extends SecondPhone {
public void video() {
System.out.println("手機可以看視頻");
}
public void music() {
System.out.println("手機可以聽音樂");
}
}
~~~
~~~
package com.dodoke.phone.model;
/**
* 最新一代手機
* @author LiXinRong
*
*/
public class FourthPhone extends ThirdPhone {
public void photo() {
System.out.println("手機可以拍照");
}
public void network() {
System.out.println("手機可以上網");
}
public void game() {
System.out.println("手機可以玩游戲");
}
}
~~~
~~~
package com.dodoke.phone.test;
import com.dodoke.phone.model.FourthPhone;
public class Test {
public static void main(String[] args) {
FourthPhone phone = new FourthPhone();
phone.call();
phone.message();
phone.video();
phone.game();
phone.network();
phone.photo();
}
}
~~~
我們可以看到,無論是其他幾代的什么功能都可以在四代手機中實現。我們可以很簡單的實現這樣一個手機演變史的記錄。
但是,現在相機可以拍照,電腦可以上網、 玩游戲, 智能手表也可以打電話。那么當程序中需要繼續滿足這個需求的時候,代碼該如何調整? 電話,電腦,相機,智能手表。 他們沒有辦法抽取一個公共的父類。因為他們的行為是相互交叉,具有相似的能力。但硬性拆出一個公共父類, 就不太合理。那就得創建一個電腦類,智能手表類,相機類,在每個類中增加相應的功能等等.。只要在測試類中生成這幾個類的實例對象, 調用他們的方法就可以實現相應的內容。
~~~
public class Camera {
public void photo() {
System.out.println("相機可以拍照");
}
}
~~~
~~~
public class Computer {
public void video() {
System.out.println("電腦可以看視頻");
}
public void music() {
System.out.println("電腦可以聽音樂");
}
public void network() {
System.out.println("電腦可以上網");
}
public void game() {
System.out.println("電腦可以玩游戲");
}
}
~~~
但是這幾種類型中間真的就沒辦法建立關聯了嗎? 他們之間的確不能抽取具有公共特征的父類, 但是他們當中很多類型中間是具有相同行為能力的。
那么,在Java中,就可以通過接口實現這些行為的關聯
### 接口實現
~~~
public interface IPhoto {
public void photo();
}
~~~
~~~
package com.dodoke.phone.model;
import com.dodoke.phone.interfaces.IPhoto;
public class Camera implements IPhoto{
@Override
public void photo() {
System.out.println("相機可以拍照");
}
}
~~~
~~~
public class FourthPhone extends ThirdPhone implements IPhoto {
@Override
public void photo() {
System.out.println("手機可以拍照");
}
public void network() {
System.out.println("手機可以上網");
}
public void game() {
System.out.println("手機可以玩游戲");
}
}
~~~
~~~
public class Test {
public static void main(String[] args) {
FourthPhone phone = new FourthPhone();
phone.call();
phone.message();
phone.video();
phone.game();
phone.network();
phone.photo();
System.out.println("=====================");
//要求相機拍照可以,手機拍照也可以
IPhoto ip = new Camera();
ip.photo();
ip = new FourthPhone();
ip.photo();
}
}
~~~
小結:
1. 接口中的方法沒有方法體。
2. 類使用implements關鍵字來實現接口。例如:`public class Camera implements IPhoto{}`
3. 如果一個類實現了某個接口,那么必須重寫此接口中的所有方法(如果不實現則此類必須設置為抽象類)。
4. 接口無法直接實例化出對象,用接口的引用指向實現類的實例對象。例如:`IPhoto ip=new FourthPhone();` 實例化的對象ip只能調用`IPhoto`這個接口中的方法,調用方法具體的實現細節則由其實例化對象時使用的類決定(如在此如果`ip.photo();` 實際上是運行的`FourthPhone`類中的`photo()`方法)。
### 接口成員
在講接口給的成員之前,我們再來提一下接口,就如同抽象類是利用繼承給子類指定規范一樣,接口也是給一些沒有關系的類制定了需要遵守的規范。接口不關心這些類的內部數據,也不關心這些類里面的方法的實現細節,它只規定這些類里面必須提供某些細節。
接下來,我們繼續結合具體案例來看一看接口中的內容
#### 抽象方法和常量
~~~Java
package com.dodoke.phone.interfaces;
//訪問修飾符 只能是public 和 默認default
public interface INet {
//接口中抽象方法可以不寫abstract關鍵字
public void network();
//接口中抽象方法可以不寫public,但是依然會按照public的限定范圍使用
void connection();
//接口中,可以包含常量
public static final int TEMP = 20;
//關于常量的修飾符可以省略,默認public static final
int TEMP2 = 15;
}
~~~
測試
~~~java
System.out.println(INet.TEMP);
INet it = new Computer();//接口引用指向具體實現類
System.out.println(it.TEMP);
~~~
>[warning]如果在實現類中存在和接口同名的常量,在接口引用指向實現類時,調用的是接口中定義的常量信息
#### 默認方法和靜態方法
~~~
//訪問修飾符 只能是public 和 默認default
public interface INet {
//接口中抽象方法可以不寫abstract關鍵字
public void network();
//接口中抽象方法可以不寫public,但是依然會按照public的限定范圍使用
void connection();
//接口中,可以包含常量
public static final int TEMP = 20;
//關于常量的修飾符可以省略,默認public static final
int TEMP2 = 15;
/**
* jdk 1.8 以后提供了默認方法
* 可以帶方法體,默認方法不一定要被實現
*/
default void Connection() {
System.out.println("我是接口中的默認鏈接");
}
/**
* jdk 1.8 以后提供了靜態方法
* 可以帶方法體
*/
static void stop() {
System.out.println("我是接口中的靜態方法");
}
}
~~~
測試:
~~~
INet it = new Computer();
it.Connection();
//it.stop();不能調用
INet.stop();
~~~
默認方法可以在實現類中重寫,也可以不重寫,并可以通過**接口的引用**調用。靜態方法不可以在實現類中重寫,只能通過**接口名**調用。
### 多接口重名處理
在Java中,只能單繼承,但是可以實現多個接口
**同名方法**
~~~
public class SmartWatch implements INet,IPhoto{
public void call() {
System.out.println("智能手表可以打電話");
}
public void message() {
System.out.println("智能手表可以發短信");
}
@Override
public void photo() {
System.out.println("智能手表可以拍照");
}
@Override
public void network() {
System.out.println("智能手表可以上網");
}
}
~~~
如果在接口中存在同名的默認方法,要么刪除其中一個接口的方法,要么在實現類中,自己定義一個同名的方法。在接口引用指向實現類的時候,調用的是實現類中的方法。
一個類可以繼承一個父類,同時實現若干接口
~~~
public class FourthPhone extends ThirdPhone implements IPhoto,INet {
@Override
public void photo() {
System.out.println("手機可以拍照");
}
@Override
public void network() {
System.out.println("手機可以上網");
}
public void game() {
System.out.println("手機可以玩游戲");
}
}
~~~
如果在父類和接口中都存在同名方法,在`FourthPhone` 沒有重寫該方法不會報錯,但最終指向的是父類中的同名方法。如果重寫了該方法,最終指向的是`FourthPhone`中重寫的方法。
**同名常量**
~~~
interface One {
static int X = 11;
}
interface Two {
final int X = 22;
}
class Three {
public int X = 33;//父類中的屬性與接口中的常量同名的時候,無法解析
}
public class Test1 extends Three implements One,Two{
public void test() {
System.out.println(One.X);
System.out.println(Two.X);
//System.out.println(X);報錯
}
public static void main(String[] args) {
new Test1().test();
}
}
~~~
類實現了多接口時,如果多接口中出現了重名常量,在此類中通過接口名.變量的方式訪問。
類繼承父類又實現了多接口時,如果父類、多接口中出現了重名常量,只能在該實現類中自己再定義這個重名的變量,才能消除歧義。
### 接口的繼承
接口之間同樣也是存在繼承關系的。在Java中,接口之間可以實現多繼
~~~
public interface IFather1 {
void say();
default void connection() {
System.out.println("我是IFather1中的連接方法");
}
}
~~~
~~~
public interface IFather2 {
void fly();
default void connection() {
System.out.println("我是IFather2中的連接方法");
}
}
~~~
~~~
public interface ISon extends IFather1,IFather2{
void run();
default void connection() {
System.out.println("我是兒子的連接方法");
}
}
~~~
~~~
public class Demo implements ISon{
@Override
public void say() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void run() {
// TODO Auto-generated method stub
}
}
~~~
總結:
1.子接口允許有多個父接口
2.子接口繼承多個父接口,需重寫父接口中所有的抽象方法
3.多個父接口默認方法重名時子接口會報錯,解決方案:子接口重寫重名的默認方法
## 內部類
### 導學
在程序開發中為了更加準確的描述結構體的作用,通常擁有各種嵌套結構。而程序**類**也是允許嵌套的!
內部類(內部定義普通類,抽象類,接口的統稱)是指一種嵌套的結構關系,即在一個類的內部除了定義屬性和方法外還可以定義一個類結構,這樣的形式使得程序的結構定義更加靈活。
>[info] 內部類是一種常見的嵌套結構,利用這樣的結構可以使內部類與外部類共存,并且方便的進行使用操作的訪問。內部類也可以進一步擴展到匿名內部類的使用,在jdk 1.8后所提供的Lambda表達式與方法引用也可以簡化代碼結構
**示例:**
~~~java
package lamda.dodoke.demo1;
class Outer {//外部類(非公有類)
private String msg = "Hello,World";//定義私有成員屬性
public void fun() {//定義普通方法
Inner in = new Inner();//實例化內部類對象
in.print();//調用內部類方法
}
class Inner {//在Outer類的內部定義Inner內部類
public void print() {//定義內部類的方法
System.out.println(Outer.this.msg);//調用Outer類的屬性
}
}
}
public class JavaDemo {//在一個Java文件中可以有多個類,但是只能有一個public修飾的類
public static void main(String[] args) {
Outer out = new Outer();//實例化外部類對象
out.fun();//執行外部類方法
}
}
~~~
準確來說,內部類會使一個類的內部充斥著其他的類結構,所以內部類在整體設計中最大的缺點就是破壞了良好的程序結構,造成代碼結構的混亂。但他最大的優點在于可以方便的訪問外部類的私有成員。所以我們使用內部類,更多的時候是希望**某一個類只為單獨一個類服務**。
### 內部類注意點
* 內部類仍然是一個獨立的類,在編譯之后內部類會被編譯成獨立的`.class`文件,前面冠以外部類的類名和`$`符號。
* 內部類不能用普通的方式訪問。內部類是外部類的一個成員,因此內部類可以自由地訪問外部類的成員變量,無論是否為 private 的。
* 內部類聲明成靜態的,就不能隨便訪問外部類的成員變量,仍然是只能訪問外部類的靜態成員變量。
### 成員內部類
成員內部類(實例內部類)是指沒有用 static 修飾的內部類,有的地方也稱為非靜態內部類。示例如下:
~~~
public class Out {
class Inner {}
}
~~~
* 在外部類的靜態方法和外部類以外的其他類中,必須通過外部類的實例創建內部類的實例。
~~~
public class Outer {
class Inner {}
Inner inner = new Inner(); // 不需要創建外部類實例
public void method1() {
Inner i = new Inner(); // 不需要創建外部類實例
}
public static void method2() {
Inner i = new Outer().new Inner(); // 需要創建外部類實例
}
}
//注意是否在同一個包中
~~~
* 在實例內部類中,可以訪問外部類的所有成員。
~~~
public class Outer {
public int a = 100;
static int b = 100;
final int c = 100;
private int d = 100;
public String method3() {
return "實例方法";
}
public static String method4() {
return "靜態方法";
}
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 in = new Outer().new Inner();
System.out.println(in.a2); // 輸出 101
System.out.println(in.b2); // 輸出 101
System.out.println(in.c2); // 輸出 101
System.out.println(in.d2); // 輸出 101
System.out.println(in.str1); // 輸出實例方法
System.out.println(in.str2); // 輸出靜態方法
}
}
~~~
* 在外部類中不能直接訪問內部類的成員,而必須通過內部類的實例去訪問。如果類 A 包含內部類 B,類 B 中包含內部類 C,則在類 A 中不能直接訪問類 C,而應該通過類 B 的實例去訪問類 C。
* 外部類實例與內部類實例是一對多的關系,也就是說一個內部類實例只對應一個外部類實例,而一個外部類實例則可以對應多個內部類實例。
~~~
public class Outer {
int a = 10;
class Inner {
int a = 20;
int a1 = this.a;
int b3 = Outer.this.a;
}
}
~~~
* 在實例內部類中不能定義 static 成員,除非同時使用 final 和 static 修飾。
### 靜態內部類
靜態內部類是指使用 static 修飾的內部類。示例代碼如下:
~~~
public class Outer {
static class Inner {} // 靜態內部類
}
~~~
* 在創建靜態內部類的實例時,不需要創建外部類的實例。
~~~
class OtherClass {
Outer.Inner oi = new Outer.Inner();
}
~~~
* 靜態內部類中可以定義靜態成員和實例成員。外部類以外的其他類需要通過完整的類名訪問靜態內部類中的靜態成員,如果要訪問靜態內部類中的實例成員,則需要通過靜態內部類的實例。
~~~
public class Outer {
static class Inner {
int a = 0;
static int b = 0;
}
}
class OtherClass {
Outer.Inner oi = new Outer.Inner();
int a2 = oi.a; // 訪問實例成員
int b2 = Outer.Inner.b; // 訪問靜態成員
}
~~~
* 靜態內部類可以直接訪問外部類的靜態成員,如果要訪問外部類的實例成員,則需要通過外部類的實例去訪問。
~~~
public class Outer {
int a = 0;
static int b = 0;
static class Inner {
Outer o = new Outer();
int a2 = o.b; // 訪問實例變量
int b2 = b; // 訪問靜態變量
}
}
~~~
### 方法內部類
局部內部類(方法內部類)是指在一個方法中定義的內部類。示例代碼如下:
~~~
public class Test {
public void method() {
class Inner {} // 局部內部類
}
}
~~~
* 局部內部類與局部變量一樣,不能使用訪問控制修飾符(public、private 和 protected)和 static 修飾符修飾。
* 局部內部類只在當前方法中有效。
~~~
public class Test {
Inner i = new Inner(); // 編譯出錯
Test.Inner ti = new Test.Inner(); // 編譯出錯
Test.Inner ti2 = new Test().new Inner(); // 編譯出錯
public void method() {
class Inner {}
Inner i = new Inner();
}
}
~~~
* 局部內部類中不能定義 static 成員。
* 局部內部類中還可以包含內部類,但是這些內部類也不能使用訪問控制修飾符(public、private 和 protected)和 static 修飾符修飾。
* 在局部內部類中可以訪問外部類的所有成員。
* 在局部內部類中可以直接訪問當前方法中的參數與變量。如果方法中的成員與外部類中的成員同名,則可以使用`外部類.this.變量`的形式訪問外部類中的成員。
~~~
public class Test {
int a = 0;
int d = 0;
public void method() {
int b = 0;
final int c = 0;
final int d = 10;
class Inner {
int a2 = a; // 訪問外部類中的成員
int b2 = b; // 訪問外部類中的成員
int c2 = c; // 訪問方法中的成員
int d2 = d; // 訪問方法中的成員
int d3 = Test.this.d; //訪問外部類中的成員
}
Inner i = new Inner();
System.out.println(i.d2); // 輸出 10
System.out.println(i.d3); // 輸出 0
}
}
~~~
### 匿名內部類
## 匿名內部類
匿名類是指沒有類名的內部類,必須在創建時使用 new 語句來聲明類。其語法形式如下:
~~~
new <類或接口>() {
// ....
}
~~~
這種形式的 new 語句聲明一個新的匿名類,它對一個給定的類進行擴展,或者實現一個給定的接口。使用匿名類可使代碼更加簡潔、緊湊,模塊化程度更高。
匿名類有兩種實現方式:
* 繼承一個類,重寫其方法。
* 實現一個接口(可以是多個),實現其方法
~~~
public class Out {
void show() {
System.out.println("調用 Out 類的 show() 方法");
}
}
public class Test {
public static void main(String[] args) {
Out o = new Out() {
void show() {
System.out.println("調用匿名類中的 show() 方法");
}
}
o.show(); // 調用匿名類中的 show() 方法
}
}
~~~
* 匿名類和局部內部類一樣,可以訪問外部類的所有成員。
~~~
public static void main(String[] args) {
int a = 10;
final b = 10;
Out o = new Out() {
void show() {
System.out.println(a); // 編譯通過
System.out.println(b); // 編譯通過
}
}
o.show();
}
~~~
* 匿名類中允許使用非靜態代碼塊進行成員初始化操作。
~~~
Out o = new Out() {
int i;
{
i = 10;
}
public void show() {
System.out.println("i");
}
}
~~~
* 匿名類的非靜態代碼塊會在父類的構造方法之后被執行。
## 練習
一、編程
1. 閱讀下面的Java代碼,能夠填寫在橫線處的語句是

~~~
A. private int MAX\_LOG\_SIZE = 1000;
B. public void print() {}
C. private Boolean saveToFile(String fileNmae);
D. int getSize();
~~~
2. 下列關于Java中接口的說法不正確的是
~~~
A. 接口中方法的訪問修飾符默認為public
B. 接口中的方法如果寫成void test();的形式,默認是抽象方法
C. 實現接口的類中在重寫接口中方法時訪問修飾符可以為protected
D. 當類實現接口時,需要實現接口中所有的抽象方法,否則需要將該類設置為抽象類
~~~
3. 運行下列代碼時,哪個位置會發生編譯報錯

~~~
A. 位置1
B. 位置2
C. 位置3
D. 不存在錯誤
~~~
4. 在實現類中的劃線處加入下列哪條代碼可以調用 IAa 接口中默認的方法

~~~
A. IAa.show();
B. super.show();
C. IAa.super.show();
D. B.super.show();
~~~
5. 下列代碼的運行結果是

~~~
A. 10temp
B. temp10
C. 1010
D. temptemp
~~~
6. 已知外部類Out中含有成員內部類Inner,在主方法中怎么獲取內部類Inner的實例化對象inner(多選)
~~~
A. Out o = new Out();
Out.Inner inner = o.new Out();
B. Out o = new Out();
Out.Inner inner = o.new Inner();
C. Out.Inner inner = new Inner();
D. Out.Inner inner = new Out().new Inner();
~~~
7. 關于下列代碼,說法正確的是

~~~
A. 代碼編譯成功
B. 代碼編譯錯誤,錯誤發生在位置1
C. 代碼編譯錯誤,錯誤發生在位置2
D. 代碼編譯錯誤,錯誤發生在位置3
~~~
二、編程
1. 使用接口的知識, 定義接口IFly,創建三個類Plane類、Bird類、Balloon類,分別重寫接口中的fly( )方法,然后再測試類中進行調用。
程序運行參考效果如圖所示:

任務分析:
1、創建接口IFly( )
方法:創建抽象方法 fly() 方法
2、創建子類:Plane
方法:實現接口中的方法fly( ),輸出信息“飛機在天上飛”
創建子類:Bird
方法:實現接口中的方法fly( ),輸出信息“小鳥在天空翱翔"”
創建子類:Balloon(氣球)
方法:實現接口中的方法fly( ),輸出信息“氣球飛上天空”
3、創建測試類,分別創建Plane、Bird、Balloon類的對象,調用 fly( ) 方法,輸出效果參考效果圖