溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

在Java中什么是內(nèi)部類

發(fā)布時(shí)間:2021-12-30 14:55:25 來(lái)源:億速云 閱讀:106 作者:小新 欄目:編程語(yǔ)言

小編給大家分享一下在Java中什么是內(nèi)部類,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

前言

在Java中什么是內(nèi)部類

一、什么是內(nèi)部類?

在Java中,可以將一個(gè)類的定義放在另外一個(gè)類的定義內(nèi)部,這就是內(nèi)部類。內(nèi)部類本身就是類的一個(gè)屬性,與其他屬性 定義方式一致。

一個(gè)內(nèi)部類的例子:

public class Outer {   private int radius  = 1 ;  public static int count  = 2 ;        public Outer () {       }     class inner {   public void visitOuter () {     System.out.println( "visit outer private member variable:" + radius);     System.out.println( "visit outer static variable:" + count);    }       } }

二、內(nèi)部類的種類

內(nèi)部類可以分為四種:成員內(nèi)部類、局部?jī)?nèi)部類、匿名內(nèi)部類和靜態(tài)內(nèi)部類。

在Java中什么是內(nèi)部類

靜態(tài)內(nèi)部類

定義在類內(nèi)部的靜態(tài)類,就是靜態(tài)內(nèi)部類。

 public class Outer {         private static int radius = 1;          static class StaticInner {           public void visit() {               System.out.println("visit outer static  variable:" + radius);           }       }  }

靜態(tài)內(nèi)部類可以訪問(wèn)外部類所有的靜態(tài)變量,而不可訪問(wèn)外部類的非靜態(tài)變量;靜態(tài)內(nèi)部類的創(chuàng)建方式, new外部類.靜態(tài)內(nèi)部類(),如下:

 Outer.StaticInner inner = new Outer.StaticInner();  inner.visit();

成員內(nèi)部類

定義在類內(nèi)部,成員位置上的非靜態(tài)類,就是成員內(nèi)部類。

 public class Outer {      private static  int radius = 1;     private int count =2;       class Inner {         public void visit() {             System.out.println("visit outer static  variable:" + radius);             System.out.println("visit outer   variable:" + count);        }    }  }

成員內(nèi)部類可以訪問(wèn)外部類所有的變量和方法,包括靜態(tài)和非靜態(tài),私有和公有。成員內(nèi)部類依賴于外部類的實(shí)例,它的創(chuàng)建方式  外部類實(shí)例.new內(nèi)部類(),如下:

Outer outer = new Outer();  Outer.Inner inner = outer.new Inner();  inner.visit();

局部?jī)?nèi)部類

定義在方法中的內(nèi)部類,就是局部?jī)?nèi)部類。

public class Outer {     private  int out_a = 1;    private static int STATIC_b = 2;     public void testFunctionClass(){        int inner_c =3;        class Inner {           private void fun(){               System.out.println(out_a);               System.out.println(STATIC_b);               System.out.println(inner_c);           }       }       Inner  inner = new Inner();       inner.fun();    }    public static void testStaticFunctionClass(){       int d =3;        class Inner {           private void fun(){               // System.out.println(out_a); 編譯錯(cuò)誤,定義在靜態(tài)方法中的局部類不可以訪問(wèn)外部類的實(shí)例變量               System.out.println(STATIC_b);               System.out.println(d);           }        }       Inner  inner = new Inner();       inner.fun();    }  }

定義在實(shí)例方法中的局部類可以訪問(wèn)外部類的所有變量和方法,定義在靜態(tài)方法中的局部類只能訪問(wèn)外部類的靜態(tài)變量和方法。局部?jī)?nèi)部類的創(chuàng)建方式,在對(duì)應(yīng)方法內(nèi),  new內(nèi)部類(),如下:

public static void testStaticFunctionClass(){    class Inner {    }    Inner inner = new Inner(); }

匿名內(nèi)部類

匿名內(nèi)部類就是沒(méi)有名字的內(nèi)部類,日常開(kāi)發(fā)中使用的比較多。

