Java巢狀類(NestedClasses)總結

茶花盛開發表於2017-04-27

Nested Classes定義

在java語言規範裡面,巢狀類(Nested Classes)定義是:

A nested class is any class whose declaration occurs within the body of another class or interface. A top level class is a class that is not a nested class. 

說的簡單一點,就是定義在類裡面的類。一般把定義內部類的外圍類成為包裝類(enclosing class)或者外部類

 

巢狀類分類

根據nested class定義的地方,可以分為member nested class,local nested class , anonymous nested class

member nested class(成員巢狀類) :成員巢狀類 作為 enclosing class 的成員定義的,成員巢狀類有enclosing class屬性

local nested class (區域性巢狀類): 區域性巢狀類定義在 enclosing class 的方法裡面,區域性巢狀類有enclosing class 屬性和enclosing method 屬性

anonymous nested class(匿名巢狀類):匿名巢狀類沒有顯示的定義一個類,直接通過new 的方法建立類的例項。一般回撥模式情況下使用的比較多

 

member nested class 可以使用public,private,protected訪問控制符,也可以用static,final關鍵字

local nested class 可以使用final關鍵字

anonymous nested class 不使用任何關鍵字和訪問控制符

見下面的程式碼

public class EnclosingClass
{
 
    public static final class NestedMemberClass
{
 
    }
 
    public void nestedLocalClass()
{
 
        final class NestedLocalClass
{
 
        }
    }
 
    public void nestedAnonymousClass()
{
 
        new Runnable()
{
 
            @Override
            public void run()
{
            }
        };
    }
}

 如果你想學習java可以來這個群,首先是二二零,中間是一四二,最後是九零六,裡面有大量的學習資料可以下載

在大多數情況下,一般把nested classes 分為兩種:

Static Nested Classes(靜態巢狀類): 就是用static修飾的成員巢狀類

InnerClass:靜態巢狀類之外所有的巢狀類的總稱,也就是沒有用static定義的nested classes,Inner Classes 不能定義為static,不能有static方法和static初始化語句塊。在JLS(java語言規範)裡面是這麼定義的:

An inner class is a nested class that is not explicitly or implicitly declared static. Inner classes may not declare static initializers (§8.7) or member inter- faces 

 

其中Inner Class又可以分為三種:

1 inner member classes :沒有用static 修飾的成員內部類

2 local inner classes : 定義在方法裡面的內部類,方法可以是static的也可以是非static的,也可以是構造器方法。

3 anonymous inner classes :定義在方法裡面匿名類,方法可以是static的也可以是非static的

巢狀類訪問規則

Static Nested Classes 以及 inner classes 有一些限制規則,下面介紹一下這些規則。

  • Static Nested Classes訪問規則

用Static修飾的Nested Classes,只能訪問外部類的非static變數。對於public 的 static Nested Classes 可以用 new 外部類.內部類()的方式直接建立。而預設的static Nested Classes 可以在同一包名下,用 new 外部類.內部類()的方式建立。其實和外部類的方式差不多。靜態成員類可以使用訪問控制符,可以使用static修飾,可以是abstract抽象類

 

public class StaticNestedClass
{
 
    //
私有區域性
    private int i
=
0;
 
    //
靜態
    public static int j
=
0;
 
    //
不變值
    private final int k
=
0;
 
    //
static final
    private static final int m
=
0;
 
    //
靜態巢狀內,這裡不是innerclass,可以直接new出來
    public static class PublicNestedClass
{
 
        private void test1()
{
            //
System.out.println(i); 非innerClass不能訪問enclosing類的非static屬性
            System.out.println(j);
            System.out.println(m);
            //
System.out.println(k); 非innerClass不能訪問enclosing類的非static屬性
        }
 
        //
可以定義static方法
        private static void test2()
{
 
        }
    }
 
    //
靜態巢狀內,這裡不是innerclass,由於是私有的,不可以直接new出來
    private static class PrivateNestedClass
{
 
    }
}

  

下面的例子演示了static Nested class的建立

public class TestClass
{
 
    public static void main(String[]
args) {
         
        //任何地方都可以建立
        StaticNestedClass.PublicNestedClass
publicNestedClass =
new StaticNestedClass.PublicNestedClass();
         
        //可以在同一package下建立
        StaticNestedClass.DefaultNestedClass
defaultNestedClass =
new StaticNestedClass.DefaultNestedClass();
        //編譯錯誤,無法訪問內部內
        //StaticNestedClass.PrivateNestedClass
privateNestedClass = new StaticNestedClass.PrivateNestedClass();
    }
}

  

  • Inner Class訪問規則

