天天看點

雪習新知識:Java 内部類

本文出自 javascript:void(0),轉載請注明出處。

嵌套類,内部類,靜态内部類,靜态嵌套類。匿名類,成員類,局部類,傻傻分不清?

各種類,各種累!本文為你抽絲剝繭,庖丁解牛。娓娓道來。

首先聲明一下,本文要講的不是一個檔案中面并列的兩個類,而是在一個類裡面定義另外一個類。

1. 幾個樣例

例1:Adapter

public class ListViewActivity extends Activity {

    // some stuff

    class MyAdapter extends BaseAdapter {
        // some stuff
    }
}           

例2:AlertDialog.Builder

使用方法:

public class ListViewActivity extends Activity {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("對話框");
        AlertDialog dialog = builder.create();
        dialog.show();
}           

AlertDialog.Builder 相應的 SDK 源碼:

public class AlertDialog extends Dialog implements DialogInterface {

    // unrelated code

    public static class Builder {
        private final AlertController.AlertParams P;
        private int mTheme;
        ...
    }

    // unrelated code
}           

例3:Thread

new Thread() { 

     @Override 
     public void run() { 
         // TODO Auto-generated method stub 
     } 
}.start();            

例4:Button.OnClickListener

((Button)findViewById(R.id.start)).setOnClickListener(new Button.OnClickListener() { 
    @Override 
    public void onClick(View v) { 
    } 
});           

當中,OnClickListener 是 interface,相應的源碼在 View.java 中:

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {

    // more code

    public interface OnClickListener {
        void onClick(View v);
    }

    // more code
}           

例5:

public class Parcel4 { 
    public Destination destination(String s) { 
        class PDestination implements Destination { 
            private String label; 

            private PDestination(String whereTo) { 
                label = whereTo; 
            } 

            public String readLabel() { 
                return label; 
            } 
        } 
        return new PDestination(s); 
    } 

    public static void main(String[] args) { 
        Parcel4 p = new Parcel4(); 
        Destination d = p.destination("Tasmania"); 
    } 
} 

public class Parcel5 { 
    private void internalTracking(boolean b) { 
        if (b) { 
            class TrackingSlip { 
                private String id; 
                TrackingSlip(String s) { 
                    id = s; 
                } 
                String getSlip() { 
                    return id; 
                } 
            } 
            TrackingSlip ts = new TrackingSlip("slip"); 
            String s = ts.getSlip(); 
        } 
    } 

    public void track() { 
        internalTracking(true); 
    } 

    public static void main(String[] args) { 
        Parcel5 p = new Parcel5(); 
        p.track(); 
    } 
}            

例6:Android Guide 對于 Fragment 的官方文檔 Example (注意 TitlesFragment 是 static 的):

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
    ...
    }
}

public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    }
}
           

例7:Handler

// Handler聲明為static類,對外部類成員的引用改由WeakReference實作,如:
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tipTv = (TextView) findViewById(R.id.tiptv);

        mHandler = new MyHandler(tipTv);
        mHandler.sendEmptyMessageDelayed(MSG_TICK, 2000);
    }


    static class MyHandler extends Handler {
        private final WeakReference<TextView> mTvRef;

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mTvRef.get() instanceof TextView) {
                mTvRef.get().setText("test");
            }
        }

        MyHandler(TextView textView) {
            mTvRef = new WeakReference<TextView>(textView);
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }           

例8:Google 給的 listview 優化的建議:

static class ViewHolder {
    TextView text;
    ImageView icon;
}

public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.list_item_icon_text, null);

            holder = new ViewHolder();
            holder.text = (TextView) convertView.findViewById(R.id.text);
            holder.icon = (ImageView) convertView.findViewById(R.id.icon);

            convertView.setTag(holder);
        } else {
        }
        holder.text.setText(DATA[position]);
        holder.icon.setImageBitmap((position & 1) == 1 ?           

mIcon1 : mIcon2); return convertView; }

2. 眼花缭亂的各種類

那麼問題來了,以上樣例中。哪幾個屬于内部類?

要推斷某個類是不是内部類。就要知道内部類的定義。

那麼問題來了,誰下的内部類的定義才是最權威的?

《Thinking in Java》。《Effective Java》,《Java 核心技術》《xxx天精通Java》。《Java 高手進階》。還是某某部落格?

都不是。

Java 技術領域有 3 本書:

  • Java Virtual Machine Specification
  • Java Language Specification
  • Design Patterns: Elements of Reusable Object-Oriented Software

從底層的虛拟機原理。到包羅萬象的程式設計語言規範,再到頂層的系統設計,應有盡有,比人類曆史上銷量第一的《聖經》和第二的《毛主席語錄》都權威。

Java Language Specification(jls8,8.1.3節)對内部類的定義:

An inner class is a nested class that is not explicitly or implicitly declared static.

翻譯成中文是:内部類是沒有顯式或隐式的 static 修飾的嵌套類(nested class)。