 public class Outer {          private void test(final int i) {           new Service() {               public void method() {                   for (int j = 0; j < i; j++) {                       System.out.println("匿名內(nèi)部類" );                   }               }          }.method();      }   }   //匿名內(nèi)部類必須繼承或?qū)崿F(xiàn)一個(gè)已有的接口   interface Service{      void method();  }

除了沒(méi)有名字,匿名內(nèi)部類還有以下特點(diǎn):

  • 匿名內(nèi)部類必須繼承一個(gè)抽象類或者實(shí)現(xiàn)一個(gè)接口。

  • 匿名內(nèi)部類不能定義任何靜態(tài)成員和靜態(tài)方法。

  • 當(dāng)所在的方法的形參需要被匿名內(nèi)部類使用時(shí),必須聲明為 final。

  • 匿名內(nèi)部類不能是抽象的,它必須要實(shí)現(xiàn)繼承的類或者實(shí)現(xiàn)的接口的所有抽象方法。

匿名內(nèi)部類創(chuàng)建方式:

new 類/接口{    //匿名內(nèi)部類實(shí)現(xiàn)部分  }

三、內(nèi)部類的優(yōu)點(diǎn)

我們?yōu)槭裁匆褂脙?nèi)部類呢?因?yàn)樗幸韵聝?yōu)點(diǎn):

  • 一個(gè)內(nèi)部類對(duì)象可以訪問(wèn)創(chuàng)建它的外部類對(duì)象的內(nèi)容,包括私有數(shù)據(jù)!

  • 內(nèi)部類不為同一包的其他類所見(jiàn),具有很好的封裝性;

  • 內(nèi)部類有效實(shí)現(xiàn)了“多重繼承”,優(yōu)化 java 單繼承的缺陷。

  • 匿名內(nèi)部類可以很方便的定義回調(diào)。

一個(gè)內(nèi)部類對(duì)象可以訪問(wèn)創(chuàng)建它的外部類對(duì)象的內(nèi)容,包括私有數(shù)據(jù)!

  public class Outer {        private  int radius = 1;         protected void test(){          System.out.println("我是外部類方法");      }        class Inner {         public void visit() {             System.out.println("訪問(wèn)外部類變量" + radius);             test();         }     }  }

我們可以看到,內(nèi)部類Inner是可以訪問(wèn)外部類Outer的私有變量radius或者方法test的。

內(nèi)部類不為同一包的其他類所見(jiàn),具有很好的封裝性

當(dāng)內(nèi)部類使用  private修飾時(shí),這個(gè)類就對(duì)外隱藏了。當(dāng)內(nèi)部類實(shí)現(xiàn)某個(gè)接口,并且進(jìn)行向上轉(zhuǎn)型,對(duì)外部來(lái)說(shuō),接口的實(shí)現(xiàn)已經(jīng)隱藏起來(lái)了,很好體現(xiàn)了封裝性。

  //提供的接口   interface IContent{       String getContents();   }     public class Outer {       //私有內(nèi)部類屏蔽實(shí)現(xiàn)細(xì)節(jié)        private class PContents implements IContent{            @Override            public String getContents() {               System.out.println("獲取內(nèi)部類內(nèi)容");               return "內(nèi)部類內(nèi)容";           }       }        //對(duì)外提供方法      public IContent getIContent() {          return new PContents();      }        public static void main(String[] args) {          Outer outer=new Outer();          IContent a1=outer.getIContent();          a1.getContents();      }  }

我們可以發(fā)現(xiàn),Outer外部類對(duì)外提供方法getIContent,用內(nèi)部類實(shí)現(xiàn)細(xì)節(jié),再用private修飾內(nèi)部類,屏蔽起來(lái),把Java的封裝性表現(xiàn)的淋漓盡致。

內(nèi)部類有效實(shí)現(xiàn)了“多重繼承”,優(yōu)化 java 單繼承的缺陷。