inner member classes(內部成員類) 可以訪問外部類的所有例項屬性,靜態屬性。因為內部成員類持有一個外部物件的引用,內部類的例項可以對外部類的例項屬性進行修改。如果是public的 inner  member classes,可以通過 外部類例項.new 內部類()的方式進行建立,當呼叫內部類的構造器的時候,會把當前建立的內部類物件例項中持有的外部物件引用賦值為當前建立內部類的外部類例項。內部成員類可以是使用訪問控制符,可以定義為final,也可以是抽象類。

 

  

public class MemberInnerClass
{
 
    //
私有區域性
    public int i
=
0;
 
    //
靜態
    private static int j
=
0;
 
    //
不變值
    private final int k
=
0;
 
    //
static final
    private static final int m
=
0;
 
    public class PublicMemberInnerClass
{
        //
enclosing Class的屬性都可以訪問
        public void test()
{
            System.out.println(i);
            System.out.println(j);
            System.out.println(m);
            System.out.println(k);
        }
 
        public MemberInnerClass
getOutterClass() {
            return MemberInnerClass.this;
        }
        //
這裡會報錯,不允許定義static方法
        //
private static final void test();
    }
 
    //
私有的innerclass 外部不能訪問
    private class PrivateMemberInnerClass
{
    }
 
    //
公開區域性類,外部可以訪問和建立,但是隻能通過OutterClass例項建立
 
    class DefaultMemberInnerClass
{
        public MemberInnerClass
getOutterClass() {
            return MemberInnerClass.this;
        }
    }
 
}

  

下面例子演示了內部成員類的建立

public class TestClass
{
 
    public static void main(String[]
args) {
 
        //
任何地方都可以建立
        MemberInnerClass
t =
new MemberInnerClass();
 
        //
可以建立,pmic裡面儲存對t的引用
        MemberInnerClass.PublicMemberInnerClass
pmic = t.
new PublicMemberInnerClass();
 
        //
可以在同一package下建立,dmic儲存對t的引用
        MemberInnerClass.DefaultMemberInnerClass
dmic = t.
new DefaultMemberInnerClass();
 
        //
編譯錯誤,無法訪問內部內
        //
MemberInnerClass.PrivateMemberInnerClass pmic = t.new
        //
PrivateMemberInnerClass();
 
        //
下面驗證一下outterClass是同一個物件
        System.out.println(pmic.getOutterClass()
== t);
        System.out.println(dmic.getOutterClass()
== t);
 
    }
}

 執行程式,列印結果:

true
true

  

2 local inner classes(區域性類)

區域性類 定義在類方法裡面。這個方法既可以是靜態方法,也可以是例項方法,也可以是構造器方法或者靜態初始化語句塊。

區域性類可以定義在一個static上下文裡面 和 非static上下文裡面。區域性類不能有訪問控制符(private,public,protected修飾),可以是抽象的,也可以定義為final

定義在static上下文(static 欄位初始化,static初始化塊,static方法)裡面的local inner classes 可以訪問類的靜態屬性,如果定義在靜態方法裡面的區域性類,還可以方法裡面定義的final變數。在static上下文定義的區域性類,沒有指向父類例項變數的引用,因為static方法不屬於類的例項,屬於類本身。而且區域性類不能在外部進行建立,只能在方法呼叫的時候進行建立

 

public class LocalInnerClass
{
 
    //
私有區域性
    private int i
=
0;
 
    //
靜態
    public static int j
=
0;
 
    //
不變值
    private final int k
=
0;
 
    //
static final
    private static final int m
=
0;
 
    public static void test()
{
        final int a
=
0;
        int b
=
0;
        //
local inner class不能夠有訪問控制符 比如public private
        abstract class LocalStaticInnerClass
{
            //
local inner class不能定義靜態屬性
            //
private static int c;
            private int d
=
0;
            public LocalStaticInnerClass()
{
                //
可以訪問方法裡面定義的final 變數
                System.out.println(a);
                //
不能訪問b 因為b不是final
                //
System.out.println(b);      
                //
定義在static上下文裡面的local inner class 不能訪問外部類的非static欄位
                //
System.out.println(i);
                //
System.out.println(k);
                System.out.println(j);
                System.out.println(m);
            }
            //
local inner class不能定義靜態方法
            //
public static void test(){}
        }
    }
 
