Java中的內(nèi)部類超詳細(xì)講解
什么是內(nèi)部類?
內(nèi)部類就是定義在另一個(gè)類內(nèi)部的類。寫在成員位置的,屬于外部類的成員。
想象一下我們的日常生活:一臺電腦包含CPU、內(nèi)存、硬盤等組件。從面向?qū)ο蟮慕嵌瓤?,我們可以將電腦看作一個(gè)類(Computer),而CPU、內(nèi)存等組件則可以看作電腦內(nèi)部的類(CPU, Memory, HardDisk)。這些組件屬于電腦,在電腦內(nèi)部工作,與電腦有緊密的聯(lián)系。
這就像Java中的內(nèi)部類概念:一個(gè)類定義在另一個(gè)類的內(nèi)部,形成一種"類中有類"的結(jié)構(gòu)。
生活中的內(nèi)部類例子
// 電腦類
public class Computer {
private String brand;
private double price;
// CPU內(nèi)部類
class CPU {
private String model;
private int cores;
public void run() {
// CPU可以訪問電腦的品牌信息
System.out.println(brand + "電腦的" + model + "處理器正在運(yùn)行...");
}
}
// 顯卡內(nèi)部類
class GraphicsCard {
private String model;
private int memory;
public void display() {
// 顯卡也可以訪問電腦的信息
System.out.println(brand + "電腦的" + model + "顯卡正在渲染畫面...");
}
}
// 電腦使用內(nèi)部組件
public void start() {
CPU cpu = new CPU();
cpu.model = "Intel i7";
cpu.run();
GraphicsCard gc = new GraphicsCard();
gc.model = "NVIDIA RTX 3080";
gc.display();
}
}
在這個(gè)例子中:
Computer是外部類,代表整臺電腦CPU和GraphicsCard是內(nèi)部類,代表電腦的組件- 內(nèi)部類可以訪問外部類的屬性(
brand) - 外部類可以直接創(chuàng)建和使用內(nèi)部類
這種設(shè)計(jì)反映了現(xiàn)實(shí)世界中的"整體-部分"關(guān)系,內(nèi)部類就像是外部類的一個(gè)組成部分。
public class Outer { // 外部類
private int num = 10;
class Inner { // 內(nèi)部類
// 內(nèi)部類的代碼
}
}
為什么需要內(nèi)部類?
理解內(nèi)部類存在的原因,可以通過更多的生活例子來理解:
生活中的例子
汽車與發(fā)動機(jī)
- 汽車(Car)是外部類,發(fā)動機(jī)(Engine)是內(nèi)部類
- 發(fā)動機(jī)是汽車的核心組件,與汽車緊密相關(guān)
- 發(fā)動機(jī)需要訪問汽車的各種狀態(tài)(油量、溫度等)
- 一般不會將發(fā)動機(jī)單獨(dú)使用,它主要為汽車服務(wù)
手機(jī)與應(yīng)用程序
- 手機(jī)(Phone)是外部類,應(yīng)用程序(App)是內(nèi)部類
- 應(yīng)用程序需要訪問手機(jī)的功能(攝像頭、存儲空間等)
- 應(yīng)用程序主要為手機(jī)提供特定功能
內(nèi)部類的存在意義
提高封裝性
- 內(nèi)部類可以訪問外部類的所有成員,包括私有成員
- 內(nèi)部類本身可以對外隱藏,只有外部類能訪問它
- 實(shí)現(xiàn)高內(nèi)聚、低耦合的設(shè)計(jì)理念
實(shí)現(xiàn)多重繼承
- Java不支持類的多重繼承,但內(nèi)部類可以繼承其他類
- 通過在一個(gè)類中創(chuàng)建多個(gè)內(nèi)部類,每個(gè)內(nèi)部類繼承不同的類,實(shí)現(xiàn)類似多重繼承的效果
更好地實(shí)現(xiàn)回調(diào)機(jī)制
- 匿名內(nèi)部類特別適合用于事件處理和回調(diào)機(jī)制
- 簡化了接口實(shí)現(xiàn)的代碼結(jié)構(gòu)
隱藏實(shí)現(xiàn)細(xì)節(jié)
- 將實(shí)現(xiàn)細(xì)節(jié)隱藏在外部類內(nèi)部,對外只暴露必要的接口
- 如集合框架中的迭代器實(shí)現(xiàn),就是通過內(nèi)部類完成的
組織邏輯上緊密相關(guān)的類
- 當(dāng)一個(gè)類只對另一個(gè)類有用時(shí),將其定義為內(nèi)部類可以更好地組織代碼
- 體現(xiàn)了"has-a"關(guān)系中的"a"是專屬于宿主對象的情況
內(nèi)部類的分類
Java中的內(nèi)部類主要分為四種類型:
- 成員內(nèi)部類:寫在類成員位置的內(nèi)部類
- 靜態(tài)內(nèi)部類:使用static修飾的內(nèi)部類
- 局部內(nèi)部類:定義在方法中的內(nèi)部類
- 匿名內(nèi)部類:沒有名字的內(nèi)部類,隱藏了類的名字
1. 成員內(nèi)部類
什么是成員內(nèi)部類?
成員內(nèi)部類是定義在類成員位置的內(nèi)部類,就像一個(gè)普通的成員變量一樣。
成員內(nèi)部類的特點(diǎn)
- 可以使用外部類的所有成員和方法,即使是private的
- 在JDK 16之前,成員內(nèi)部類中不能定義靜態(tài)成員
- 成員內(nèi)部類可以被訪問修飾符修飾:
private、default、protected、public - 成員內(nèi)部類不能脫離外部類對象獨(dú)立存在,需要先創(chuàng)建外部類對象
如何使用成員內(nèi)部類?
方式一:在外部類中直接創(chuàng)建內(nèi)部類對象并使用
public class Outer {
private int num = 10;
// 成員內(nèi)部類
class Inner {
public void show() {
System.out.println("外部類成員變量num=" + num);
}
}
// 在外部類中使用內(nèi)部類
public void method() {
Inner i = new Inner();
i.show();
}
}
方式二:在其他類中使用成員內(nèi)部類
public class Test {
public static void main(String[] args) {
// 先創(chuàng)建外部類對象
Outer outer = new Outer();
// 再創(chuàng)建內(nèi)部類對象
Outer.Inner inner = outer.new Inner();
// 使用內(nèi)部類方法
inner.show();
}
}
成員內(nèi)部類訪問外部類同名成員
如果內(nèi)部類和外部類有同名的成員變量,可以使用外部類名.this.成員變量來訪問外部類的成員:
public class Outer {
private int num = 10;
class Inner {
private int num = 20;
public void show() {
int num = 30;
System.out.println("局部變量:" + num); // 30
System.out.println("內(nèi)部類成員變量:" + this.num); // 20
System.out.println("外部類成員變量:" + Outer.this.num); // 10
}
}
}
2. 靜態(tài)內(nèi)部類
什么是靜態(tài)內(nèi)部類?
靜態(tài)內(nèi)部類是使用static關(guān)鍵字修飾的內(nèi)部類,是一種特殊的成員內(nèi)部類。
靜態(tài)內(nèi)部類的特點(diǎn)
- 靜態(tài)內(nèi)部類只能訪問外部類的靜態(tài)成員,不能直接訪問非靜態(tài)成員
- 靜態(tài)內(nèi)部類可以包含靜態(tài)成員,也可以包含非靜態(tài)成員
- 創(chuàng)建靜態(tài)內(nèi)部類對象時(shí),不需要依賴外部類對象
靜態(tài)內(nèi)部類的使用
創(chuàng)建靜態(tài)內(nèi)部類對象的格式:
外部類名.內(nèi)部類名 對象名 = new 外部類名.內(nèi)部類名();
示例代碼:
public class Car { // 外部類
private String carName;
private static int carAge = 10;
// 靜態(tài)內(nèi)部類
static class Engine {
private String engineName;
public void show() {
// 可以訪問外部類靜態(tài)成員
System.out.println("汽車年齡:" + carAge);
// 不能訪問外部類非靜態(tài)成員
// System.out.println(carName); // 編譯錯(cuò)誤
}
}
}
// 使用靜態(tài)內(nèi)部類
public class Test {
public static void main(String[] args) {
// 直接創(chuàng)建靜態(tài)內(nèi)部類對象,不需要外部類對象
Car.Engine engine = new Car.Engine();
engine.show();
}
}
靜態(tài)內(nèi)部類的方法調(diào)用
- 調(diào)用靜態(tài)內(nèi)部類的靜態(tài)方法:
外部類名.內(nèi)部類名.方法名(); - 調(diào)用靜態(tài)內(nèi)部類的非靜態(tài)方法:先創(chuàng)建靜態(tài)內(nèi)部類對象,再用對象調(diào)用方法
public class Outer {
// 靜態(tài)內(nèi)部類
static class Inner {
// 靜態(tài)方法
public static void staticMethod() {
System.out.println("靜態(tài)內(nèi)部類的靜態(tài)方法");
}
// 非靜態(tài)方法
public void normalMethod() {
System.out.println("靜態(tài)內(nèi)部類的非靜態(tài)方法");
}
}
}
// 調(diào)用方法
public class Test {
public static void main(String[] args) {
// 調(diào)用靜態(tài)方法
Outer.Inner.staticMethod();
// 調(diào)用非靜態(tài)方法
Outer.Inner inner = new Outer.Inner();
inner.normalMethod();
}
}
3. 局部內(nèi)部類
什么是局部內(nèi)部類?
局部內(nèi)部類是定義在方法中的類,像局部變量一樣,只能在定義它的方法內(nèi)部使用。
局部內(nèi)部類的特點(diǎn)
- 只能在定義它的方法內(nèi)部使用
- 可以訪問外部類的所有成員
- 可以訪問方法中的final或effectively final(Java 8以后)的局部變量
局部內(nèi)部類的使用
public class Outer {
private int outerField = 10;
public void method() {
final int localVar = 20; // final局部變量
int effectivelyFinal = 30; // effectively final變量(不會被修改)
// 局部內(nèi)部類
class LocalInner {
public void show() {
// 訪問外部類成員
System.out.println("外部類成員:" + outerField);
// 訪問方法中的局部final變量
System.out.println("局部變量:" + localVar);
// 訪問effectively final變量
System.out.println("Effectively final變量:" + effectivelyFinal);
}
}
// 創(chuàng)建局部內(nèi)部類對象并調(diào)用方法
LocalInner inner = new LocalInner();
inner.show();
// 注意:這里不能修改effectivelyFinal的值
// effectivelyFinal = 40; // 這樣會導(dǎo)致編譯錯(cuò)誤
}
}
4. 匿名內(nèi)部類
什么是匿名內(nèi)部類?
匿名內(nèi)部類是隱藏了名字的內(nèi)部類,本質(zhì)上是一個(gè)沒有名字的局部內(nèi)部類,它必須繼承一個(gè)類或?qū)崿F(xiàn)一個(gè)接口。
匿名內(nèi)部類的特點(diǎn)
- 沒有顯式的類名
- 在聲明的同時(shí)完成實(shí)例化
- 一般用于實(shí)現(xiàn)接口或繼承類
- 編譯后會生成
外部類名$數(shù)字.class文件
匿名內(nèi)部類的格式
new 類名或接口名() {
// 重寫方法
};
匿名內(nèi)部類的使用場景
當(dāng)接口的實(shí)現(xiàn)類(或父類的子類)只使用一次時(shí),可以使用匿名內(nèi)部類簡化代碼。
匿名內(nèi)部類示例
1. 實(shí)現(xiàn)接口的匿名內(nèi)部類
public class Test {
public static void main(String[] args) {
// 使用匿名內(nèi)部類實(shí)現(xiàn)Runnable接口
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("這是匿名內(nèi)部類實(shí)現(xiàn)的run方法");
}
};
new Thread(r).start();
// 更簡潔的寫法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("直接創(chuàng)建匿名內(nèi)部類");
}
}).start();
}
}
2. 繼承類的匿名內(nèi)部類
abstract class Animal {
public abstract void eat();
}
public class Test {
public static void main(String[] args) {
// 使用匿名內(nèi)部類繼承抽象類
Animal a = new Animal() {
@Override
public void eat() {
System.out.println("狗吃骨頭");
}
};
a.eat();
}
}
3. 帶參數(shù)的匿名內(nèi)部類
interface Calculator {
int calculate(int a, int b);
}
public class Test {
public static void main(String[] args) {
// 使用匿名內(nèi)部類實(shí)現(xiàn)帶參數(shù)的接口
Calculator c = new Calculator() {
@Override
public int calculate(int a, int b) {
return a + b;
}
};
System.out.println("計(jì)算結(jié)果:" + c.calculate(10, 20));
}
}
內(nèi)部類的實(shí)際應(yīng)用
1. 實(shí)現(xiàn)多重繼承
Java不支持類的多繼承,但可以通過內(nèi)部類模擬實(shí)現(xiàn):
class A {
public void methodA() {
System.out.println("來自A類的方法");
}
}
class B {
public void methodB() {
System.out.println("來自B類的方法");
}
}
// 通過內(nèi)部類實(shí)現(xiàn)對A和B功能的同時(shí)使用
class C {
// 繼承A的內(nèi)部類
private class InnerA extends A {
public void methodA() {
super.methodA();
}
}
// 繼承B的內(nèi)部類
private class InnerB extends B {
public void methodB() {
super.methodB();
}
}
// 對外提供方法
public void methodA() {
new InnerA().methodA();
}
public void methodB() {
new InnerB().methodB();
}
}
2. 封裝實(shí)現(xiàn)細(xì)節(jié)
內(nèi)部類可以用來隱藏實(shí)現(xiàn)細(xì)節(jié),如集合類中的迭代器實(shí)現(xiàn):
public class MyArrayList<E> {
private Object[] elements;
private int size;
// 其他代碼...
// 使用內(nèi)部類實(shí)現(xiàn)迭代器
private class MyIterator implements Iterator<E> {
private int cursor;
@Override
public boolean hasNext() {
return cursor < size;
}
@Override
public E next() {
if (cursor >= size) {
throw new NoSuchElementException();
}
return (E) elements[cursor++];
}
}
// 對外提供獲取迭代器的方法
public Iterator<E> iterator() {
return new MyIterator();
}
}
3. 回調(diào)機(jī)制
匿名內(nèi)部類常用于實(shí)現(xiàn)事件監(jiān)聽和回調(diào):
// 在Swing中使用匿名內(nèi)部類處理按鈕點(diǎn)擊
JButton button = new JButton("點(diǎn)擊我");
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("按鈕被點(diǎn)擊了!");
}
});
內(nèi)部類與Lambda表達(dá)式
在Java 8之后,對于只有一個(gè)抽象方法的接口(函數(shù)式接口),可以使用Lambda表達(dá)式代替匿名內(nèi)部類,使代碼更加簡潔:
// 使用匿名內(nèi)部類
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("使用匿名內(nèi)部類");
}
};
// 使用Lambda表達(dá)式
Runnable r2 = () -> System.out.println("使用Lambda表達(dá)式");
// 啟動線程
new Thread(r1).start();
new Thread(r2).start();
// 直接使用Lambda創(chuàng)建線程
new Thread(() -> System.out.println("直接使用Lambda")).start();
內(nèi)部類編譯后的文件命名規(guī)則
編譯含有內(nèi)部類的Java文件后,會生成多個(gè).class文件:
- 成員內(nèi)部類:
外部類名$內(nèi)部類名.class - 靜態(tài)內(nèi)部類:
外部類名$內(nèi)部類名.class - 局部內(nèi)部類:
外部類名$數(shù)字內(nèi)部類名.class - 匿名內(nèi)部類:
外部類名$數(shù)字.class
例如,如果有以下類定義:
public class Outer {
class Inner {}
static class StaticInner {}
public void method() {
class LocalInner {}
new Runnable() {
public void run() {}
};
}
}
編譯后將生成以下文件:
Outer.classOuter$Inner.classOuter$StaticInner.classOuter$1LocalInner.classOuter$1.class(匿名內(nèi)部類)
內(nèi)部類面試常見問題
1. 內(nèi)部類的各種差異比較
| 特性 | 成員內(nèi)部類 | 靜態(tài)內(nèi)部類 | 局部內(nèi)部類 | 匿名內(nèi)部類 |
|---|---|---|---|---|
| 定義位置 | 類成員位置 | 類成員位置 | 方法內(nèi)部 | 方法內(nèi)部 |
| 訪問修飾符 | 可以使用 | 可以使用 | 不能使用 | 不能使用 |
| 是否可靜態(tài) | JDK16前不可 | 可以 | 不可以 | 不可以 |
| 是否需要外部類對象 | 需要 | 不需要 | 需要 | 需要 |
| 是否可以訪問外部類非靜態(tài)成員 | 可以 | 不可以 | 可以 | 可以 |
| 是否可以訪問外部類靜態(tài)成員 | 可以 | 可以 | 可以 | 可以 |
2. 為什么局部內(nèi)部類只能訪問final局部變量?
這是因?yàn)榫植孔兞吭诜椒ńY(jié)束后就會被銷毀,而局部內(nèi)部類對象可能在方法結(jié)束后仍然存在。如果允許內(nèi)部類修改局部變量,當(dāng)變量已經(jīng)被銷毀后,內(nèi)部類卻還引用這個(gè)變量,會導(dǎo)致數(shù)據(jù)不一致。為了解決這個(gè)問題,Java要求局部內(nèi)部類訪問的局部變量必須是final的(Java 8后可以是effectively final)。
內(nèi)部類與外部類的關(guān)系
內(nèi)部類與外部類的關(guān)聯(lián)
內(nèi)部類持有外部類的引用
- 非靜態(tài)內(nèi)部類隱式持有外部類的引用(
Outer.this) - 這也是為什么非靜態(tài)內(nèi)部類能訪問外部類所有成員的原因
- 注意:這可能導(dǎo)致內(nèi)存泄漏,當(dāng)內(nèi)部類對象生命周期比外部類對象長時(shí)
- 非靜態(tài)內(nèi)部類隱式持有外部類的引用(
編譯后的實(shí)現(xiàn)細(xì)節(jié)
- 內(nèi)部類編譯后會生成獨(dú)立的
.class文件 - 非靜態(tài)內(nèi)部類的構(gòu)造函數(shù)會隱式接收外部類的引用
- 訪問外部類私有成員時(shí),編譯器會生成特殊的訪問方法
- 內(nèi)部類編譯后會生成獨(dú)立的
// 編譯前的代碼
public class Outer {
private int x = 10;
class Inner {
void access() {
System.out.println(x); // 訪問外部類的私有成員
}
}
}
// 編譯器處理后的邏輯(簡化表示)
public class Outer {
private int x = 10;
// 為內(nèi)部類提供的訪問方法
static int access$000(Outer outer) {
return outer.x;
}
class Inner {
final Outer this$0; // 持有外部類引用
Inner(Outer outer) {
this$0 = outer; // 保存外部類引用
}
void access() {
System.out.println(Outer.access$000(this$0)); // 通過特殊方法訪問
}
}
}
總結(jié)
內(nèi)部類是Java中一個(gè)強(qiáng)大的特性,它允許我們在一個(gè)類中定義另一個(gè)類,增強(qiáng)了封裝性和代碼的組織結(jié)構(gòu)。主要分為四種類型:
- 成員內(nèi)部類:像普通成員一樣的內(nèi)部類
- 靜態(tài)內(nèi)部類:使用static修飾的內(nèi)部類
- 局部內(nèi)部類:定義在方法中的內(nèi)部類
- 匿名內(nèi)部類:沒有名字的內(nèi)部類
內(nèi)部類存在的主要意義:
- 提高封裝性,隱藏實(shí)現(xiàn)細(xì)節(jié)
- 實(shí)現(xiàn)類似多重繼承的功能
- 更好地組織邏輯上緊密相關(guān)的類
- 簡化事件處理和回調(diào)機(jī)制的實(shí)現(xiàn)
- 提供更靈活的訪問控制
使用內(nèi)部類的建議:
- 當(dāng)一個(gè)類只對另一個(gè)類有用時(shí),考慮使用內(nèi)部類
- 需要訪問外部類私有成員時(shí),使用非靜態(tài)內(nèi)部類
- 不需要訪問外部類實(shí)例成員時(shí),優(yōu)先使用靜態(tài)內(nèi)部類(減少內(nèi)存引用)
- 僅在方法內(nèi)使用的類,定義為局部內(nèi)部類
- 實(shí)現(xiàn)接口或擴(kuò)展類且只使用一次時(shí),考慮使用匿名內(nèi)部類或Lambda表達(dá)式
隨著Java的發(fā)展,內(nèi)部類與Lambda表達(dá)式、方法引用等新特性結(jié)合使用,可以使代碼更加簡潔和易讀。掌握內(nèi)部類是成為Java高級開發(fā)者的必備技能。
到此這篇關(guān)于Java中內(nèi)部類的文章就介紹到這了,更多相關(guān)Java內(nèi)部類內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 實(shí)用注解篇之@Qualifier 深度解析及實(shí)戰(zhàn)案例
在Spring框架中,@Qualifier是一個(gè)常見的注解,主要用于解決依賴注入(DI)時(shí)的歧義性,本文給大家介紹Java 實(shí)用注解篇之@Qualifier 深度解析及實(shí)戰(zhàn)案例,感興趣的朋友一起看看吧2025-06-06
利用Java實(shí)現(xiàn)簡單的猜數(shù)字小游戲
這篇文章主要為大家詳細(xì)介紹了如何利用java語言實(shí)現(xiàn)猜數(shù)字小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
Intellij無法創(chuàng)建java文件解決方案
這篇文章主要介紹了Intellij無法創(chuàng)建java文件解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10
SpringBoot中使用MyBatis-Plus詳細(xì)步驟
MyBatis-Plus是MyBatis的增強(qiáng)工具,簡化了MyBatis的使用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2025-01-01
Java數(shù)組轉(zhuǎn)換為List的四種方式
這篇文章主要介紹了Java開發(fā)技巧數(shù)組轉(zhuǎn)List的四種方式總結(jié),每種方式結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09
java并發(fā)學(xué)習(xí)之BlockingQueue實(shí)現(xiàn)生產(chǎn)者消費(fèi)者詳解
這篇文章主要介紹了java并發(fā)學(xué)習(xí)之BlockingQueue實(shí)現(xiàn)生產(chǎn)者消費(fèi)者詳解,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11

