打好Java基礎(chǔ),從使用內(nèi)部類(lèi)開(kāi)始!
本文轉(zhuǎn)載自微信公眾號(hào)「小菜良記」,作者蔡不菜丶 。轉(zhuǎn)載本文請(qǐng)聯(lián)系小菜良記公眾號(hào)。
本文主要介紹 Java中內(nèi)部類(lèi)的用法
今天又周五了呀,正在想明天周六有啥安排的時(shí)候,一聲驚訝聲打斷了我
小蔡小菜,你看看這組代碼,好靈活啊
聽(tīng)到領(lǐng)桌小王的驚訝,我扭頭看了下他的屏幕,這不就是內(nèi)部類(lèi)么。用的好當(dāng)然就靈活啦,只是我們平常沒(méi)怎么用。
內(nèi)部類(lèi)用的好真的好靈活呀,我對(duì)這一塊還不是很熟悉,看來(lái)還得多學(xué)習(xí)學(xué)習(xí)!小菜,看你的樣子好像挺了解的,你能給我講講嗎?
看著小王如饑似渴的眼色,我不由有點(diǎn)心虛,內(nèi)心活動(dòng)也是極其復(fù)雜:我平時(shí)也沒(méi)咋用,只是有個(gè)大概的了解,講出來(lái)不就獻(xiàn)丑了,連忙聲道:
好說(shuō)好說(shuō),不過(guò)今天都周五了,也不差這一時(shí)半會(huì),咱們還是想想明天有啥活動(dòng),等下周來(lái)我再給你好好講講!
小王仿佛被我忽悠過(guò)去了,也沒(méi)看到我眼神中的慌亂,答應(yīng)了下來(lái)。
好在有驚無(wú)險(xiǎn),周末還能有啥安排,趕緊把內(nèi)部類(lèi)安排上!
初識(shí)
比起面向?qū)ο缶幊讨衅渌母拍顏?lái),接口和內(nèi)部類(lèi)更深?yuàn)W復(fù)雜,比如 C++ 就沒(méi)有這些。將兩者結(jié)合起來(lái),可以解決 C++ 中用多重繼承所能解決的問(wèn)題,然后,多重繼承在 C++ 中被證明是相當(dāng)難以使用的,相比較而言,Java 的接口和內(nèi)部類(lèi)就容易理解多了!
一、內(nèi)部類(lèi)如何創(chuàng)建
內(nèi)部類(lèi),顧名思義就是類(lèi)中類(lèi),將類(lèi)定義在外圍類(lèi)里面:
- public class Animal {
 - class Monkey{
 - private String name = "monkey";
 - public String getName() {
 - return name;
 - }
 - }
 - class Pig {
 - private String color;
 - Pig(String color) {
 - this.color = color;
 - }
 - String getColor() {
 - return color;
 - }
 - }
 - public void getAnimal(String note) {
 - Monkey monkey = new Monkey();
 - Pig pig = new Pig(note);
 - System.out.println(pig.getColor());
 - }
 - public static void main(String[] args) {
 - Animal animal = new Animal();
 - animal.getAnimal("pink");
 - }
 - }
 - /* OUTPIT:
 - pink
 - */
 
因?yàn)镸onkey和Pig兩個(gè)類(lèi)是定義在 Animal 類(lèi)中,因此使用起這兩個(gè)內(nèi)部類(lèi)跟使用普通類(lèi)沒(méi)什么區(qū)別。下面這組代碼相信小伙伴也不陌生:
- public class Animal {
 - class Monkey{
 - }
 - class Pig {
 - }
 - public Monkey getMonkey() {
 - return new Monkey();
 - }
 - public Pig getPig() {
 - return new Pig();
 - }
 - public static void main(String[] args) {
 - Animal animal = new Animal();
 - Animal.Monkey monkey = animal.getMonkey();
 - Animal.Pig pig = animal.getPig();
 - }
 - }
 
