總結一下內部類,如有錯誤或者不足,歡迎交流討論。
內部類的定義,作用 內部類的分類 和內部類相關的幾個問題在一個類的內部定義一個類,就是內部類。內部類提供了某種進入外圍類的窗口;每個內部類都能獨立繼承子一個實現(接口或抽象類),無論其外圍類是否實現,對內部類都沒有影響;實現多繼承,Java的中繼承是單根繼承,只能繼承自一個類,雖然可以實現多個接口,但是如果要繼承自多個類,那麼只有內部類才可以解決,思路是外圍類繼承一個類,內部類繼承另外一個類,雖然內部類只繼承了一個類,但是因為它有一個隱式的指向外圍類對象的引用(非靜態內部類才可以),它可以訪問外圍類的成員,若是要訪問外圍類的方法,可以定義一個生成對外圍類對象的引用的方法,這樣就是實現了“多繼承”。
直接在一個類裡面再定義一個類,不在某個方法裡面。創建內部類的對象不能像普通類一樣,由於隱含有指向外圍類對象的引用,必須先創建外圍類。如下所示(還實現了繼承自兩個類):
package innerclass;
/**
* 普通內部類,並實現“多繼承”
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
class A{
public A(){
System.out.println("class A constructor");
}
public void f(){
System.out.println("class A f()");
}
}
class B{
public B(){
System.out.println("class B constructor");
}
public void f(){
System.out.println("class B f()");
}
class InnerClass extends A{
public InnerClass(){
System.out.println("class InnerClass constructor");
}
//返回生成內部類的外圍類對象的引用
public B outer(){
return B.this;
}
}
//典型新建內部類情況,外圍類有一個返回內部類對象的引用的方法
public InnerClass inner(){
return new InnerClass();
}
}
public class CommonInnerClass {
public static void main(String[] args) {
B b = new B();
//通過外圍類創建內部類
B.InnerClass ic1 = b.new InnerClass();
//通過外圍類的方法創建內部類,典型方法
B.InnerClass ic2 = b.inner();
ic1.f();
//使用內部類訪問外圍類的f()
ic2.outer().f();
//驗證outer()方法返回的引用就是創建內部類的外圍類的引用,打印其hashcode
System.out.println(b);
System.out.println(ic2.outer());
}
}
/**
輸出:
class B constructor
class A constructor
class InnerClass constructor
class A constructor
class InnerClass constructor
class A f()
class B f()
innerclass.B@2a139a55
innerclass.B@2a139a55
*/
在方法中定義的類,它不能有訪問說明符,因為不是外圍類的一部分,它屬於包含它的方法。另外,如果要在內部類中使用在外部定義的對象,為了防止在外圍類中被修改,應將該對象引用修飾為final。
package innerclass;
/**
* 局部類
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
interface Counter{
int next();
}
public class LocalInnerClass {
private int count = 0;
//內部類要使用name,定義為final是為了防止外圍類對name進行了修改
Counter getCounter(final String name){
class LocalCounter implements Counter{
public LocalCounter(){
System.out.println("LocalCounter constructor");
}
public int next(){//繼承、實現不能縮小訪問范圍
System.out.print(name);
return count++;
}
}
return new LocalCounter();
}
public static void main(String[] args) {
LocalInnerClass l = new LocalInnerClass();
Counter c = l.getCounter(" LocalInnerClass ");
for (int i = 0; i < 4; i++)
System.out.println(c.next());
}
}
/**
* 輸出:
LocalCounter constructor
LocalInnerClass 0
LocalInnerClass 1
LocalInnerClass 2
LocalInnerClass 3
*/
匿名內部類看起來就像在創建一個類的對象時,突然插入了一個類的定義。因為沒有名字,所以沒有構造器。沒有構造器也帶來了一個有趣的問題,如何初始化呢?在初始化的博客中,我寫到,實例初始化在構造器之前,就是因為匿名內部類的存在,它沒有構造器,可以利用實例初始化當做構造器使用,所以,實例初始化是在構造器之前執行。
如果某一內部類只是需要一個對象,那麼定義為匿名內部類最合適不過了。不過也有缺點,匿名內部類只能實現一個接口或繼承自一個類,不能實現兩個接口,或者一個接口並繼承自一個類,因為是匿名的,若可以兩個,那麼返回類型(通過new)無法確定。
package innerclass;
/**
* 匿名內部類,在一個包內,Counter接口在上面已經定義了
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
public class AnonymousInnerClass {
private int count = 0;
Counter getCounter(final String name){
return new Counter(){
//實例初始化,當做構造器使用
{
System.out.println("Instance initialization");
}
public int next(){
System.out.print(name);
return count++;
}
};
}
public static void main(String[] args) {
AnonymousInnerClass a = new AnonymousInnerClass();
Counter c2 = a.getCounter(" Anonymous inner ");
for(int i = 0 ; i < 4;i++)
System.out.println(c2.next());
}
}
/**
* 輸出:
Instance initialization
Anonymous inner 0
Anonymous inner 1
Anonymous inner 2
Anonymous inner 3
*/
又名嵌套類(注意不是嵌套的類)。如其名,內部類的定義前加了static修飾符,這時靜態內部類與外圍類無聯系,創建靜態內部類的對象不需要先創建外圍類的對象,也不能訪問非靜態的外圍類對象,也稱為嵌套類。
靜態內部類與普通內部類的區別除了上述外,普通內部類的成員或方法無法被static修飾。這是一個編譯問題,先從靜態成員變量說起,靜態成員變量是在類加載時初始化的,也就是第一次創建類的對象或者訪問靜態成員變量,它在類的內存內存分布中是有確切的位置的,對於內部類來說,其內存的分配和非靜態的成員變量是類似的,不創建外圍類的對象,那麼成員變量的值就不確定,從而其內存位置也不確定,且外圍類對象的內存位置也不確定,其引用的內存位置也就不能確定,而普通內部類的創建依賴外圍類的對象的創建,所以,普通的內部類是不能有static修飾的成員變量和方法的。
package innerclass;
/**
* 靜態內部類
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
interface Contents{
int value();
}
interface Destination{
String readLabel();
}
public class NestedClass {
private static class ParcelContents implements Contents{
private int i = 11;
public int value(){
return i;
}
void printf(){
System.out.println("ParcelContents: "+i);
}
}
protected static class ParcelDestination implements Destination{
private String label;
private ParcelDestination(String label){
this.label = label;
}
public String readLabel(){
return label;
}
public static void f(){}
static int x = 10;
static class AnotherLevel{
static int x = 10;
public static void f(){}
}
}
public static Destination destination(String s){
return new ParcelDestination(s);
}
public static Contents contents(){
return new ParcelContents();
}
public static void main(String[] args) {
Contents c = contents();
Destination d = destination("Yanni");
c.value();
}
}
這段例程例程有些地方還需要分析,在後面的內部類與向上轉型。
接口內部類,即在接口裡面定義的類,接口中的任何類都是隱式public和static的。例如:
package innerclass;
/**
* 接口內部類
* @author Administrator @date 2016年3月28日
* http://blog.csdn.net/xiaoguobaf
*/
public interface ClassInsideInterface {
void howdy();
class Test implements ClassInsideInterface{
@Override
public void howdy() {
System.out.println("howdy");
}
public static void main(String[] args){
new Test().howdy();
}
}
}
編譯器不會報錯,接口內部類可用來實現接口的公共代碼,該代碼可以被不同的實現所公用。
為每個類編寫測試,可用嵌套類,就不用每個都編寫。
package innerclass;
/**
* 內部類和向上轉型
* @author Administrator @date 2016年3月29日
* http://blog.csdn.net/xiaoguobaf
*/
public class InnerClassAndUpcasting {
private class ParcelContents implements Contents{
private int i = 11;
public int value(){
return i;
}
//默認是private的,隨類,實際上沒什麼用,搭配public配合接口,向上轉型為接口類型,可實現完全隱藏實現細節
void printf(){
System.out.println("ParcelContents: "+i);
}
//只在InnerClassAndUpcasting中能訪問,無特色,在protected修飾的類中還可以訪問,但沒有必要,不如聲明為private的,
public void callPrintf(){
printf();
}
}
protected class ParcelDestination implements Destination{
private String label;
private ParcelDestination(String label){
this.label = label;
}
public String readLabel(){
return label;
}
}
public Destination destination(String s){
return new ParcelDestination(s);
}
public Contents contents(){
return new ParcelContents();
}
public static void main(String[] args) {
InnerClassAndUpcasting inau = new InnerClassAndUpcasting();
Contents c = inau.contents();
Destination d = inau.destination("Yanni");
//必須先要向下轉型才能訪問子類型的方法,因為繼承擴展信息,父類不能訪問子類中擴展的信息
// c.printf();//提示方法沒有定義
((ParcelContents)c).printf();
((ParcelContents)c).callPrintf();
d.readLabel();
}
}
/**
* 一個class文件裡面可以有兩個main函數,只是在執行時eclipse將提示你要執行哪個,我這裡是為了方便測試
* @author Administrator @date 2016年3月29日
* http://blog.csdn.net/xiaoguobaf
*/
class Test {
public static void main(String[] args){
InnerClassAndUpcasting inau1 = new InnerClassAndUpcasting();
Contents c1 = inau1.contents();
System.out.println(c1.value());
//無法向下轉型,由於ParcelContents類是private,出了其外圍類InnerClassAndUpcasting,無法訪問
//((ParcelContents)c).printf();
}
}
/**
* 輸出:
ParcelContents: 11
ParcelContents: 11
11
*/
多層嵌套的內部類的訪問
package innerclass;
/**
* 內部類的繼承
* @author Administrator @date 2016年3月29日
* http://blog.csdn.net/xiaoguobaf
*/
class WithInner{
class Inner{}
}
public class InnerClassInheriting extends WithInner.Inner{
InnerClassInheriting(WithInner wi){
wi.super();
}
public static void main(String[] args){
WithInner wi = new WithInner();
InnerClassInheriting ic = new InnerClassInheriting(wi);
}
}
內部類可以覆蓋麼
內部類不可以覆蓋,就像在父類和子類中定義同名同屬性的成員類似,是不同的。
內部類與閉包、回調
閉包,是一個可調用的對象,它的信息來自於創建它的作用域,內部類是面向對象的閉包,因為它包含外圍類對象的信息,還默認擁有(非static)一個指向外圍類對象的引用。
回調:簡單的來說,就是你調用我,我調用你,先寫到這裡。看TIJ我還覺得上面例子有點想策略模式,查的結果有的說想觀察者模式。