我們知道Java世界中,一個(gè)類只能有一個(gè)直接父類,即以單繼承方式存在。但是內(nèi)部類讓“多繼承”成為可能:

  • 一般來(lái)說(shuō),內(nèi)部類繼承某個(gè)類或者實(shí)現(xiàn)某個(gè)接口,內(nèi)部類的代碼操作創(chuàng)建它的外圍類的對(duì)象。內(nèi)部類提供了某種進(jìn)入其外圍類的窗口。

  • 每個(gè)內(nèi)部類都可以隊(duì)里的繼承自一個(gè)(接口的)實(shí)現(xiàn),所以無(wú)論外圍類是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)于內(nèi)部類沒(méi)有影響

  • 接口解決了部分問(wèn)題,一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,內(nèi)部類允許繼承多個(gè)非接口類型(類或抽象類)。

一份來(lái)自Java編程思想,內(nèi)部類實(shí)現(xiàn)“多繼承”的溫暖如下:

class D {}  abstract class E{}  class Z extends D {  E makeE(){ return new E() {}; }  }  public class MultiImplementation {  static void takesD(D d) {}  static void takesE(E e) {}  public static void main(String[] args){  Z z = new Z();  takesD(z);  takesE(z.makeE());  }  }

代碼中出現(xiàn)了一個(gè)類D,一個(gè)抽象類E。然后,用類Z繼承D,內(nèi)部類構(gòu)造返回E。因此,當(dāng)你不管要的是D還是E,Z都可以應(yīng)付,“多繼承”的特點(diǎn)完美表現(xiàn)出來(lái)。

匿名內(nèi)部類可以很方便的定義回調(diào)。

什么是回調(diào)?假設(shè)有兩個(gè)類A和B,在A中調(diào)用B的一個(gè)方法b,而b在執(zhí)行又調(diào)用了A的方法c,則c就稱為回調(diào)函數(shù)。

在Java中什么是內(nèi)部類

