变长参数概念
在Java5 中提供了变长参数(varargs),也就是在方法定义中可以使用个数不确定的参数,对于同一方法可以使用不同个数的参数调用。形如 function(T …args)。但是需要明确的一点是,java方法的变长参数只是语法糖,其本质上还是将变长的实际参数 varargs 包装为一个数组。
看下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class VariVargs { public static void main(String []args) { test("hello","welcome","hi"); } public static void test(String ...args) { for(String string:args) { System.out.println(string); } } } |
将上述代码编译后,再反编译得到的结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import java.io.PrintStream; public class VariVargs { public static void main(String[] args) { test( new String[] { "hello", "welcome", "hi" }); } public static void test(String[] args) { String[] arrayOfString = args; //here int j = args.length; for (int i = 0; i < j; i++) { String string = arrayOfString[i]; //String string = arrayOfString[i]; System.out.println(string); } } } |
从上面的代码可以看出java的编译器将变长参数转换成了数组,并且显示的使用了arrayOfString指向了args。后面对变长参数的操作都转换为对arrayOfString的操作。(如果变长参数是Integer型的,数组名就是arrayOfInteger。其他类型类似)
变长参数非常容易理解和使用。但是在使用过程中也会遇到一些小的陷阱,但是只要我们理解了变长参数的本质,那么都能够很容易的理解和避免这些陷阱。
变长参数规则
调用方法时能与固定参数函数以及可变参数函数都匹配时。优先调用固定参数方法
在调用方法时能与固定参数函数以及可变参数函数都匹配时,JVM会优先匹配固定参数方法,下面举一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class VariVargsTest1 { //变长参数和固定参数同时存在 public static void main(String[] args) { // TODO Auto-generated method stub test("hello"); } public static void test(String...args) { System.out.println("调用可变参数函数****"); } public static void test(String test) { System.out.println("调用固定参数函数----"); } } |
执行代码,输出:调用固定参数函数—-
调用方法时,两个变长参数函数都匹配时无法通过编译
例子如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class VariVargsTest2 { //调用方法的参数与两个变长参数匹配,无法通过编译 public static void main(String[] args) { // TODO Auto-generated method stub test("hello"); //1 } public static void test(String ...args) { System.out.println("变长参数1"); } public static void test(String string,String...args) { System.out.println("变长参数2"); } } |
当没有1处代码时,程序是能够编译通过的。但是当添加了1处代码后无法通过编译,给出的错误是:The method test(String[]) is ambiguous for the type VariVargsTest2。编译器不知道选取哪个方法
一个方法只能有一个变长参数,且只能是参数列表的最后一个
由于规定变长参数只能是参数列表的最后一个。那么一个方法就不会有多个变长参数。
1 2 3 4 5 6 7 8 9 10 11 | public class VariVargs3 { public static void main(String[] args) { // TODO Auto-generated method stub } public static void test(String...args1,Integer...args2) //1 编译出错 { } } |
在代码1处编译出错:The variable argument type String of the method test must be the last parameter
变长参数的函数重载问题
首先我们回忆一下函数重载的定义和规则
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型呢?可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。重载规则:
-
被重载的方法必须改变参数列表
-
被重载的方法可以改变返回类型
-
被重载的方法可以改变访问修饰符
-
被重载的方法可以声明新的或更广的检查异常
-
方法能够在同一个类中或者在一个子类中被重载
变长参数与数组重载
经过上面的分析,我们知道编译器将变长参数转换为了数组,所以这两个方法的参数列表是一样的,不满足重载的第一条规则,用一个小例子证明一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class VariVargsTest41 { public static void main(String[] args) { // TODO Auto-generated method stub } //变长参数与数组重载 public static void test(String ...args) { } public static void test(String []args) { } } |
上述代码无法通过编译:Duplicate method test(String[]) in type VariVargsTest41
可变参数方法与可变参数方法重载
上面已经分析过,这里不再说明
可变参数方法与无参方法的重载
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class VariVargsTest4 { public static void test(String ...args) { System.out.println("调用变长参数函数"); } public static void test() { System.out.println("调用无参函数"); } public static void main(String []args) { test(); //调用方法是无参的 } |
输出:调用无参函数。
可变参数的重写问题
函数重写的定义和规则
重写(Override)是子类对父类的允许访问的方法的实现过程进行重新编写!返回值和形参都不能改变。即外壳不变,核心重写!
重写的好处在于子类可以根据需要,定义特定于自己的行为。也就是说子类能够根据需要实现父类的方法。重写规则:
-
参数列表必须完全与被重写的方法相同,否则不能称其为重写而是重载
-
返回的类型必须一直与被重写的方法的返回类型相同,否则不能称其为重写而是重载
-
访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
-
重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常
验证
我们先定义两个类:
1 2 3 4 5 6 7 | public class Base { public void test(String string) { System.out.println("Base"); } } |
1 2 3 4 5 6 7 | public class Child extends Base{ public void test(String ...args) { System.out.println("Child"); } } |
Child类是Base类的子类
先看下面的测试程序:
1 2 3 4 5 6 7 8 | public static void main(String []args) { Base base=new Child(); base.test("one"); Child child=new Child(); child.test("one"); child.test("one","two"); } |
上述代码执行的输出结果是:
Base
BaseChildbase变量是Base类型的,当调用的test函数的参数只有一个时,便会调用Base类中的test(String string)方法。如果是base.test(“one”,”two”)这样调用的话,程序无法通过编译:The method test(String) in the type Base is not applicable for the arguments (String, String)child变量是Child类型的,当调用的test函数的参数只有一个时,因为Child类继承了Base类,Child类继承了Base类中的test(String string)方法。Child有可变参数方法也有继承来的单参数方法,所以虚拟机会不管变长参数方法,而直接调用完全匹配的那个方法,输出“Base”。
总结
-
-
变长参数确实方便了我们的编程,在使用变长参数时,我们需要明确的一点是:这货就是一颗语法糖,天啊我们一直是在对数组做操作
-
我们在使用变长参数时,需要注意变长参数的使用规则
-
注意变长参数方法的重载和重写问题
-
参考资料
- from: https://jssyjam.github.io/2016/05/02/Java%E8%AF%AD%E6%B3%95%E7%B3%96%E5%88%9D%E6%8E%A2%EF%BC%88%E4%B8%89%EF%BC%89-%E5%8F%98%E9%95%BF%E5%8F%82%E6%95%B0-md/
-
-