內部類 Inner Class
一個內部類可以定義在另一個類里,可以定義在函數里,甚至可以作為一個表達式的一部分。
Java中的內部類共分為四種:
靜態內部類static inner class (also called nested class)
成員內部類member inner class
局部內部類local inner class
匿名內部類anonymous inner class
1 成員內部類 member inner class
1.1 形式
成員內部類也是定義在另一個類中,但是定義時不用static修飾。形如:
1 class Outer { 2 class Inner{ 3 4 } 5 }
編譯之后會產生如下2個class文件:
---->這可以將相關的類組織在一起,從而降低了命名空間的混亂。
成員內部類的修飾符:
對於普通的類,可用的修飾符有final、abstract、strictfp、public和默認的包訪問。
但是成員內部類更像一個成員變量和方法。
可用的修飾符有:final、abstract、public、private、protected、strictfp和static。
一旦用static修飾內部類,它就變成靜態內部類了。
1.2 創建內部類實例
成員內部類就像一個實例變量,他依賴於外部類的實例存在 --> 必須先有外部類的實例 才能創建成員內部類對象
在外部類里面創建成員內部類的實例:this.new Innerclass(); 或可以用 Inner inner = new Inner(); 方法直接創建
在外部類之外創建內部類的實例:(new Outerclass()).new Innerclass();
或 Inner inner = new Outer().new Inner()
或 Outer outer = new Outer(); Inner inner = outer.new Inner();
案例:從外部類的非靜態方法中實例化內部類對象。
1 class Outer { 2 private int i = 10; 3 public void makeInner(){ 4 Inner in = new Inner(); 5 in.seeOuter(); 6 } 7 class Inner{ 8 public void seeOuter(){ 9 System.out.print(i); 10 } 11 } 12 }
表面上,我們並沒有創建外部類的對象就實例化了內部類對象,和上面的話矛盾。
事實上,如果不創建外部類對象也就不可能調用makeInner()方法,所以到頭來還是要創建外部類對象的。
你可能試圖把makeInner()方法修飾為靜態方法,即static public void makeInner()。
這樣不創建外部類就可以實例化外部類了!但是在一個靜態方法里能訪問非靜態成員和方法嗎?顯然不能。
--> 必須先有外部類的實例 才能創建成員內部類對象
案例:從外部類的靜態方法中實例化內部類對象
1 class Outer { 2 private int i = 10; 3 class Inner{ 4 public void seeOuter(){ 5 System.out.print(i); 6 } 7 } 8 public static void main(String[] args) { 9 Outer out = new Outer(); 10 Outer.Inner in = out.new Inner(); 11 //Outer.Inner in = new Outer().new Inner(); 12 in.seeOuter(); 13 } 14 }
被注釋掉的那行是它上面兩行的合並形式,一條簡潔的語句。
對比一下:在外部類的非靜態方法中實例化內部類對象是普通的new方式:Inner in = new Inner();
在外部類的靜態方法中實例化內部類對象,必須先創建外部類對象:Outer.Inner in = new Outer().new Inner();
1.3 成員內部類操作外部類
成員內部類可以訪問它的外部類的所有成員變量和方法,不管是靜態的還是非靜態的都可以。
內部類就像一個實例成員一樣存在於外部類,所以內部類可以訪問外部類的所有成員就想訪問自己的成員一樣沒有限制。
內部類中的this指的是內部類的實例對象本身,如果要用外部類的實例對象就可以用類名.this的方式獲得。
普通的類可以用this引用當前的對象,內部類也是如此。
但是假若內部類想引用外部類當前的對象呢?用“外部類名”.this;的形式,如下例的Outer.this。
1 class Outer { 2 class Inner{ 3 public void seeOuter(){ 4 System.out.println(this); 5 System.out.println(Outer.this); 6 } 7 } 8 9 public static void main(String[] strs){ 10 new Outer().new Inner().seeOuter(); 11 } 12 } 13 14 輸出: 15 Outer$Inner@61de33 16 Outer@14318bb
1.4 內部類對象中不能有靜態成員
原因很簡單,內部類的實例對象是外部類實例對象的一個成員,若沒有外部類對象,內部類就不會存在,何談靜態成員呢。
1 class Outer { 2 class Inner{ 3 static int i = 0; 4 public void seeOuter(){ 5 System.out.println(this); 6 System.out.println(Outer.this); 7 } 8 } 9 10 public static void main(String[] strs){ 11 new Outer().new Inner().seeOuter(); 12 } 13 }
我們編譯這個類:
1 E:\>javac Outer.java 2 Outer.java:3: 內部類不能有靜態聲明 3 static int i = 0; 4 ^ 5 1 錯誤
2 局部內部類local inner class
局部內部類local inner class 也可以成為方法內部類
顧名思義,就是把類放在方法內。局部內部類定義在方法中,比方法的范圍還小。是內部類中最少用到的一種類型。
像局部變量一樣,不能被public, protected, private和static修飾。
局部內部類在方法中定義,所以只能在方法中使用,即只能在方法當中生成局部內部類的實例並且調用其方法。
1 class Outer { 2 public void doSomething(){ 3 class Inner{ 4 public void seeOuter(){ 5 System.out.println("inner class"); 6 } 7 } 8 9 Inner inner = new Inner(); 10 inner.seeOuter(); 11 } 12 13 public static void main(String ... args){ 14 new Outer().doSomething(); 15 } 16 }
輸出:
inner class
局部內部類只能在聲明的方法內是可見的,因此定義局部內部類之后,想用的話就要在方法內直接實例化,
記住這里順序不能反了,一定是要先聲明后使用,否則編譯器會說找不到。
方法內部類的修飾符:
與成員內部類不同,方法內部類更像一個局部變量。
可以用於修飾方法內部類的只有final和abstract。
注意事項:
A: 方法內部類只能在定義該內部類的方法內實例化,不可以在此方法外對其實例化
B: 方法內部類對象不能使用該內部類所在方法的非final局部變量。
原因:
因為方法的局部變量位於棧上,只存在於該方法的生命期內。當一個方法結束,其棧結構被刪除,局部變量成為歷史。
但是該方法結束之后,在方法內創建的內部類對象可能仍然存在於堆中!例如,如果對它的引用被傳遞到其他某些代碼,並存儲在一個成員變量內。
正因為不能保證局部變量的存活期和方法內部類對象的一樣長,所以內部類對象不能使用它們。下面是完整的例子:
1 class Outer { 2 public void doSomething(){ 3 final int a =10; 4 class Inner{ 5 public void seeOuter(){ 6 System.out.println(a); 7 } 8 } 9 Inner in = new Inner(); 10 in.seeOuter(); 11 } 12 public static void main(String[] args) { 13 Outer out = new Outer(); 14 out.doSomething(); 15 } 16 }
C:靜態方法內的方法內部類。
靜態方法是沒有this引用的,因此在靜態方法內的內部類遭受同樣的待遇,即:只能訪問外部類的靜態成員。
3 匿名內部類Anonymous Inner Class
顧名思義,沒有名字的內部類。
匿名內部類就是沒有名字的局部內部類,不使用關鍵字class, extends, implements, 沒有構造方法。
匿名內部類隱式地繼承了一個父類或者實現了一個接口。
匿名內部類使用得比較多,通常是作為一個方法參數。
A、繼承式的匿名內部類。
1 class Car { 2 public void drive(){ 3 System.out.println("Driving a car!"); 4 } 5 } 6 7 8 class Test{ 9 public static void main(String[] args) { 10 Car car = new Car(){ 11 public void drive(){ 12 System.out.println("Driving another car!"); 13 } 14 }; 15 car.drive(); 16 } 17 }
結果輸出了:Driving another car!
建立匿名內部類的關鍵點是重寫父類的一個或多個方法。再強調一下,是重寫父類的方法,而不是創建新的方法。
因為用父類的引用不可能調用父類本身沒有的方法!創建新的方法是多余的。簡言之,參考多態。
B、接口式的匿名內部類。
1 interface Vehicle { 2 public void drive(); 3 } 4 5 6 class Test{ 7 public static void main(String[] args) { 8 Vehicle v = new Vehicle(){ 9 public void drive(){ 10 System.out.println("Driving a car!"); 11 } 12 }; 13 v.drive(); 14 }
這種形式的代碼我們一定寫過,這也是內部類的存在的一個重要作用:便於編寫 線程和事件驅動的代碼
1 public class ThreadDemo { 2 public static void main(String[] args) { 3 4 new Thread(new Runnable(){ 5 public void run(){ 6 System.out.println("Hello World!"); 7 } 8 }).start(); 9 } 10 } 11 12 13 E:\>javac ThreadDemo.java 14 E:\>java ThreadDemo 15 Hello World!
1 public class SwingTest 2 { 3 public static void main(String[] args) 4 { 5 JFrame frame = new JFrame("JFrame"); 6 JButton button = new JButton("JButton"); 7 8 button.addActionListener(new ActionListener(){ 9 10 @Override 11 public void actionPerformed(ActionEvent arg0){ 12 System.out.println("Hello World"); 13 14 } 15 }); 16 17 frame.getContentPane().add(button); 18 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 19 frame.setSize(200, 200); 20 21 frame.addWindowListener(new WindowAdapter() { 22 23 @Override 24 public void windowClosing(WindowEvent e){ 25 System.out.println("Closing"); 26 System.exit(0); 27 } 28 }); 29 30 frame.setVisible(true); 31 } 32 }
若你了解安卓的話 會發現這樣的代碼編寫方式有着很多的應用
好處就是簡化我們的代碼。
C、參數式的匿名內部類。
1 class Bar{ 2 void doStuff(Foo f){} 3 } 4 5 inteface Foo{ 6 void foo(); 7 } 8 9 class Test{ 10 static void go(){ 11 Bar b = new Bar(); 12 b.doStuff(new Foo(){ 13 public void foo(){ 14 System.out.println("foofy"); 15 } 16 }); 17 } 18 } 19
4 靜態內部類static inner class
在定義成員內部類的時候,可以在其前面加上一個權限修飾符static。此時這個內部類就變為了靜態內部類。
同樣會被編譯成一個完全獨立的.class文件,名稱為OuterClass$InnerClass.class的形式。
只可以訪問外部類的靜態成員和靜態方法,包括了私有的靜態成員和方法。
生成靜態內部類對象的方式為:OuterClass.InnerClass inner = new OuterClass.InnerClass();
靜態內部類使用代碼:
1 package com.learnjava.innerclass; 2 3 class StaticInner 4 { 5 private static int a = 4; 6 7 // 靜態內部類 8 public static class Inner 9 { 10 public void test() 11 { 12 // 靜態內部類可以訪問外部類的靜態成員 13 // 並且它只能訪問靜態的 14 System.out.println(a); 15 } 16 17 } 18 } 19 20 public class StaticInnerClassTest 21 { 22 23 public static void main(String[] args) 24 { 25 StaticInner.Inner inner = new StaticInner.Inner(); 26 inner.test(); 27 } 28 }
與一般內部類不同,在靜態代碼中不能夠使用this操作,所以在靜態內部類中只可以訪問外部類的靜態變量和靜態方法。
使用靜態內部類的目的和使用內部類相同。如果一個內部類不依賴於其外部類的實例變量,或與實例變量無關,則選擇應用靜態內部類。
可以在靜態內部類的方法中,直接訪問外部類的靜態變量和調用靜態方法。但不允許訪問外部類的實例變量以及實例方法。
靜態內部類的實例方法中亦只允許訪問外部類的靜態成員。
靜態內部類不同於其他3種內部類,他有着自己特殊的特性,參看:解析靜態內部類的使用目的與限制
5 小結
5.1 幾種內部類的共性:
A、內部類仍然是一個獨立的類,在編譯之后會內部類會被編譯成獨立的.class文件,但是前面冠以外部類的類命和$符號。
B、內部類不能用普通的方式訪問。內部類是外部類的一個成員,因此內部類可以自由地訪問外部類的成員變量,無論是否是private的。
5.2 java中為什么要引入內部類?還有匿名內部類?
1)可以是單繼承的一種補充解決方案 inner classes能有效實際地允許“多重實現繼承(multiple implementation)”
Java中一個類只能繼承一個類 可以通過內部類達到繼承多個類的效果
:每個inner class都能夠各自繼承某一實現類(implementation),因此,inner class不受限於outer class是否已繼承自某一實現類。
2)針對具體的問題提供具體的解決方案,同時又能對外隱藏實現細節 看具體的案例
案例1
在集合中可以使用Iterator遍歷 但每一種集合的數據結構不同 導致遍歷的方法必然也不同
所以Java在每個具體的集合里定義了一個內部類Itr 他實現了Iterator接口 從而根據所在類的具體情況進行遍歷
1 public interface Iterator {//迭代器的功能 2 boolean hasNext(); 3 Object next(); 4 } 5 6 public interface Iterable {//返回迭代器的能力 7 Iterator iterator(); 8 } 9 10 public interface Collection extends Iterable { 11 Iterator iterator(); 12 } 13 14 public interface List extends Collection { 15 Iterator iterator(); 16 } 17 18 public class ArrayList implements List { 19 public Iterator iterator() { 20 return new Itr(); 21 } 22 23 private class Itr implements Iterator { 24 //每種集合的具體實現采用了不同的數據結構 25 public boolean hasNext() {......} 26 public Object next(){......} 27 } 28 } 29 30 Collection c = new ArrayList(); 31 c.add("hello"); 32 c.add("world"); 33 c.add("java"); 34 Iterator it = c.iterator(); //new Itr(); 35 while(it.hasNext()) { 36 String s = (String)it.next(); 37 System.out.println(s); 38 }
Itr是private的類 對外並不可見 因為我的遍歷方法我自己知道就可以了 別人並不需要了解
集合類有實現了Tterable接口 通過里面的iterator方法獲取該內部類實例 外部不能直接創建該內部類的實例
案例2
為某個類提供特定的數據結構 : 為了共同解決一個具體的問題 但又可以對外部保持透明
如HashMap中有Entry ConcurrentHashMap有HashEntry 和Segment
看HashMap中的內部類:
1 public class HashMap<K,V> 2 extends AbstractMap<K,V> 3 implements Map<K,V>, Cloneable, Serializable 4 { 5 6 static final Entry<?,?>[] EMPTY_TABLE = {}; 7 transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; 8 9 static class Entry<K,V> implements Map.Entry<K,V> { 10 final K key; 11 V value; 12 Entry<K,V> next; 13 int hash; 14 // ... ... 15 } 16 }
關於HashMap 可以參考:HashMap源碼解析
關於ConcurrentHashMap:ConcurrentHashMap ConcurrentHashMap原理分析