    public void test2()
{
        final int a
=
0;
        int b
=
0;
        final class LocalNonStaticInnerClass{  
            public LocalNonStaticInnerClass()
{
                //定義在非static上下文的local
inner class 可以訪問外部類的所有屬性
                System.out.println(i);
                System.out.println(k);
                System.out.println(j);
                System.out.println(m);
            }
        }
    }
 
}

 

 3 anonymous inner classes (匿名類)也是定義在方法裡面,匿名類和區域性類訪問規則一樣,只不過內部類顯式的定義了一個類,然後通過new的方式建立這個區域性類例項,而匿名類直接new一個類例項,沒有定義這個類。匿名類最常見的方式就是回撥模式的使用,通過預設實現一個介面建立一個匿名類然後,然後new這個匿名類的例項。

public class AnonymousInnerClass
{
    //訪問規則和區域性類一樣
    public void test()
{
         
        //匿名類實現
        new Thread(new Runnable()
{
 
            @Override
            public void run()
{
 
            }
        }).start();
         
        //非匿名類實現
        class NoneAnonymousClassimplements Runnable{
            public void run()
{
 
            }
        }  
        NoneAnonymousClass
t =
new NoneAnonymousClass();
        new Thread(t).start();
    }
}

巢狀類的層次

巢狀類是可以有層次的,也就是說巢狀類裡面還是定義類,成為巢狀類中的巢狀類。虛擬機器如何保證巢狀類正確的巢狀層層次?

對於merber class,內部巢狀類的可以表示為 A$B 其中A為外部類,B為內部成員類 ,如果B裡面又有成員為C的巢狀類,那麼C就可以表示為A$B$C,如果A定義了兩個同名member class,那麼編譯器就會報錯。如果B裡面又包含了為名B的nested class,則編譯器會報錯.

對於local inner Class,區域性類可以表示為A$1B的方式,其中A為外部類,B為第一個區域性類 如果在不同的方法裡面定義了同名的區域性類B,編譯器是可以編譯通過的,那麼定義的第二個區域性類B可以表示為A$2B,如果在同一個方法裡面同定義兩個相同的區域性類B,那麼編譯錯是要報錯的。如果B裡面又定義了同名的成員類,則可以表示為A$1B$B。

對於anonymous inner classes,匿名類可以表示為A$1的方式,代表程式裡面有一個匿名類。如果有N個,可以表示為A$N的方式(N為自然數).

看看下面的例子

public class NestedClassLevel
{
 
    class A
{
        //
編譯器會報錯,A裡面不能在定義名為A的nested classes
        //
class A{}
        public void test()
{
            class B
{
            }
        }
    }
 
    //可以在繼續定義B
    class B
{
        public void test(){
            //可以無限定義匿名類
            new Runnable()
{
                public void run()
{
                    //可以無限定義匿名類
                    new Runnable()
{           
                        public void run()
{        
                        }
                    };
                }
            };
        }
    }
 
    //
只能定義一個B
    //
class B{}
 
    public void test()
{
        //
可以定義A
        class A
{
            public void test()
{
                //可以有同名的區域性類B和成員類B
                class B
{
                    public void test()
{
                         
                    }
                }
                //區域性類A裡面不能在定義A
                //class
A{}
            }
        }
        //可以有同名的區域性類B和成員類B
        class B
{
 
        }
    }
 
}

 

對於定義在非static上下文裡面的nested類層次,比如A$B$1C ,則最內層的巢狀類C有一個指向B例項的引用,B有一個指向A例項的引用,最終最內層的巢狀類可以訪問A中的屬性可以方法,一般把B成為A的直接巢狀類。但是A不可以訪問B或者C中屬性或者方法。

nested interface

由於interface預設是定義為一個 public static的特殊類,所以interface可以直接作為 static member class。可以通過A.B的方式進行訪問。

 

nested class的應用

在java提供的基本類庫裡面,大量使用nested classes。比如我們知道的map類。其中 Map類裡面有一個定義了Entry類abstract inner class。所以我們在遍歷map的時候,一般使用

for (Map.Entry entry:map.entrySet()){

}

 

總結:nested類是java裡面比較複雜的一個概念,必須詳細瞭解jvm中對於巢狀類的實現以及java編譯器對巢狀類的處理才可以深入瞭解巢狀類細節。


相關文章