當(dāng)然,回調(diào)函數(shù)也可以是a函數(shù),這就是同步回調(diào),最簡(jiǎn)單的回調(diào)方式?;卣{(diào)應(yīng)用場(chǎng)景挺多的,如android中的事件監(jiān)聽(tīng)器。匿名內(nèi)部類可以很方便的定義回調(diào),看個(gè)例子

  //定義一個(gè)CallBack接口   public interface CallBack {       void execute();   }      public class TimeTools {          /**        * 測(cè)試函數(shù)調(diào)用時(shí)長(zhǎng),通過(guò)定義CallBack接口的execute方法       * @param callBack       */      public   void  testTime(CallBack callBack) {          long  beginTime = System.currentTimeMillis(); //記錄起始時(shí)間          callBack.execute(); ///進(jìn)行回調(diào)操作          long  endTime = System.currentTimeMillis(); //記錄結(jié)束時(shí)間          System.out.println("[use time]:"  + (endTime - beginTime)); //打印使用時(shí)間      }        public   static   void  main(String[] args) {          TimeTools tool = new  TimeTools();          tool.testTime(new  CallBack(){              //匿名內(nèi)部類,定義execute方法              public   void  execute(){                  TestTimeObject testTimeObject = new TestTimeObject();                  testTimeObject.testMethod();              }          });      }  }

在調(diào)用testTime()測(cè)時(shí)間的時(shí)候,用匿名內(nèi)部類實(shí)現(xiàn)一個(gè)方法execute(),在該方法內(nèi)搞事情(執(zhí)行目標(biāo)函數(shù)),執(zhí)行完后,又回到testTime方法,很好了實(shí)現(xiàn)測(cè)試函數(shù)調(diào)用時(shí)長(zhǎng)的功能。顯然,匿名內(nèi)部類讓回調(diào)實(shí)現(xiàn)變得簡(jiǎn)單。

四、內(nèi)部類的底層

內(nèi)部類標(biāo)志符

每個(gè)內(nèi)部類都會(huì)產(chǎn)生一個(gè).class文件,其中包含了如何創(chuàng)建該類型的對(duì)象的全部信息。內(nèi)部類也必須生成一個(gè).class文件以包含它們的Class對(duì)象信息。內(nèi)部類文件的命名有嚴(yán)格規(guī)則:外圍類的名字+$+內(nèi)部類的名字。

一個(gè)簡(jiǎn)單例子:

public class Outer {      class Inner{      }  }

javac Outer.java編譯完成后, 生成的class文件如下: 

在Java中什么是內(nèi)部類

如果內(nèi)部類是匿名的,編譯器會(huì)簡(jiǎn)單地產(chǎn)生一個(gè)數(shù)字作為其標(biāo)識(shí)符。如果內(nèi)部類是嵌套在別的內(nèi)部類之中(靜態(tài)內(nèi)部類),只需直接將它們的名字加在其外圍類標(biāo)志符與“$”的后面。

為什么內(nèi)部類可以訪問(wèn)外部類的成員,包括私有數(shù)據(jù)?

由上一小節(jié),我們知道內(nèi)部類可以訪問(wèn)外部類的成員,包括私有數(shù)據(jù)。那么它是怎么做到的呢?接下來(lái)揭曉答案。

先看這個(gè)簡(jiǎn)單地例子:

  public class Outer {          private int i = 0;         class Inner{           void method(){               System.out.println(i);           }       }  }

一個(gè)外部類Outer,一個(gè)外部類私有屬性i,一個(gè)內(nèi)部類Inner,一個(gè)內(nèi)部類方法method。內(nèi)部類方法訪問(wèn)了外部類屬性i。

先編譯,javac Outer.java,生成.class文件,如下:

在Java中什么是內(nèi)部類

用命令 javap-classpath.-vOuter$Inner,反編譯Outter$Inner.class文件得到以下信息:

在Java中什么是內(nèi)部類

我們可以看到這一行,它是一個(gè)指向外部類對(duì)象的指針:

final innerclass.Outer this$0;

雖然編譯器在創(chuàng)建內(nèi)部類時(shí)為它加上了一個(gè)指向外部類的引用, 但是這個(gè)引用是怎樣賦值的呢?編譯器會(huì)為內(nèi)部類的構(gòu)造方法添加一個(gè)參數(shù),進(jìn)行初始化,  參數(shù)的類型就是外部類的類型,如下:

innerclass.Outer$Inner(innerclass.Outer);

成員內(nèi)部類中的Outter this&0 指針便指向了外部類對(duì)象,因此可以在成員內(nèi)部類中隨意訪問(wèn)外部類的成員。

局部?jī)?nèi)部類和匿名內(nèi)部類訪問(wèn)局部變量的時(shí)候,為什么變量必須要加上final?

局部?jī)?nèi)部類和匿名內(nèi)部類訪問(wèn)局部變量的時(shí)候,為什么變量必須要加上final呢?它內(nèi)部原理是什么呢?

先看這段代碼:

  public class Outer {          void outMethod(){           final int a =10;           class Inner {               void innerMethod(){                   System.out.println(a);               }             }      }  }

反編譯(Outer$1Inner)得到以下信息:

在Java中什么是內(nèi)部類

我們?cè)趦?nèi)部類innerMethod方法中,可以看到以下這條指令:

3:bipush   10
  • 它表示將常量10壓入棧中,表示使用的是一個(gè)本地局部變量。

  • 其實(shí),如果一個(gè)變量的值在編譯期間可以確定(demo中確定是10了),則編譯器會(huì)默認(rèn)在匿名內(nèi)部類(局部?jī)?nèi)部類)的常量池中添加一個(gè)內(nèi)容相等的字面量或直接將相應(yīng)的字節(jié)碼嵌入到執(zhí)行字節(jié)碼中。

  • 醬紫可以確保局部?jī)?nèi)部類使用的變量與外層的局部變量區(qū)分開(kāi),它們只是值相等而已。

