Java 5 では可変長引数が提供され、メソッド呼び出し時に任意の長さの引数を渡すことができます。可変長引数は Java のシンタックスシュガーであり、本質的には配列に基づく実装です:

void foo(String... args);
void foo(String[] args);
//方法签名 
([Ljava/lang/String;)V // public void foo(String[] args)

メソッドの定義

メソッドを定義するとき、最後の仮引数の後に3つのドット(...)を付けます…これにより、その仮引数は複数のパラメータ値を受け取ることができ、複数のパラメータ値は配列として渡されます。上記の定義にはいくつかの注意点があります:

  • 可変引数は関数の最後のパラメータとしてのみ使用できますが、その前には他のパラメータがあってもなくても構いません

  • 可変引数は最後のパラメータでなければならないため、1つの関数が持てる可変引数は最大で1つです

  • Java の可変引数は、コンパイラによって配列に変換されます

  • 可変長引数はバイトコードにコンパイルされた後、メソッドシグネチャでは配列の形で現れます。これら2つのメソッドのシグネチャは同じであり、メソッドのオーバーロードとして扱うことはできません。同時に存在する場合、コンパイルは通りません。可変引数は配列と互換性がありますが、逆は成立しません。

    public void foo(String...varargs){}
    
    foo("arg1", "arg2", "arg3");
    
    //上述过程和下面的调用是等价的
    foo(new String[]{"arg1", "arg2", "arg3"});
  • J2SE 1.5 では新しくジェネリクスのメカニズムにより、特定の条件下で型をパラメータ化できます。例えば、クラスを記述する際に、メソッドの仮引数の型を識別子(Tなど)で表すことができ、この識別子が実際にどの型を表すかは、そのクラスのインスタンスを生成するときに指定します。このメカニズムにより、より十分なコードの再利用と、より厳密なコンパイル時の型チェックが可能になります。ただし、ジェネリクスのメカニズムは個数が可変の仮引数とは併用できません。不特定の数の実引数と一致する仮引数の型を識別子で表すと、その場合、コンパイラは"generic array creation"というエラーを出します

    public class Varargs {
    
        public static void test(String... args) {
            for(String arg : args) {//当作数组用foreach遍历
                System.out.println(arg);
            }
        }
        //Compile error
        //The variable argument type Object of the method must be the last parameter
        //public void error1(String... args, Object o) {}
        //public void error2(String... args, Integer... i) {}
    
        //Compile error
        //Duplicate method test(String...) in type Varargs
        //public void test(String[] args){}
    }

可変引数メソッドの呼び出し

可変引数メソッドを呼び出すときは、0個から任意の個数の引数を渡すことができ、コンパイラは可変引数を配列に変換します。また、配列を直接渡すこともできます。例は以下のとおりです:

public class Varargs {

    public static void test(String... args) {
        for(String arg : args) {
            System.out.println(arg);
        }
    }

    public static void main(String[] args) {
        test();//0个参数
        test("a");//1个参数
        test("a","b");//多个参数
        test(new String[] {"a", "b", "c"});//直接传递数组
    }
}

メソッドのオーバーロード

固定引数が優先的にマッチする

オーバーロードされたメソッドを呼び出すとき、その呼び出しが固定引数のオーバーロードメソッドにも可変長引数のオーバーロードメソッドにもマッチする場合は、固定引数のメソッドが選択されます:

public class Varargs {

    public static void test(String... args) {
        System.out.println("version 1");
    }

    public static void test(String arg1, String arg2) {
        System.out.println("version 2");
    }
    public static void main(String[] args) {
        test("a","b");//version 2 优先匹配固定参数的重载方法
                test();//version 1
    }
}

> 複数の可変引数にマッチする

オーバーロードされたメソッドを呼び出すとき、その呼び出しが、可変長引数を持つ2つのオーバーロードメソッドの両方にマッチする場合、コンパイルエラーになります:

public class Varargs {

    public static void test(String... args) {
        System.out.println("version 1");
    }

    public static void test(String arg1, String... arg2) {
        System.out.println("version 2");
    }
    public static void main(String[] args) {
        test("a","b");//Compile error
    }
}

メソッドのオーバーライド

可変長引数を持つメソッドのオーバーロードを避ける

コンパイラが固定引数を優先的にマッチさせる方法で具体的な呼び出しメソッドを決定できる場合でも、コードを読む際には依然として罠に陥りやすいです。可変長引数のオーバーロードは慎重に検討すべきです。

null値と空値で可変長メソッドを脅かさないようにする

public class Client {  
     public void methodA(String str,Integer... is){       
     }  

     public void methodA(String str,String... strs){          
     }  

     public static void main(String[] args) {  
           Client client = new Client();  
           client.methodA("China", 0);  
           client.methodA("China", "People");  
           client.methodA("China");  //compile error
           client.methodA("China",null);  //compile error
     }  
}

修正は次のとおりです:

public static void main(String[] args) {  
     Client client = new Client();  
     String[] strs = null;  
     client.methodA("China",strs);  
}

コンパイラにこのnull値がString型であることを認識させれば、コンパイルはスムーズに通り、エラーの発生を減らせます。

可変長メソッドのオーバーライドも規則に従う

package com;
public class VarArgsTest2 {
    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        // 向上转型
        Base base = new Sub();
        base.print("hello");
        // 不转型
        Sub sub = new Sub();
        sub.print("hello");//compile error
    }
}
// 基类
class Base {
    void print(String... args) {
        System.out.println("Base......test");
    }
}
// 子类,覆写父类方法
class Sub extends Base {
    @Override
    void print(String[] args) {
        System.out.println("Sub......test");
    }
}