将上述定義切割成 3 個重點:

  • explicitly or implicitly

    看到 explicitly or implicitly 這個詞組,不知道大家有沒有想到 Android 中 Activity 的跳轉方式?Activity 的跳轉方式有顯式(explicit)和隐式(implicit) 2 種方式;在這裡。explicitly 是指帶 static 修飾符,implicitly 是指盡管沒帶 static 可是等效于帶 static 的情況,實際是指在類内部聲明接口的情況。

  • static

    請看第3節

  • nested class

    請看第 3 節

3. 嵌套類(nested class)

雪習新知識:Java 内部類

一張圖看懂各種類

Java 同意在一個類裡面定義另外一個類,裡面的這個類被稱為嵌套類。

形式例如以下:

class OuterClass {
    ...
    class NestedClass {
        ...
    }
}           

當中。依據依據嵌套類前面有無 static 修飾符,能夠将嵌套類分為靜态嵌套類(static nested class)和非靜态嵌套類(non-static nested class)。後者又稱為内部類(inner class)。

class OuterClass {
    ...
    static class StaticNestedClass {
        ...
    }
    class InnerClass {
        ...
    }
}           

3.1 内部類(inner class)

3.1.1 局部内部類(local class)

局部内部類是指定義在語句塊内的類,語句塊是指成對大括号形成的塊,能夠是一個方法體。或者 if 語句,或者 for 循環語句。

以下這個樣例(引自 Orcale 官方文檔 并做了簡單改動),作用是驗證電話号碼位數是否合法。在 validatePhoneNumber 方法體内定義了局部内部類 PhoneNumber:

public class LocalClassExample {

    static String regularExpression = "[^0-9]";

    public static void validatePhoneNumber(
        String phoneNumber1, String phoneNumber2) {

        final int numberLength = 11;

        // Valid in JDK 8 and later:

        // int numberLength = 11;

        class PhoneNumber {

            String formattedPhoneNumber = null;

            PhoneNumber(String phoneNumber){
                // numberLength = 7;
                String currentNumber = phoneNumber.replaceAll(
                  regularExpression, "");
                if (currentNumber.length() == numberLength)
                    formattedPhoneNumber = currentNumber;
                else
                    formattedPhoneNumber = null;
            }

            public String getNumber() {
                return formattedPhoneNumber;
            }

            // Valid in JDK 8 and later:

//            public void printOriginalNumbers() {
//                System.out.println("Original numbers are " + phoneNumber1 +
//                    " and " + phoneNumber2);
//            }
        }

        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);

        // Valid in JDK 8 and later:

//        myNumber1.printOriginalNumbers();

        if (myNumber1.getNumber() == null) 
            System.out.println("First number is invalid");
        else
            System.out.println("First number is " + myNumber1.getNumber());
        if (myNumber2.getNumber() == null)
            System.out.println("Second number is invalid");
        else
            System.out.println("Second number is " + myNumber2.getNumber());

    }

    public static void main(String... args) {
        validatePhoneNumber("123-456-78901", "456-7890");
    }
}           

上述樣例首先過濾掉全部非 0-9 數字的字元。然後檢查剩下的數字是否夠 11 位,輸出例如以下:

First number is 12345678901
Second number is invalid
Accessing Members of an Enclosing Class           

和内部類一樣,局部内部類不能定義或聲明 static 類型的成員方法或成員變量(常量除外)。定義在 static 方法内部的類。如 PhoneNumber。僅僅能引用外部類的 static 成員。

比方,假設不把 regularExpression 聲明為 static,Java 編譯器會抛出相似“non-static variable regularExpression cannot be referenced from a static context.”的異常資訊。

局部内部類不能是 static 的。能夠引用語句塊的執行個體成員,并且不能包括 static 類型的成員。

在一個語句塊中不能聲明 interface。由于 interface 天生就是 static 的。

比如,以下的代碼是無法編譯的由于 interface HelloThere 在 greetInEnglish 方法體内:

public void greetInEnglish() {
        interface HelloThere {
           public void greet();
        }
        class EnglishHelloThere implements HelloThere {
            public void greet() {
                System.out.println("Hello " + name);
            }
        }
        HelloThere myGreeting = new EnglishHelloThere();
        myGreeting.greet();
    }           

局部内部類中不同意聲明 static 類型的成員方法或成員 interface。以下的代碼段無法編譯由于方法 EnglishGoodbye.sayGoodbye 是 static 的,編譯器會報錯:“modifier ‘static’ is only allowed in constant variable declaration”:

public void sayGoodbyeInEnglish() {
        class EnglishGoodbye {
            public static void sayGoodbye() {
                System.out.println("Bye bye");
            }
        }
        EnglishGoodbye.sayGoodbye();
    }           

局部内部類能夠擁有的 static 類型的成員是常量(常量是基本類型的資料結構,或者是字元串,被 final 修飾。并且能在編譯期間通過常量表達式賦初值,一般是字元串或者算術表達式)。下列代碼段能夠編譯通過由于 EnglishGoodbye.farewell 是常量:

public void sayGoodbyeInEnglish() {
        class EnglishGoodbye {
            public static final String farewell = "Bye bye";
            public void sayGoodbye() {
                System.out.println(farewell);
            }
        }
        EnglishGoodbye myEnglishGoodbye = new EnglishGoodbye();
        myEnglishGoodbye.sayGoodbye();
    }           

3.1.2 成員内部類(member class)

3.2 靜态嵌套類(static nested class)

3.2.0 interface

首先來說下嵌套的 interface,interface 屬于 class 的一種,之是以把它放在此節,是由于 interface 是一種隐式的靜态嵌套類。思考例如以下兩點:

  • View.OnClickListener,OnClickListener 是定義在 View.java 内部的 interface,我們的使用方法是 class Clazz implements View.OnClickListener,這可不就是 static nested class 的使用方法嗎?!
  • 全部的 interface。無論是嵌套在某個類内部的 interface。還是單獨定義在某個檔案中名稱與檔案名稱相同的 interface,是能夠聲明成員變量的,并且變量是 static final 即常量類型(注意這是到 IT 筆試/面試題)。這也佐證 interface 是 static 的。

    至于我們為什麼非常少在 interface 中聲明成員變量,那是由于 interface 的初衷是賦予子類某些“行為”即子類實作 interface 的方法,完畢自己獨特的“行為”,通過超類(父類或 interface)指針指向子類對象的形式,在執行過程中自己去調用子類的獨特的方法,這就是“多态”,這就是“面向接口程式設計”。

3.2.1 執行個體化方式

與内部類的執行個體化相反,靜态嵌套類的執行個體化不依賴其外部類的執行個體,即不須要先執行個體化外部類,然後才幹由外部類執行個體建立靜态嵌套類執行個體。

并且。靜态嵌套類僅僅能訪問其外部類的靜态成員。

/* 以下程式示範怎樣在java中建立靜态嵌套類和内部類 */
class OuterClass {
   private static String msg = "GeeksForGeeks";

   // 靜态嵌套類
   public static class StaticNestedClass{

       // 靜态嵌套類僅僅能訪問外部類的靜态成員
       public void printMessage() {

         // 試着将msg改成非靜态的,這将導緻編譯錯誤 
         System.out.println("Message from nested static class: " + msg); 
       }
    }

    // 内部類
    public class InnerClass {

       // 無論是靜态方法還是非靜态方法都能夠在内部類中訪問
       public void display() {
          System.out.println("Message from non-static nested class: "+ msg);
       }
    }
} 

class Main {
    // 怎麼建立靜态嵌套類和内部類的執行個體
    public static void main(String args[]){

       // 建立靜态嵌套類的執行個體
       OuterClass.StaticNestedClass printer = new OuterClass.StaticNestedClass();

       // 建立靜态嵌套類的非靜态方法
       printer.printMessage();   

       // 為了建立内部類,我們須要外部類的執行個體
       OuterClass outer = new OuterClass();        
       OuterClass.InnerClass inner = outer.new InnerClass();

       // 調用内部類的非靜态方法
       inner.display();

       // 我們也能夠結合以上步驟,一步建立的内部類執行個體
       OuterClass.InnerClass innerObject = new OuterClass().new InnerClass();

       // 相同我們如今能夠調用内部類方法
       innerObject.display();
    }
}           

3.2.2 使用場景

靜态嵌套類的使用場景,能夠參考其文法,即,靜态嵌套類不依賴外部類的詳細執行個體。外部類的全部執行個體公用的部分。

以電腦類 Caculator 為例。Caculator 中的運算符 Operator 我們打算使用嵌套類來實作。

Operator 的成員變量 PLUS、MINUS 等變量。顯然是全部 Caculator 公用的,這時我們最好将其聲明為 static。這樣全部的運算符都能夠通過 Caculator.Operator.PLUS 的方式調用。

此外。能夠使用靜态嵌套類實作單例模式(很多其它實作方式見《單例模式》)。

執行個體代碼例如以下:

public class Singleton {
    private Singleton() {}
    private static class SingletonHolder {
        public static Singleton INSTANCE = new Singleton();
    }

    public Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}           

3.2.3 優先使用靜态嵌套類

内部類執行個體會持有其外部執行個體的引用,這樣會增大内部類執行個體占用的空間。

并且。GC 依據可達性原則回收對象。内部執行個體引用外部執行個體會添加外部執行個體存活的時間,不利于外部執行個體被及時回收,不利于性能提升。

比較穩妥的方法是,全部的内部類全部聲明成 static 的,然後依據 IDE 的提示進行改動。實在無法進行下去的(如内部類引用了外部類的某個非靜态成員,而該靜态成員無法改成 static 類型),則使用内部類,否則就使用靜态嵌套類。

在使用 Firebug 進行代碼檢查時,Firebug 會提示将内部類改為靜态嵌套類,并将該問題歸為性能大類,截圖例如以下:

雪習新知識:Java 内部類

給出的理由例如以下:

This class is an inner class, but does not use its embedded reference to the object which created it. This reference makes the instances of the class larger, and may keep the reference to the creator object alive longer than necessary. If possible, the class should be made static.