通過(guò)定義方法,來(lái)返回執(zhí)行內(nèi)部類(lèi)的引用。不知道細(xì)心的小伙伴有沒(méi)有注意到內(nèi)部類(lèi)的引用有點(diǎn)奇怪:Animal.Monkey。這也是內(nèi)部類(lèi)的區(qū)別之一,如果要在外部類(lèi)的非靜態(tài)方法之外獲取某個(gè)內(nèi)部類(lèi)的對(duì)象,需要「具體指明這個(gè)對(duì)象的類(lèi)型」:OuterClassName.InnerClassName
二、內(nèi)外相連
內(nèi)部類(lèi)存在于外部類(lèi)里層,因此也具有一定特權(quán):內(nèi)部類(lèi)可以訪(fǎng)問(wèn)外圍對(duì)象的所有成員,不需要任何特殊條件,此外,內(nèi)部類(lèi)還擁有外部類(lèi)的所有元素的訪(fǎng)問(wèn)權(quán)。
- public class OuterArray {
 - private Integer[] ints;
 - private int next = 0;
 - public OuterArray(int size) {
 - ints = new Integer[size];
 - }
 - public void add(int x) {
 - if (next < ints.length) {
 - ints[next++] = x;
 - }
 - }
 - class InnerArray {
 - private int i = 0;
 - public boolean end() {
 - return i == ints.length;
 - }
 - public int current() {
 - return ints[i];
 - }
 - public void next() {
 - if (i < ints.length) {
 - i++;
 - }
 - }
 - }
 - public static void main(String[] args) {
 - OuterArray outerArray = new OuterArray(10);
 - for (int i = 0; i < 10; i++) {
 - outerArray.add(i);
 - }
 - InnerArray innerArray = outerArray.new InnerArray();
 - while (!innerArray.end()) {
 - System.out.print(innerArray.current()+" ");
 - innerArray.next();
 - }
 - }
 - }
 
上組代碼中我們可以看到,InnerArray可以訪(fǎng)問(wèn)到OuterArray中的每一個(gè)屬性,就像自己擁有它們一樣,這帶來(lái)了很大的方便。
三、new 和 this
這兩個(gè)關(guān)鍵字我們肯定都不陌生了,我們平時(shí)用到最多的肯定就是new一個(gè)對(duì)象出來(lái)。
- public class OuterClass {
 - class InnerClass {
 - }
 - public static void main(String[] args) {
 - OuterClass outer = new OuterClass();
 - }
 - }
 
當(dāng)我們需要OuterClass對(duì)象的時(shí)候,我們順手就來(lái)了個(gè)new OuterClass(),但是如果我們需要的是InnerClass對(duì)象,那么又該如何處理呢?答案便是:
- InnerClass inner = outer.new InnerClass();
 
可能覺(jué)得有點(diǎn)奇怪,為什么此處的new需要以O(shè)uterClass對(duì)象引用,這是因?yàn)閮?nèi)部類(lèi)對(duì)象會(huì)暗暗地連接到創(chuàng)建它的外部類(lèi)對(duì)象上,因此必須使用外部類(lèi)的對(duì)象來(lái)創(chuàng)建內(nèi)部類(lèi)對(duì)象。如果你創(chuàng)建的是「嵌套類(lèi)」(靜態(tài)內(nèi)部類(lèi)),那么它就不需要對(duì)外部類(lèi)對(duì)象的引用。
this關(guān)鍵字是用來(lái)生成對(duì)外部類(lèi)對(duì)象的引用,這樣產(chǎn)生的引用自動(dòng)具有正確的類(lèi)型:
- public class OuterClass {
 - class InnerClass {
 - public OuterClass getOuterClass() {
 - return OuterClass.this;
 - }
 - }
 - public static void main(String[] args) {
 - OuterClass outer = new OuterClass();
 - InnerClass inner = outer.new InnerClass();
 - OuterClass outerClass = inner.getOuterClass();
 - }
 - }
 
四、局部?jī)?nèi)部類(lèi)
我們上面看到的內(nèi)部類(lèi)都是定義在外部類(lèi)中,這也是內(nèi)部類(lèi)的典型用處。但是,我們也可以在一個(gè)方法里面或者任意的作用域里面定義內(nèi)部類(lèi)。這種也被稱(chēng)為局部?jī)?nèi)部類(lèi):
- public class OuterClass {
 - public Animal getPig(String color) {
 - class Pig extends Animal {
 - @Override
 - public void getAnimal(String color) {
 - super.getAnimal(color);
 - }
 - }
 - return new Pig();
 - }
 - public static void main(String[] args) {
 - OuterClass outerClass = new OuterClass();
 - Animal pink = outerClass.getPig("pink");
 - }
 - }
 
