不定長度引數/內部類別

May 30, 2022

在呼叫方法時,若方法的引數個數事先無法決定該如何處理?例如 System.out.printf 方法就無法事先決定引數個數:

System.out.printf("%d", 10);
System.out.printf("%d %d", 10, 20);
System.out.printf("%d %d %d", 10, 20, 30);

不定長度引數

不定長度引數(Variable-length Argument)可以輕鬆解決這個問題。直接來看示範:

package cc.openhome;

public class MathTool {
    public static int sum(int... numbers) {
        int sum = 0;
        for(int number : numbers) {
            sum += number;
        }
        return sum;
    }
}

要使用不定長度引數,宣告參數列時要於型態關鍵字後加上 ...,在 sum 方法可使用增強式 for 迴圈來取得不定長度引數中的每個元素,你可以如此使用:

System.out.println(MathTool.sum(1, 2));
System.out.println(MathTool.sum(1, 2, 3));
System.out.println(MathTool.sum(1, 2, 3, 4));

不定長度引數是編譯器蜜糖,反編譯後就可以一窺究竟:

public static transient int sum(int ai[]) {
    int i = 0;
    int ai1[] = ai;
    int j = ai1.length;
    for(int k = 0; k < j; k++) {
        int l = ai1[k];
        i += l;
    }
    return i;
}

不定長度引數實際是展開為陣列,而呼叫不定長度引數的客戶端,例如 System.out.println(MathTool.sum(1, 2, 3)),展開後也是變為陣列:

System.out.println(
    sum(new int[] {1, 2, 3})
);

使用不定長度引數時,方法上宣告的不定長度參數必須是參數列最後一個。例如以下是合法宣告:

public void some(int arg1, int arg2, int... varargs) {
     ...
}

以下方式是不合法宣告:

public void some(int... varargs, int arg1, int arg2) {
     ...
}

使用兩個以上不定長度引數也是不合法的:

public void some(int... varargs1, int... varargs2) {
     ...
}

內部類別

可以在類別中再定義類別,稱為內部類別(Inner class),例如以下程式片段建立了非靜態的內部類別:

class Some {
    class Other {
    }
}

雖然實務上很少看到接下來的寫法,不過要使用 SomeOther 類別,必須先建立實例 Some 實例。例如:

var s = new Some();
Some.Other o =  s.new Other();

內部類別也可以使用 publicprivate 等宣告。例如:

class Some {
    private class Other {
    }
}

內部類別本身可以存取外部類別的成員,通常非靜態內部類別會宣告為 private,這類內部類別是輔助類別中某些操作而設計,外部不用知道內部類別的存在。

內部類別也可以宣告為 static。例如:

class Some {
    static class Other {
    }
}

一個被宣告為 static 的內部類別,是將外部類別當作名稱空間。你可以如下建立類別實例:

Some.Other o = new Some.Other();

被宣告為 static 的內部類別,是將外部類別當作名稱空間,是個獨立類別,它可以存取外部類別 static 成員,但不可存取外部類別非 static 成員。

方法中也可以宣告類別,這通常是輔助方法中演算之用,方法外無法使用。例如:

class Some {
    public void doSome() {
        class Other {
        }
    }
}

實務上比較少看到在方法中定義具名的內部類別,倒很常看到方法中定義匿名內部類別(Anonymous inner class)並直接實例化,這跟類別繼承或介面實作有關,以下先看一下語法,細節留到談到繼承與介面時再作討論:

var o = new Object() {
    public String toString() {
        return "無聊的語法示範而已";
    }
};

如果要稍微解釋一下,這個語法定義了一個沒有名稱的類別,它繼承 Object 類別,並重新定義(Override)了 toString 方法,new 表示實例化這個沒有名稱的類別。

匿名類別語法本身,在某些場合有時有些囉嗦,Lambda 有一部份目的是用來解決匿名類別語法囉嗦的問題,之後會再討論。

分享到 LinkedIn 分享到 Facebook 分享到 Twitter