リフレクションとか、JMockitのDeencapsulationとかをいじってるときに気をつけてないと結構はまるポイントになる気がするのでまとめました。
とりあえずいろんなパターンで動かす
Main.java
public class Main {
public static void main(String[] args) {
// 1
test(1, "a", "b", "c");
// 2
test(2, new String[] { "a", "b", "c" });
// 3
test(3, new String[] { "a", "b", "c" }, "d");
// 4
test(4, new int[] { 1, 2, 3 });
}
private static void test(int number, Object... args) {
System.out.println("No." + number + " length = " + args.length);
}
}
可変長引数のパラメータにいろんなパターンで値を渡して、受け取ったパラメータのサイズを出力しています。
コンソールにはどう出力されるでしょうか?
出力結果
コンソール
No.1 length = 3
No.2 length = 3
No.3 length = 2
No.4 length = 1
解説
可変長引数の定義の実態が配列であることを意識し、コンパイル時にどんな形になるかを考えると分かりやすい。
受け取り側はコンパイル時に下記引数を持つメソッドに変換される。
private static void test(int number, Object[] args) {
...
}
No.1
コンパイル時に受け取り側の配列に変換される。
test(1, new Object[]{"a", "b", "c"});
サイズは3となる。
No.2
すでに配列で共変なため、変換が行われない。
test(2, new String[]{"a", "b", "c"});
サイズは3となる。
No.3
コンパイル時に受け取り側の配列に変換される。
test(3, new Object[] { new String[] { "a", "b", "c" }, "d" });
サイズは2となる。
No.4
すでに配列だが、プリミティブ型の配列はそのままObjectの配列にキャストすることが出来ないので、コンパイル時にObjectの配列に包まれた状態に変換される。
test(4, new Object[] { new int[] { 1, 2, 3 } });
サイズは1となる。
あとがき
- No.2で配列自体を1つのデータとして渡したい場合は以下のように明示的にキャストをすると実現出来る。
//No.2 length = 1
test(2, (Object) new String[]{"a", "b", "c"});
- 変な動きをして困ったら逆コンパイル(java → class → java)してみるといいかもしれない。
java8対応の逆コンパイラはCFR, FernFlower, Procyonとかあってなんかいろいろ特徴があるみたいです → いつか記事にしたい