以上例子,為什么要加final呢?是因?yàn)樯芷诓灰恢拢? 局部變量直接存儲(chǔ)在棧中,當(dāng)方法執(zhí)行結(jié)束后,非final的局部變量就被銷毀。而局部?jī)?nèi)部類對(duì)局部變量的引用依然存在,如果局部?jī)?nèi)部類要調(diào)用局部變量時(shí),就會(huì)出錯(cuò)。加了final,可以確保局部?jī)?nèi)部類使用的變量與外層的局部變量區(qū)分開(kāi),解決了這個(gè)問(wèn)題。

我們?cè)賮?lái)看一段代碼,其實(shí)就是把變量a挪到傳參方式進(jìn)來(lái)。

  public class Outer {          void outMethod(final int a){           class Inner {               void innerMethod(){                   System.out.println(a);               }           }       }  }

反編譯可得:

在Java中什么是內(nèi)部類

我們看到匿名內(nèi)部類Outer$1Inner的構(gòu)造器含有兩個(gè)參數(shù),一個(gè)是指向外部類對(duì)象的引用,一個(gè)是int型變量,很顯然,這里是將變量innerMethod方法中的形參a以參數(shù)的形式傳進(jìn)來(lái)對(duì)匿名內(nèi)部類中的拷貝(變量a的拷貝)進(jìn)行賦值初始化。

那么,新的問(wèn)題又來(lái)了,既然在innerMethod方法中訪問(wèn)的變量a和outMethod方法中的變量a不是同一個(gè)變量,當(dāng)在innerMethod方法中修改a會(huì)怎樣?那就會(huì)造成數(shù)據(jù)不一致的問(wèn)題了。

怎么解決呢?使用final修飾符,final修飾的引用類型變量,不允許指向新的對(duì)象,這就解決數(shù)據(jù)不一致問(wèn)題。注意: 在Java8  中,被局部?jī)?nèi)部類引用的局部變量,默認(rèn)添加final,所以不需要添加final關(guān)鍵詞。

五、內(nèi)部類的應(yīng)用場(chǎng)景。

一般我們?cè)谀男﹫?chǎng)景下使用內(nèi)部類呢?

場(chǎng)景之一:一些多算法場(chǎng)合

一些算法多的場(chǎng)合,也可以借助內(nèi)部類,如:

  Arrays.sort(emps,new Comparator(){     Public int compare(Object o1,Object o2)     {      return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears();     }   });

場(chǎng)景二:解決一些非面向?qū)ο蟮恼Z(yǔ)句塊。

如果一些語(yǔ)句塊,包括if&hellip;else語(yǔ)句,case語(yǔ)句等等比較多,不好維護(hù)擴(kuò)展,那么就可以借助內(nèi)部類+設(shè)計(jì)模式解決。

場(chǎng)景之三:適當(dāng)使用內(nèi)部類,使得代碼更加靈活和富有擴(kuò)展性。

適當(dāng)?shù)氖褂脙?nèi)部類,可以使得你的代碼更加靈活和富有擴(kuò)展性。如JDK的lamda表達(dá)式,用內(nèi)部類非常多,代碼優(yōu)雅很多。如下:

// JDK8 Lambda表達(dá)式寫法  new Thread(() -> System.out.println("Thread run()")).start();

場(chǎng)景四:當(dāng)某個(gè)類除了它的外部類,不再被其他的類使用時(shí)。

如果一個(gè)類,不能為其他的類使用;或者出于某種原因,不能被其他類引用。那我們就可以考慮把它實(shí)現(xiàn)為內(nèi)部類。數(shù)據(jù)庫(kù)連接池就是這樣一個(gè)典型例子。

六、內(nèi)部類常見(jiàn)面試題

最后,我們來(lái)看一道經(jīng)典內(nèi)部類面試題吧。

  public class Outer {       private int age = 12;          class Inner {           private int age = 13;           public void print() {               int age = 14;               System.out.println("局部變量:" + age);               System.out.println("內(nèi)部類變量:" + this.age);              System.out.println("外部類變量:" + Outer.this.age);          }      }       public static void main(String[] args) {          Outer.Inner in = new Outer().new Inner();          in.print();      }    }

運(yùn)行結(jié)果:

在Java中什么是內(nèi)部類

以上是“在Java中什么是內(nèi)部類”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI