自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Java程序員必備基礎(chǔ):內(nèi)部類解析

開發(fā) 后端
整理了一下內(nèi)部類的相關(guān)知識(shí),算是比較全,比較基礎(chǔ)的,希望大家一起學(xué)習(xí)進(jìn)步。

 [[312577]]

前言

整理了一下內(nèi)部類的相關(guān)知識(shí),算是比較全,比較基礎(chǔ)的,希望大家一起學(xué)習(xí)進(jìn)步。

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

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

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

  1. public class Outer 
  2.  
  3.  private int radius  
  4.  public static int count    
  5.      
  6. public Outer ()      
  7.     
  8. class inner 
  9.   public void visitOuter () 
  10.   System.out.println"visit outer private member variable:" radius); 
  11.   System.out.println"visit outer static variable:" count); 
  12.  
  13.       

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

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

 

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

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

  1.  public class Outer { 
  2.    
  3.      private static int radius = 1; 
  4.    
  5.       static class StaticInner { 
  6.           public void visit() { 
  7.               System.out.println("visit outer static  variable:" + radius); 
  8.           } 
  9.       } 
  10.  } 

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

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

成員內(nèi)部類

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

  1.  public class Outer { 
  2.  
  3.     private static  int radius = 1; 
  4.     private int count =2; 
  5.  
  6.      class Inner { 
  7.         public void visit() { 
  8.             System.out.println("visit outer static  variable:" + radius); 
  9.             System.out.println("visit outer   variable:" + count); 
  10.        } 
  11.    } 
  12.  } 

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

  1. Outer outer = new Outer(); 
  2.  
  3. Outer.Inner inner = outer.new Inner(); 
  4.  
  5. inner.visit(); 

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

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

  1. public class Outer { 
  2.  
  3.    private  int out_a = 1; 
  4.    private static int STATIC_b = 2; 
  5.  
  6.    public void testFunctionClass(){ 
  7.        int inner_c =3; 
  8.        class Inner { 
  9.           private void fun(){ 
  10.               System.out.println(out_a); 
  11.               System.out.println(STATIC_b); 
  12.               System.out.println(inner_c); 
  13.           } 
  14.       } 
  15.       Inner  inner = new Inner(); 
  16.       inner.fun(); 
  17.    } 
  18.    public static void testStaticFunctionClass(){ 
  19.       int d =3; 
  20.        class Inner { 
  21.           private void fun(){ 
  22.               // System.out.println(out_a); 編譯錯(cuò)誤,定義在靜態(tài)方法中的局部類不可以訪問外部類的實(shí)例變量 
  23.               System.out.println(STATIC_b); 
  24.               System.out.println(d); 
  25.           } 
  26.        } 
  27.       Inner  inner = new Inner(); 
  28.       inner.fun(); 
  29.    } 
  30.  } 

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

  1. public static void testStaticFunctionClass(){ 
  2. class Inner { 
  3. Inner inner = new Inner(); 

匿名內(nèi)部類

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

  1.  public class Outer { 
  2.    
  3.       private void test(final int i) { 
  4.           new Service() { 
  5.               public void method() { 
  6.                   for (int j = 0; j < i; j++) { 
  7.                       System.out.println("匿名內(nèi)部類" ); 
  8.                   } 
  9.               } 
  10.          }.method(); 
  11.      } 
  12.   } 
  13.   //匿名內(nèi)部類必須繼承或?qū)崿F(xiàn)一個(gè)已有的接口 
  14.   interface Service{ 
  15.      void method(); 
  16.  } 

除了沒有名字,匿名內(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)建方式: 

  1. new 類/接口{ 
  2.  
  3. //匿名內(nèi)部類實(shí)現(xiàn)部分 
  4.  

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

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

  • 一個(gè)內(nèi)部類對(duì)象可以訪問創(chuàng)建它的外部類對(duì)象的內(nèi)容,包括私有數(shù)據(jù)!
  • 內(nèi)部類不為同一包的其他類所見,具有很好的封裝性;
  • 內(nèi)部類有效實(shí)現(xiàn)了“多重繼承”,優(yōu)化 java 單繼承的缺陷。
  • 匿名內(nèi)部類可以很方便的定義回調(diào)。

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

  1.   public class Outer { 
  2.   
  3.      private  int radius = 1; 
  4.    
  5.      protected void test(){ 
  6.          System.out.println("我是外部類方法"); 
  7.      } 
  8.   
  9.      class Inner { 
  10.         public void visit() { 
  11.             System.out.println("訪問外部類變量" + radius); 
  12.            test(); 
  13.         } 
  14.     } 
  15.  } 

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

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

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

  1.   //提供的接口 
  2.   interface IContent{ 
  3.       String getContents(); 
  4.   } 
  5.   
  6.   public class Outer { 
  7.       //私有內(nèi)部類屏蔽實(shí)現(xiàn)細(xì)節(jié) 
  8.        private class PContents implements IContent{ 
  9.            @Override 
  10.          public String getContents() { 
  11.               System.out.println("獲取內(nèi)部類內(nèi)容"); 
  12.               return "內(nèi)部類內(nèi)容"
  13.           } 
  14.       } 
  15.   
  16.      //對(duì)外提供方法 
  17.      public IContent getIContent() { 
  18.          return new PContents(); 
  19.      } 
  20.   
  21.      public static void main(String[] args) { 
  22.          Outer outer=new Outer(); 
  23.          IContent a1=outer.getIContent(); 
  24.          a1.getContents(); 
  25.      } 
  26.  } 

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

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

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

  • 一般來說,內(nèi)部類繼承某個(gè)類或者實(shí)現(xiàn)某個(gè)接口,內(nèi)部類的代碼操作創(chuàng)建它的外圍類的對(duì)象。內(nèi)部類提供了某種進(jìn)入其外圍類的窗口。
  • 每個(gè)內(nèi)部類都可以隊(duì)里的繼承自一個(gè)(接口的)實(shí)現(xiàn),所以無論外圍類是否已經(jīng)繼承了某個(gè)(接口的)實(shí)現(xiàn),對(duì)于內(nèi)部類沒有影響
  • 接口解決了部分問題,一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,內(nèi)部類允許繼承多個(gè)非接口類型(類或抽象類)。

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

  1. class D {} 
  2.  
  3. abstract class E{} 
  4.  
  5. class Z extends D { 
  6.  
  7. E makeE(){ return new E() {}; } 
  8.  
  9.  
  10. public class MultiImplementation { 
  11.  
  12. static void takesD(D d) {} 
  13.  
  14. static void takesE(E e) {} 
  15.  
  16. public static void main(String[] args){ 
  17.  
  18. Z z = new Z(); 
  19.  
  20. takesD(z); 
  21.  
  22. takesE(z.makeE()); 
  23.  
  24.  

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

匿名內(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ù)。 

 

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

  1.   //定義一個(gè)CallBack接口 
  2.   public interface CallBack { 
  3.       void execute(); 
  4.   } 
  5.    
  6.   public class TimeTools { 
  7.    
  8.       /** 
  9.        * 測(cè)試函數(shù)調(diào)用時(shí)長(zhǎng),通過定義CallBack接口的execute方法 
  10.       * @param callBack 
  11.       */ 
  12.      public   void  testTime(CallBack callBack) { 
  13.          long  beginTime = System.currentTimeMillis(); //記錄起始時(shí)間 
  14.          callBack.execute(); ///進(jìn)行回調(diào)操作 
  15.          long  endTime = System.currentTimeMillis(); //記錄結(jié)束時(shí)間 
  16.          System.out.println("[use time]:"  + (endTime - beginTime)); //打印使用時(shí)間 
  17.      } 
  18.   
  19.      public   static   void  main(String[] args) { 
  20.          TimeTools tool = new  TimeTools(); 
  21.          tool.testTime(new  CallBack(){ 
  22.              //匿名內(nèi)部類,定義execute方法 
  23.              public   void  execute(){ 
  24.                  TestTimeObject testTimeObject = new TestTimeObject(); 
  25.                  testTimeObject.testMethod(); 
  26.              } 
  27.          }); 
  28.      } 
  29.  } 

在調(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)單例子: 

  1. public class Outer { 
  2.  
  3. class Inner
  4.  
  5.  

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

 

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

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

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

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

  1.   public class Outer { 
  2.    
  3.       private int i = 0; 
  4.   
  5.       class Inner
  6.           void method(){ 
  7.               System.out.println(i); 
  8.           } 
  9.       } 
  10.  } 

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

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

 

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

 

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

  1. final innerclass.Outer this$0; 

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

  1. innerclass.Outer$Inner(innerclass.Outer); 

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

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

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

先看這段代碼: 

  1.   public class Outer { 
  2.    
  3.       void outMethod(){ 
  4.           final int a =10; 
  5.           class Inner { 
  6.               void innerMethod(){ 
  7.                   System.out.println(a); 
  8.               } 
  9.    
  10.          } 
  11.      } 
  12.  } 

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

 

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

  1. 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ū)分開,它們只是值相等而已。

以上例子,為什么要加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ū)分開,解決了這個(gè)問題。

我們?cè)賮砜匆欢未a,其實(shí)就是把變量a挪到傳參方式進(jìn)來。 

  1.   public class Outer { 
  2.    
  3.       void outMethod(final int a){ 
  4.           class Inner { 
  5.               void innerMethod(){ 
  6.                   System.out.println(a); 
  7.               } 
  8.           } 
  9.       } 
  10.  } 

反編譯可得: 

 

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

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

怎么解決呢?使用final修飾符,final修飾的引用類型變量,不允許指向新的對(duì)象,這就解決數(shù)據(jù)不一致問題。注意: 在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)部類,如: 

  1.   Arrays.sort(emps,new Comparator(){ 
  2.     Public int compare(Object o1,Object o2) 
  3.     { 
  4.      return ((Employee)o1).getServedYears()-((Employee)o2).getServedYears(); 
  5.     } 
  6.   }); 

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

如果一些語句塊,包括if…else語句,case語句等等比較多,不好維護(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)雅很多。如下: 

  1. // JDK8 Lambda表達(dá)式寫法 
  2.  
  3. 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)部類常見面試題

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

  1.   public class Outer { 
  2.       private int age = 12; 
  3.    
  4.       class Inner { 
  5.           private int age = 13; 
  6.           public void print() { 
  7.               int age = 14; 
  8.               System.out.println("局部變量:" + age); 
  9.               System.out.println("內(nèi)部類變量:" + this.age); 
  10.              System.out.println("外部類變量:" + Outer.this.age); 
  11.          } 
  12.      } 
  13.  
  14.      public static void main(String[] args) { 
  15.          Outer.Inner in = new Outer().new Inner(); 
  16.          in.print(); 
  17.      } 
  18.   
  19.  } 

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

 

 

責(zé)任編輯:華軒 來源: 撿田螺的小男孩
相關(guān)推薦

2020-05-06 15:59:07

JavaScript程序員技術(shù)

2020-05-09 11:20:02

Java結(jié)構(gòu)圖虛擬機(jī)

2009-06-25 09:33:43

Java API程序員

2020-04-20 11:19:00

Java開發(fā)序列化

2020-07-20 07:46:01

程序員加簽驗(yàn)簽

2011-03-29 14:11:15

內(nèi)部類

2020-03-16 08:22:11

Java程序員虛擬機(jī)

2020-04-20 19:00:30

程序員分布式事務(wù)架構(gòu)

2011-06-11 20:59:12

程序員

2014-08-15 14:25:48

Android程序員資源

2014-08-20 10:28:29

Android

2020-01-15 11:14:21

Java算法排序

2019-09-25 11:39:07

程序員編程技術(shù)

2022-10-24 09:00:47

畫圖工具程序員XMind

2009-07-02 15:10:17

Java程序員面試

2021-05-06 07:04:35

安全漏洞業(yè)務(wù)

2015-08-20 14:34:25

程序員java基礎(chǔ)網(wǎng)絡(luò)編程

2020-09-21 07:00:42

Java內(nèi)部類接口

2019-07-19 10:04:05

Java程序員Tomcat

2020-08-11 07:31:39

JavaVolatile模型
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)