Pig類(lèi)是getPig()方法的一部分,而不是OuterClass的一部分,所以在getPig()之外不能訪(fǎng)問(wèn)Pig類(lèi)。
五、匿名內(nèi)部類(lèi)
在了解什么是匿名內(nèi)部類(lèi)之前,我們先看一組代碼:
- public class OuterClass {
 - public Animal animal() {
 - return new Animal(){
 - private String name = "monkey";
 - @Override
 - public String toString() {
 - return "animal{" +
 - "name='" + name + '\'' +
 - '}';
 - }
 - };
 - }
 - public static void main(String[] args) {
 - OuterClass outerClass = new OuterClass();
 - System.out.println(outerClass.animal());
 - }
 - }
 - /* OUTPUT:
 - animal{name='monkey'}
 - */
 
animal()這個(gè)方法將返回值的生成與表示這個(gè)返回值的類(lèi)定義結(jié)合在一起。而且這個(gè)類(lèi)是匿名的,它沒(méi)有名字,正常形式應(yīng)該是這樣的:
- public class OuterClass {
 - class Monkey extends Animal {
 - private String name = "monkey";
 - @Override
 - public String toString() {
 - return "animal{" +
 - "name='" + name + '\'' +
 - '}';
 - }
 - }
 - public Animal animal() {
 - return new Monkey();
 - }
 - }
 
匿名類(lèi)再訪(fǎng)工廠:
- public interface Service {
 - void method1();
 - }
 - interface ServiceFactory{
 - Service getService();
 - }
 - class Implementation1 implements Service {
 - private Implementation1(){}
 - @Override
 - public void method1() {
 - System.out.println("Implementation1.method1()");
 - }
 - public static ServiceFactory factory = new ServiceFactory() {
 - @Override
 - public Service getService() {
 - return new Implementation1();
 - }
 - };
 - }
 - class Factories{
 - public static void main(String[] args) {
 - ServiceFactory factory = Implementation1.factory;
 - Service service = factory.getService();
 - service.method1();
 - }
 - }
 
通過(guò)內(nèi)部類(lèi)獲取外部類(lèi)的實(shí)現(xiàn),這樣子Implementation1的構(gòu)造器都可以是private的,并且沒(méi)有任何必要去創(chuàng)建作為工廠的具體類(lèi),這樣所產(chǎn)生的語(yǔ)法也更具有實(shí)際意義,也可以運(yùn)用在單例模式中。
六、嵌套類(lèi)
如果不需要內(nèi)部類(lèi)對(duì)象與外圍類(lèi)之間有聯(lián)系,就可以將內(nèi)部類(lèi)聲明為static,這通常稱(chēng)為嵌套類(lèi)。普通的內(nèi)部類(lèi)對(duì)象隱式地保存了一個(gè)引用,指向創(chuàng)建它的外圍類(lèi)對(duì)象,然而,當(dāng)內(nèi)部類(lèi)是static的時(shí)候,就意味著:
要?jiǎng)?chuàng)建嵌套類(lèi)的對(duì)象,并不需要其外圍類(lèi)的對(duì)象
不能從嵌套類(lèi)的對(duì)象中訪(fǎng)問(wèn)非靜態(tài)的外圍類(lèi)對(duì)象
- public class NestClass {
 - static class InnerNestClass{
 - }
 - public static InnerNestClass get() {
 - return new InnerNestClass();
 - }
 - public static void main(String[] args) {
 - InnerNestClass innerNestClass = get();
 - }
 - }
 
在main()方法中沒(méi)有任何NestClass對(duì)象是必須的,而是使用選取static成員的普通語(yǔ)法來(lái)調(diào)用方法。
接口內(nèi)部類(lèi)
正常情況下,不能在接口內(nèi)部放置任何代碼,但嵌套類(lèi)可以作為接口的一部分。你放到接口中的任何類(lèi)都自動(dòng)是public和static的。因?yàn)轭?lèi)是static的,只是將嵌套類(lèi)置于接口的命名空間內(nèi),這并不違反接口的規(guī)則。你甚至可以在內(nèi)部類(lèi)中實(shí)現(xiàn)其外部類(lèi)的接口:
- public interface ClassInterface {
 - void test();
 - class Test implements ClassInterface {
 - @Override
 - public void test() {
 - System.out.println("接口中的嵌套類(lèi)");
 - }
 - public static void main(String[] args) {
 - new Test().test();
 - }
 - }
 - }
 