最初のものはコンパイルが通ります。これはなぜでしょうか?実際、base オブジェクトは子クラスオブジェクト sub をアップキャストしており、仮引数リストは親クラスによって決まるため、当然通ります。一方、子クラスが直接呼び出す場合を見てみると、コンパイラは子クラスが親クラスの print メソッドをオーバーライドしているのを確認するため、必ず子クラスで再定義された print メソッドを使用します。仮引数リストが一致しなくても親クラスに戻ってマッチングすることはありません。見つかったらもう探さないからです。そのため、型不一致のエラーが発生します。

これは特例であり、オーバーライドしたメソッドの仮引数リストが親クラスと異なることがあり得ます。これはオーバーライドの定義に反し、不可解なエラーを引き起こします。

ここで、オーバーライドが満たさなければならない条件をまとめます:

  • オーバーライドするメソッドはアクセス権限を狭めてはいけません

  • 仮引数リストは被オーバーライドメソッドと同じでなければなりません(表記形式も含む)

  • 戻り値の型は被オーバーライドメソッドと同じか、そのサブクラスでなければなりません

  • オーバーライドするメソッドは、新しい検査例外や親クラスの範囲を超える例外をスローしてはいけません。ただし、より少ない、より限定された例外をスローすることや、例外をスローしないことはできます。


発生し得る問題

使用Object…可変長引数として:

public void foo(Object... args) {
    System.out.println(args.length);
}

foo(new String[]{"arg1", "arg2", "arg3"}); //3
foo(100, new String[]{"arg1", "arg1"}); //2

foo(new Integer[]{1, 2, 3}); //3
foo(100, new Integer[]{1, 2, 3}); //2
foo(1, 2, 3); //3
foo(new int[]{1, 2, 3}); //1

int[]にキャストできませんObject[]、そのため単なる配列オブジェクトとして扱われます;Integer[]にキャストできますObject[]、オブジェクト配列として使用できます。


リフレクションメソッド呼び出し時の注意点

public class Test {
    public static void foo(String... varargs){
        System.out.println(args.length);
    }

    public static void main(String[] args){
        String[] varArgs = new String[]{"arg1", "arg2"};
        try{
            Method method = Test.class.getMethod("foo", String[].class);
            method.invoke(null, varArgs);
            method.invoke(null, (Object[])varArgs);
            method.invoke(null, (Object)varArgs);
            method.invoke(null, new Object[]{varArgs});
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}

上記の4つの呼び出しのうち、最初の2つは実行時に例外をjava.lang.IllegalArgumentException: wrong number of argumentsスローし、後の2つは正常に呼び出されます。

リフレクションは実行時に取得されます。実行時の観点から見ると、可変長引数と配列は同じであるため、メソッドシグネチャは次のようになります:

//方法签名
([Ljava/lang/String;)V // public void foo(String[] varargs)

次に、Method オブジェクトのメソッド宣言を見てみましょう:

Object invoke(Object obj, Object... args)

args は可変長のパラメータですが、args の長さは、そのメソッドオブジェクトが表す実際のメソッドのパラメータリストの長さに制限されます。実行時のシグネチャから見ると、([Ljava/lang/String;)V は実際には1つの仮引数、つまり String[] varargs しか持ちません。したがって、invoke(Object obj, Object… args) の可変引数 args の実引数の長さは1のみでなければなりません。

//Object invoke(Object obj, Object... args)
//String[] varArgs = new String[]{"arg1", "arg2"};
method.invoke(null, varArgs); //varArgs长度为2,错误
method.invoke(null, (Object[])varArgs); //将String[]转换为Object[],长度为2的,错误
method.invoke(null, (Object)varArgs);//将整个String[] 转为Object,长度为1,符合
method.invoke(null, new Object[]{varArgs});//Object[]长度为1,正确。上一个和这个是等价的

いつ可変長引数を使うべきか?

Stack Overflow可変長引数の使用に関する質問があります。簡単に言うと、
メソッドが処理する必要のあるオブジェクトの数が不明な場合は、可変長引数を使用できます。これにより、メソッド呼び出しがより簡単になり、手動で配列を作成する必要がありません。new T[]{…}

原文アドレス:https://blog.csdn.net/qiuchengjia/article/details/52910888