在 JDK 1.5 中,新增了 变长参数 机制:在定义方法时,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。

变长参数是 Java 中的一个语法糖,本质上还是基于数组的实现。

它适用于参数个数不确定,类型确定的情况。

1. 定义、使用可变长参数

可变长参数用于方法的形参中。定义方法时,在最后一个形参后加上三点 ,表示该形参可以接受多个参数值(多个参数值被当成数组传入)

语法结构:

修饰符 返回值类型 方法名(参数类型... 参数名) {
    // TODO
}

可变参数方法的使用与方法参数部分使用数组是一致的。如:可以循环输出所有的参数值

public class Test {
	
	// 定义
    public void test(int... args) {
        for (int arg : args) {
            System.out.println(arg);
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
		
		// 调用
        test.test(1, 2, 3);
    }
}

传 0 个实参也行:

public static void main(String[] args) {
    Test test = new Test();
    
    test.test();
    test.test(1, 2, 3);
}

传一个数组也行:

public static void main(String[] args) {
    Test test = new Test();
    
	test.test();
	test.test(1, 2, 3);
    // 入参是一个数组
    test.test(new int[]{5, 6, 7, 8});
}

总结一下可变长参数的特点:

  1. 实参可以不传,也可以传入一个数组
  2. 当传入多个实参时,实参间用逗号“,”隔开
  3. 可变长参数只能作为方法的最后一个参数,但其前面可以有也可以没有任何其它参数
  4. 由于可变长参数必须是最后一个参数,所以一个方法中最多只能有一个可变长参数
  5. Java 中的可变长参数,会被编译器转化为一个数组

想想:为什么可变长参数只能作为方法的最后一个参数?

不能这样吗?

public void test(int... args, int a) {
	// TODO       
}

如果写成这样,那么一定会编译期报错!!那么,为什么不行呢?

因为参数个数不定,所以,当其后边还有类型参数时,java 无法区分传入的参数属于前一个可变参数还是后边的参数,所以,只能让可变参数位于最后一项。

那么,为什么传一个数组也行呢?



2. 可变长参数的实现原理

将上述代码通过反编译工具 jad 进行反编译,代码如下:

public class Test
{

    public Test()
    {
    }

    public transient void test(int args[])
    {
        int ai[] = args;
        int i = ai.length;
        for(int j = 0; j < i; j++)
        {
            int arg = ai[j];
            System.out.println(arg);
        }

    }

    public static void main(String args[])
    {
        Test test = new Test();
        test.test(new int[0]);
        test.test(new int[] {
            1, 2, 3
        });
        test.test(new int[] {
            5, 6, 7, 8
        });
    }
}

传入 0 个实参时:

test.test();  ==>  test.test(new int[0]);

传入多个实参时:

test.test(1, 2, 3);  ==>   test.test(new int[] {5, 6, 7, 8});

通过反编译代码知:编译器会将可变长参数转换为对应的数组,然后进行相应的处理。

3. 使用可变长参数的注意事项

虽然可变长参数在某些方面使用起来较为方便,但它也有自身的局限。在使用过程在,需要注意。如下:

1、拥有可变参数的方法可以被重载。在调用方法的时候,如果能够和固定参数的方法匹配,也能够与可变长参数的方法匹配,则选择固定参数的方法。如:

public class Test {

    public void testOverload(int i) {
        System.out.println("i");
    }

    public void testOverload(int i, int j) {
        System.out.println("i, j");
    }

    public void testOverload(int i, int... args) {
        System.out.println("args");
    }


    public static void main(String[] args) {
        Test test = new Test();
		
		// i
        test.testOverload(1);
        // i, j
        test.testOverload(1, 2);
        // args
        test.testOverload(1, 2, 3);
    }
}

2、如果要调用的方法可以和两个可变参数匹配,则会编译期错误

public class Test {
    
    public void testOverload(Object... args) {
        System.out.println("args");
    }

    public void testOverload(Object o, Object... args) {
        System.out.println("args");
    }


    public static void main(String[] args) {
        Test test = new Test();
		
		// 编译期报错
        test.testOverload(1);
        test.testOverload(1, 2);
    }
}

3、一个方法只能有一个可变长参数,并且这个可变长参数必须是该方法的最后一个参数。否则,会编译期报错

4、不要用数组去重载可变长参数,否则,会编译期报错

public class Test {
	// 编译期报错
    public void testOverload(Object... args) {
        System.out.println("args");
    }

	// 编译期报错
    public void testOverload(Object[] args) {
        System.out.println("args");
    }


    public static void main(String[] args) {
        Test test = new Test();
		
        test.testOverload(1);
        test.testOverload(1, 2);
    }
}

5、别让 null 值和空值威胁到变长方法

public class Test {

    public void testOverload(String dept, Integer... args) {

    }

    public void testOverload(String name, String... args) {

    }


    public static void main(String[] args) {
        Test test = new Test();
		
		// 编译期报错
        test.testOverload("人事部");
        // 编译期报错
        test.testOverload("zzc", null);
    }
}

因为两个方法都匹配,编译器不知道选哪个,于是就报错了。

同时,这里还有个非常不好的编码习惯,即:调用者隐藏了实参类型,这是非常危险的。不仅仅调用者需要“猜测”该调用哪个方法,而且被调用者也可能产生内部逻辑混乱的情况。作出如下修改:

public static void main(String[] args) {
    Test test = new Test();

    test.testOverload("人事部");
    String[] strs = null;
    test.testOverload("zzc", strs);
}

6、重写可变参数方法也要循规蹈矩

public class Test {

    public static void main(String[] args) {
        Parent parent = new Sub();
        parent.print("hello");
        
        Sub sub = new Sub();
        // 编译报错
        sub.print("hello");
    }
}


class Parent {
    void print(String... msg) {
        System.out.println("Parent#print()");
    }
}

class Sub extends Parent {
    @Override
    void print(String[] msg) {
        System.out.println("Sub#print()");
    }
}

第一个能编译通过,这是为什么呢?事实上,Parent 对象把子类对象 Sub 做了向上转型,形参列表是由父类决定的,当然能通过。再看看子类直接调用的情况,这时,编译器看到子类覆写了父类的 print() 方法,因此肯定使用子类重新定义的 print() 方法,尽管参数列表不匹配也不会跑到父类再去匹配下,因为找到了就不再找了,因此有了类型不匹配的错误

这是个特例,重写的方法参数列表与父类不相同,这违背了重写的定义,并且会引发莫名其妙的错误

重写方法必须满足的条件如下:

  1. 重写方法不能缩小访问权限
  2. 参数列表必须与被重写方法相同(包括显示形式)
  3. 返回类型必须与被重写方法的相同或是其子类;
  4. 重写方法不能抛出新的异常,或者超过了父类范围的异常,但是可以抛出更少、更有限的异常,或者不抛出异常