如果你想要的創(chuàng)建某些公共代碼,使得它們可以被某個(gè)接口的所有不同實(shí)現(xiàn)所共用,那么使用接口內(nèi)部的嵌套類(lèi)會(huì)顯得很方便,盡管在 Java 8 之后可以使用 default 來(lái)默認(rèn)實(shí)現(xiàn)接口方法。
七、繼承內(nèi)部類(lèi)
內(nèi)部類(lèi)作為一種類(lèi),被繼承當(dāng)然也是被允許的。但是因?yàn)閮?nèi)部類(lèi)的構(gòu)造器必須連接到指向其外圍類(lèi)對(duì)象的引用,所以在繼承內(nèi)部類(lèi)的時(shí)候,那個(gè)指向外圍類(lèi)對(duì)象的引用必須被初始化,而在導(dǎo)出類(lèi)中不再存在可連接的默認(rèn)對(duì)象:
可以看到,通過(guò)這樣繼承是會(huì)報(bào)錯(cuò)的,解決方法便是:
- class ExtendClass {
 - class Inner{}
 - }
 - class WithInner extends ExtendClass.Inner {
 - public WithInner(ExtendClass extendClass) {
 - extendClass.super();
 - }
 - }
 
因此我們需要記住,如果要繼承一個(gè)內(nèi)部類(lèi)的時(shí)候,必須在構(gòu)造器內(nèi)使用外部類(lèi).super(),這樣才能提供了必要的引用,然后程序才能編譯通過(guò)。
八、覆蓋內(nèi)部類(lèi)?
當(dāng)子類(lèi)繼承父類(lèi)時(shí),子類(lèi)可以覆蓋父類(lèi)的方法。那么問(wèn)題來(lái)了,內(nèi)部類(lèi)能否被覆蓋?我們通過(guò)看一組代碼來(lái)找找答案:
- public class Flower {
 - class Bud{
 - public Bud(){
 - System.out.println("Flower.Bud");
 - }
 - }
 - public Flower(){
 - System.out.println("new Flower()");
 - new Bud();
 - test();
 - }
 - public void test() {
 - System.out.println("Flower.test()");
 - }
 - }
 - class Flower2 extends Flower{
 - class Bud{
 - public Bud(){
 - System.out.println("Flower2.Bud");
 - }
 - }
 - public void test() {
 - System.out.println("Flower2.test()");
 - }
 - public static void main(String[] args) {
 - new Flower2();
 - }
 - }
 - /* OUTPUT
 - new Flower()
 - Flower.Bud
 - Flower2.test()
 - */
 
從這個(gè)例子中我們可以看到,當(dāng)繼承了某個(gè)外圍類(lèi)的時(shí)候,內(nèi)部類(lèi)并沒(méi)有發(fā)生什么特別神奇的變化,這兩個(gè)內(nèi)部類(lèi)是完全獨(dú)立的兩個(gè)實(shí)體,各自在自己的命名空間內(nèi)。
九、為什么要使用內(nèi)部類(lèi)?
我們?cè)诨卮疬@個(gè)問(wèn)題之前先明白一件事情:
「每個(gè)內(nèi)部類(lèi)都能獨(dú)立地繼承一個(gè)(接口的)實(shí)現(xiàn),所以無(wú)論外圍類(lèi)是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)于內(nèi)部類(lèi)都沒(méi)有影響」
這句話(huà)很清楚的說(shuō)明了內(nèi)部類(lèi)的能力,如果沒(méi)有內(nèi)部類(lèi)提供的、可以繼承多個(gè)具體的或抽象的類(lèi)的能力,一些設(shè)計(jì)與編程問(wèn)題就很難解決,從這個(gè)角度看,內(nèi)部類(lèi)使得多重繼承的解決方案變得完整。接口解決了部分問(wèn)題,為內(nèi)部類(lèi)有效地實(shí)現(xiàn)了"多重繼承"。
呼~(yú) 終于把內(nèi)部類(lèi)復(fù)習(xí)的差不多了,乍看時(shí)間,今天都周末了呀!看來(lái)周末又沒(méi)安排計(jì)劃咯,不過(guò)這周過(guò)的還挺充實(shí)的,把基礎(chǔ)鞏固了一下,周一的時(shí)候還可以跟小王好好嘮嘮,想到這里,小菜不禁又陷入無(wú)限的幻想!
















 
 
 


 
 
 
 