Java工程師知識(shí)樹(shù) / Java基礎(chǔ)
Java內(nèi)部類
概念:
Java 類中不僅可以定義變量和方法灾挨,還可以定義類邑退,這樣定義在類內(nèi)部的類就被稱為內(nèi)部類。
內(nèi)部類的實(shí)現(xiàn)過(guò)程
通過(guò)反編譯內(nèi)部類的字節(jié)碼劳澄, 分析之后主要是通過(guò)以下幾步做到的:
- 編譯器自動(dòng)為內(nèi)部類添加一個(gè)成員變量地技, 這個(gè)成員變量的類型和外部類的類型相同, 這個(gè)成員變量就是指向外部類對(duì)象的引用秒拔;
- 編譯器自動(dòng)為內(nèi)部類的構(gòu)造方法添加一個(gè)參數(shù)莫矗, 參數(shù)的類型是外部類的類型, 在構(gòu)造方法內(nèi)部使用這個(gè)參數(shù)為1中添加的成員變量賦值溯警;
- 在調(diào)用內(nèi)部類的構(gòu)造函數(shù)初始化內(nèi)部類對(duì)象時(shí)趣苏, 會(huì)默認(rèn)傳入外部類的引用。
public class Outer {
private int m = 1;
public class Inner {
private void test() {
//訪問(wèn)外部類private成員
System.out.println(m);
}
}
}
//編譯梯轻,會(huì)發(fā)現(xiàn)會(huì)在編譯目標(biāo)目錄生成兩個(gè).class文件:Outer.class和Outer$Inner.class。
//將Outer$Inner.class放入IDEA中打開(kāi)尽棕,會(huì)自動(dòng)反編譯喳挑,查看結(jié)果:
public class Outer$Inner {
public Outer$Inner(Outer this$0) {
this.this$0 = this$0;
}
private void test() {
System.out.println(Outer.access$000(this.this$0));
}
}
為什么需要內(nèi)部類
其主要原因有以下幾點(diǎn):
- 內(nèi)部類方法可以訪問(wèn)該類定義所在的作用域的數(shù)據(jù),包括私有的數(shù)據(jù)
- 內(nèi)部類可以對(duì)同一個(gè)包中的其他類隱藏起來(lái),一般的非內(nèi)部類滔悉,是不允許有 private 與protected權(quán)限的伊诵,但內(nèi)部類可以可是實(shí)現(xiàn)多重繼承
- 當(dāng)想要定義一個(gè)回調(diào)函數(shù)且不想編寫(xiě)大量代碼時(shí),使用匿名內(nèi)部類比較便捷回官。
使用內(nèi)部類最吸引人的原因是:
每個(gè)內(nèi)部類都能獨(dú)立地繼承自一個(gè)(接口的)實(shí)現(xiàn)曹宴,所以無(wú)論外圍類是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)于內(nèi)部類都沒(méi)有影響歉提。大家都知道Java只能繼承一個(gè)類笛坦,它的多重繼承在我們沒(méi)有學(xué)習(xí)內(nèi)部類之前是用接口來(lái)實(shí)現(xiàn)的。但使用接口有時(shí)候有很多不方便的地方苔巨。比如我們實(shí)現(xiàn)一個(gè)接口就必須實(shí)現(xiàn)它里面的所有方法版扩。而有了內(nèi)部類就不一樣了。它可以使我們的類繼承多個(gè)具體類或抽象類侄泽。
內(nèi)部類的類型
根據(jù)定義的方式不同礁芦,內(nèi)部類分為靜態(tài)內(nèi)部類,成員內(nèi)部類挣郭,局部?jī)?nèi)部類殊鞭,匿名內(nèi)部類四種。
靜態(tài)內(nèi)部類:
定義在類內(nèi)部的靜態(tài)類非洲,就是靜態(tài)內(nèi)部類未状。 用static修飾的成員內(nèi)部類又稱為嵌套類俯画。
public class Out {
private static int a;
private int b;
public static class Inner {
public void print() {
System.out.println(a);
}
}
}
解釋:
靜態(tài)內(nèi)部類可以訪問(wèn)外部類所有的靜態(tài)變量和方法,即使是 private 的也一樣娩践。
靜態(tài)內(nèi)部類和一般類一致活翩,可以定義靜態(tài)變量、方法翻伺,構(gòu)造方法等材泄。
-
其它類使用靜態(tài)內(nèi)部類需要使用"外部類.靜態(tài)內(nèi)部類"方式,如下所示:
Out.Inner inner = new Out.Inner(); inner.print();
- Java集合類HashMap內(nèi)部就有一個(gè)靜態(tài)內(nèi)部類Entry吨岭。Entry是HashMap存放元素的抽象拉宗, HashMap 內(nèi)部維護(hù) Entry 數(shù)組用了存放元素,但是 Entry 對(duì)使用者是透明的辣辫。像這種和外部類關(guān)系密切的旦事,且不依賴外部類實(shí)例的,都可以使用靜態(tài)內(nèi)部類急灭。
成員內(nèi)部類
定義在類內(nèi)部的非靜態(tài)類姐浮,就是成員內(nèi)部類。
成員內(nèi)部類不能定義靜態(tài)方法和變量(final 修飾的除外)葬馋。這是因?yàn)槌蓡T內(nèi)部類是非靜態(tài)的卖鲤,類初始化的時(shí)候先初始化靜態(tài)成員,如果允許成員內(nèi)部類定義靜態(tài)變量畴嘶,那么成員內(nèi)部類的靜態(tài)變量初始化順序是有歧義的蛋逾。
public class Out {
private static int a;
private int b;
public class Inner {
public void print() {
System.out.println(a);
System.out.println(b);
}
}
}
局部?jī)?nèi)部類(定義在方法中的類)
定義在方法中的類,就是局部類窗悯。
如果一個(gè)類只在某個(gè)方法中使用区匣,則可以考慮使用局部類。
public class Out {
private static int a;
private int b;
public void test(final int c) {
final int d = 1;
class Inner {
public void print() {
System.out.println(c);
}
}
}
}
匿名內(nèi)部類(要繼承一個(gè)父類或者實(shí)現(xiàn)一個(gè)接口蒋院、直接使用 new 來(lái)生成一個(gè)對(duì)象的引用)
匿名內(nèi)部類我們必須要繼承一個(gè)父類或者實(shí)現(xiàn)一個(gè)接口亏钩,當(dāng)然也僅能只繼承一個(gè)父類或者實(shí)現(xiàn)一個(gè)接口。
同時(shí)它也是沒(méi)有class關(guān)鍵字悦污,這是因?yàn)槟涿麅?nèi)部類是直接使用new來(lái)生成一個(gè)對(duì)象的引用铸屉。
public abstract class Bird {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
Bird(String name) {
this.name = name;
}
public abstract int fly();
}
public class Test {
public void test(Bird bird) {
System.out.println(bird.getName() + "能夠飛 " + bird.fly() + "米");
}
public static void main(String[] args) {
Test test = new Test();
test.test(new Bird("大雁") {
public int fly() {
return 10000;
}
});
test.test(new Bird("麻雀") {
public int fly() {
return 1000;
}
});
}
}
怎么正確的使用內(nèi)部類
1.注意內(nèi)存泄露
《Effective Java》第二十四小節(jié)明確提出過(guò)。優(yōu)先使用靜態(tài)內(nèi)部類切端。這是為什么呢彻坛?
由上面的分析我們可以知道,除了嵌套類,其他的內(nèi)部類都隱式包含了外部類對(duì)象昌屉。這便是Java內(nèi)存泄露的源頭钙蒙。看代碼:
定義Outer:
public class Outer{
public List getList(String item) {
return new ArrayList() {
{
add(item);
}
};
}
}
使用Outer:
public class Test{
public static List getOutersList(){
Outer outer=new Outer();
//do something
List list=outer.getList("test");
return list;
}
public static void main(String[] args){
List list=getOutersList();
//do something with list
}
}
相信這樣的代碼一定有同學(xué)寫(xiě)出來(lái)间驮,這涉及到一個(gè)習(xí)慣的問(wèn)題:
不涉及到類成員方法和成員變量的方法躬厌,最好定義為static
我們先研究上面的代碼,最大的問(wèn)題便是帶來(lái)的內(nèi)存泄露:
在使用過(guò)程中竞帽,我們定義Outer對(duì)象完成一系列的動(dòng)作
- 使用outer得到了一個(gè)ArraList對(duì)象
- 將ArrayList作為結(jié)果返回出去扛施。
正常來(lái)說(shuō),在getOutersList方法中屹篓,我們new出來(lái)了兩個(gè)對(duì)象:outer和list疙渣,而在離開(kāi)此方法時(shí),我們只將list對(duì)象的引用傳遞出去,outer的引用隨著方法棧的退出而被銷毀堆巧。按道理來(lái)說(shuō)妄荔,outer對(duì)象此時(shí)應(yīng)該沒(méi)有作用了,也應(yīng)該在下一次內(nèi)存回收中被銷毀谍肤。
然而,事實(shí)并不是這樣啦租。按上面所說(shuō)的,新建的list對(duì)象是默認(rèn)包含對(duì)outer對(duì)象的引用的荒揣,因此只要list不被銷毀篷角,outer對(duì)象將會(huì)一直存在,然而我們并不需要outer對(duì)象系任,這便是內(nèi)存泄露内地。
怎么避免這種情況呢?
很簡(jiǎn)單:不涉及到類成員方法和成員變量的方法赋除,最好定義為static
public class Outer{
public static List getList(String item) {
return new ArrayList() {
{
add(item);
}
};
}
}
這樣定義出來(lái)的類便是嵌套類+繼承,并不包含對(duì)外部類的引用非凌。
2.應(yīng)用于只實(shí)現(xiàn)一個(gè)接口的實(shí)現(xiàn)類
- 優(yōu)雅工廠方法模式
我們可以看到举农,在工廠方法模式中,每個(gè)實(shí)現(xiàn)都會(huì)需要實(shí)現(xiàn)一個(gè)Fractory來(lái)實(shí)現(xiàn)產(chǎn)生對(duì)象的接口敞嗡,而這樣接口其實(shí)和原本的類關(guān)聯(lián)性很大的颁糟,因此我們可以將Fractory定義在具體的類中,作為內(nèi)部類存在
- 簡(jiǎn)單的實(shí)現(xiàn)接口
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("test");
}
}
).start();
}
盡量不要直接使用Thread喉悴,這里只做演示使用
Java 8 的話建議使用lambda代替此類應(yīng)用
- 同時(shí)實(shí)現(xiàn)多個(gè)接口
public class imple{
public static Eat getDogEat(){
return new EatDog();
}
public static Eat getCatEat(){
return new EatCat();
}
private static class EatDog implements Eat {
@Override
public void eat() {
System.out.println("dog eat");
}
}
private static class EatCat implements Eat{
@Override
public void eat() {
System.out.println("cat eat");
}
}
}
3.優(yōu)雅的單例類
public class Imple {
public static Imple getInstance(){
return ImpleHolder.INSTANCE;
}
private static class ImpleHolder{
private static final Imple INSTANCE=new Imple();
}
}
4.反序列化JSON接受的JavaBean
有時(shí)候需要反序列化嵌套JSON
{
"student":{
"name":"",
"age":""
}
}
類似這種棱貌。我們可以直接定義嵌套類進(jìn)行反序列化
public JsonStr{
private Student student;
public static Student{
private String name;
private String age;
//getter & setter
}
//getter & setter
}
但是注意,這里應(yīng)該使用嵌套類箕肃,因?yàn)槲覀儾恍枰屯獠款愡M(jìn)行數(shù)據(jù)交換婚脱。
核心思想:
- 嵌套類能夠訪問(wèn)外部類的構(gòu)造函數(shù)
- 將第一次訪問(wèn)內(nèi)部類放在方法中,這樣只有調(diào)用這個(gè)方法的時(shí)候才會(huì)第一次訪問(wèn)內(nèi)部類,實(shí)現(xiàn)了懶加載
總結(jié)
內(nèi)部類的理解可以按照方法來(lái)理解障贸,但是內(nèi)部類很多特性都必須剝開(kāi)語(yǔ)法糖和明白為什么需要這么做才能完全理解错森,明白內(nèi)部類的所有特性才能更好使用內(nèi)部類,在內(nèi)部類的使用過(guò)程中篮洁,一定記咨:能使用嵌套類就使用嵌套類,如果內(nèi)部類需要和外部類聯(lián)系袁波,才使用內(nèi)部類瓦阐。最后不涉及到類成員方法和成員變量的方法,最好定義為static可以防止內(nèi)部類內(nèi)存泄露篷牌。