Java中的內(nèi)部類超詳細(xì)講解
什么是內(nèi)部類?
內(nèi)部類就是定義在另一個(gè)類內(nèi)部的類。寫在成員位置的,屬于外部類的成員。
想象一下我們的日常生活:一臺電腦包含CPU、內(nèi)存、硬盤等組件。從面向?qū)ο蟮慕嵌瓤矗覀兛梢詫㈦娔X看作一個(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ā)動(dòng)機(jī)
- 汽車(Car)是外部類,發(fā)動(dòng)機(jī)(Engine)是內(nèi)部類
- 發(fā)動(dòng)機(jī)是汽車的核心組件,與汽車緊密相關(guān)
- 發(fā)動(dòng)機(jī)需要訪問汽車的各種狀態(tài)(油量、溫度等)
- 一般不會(huì)將發(fā)動(dòng)機(jī)單獨(dú)使用,它主要為汽車服務(wù)
手機(jī)與應(yīng)用程序
- 手機(jī)(Phone)是外部類,應(yīng)用程序(App)是內(nèi)部類
- 應(yīng)用程序需要訪問手機(jī)的功能(攝像頭、存儲(chǔ)空間等)
- 應(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變量(不會(huì)被修改) // 局部內(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; // 這樣會(huì)導(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)接口或繼承類
- 編譯后會(huì)生成
外部類名$數(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á)式"); // 啟動(dòng)線程 new Thread(r1).start(); new Thread(r2).start(); // 直接使用Lambda創(chuàng)建線程 new Thread(() -> System.out.println("直接使用Lambda")).start();
內(nèi)部類編譯后的文件命名規(guī)則
編譯含有內(nèi)部類的Java文件后,會(huì)生成多個(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.class
Outer$Inner.class
Outer$StaticInner.class
Outer$1LocalInner.class
Outer$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é)束后就會(huì)被銷毀,而局部內(nèi)部類對象可能在方法結(jié)束后仍然存在。如果允許內(nèi)部類修改局部變量,當(dāng)變量已經(jīng)被銷毀后,內(nèi)部類卻還引用這個(gè)變量,會(huì)導(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)部類編譯后會(huì)生成獨(dú)立的
.class
文件 - 非靜態(tài)內(nèi)部類的構(gòu)造函數(shù)會(huì)隱式接收外部類的引用
- 訪問外部類私有成員時(shí),編譯器會(huì)生成特殊的訪問方法
- 內(nèi)部類編譯后會(huì)生成獨(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-04Intellij無法創(chuàng)建java文件解決方案
這篇文章主要介紹了Intellij無法創(chuàng)建java文件解決方案,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10SpringBoot中使用MyBatis-Plus詳細(xì)步驟
MyBatis-Plus是MyBatis的增強(qiáng)工具,簡化了MyBatis的使用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),感興趣的朋友跟隨小編一起看看吧2025-01-01Java數(shù)組轉(zhuǎn)換為List的四種方式
這篇文章主要介紹了Java開發(fā)技巧數(shù)組轉(zhuǎn)List的四種方式總結(jié),每種方式結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09java并發(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