各種內部類和枚舉類的使用 + 面試題
內部類不僅經常出現在各種面試題中,還會在 Java 源碼中頻頻出現,因此只有搞明白了 Java 內部類,才能搞定面試和看懂各種 Java 源碼。
內部類
Java 內部類,分為以下四種:
- 成員內部類
- 靜態成員內部類
- 局部內部類
- 匿名內部類
下面分別來看這些內部類的使用。
成員內部類
定義
在一個類中定義了另一個類,則將定義在類中的那個類稱之為成員內部類。成員內部類也是最普通的內部類。
使用
成員內部類的使用示例如下:
class Outer {
public Outer() {
System.out.println("Outer Class.");
}
class Inner {
public void sayHi() {
System.out.println("Hi, Inner.");
}
}
}
其中 Inner 類則為成員內部類。
而成員內部類的創建和使用,請參考以下完整的示例代碼:
class InnerTest {
public static void main(String[] args) {
Outer out = new Outer();
// 創建成員內部類
Outer.Inner inner = out.new Inner();
inner.sayHi();
}
}
class Outer {
public Outer() {
System.out.println("Outer Class.");
}
class Inner {
public void sayHi() {
System.out.println("Hi, Inner.");
}
}
}
成員內部類的創建
語法:
Outer.Inner inner = new Outer().new Inner();
內部類訪問外部類
語法:
Outer.this.xxx
代碼示例:
class Outer {
private String name = "OuterClass";
public void sayHi() {
System.out.println("Hi, Outer.");
}
class Inner {
public void sayHi() {
// 內部類訪問外部類
Outer.this.sayHi();
System.out.println(Outer.this.name);
System.out.println("Hi, Inner.");
}
}
}
class InnerTest {
public static void main(String[] args) {
Outer.Inner inner = new Outer().new Inner();
inner.sayHi();
}
}
外部類訪問內部類
語法:
new Inner().xxx
代碼示例:
class Outer {
public void sayHi() {
System.out.println(new Inner().name);
System.out.println("Hi, Outer.");
}
private class Inner {
String name = "InnerClass";
public void sayHi() {
System.out.println("Hi, Inner.");
}
}
}
class InnerTest {
public static void main(String[] args) {
new Outer().sayHi();
}
}
小結
- 成員內部類可直接訪問外部類(使用:外部類.this.xxx);
- 外部成員類要訪問內部類,必須先建立成員內部類對象;
- 成員內部類可使用任意作用域修飾(public、protected、默認、private);
- 成員內部類可訪問外部類任何作用域修飾的屬性和方法;
- 外部類建立成員內部類對象之后,可以訪問任何作用域修飾的內部類屬性和方法。
靜態成員內部類
定義
在一個類中定義了另一個 static 類,則將定義在類中的那個 static 類稱之為靜態成員內部類。
靜態成員內部類也就是給內部成員類加上 static 修飾符。
使用
靜態成員內部類的使用示例如下:
class OuterClass {
public OuterClass() {
System.out.println("OuterClass Init.");
}
protected static class InnerClass {
public void sayHi() {
System.out.println("Hi, InnerClass.");
}
}
}
class InnerClassTest {
public static void main(String[] args) {
OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
innerClass.sayHi();
}
}
與內部成員類的創建方式 new Outer().new Inner() 不同,靜態成員內部類可使用 new OuterClass.InnerClass() 的方式進行創建。
注意:不能從靜態成員內部類中訪問非靜態外部類對象。
局部內部類
定義
一個類定義在另一個類的局部(方法或者任意作用域),這個類就稱之為局部內部類。
使用
局部內部類的使用示例如下:
class OutClass {
public void sayHi() {
class InnerClass {
InnerClass(String name) {
System.out.println("InnerClass:" + name);
}
}
System.out.println(new InnerClass("Three"));
System.out.println("Hi, OutClass");
}
}
class OutTest {
public static void main(String[] args) {
new OutClass().sayHi();
}
}
局部內部類特點
- 局部內部類不能使用任何訪問修飾符;
- 局部類如果在方法中,可以直接使用方法中的變量,不需要通過 OutClass.this.xxx 的方式獲得。
匿名內部類
定義
沒有名字的內部類就叫做匿名內部類。
使用
匿名內部類的使用示例如下:
interface AnonymityOuter {
void hi();
}
class AnonymityTest {
public static void main(String[] args) {
AnonymityOuter anonymityOuter = new AnonymityOuter() {
@Override public void hi() {
System.out.println("Hi, AnonymityOuter.");
}
};
anonymityOuter.hi();
}
}
其中,new AnonymityOuter() 之后的 {...} 大括號包含的部分就為匿名內部類。
匿名內部類特點
- 匿名內部類必須繼承一個父類或者實現一個接口
- 匿名內部類不能定義任何靜態成員和方法
- 匿名內部類中的方法不能是抽象的
枚舉類
枚舉類是 JDK 1.5 引入的新特性,使用關鍵字“enum”聲明。枚舉功能雖小,卻非常實用,大大方便了程序的開發者。
枚舉類的使用
請參考以下代碼:
enum ColorEnum {
RED,
BLUE,
YELLOW,
GREEN
}
class EnumTest {
public static void main(String[] args) {
ColorEnum color = ColorEnum.GREEN;
switch (color) {
case RED:
System.out.println("Red");
break;
case BLUE:
System.out.println("Blue");
break;
case YELLOW:
System.out.println("Yellow");
break;
case GREEN:
System.out.println("Green");
break;
default:
break;
}
}
}
擴展枚舉類
我們可以自定義一些枚舉類方法,擴展枚舉類的使用,請參考以下代碼:
enum ColorsEnum {
RED("紅色", 1),
BLUE("藍色", 2),
YELLOW("黃色", 3),
GREEN("綠色", 4);
ColorsEnum(String name, int index) {
this.name = name;
this.index = index;
}
private String name;
private int index;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
class EnumTest {
public static void main(String[] args) {
System.out.println(ColorsEnum.RED.getName());
System.out.println(ColorsEnum.RED.getIndex());
}
}
執行以上代碼返回的結果:
紅色
1
相關面試題
1.Java 中的內部類有哪些?
答:內部類包含以下 4 種:
- 靜態內部類:static class StaticInnerClass{};
- 成員內部類:private class InstanceInnerClass{};
- 局部內部類:定義在方法或者表達式內部;
- 匿名內部類:(new Thread(){}).start()。
2.以下關於匿名內部類說法錯誤的是?
A:匿名內部類必須繼承一個父類或者實現一個接口
B:匿名內部類中的方法不能是抽象的
C:匿名內部類可以實現接口的部分抽象方法
D:匿名內部類不能定義任何靜態成員和方法
答:C
題目解析:匿名內部類規定必須實現接口的所有抽象方法,否則程序會報錯,如下圖所示。

3.以下枚舉類比較“==”和“equals”結果一致嗎?為什么?
class EnumTest {
public static void main(String[] args) {
ColorEnum redColor = ColorEnum.RED;
ColorEnum redColor2 = ColorEnum.RED;
System.out.println(redColor == redColor2);
System.out.println(redColor.equals(redColor2));
}
}
enum ColorEnum {
RED,
BLUE
}
答:結果一致,都是 true。
題目分析:因為枚舉類重寫了 equals 方法,equals 方法里直接使用的 == 比較的,而枚舉類不能通過 new 進行創建,使用 ColorEnum.RED 得到的對象,其實使用的是對象的引用地址,所以 == 比較的結果一定是 true。equals 被重寫的源碼如下圖:
答:使用靜態內部類的好處如下:
作用域不會擴散到包外;
- 可以通過“外部類.內部類”的方式直接訪問;
- 內部類可以訪問外部類中的所有靜態屬性和方法。
5.以下代碼執行的結果是?
class OuterClass {
String name = "OuterClass";
protected static class InnerClass {
String name = "InnerClass";
public void sayHi() {
System.out.println(OuterClass.this.name);
}
}
}
class InnerClassTest {
public static void main(String[] args) {
OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
innerClass.sayHi();
}
}
答:程序報錯。
題目解析:在靜態成員內部類中不能直接訪問非靜態外部類,因此程序會報錯。
6.成員內部類和局部內部類有什么區別?
答:內部成員類和局部內部類的區別如下。
- 內部成員類可以使用任意訪問修飾符,局部內部類不能使用任何訪問修飾符;
- 局部內部類是聲明在外部類的方法或其他作用域范圍內的,內部類是直接聲明在外部類之中的,與方法和屬性平級。
7.為什么要使用內部類?內部類的使用場景有哪些?
答:使用內部類的好處有以下兩個。
- 可以作為多繼承的一種實現方式,最早內部類的實現就是平衡 Java 語言中沒有多繼承的一種方式;
- 方便將存在一定邏輯關系的類組織在一起,又可以對外界隱藏。
內部類可以作為多繼承的一種實現方式進行使用,因為每個內部類都能獨立的繼承一個類或接口,所以整個類就可以實現多繼承。
8.以下代碼執行的結果是?
class Outer {
public int num = 1;
class Inner {
public int num = 2;
public void show() {
int num = 3;
System.out.println(num);
System.out.println(this.num);
System.out.println(Outer.this.num);
}
}
}
class InnerTest {
public static void main(String[] args) {
new Outer().new Inner().show();
}
}
答:輸出內容如下。
3
2
1
9.枚舉有哪些應用場景?
答:枚舉類的主要應用場景如下:
① 枚舉類可作為高級的常量類
示例代碼如下:
public enum Color {
RED("\#FF0000", "255,0,0"),
GREEN("\#00FFFF", "0,255,255"),
YELLOW("\#FFFF00", "255,255,0");
String hex, rgb;
Color(String hex, String rgb) {
this.hex = hex;
this.rgb = rgb;
}
}
② 枚舉類可方便的用於 switch 判斷
示例代碼如下:
switch(color)
{
case RED:
System.out.println("紅燈停");
break;
case GREEN:
System.out.println("綠燈行");
break;
case YELLOW:
System.out.println("看情況");
break;
default:
System.out.println("燈壞了");
}
10.枚舉類在 JVM 中是如何實現的?
答:枚舉類在 JVM(Java 虛擬機) 中其實是通過普通的 static final 形式實現的。
題目解析:我們使用 javap 命令來分析枚舉類最終編譯的結果,查看編譯后的結果,就找到了枚舉類在 JVM 中的具體實現了。
首先定義一個枚舉類,代碼如下:
enum DBEnum {
ORACLE,
DB2,
MYSQL,
SQLSERVER
}
再使用命令 javac DBEnum.java 編譯 .class 文件,然后再使用命令 javap DBEnum.class,我們看到最終執行的結果如下:
Compiled from "EnumTest.java"
final class DBEnum extends java.lang.Enum\<DBEnum\> {
public static final DBEnum ORACLE;
public static final DBEnum DB2;
public static final DBEnum MYSQL;
public static final DBEnum SQLSERVER;
public static DBEnum[] values();
public static DBEnum valueOf(java.lang.String);
static {};
}
由此可以斷定,枚舉類在 JVM 中的實現也是通過普通的 static final 實現的。
11.枚舉類可以被繼承嗎?
答:不能被繼承,因為枚舉類編譯后的實際代碼是 final class 的形式,類被 final 修飾了自然不能被繼承。
12.枚舉類是否是線程安全的?
答:枚舉類是線程安全的,因為枚舉類被編譯后是 final class 的形式存在的,所以枚舉類是線程安全的。
13.枚舉是否可以被序列化?
答:枚舉是可以被序列化的,Oracle 官方對此給出了說明,內容如下:
Enum constants are serialized differently than ordinary serializable or externalizable objects. The serialized form of an enum constant consists solely of its name; field values of the constant are not transmitted. To serialize an enum constant, ObjectOutputStream writes the string returned by the constant's name method. Like other serializable or externalizable objects, enum constants can function as the targets of back references appearing subsequently in the serialization stream. The process by which enum constants are serialized cannot be customized; any class-specific writeObject and writeReplace methods defined by enum types are ignored during serialization. Similarly, any serialPersistentFields or serialVersionUID field declarations are also ignored--all enum types have a fixed serialVersionUID of 0L
原文地址:https://docs.oracle.com/javase/8/docs/api/java/io/ObjectOutputStream.html
大致的意思是說:枚舉的序列化和其他普通類的序列化不同,枚舉序列化的時候,只是將枚舉對象的 name 屬性輸出到結果中,反序列化的時候則是通過 java.lang.Enum 的 valueOf 方法根據名字查找枚舉對象。
總結
通過本文我們系統地學習了 Java 的各種內部類:靜態內部類、成員內部類、局部內部類、匿名內部類,知道了它們特點和區別,並學習了枚舉類了使用,知道了枚舉類在編譯之后,其實還是普通的最終類(final class)。
歡迎關注我的公眾號,回復關鍵字“Java” ,將會有大禮相送!!! 祝各位面試成功!!!

%97%E5%8F%B7%E4%BA%8C%E7%BB%B4%E7%A0%81.png)
