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