Java参数传递
一、 方法重载
一个类中可以存在多个同名的方法,只要这些方法的参数列表不同即可。
- 参数列表不同:参数个数或者参数类型不同
- 方法重载与修饰符、返回值类型等统统无关,只看参数列表
二、 可变个数的形参
从Java5.0开始,Java有了可变形参的语法,即形参的类型可以确定,但是形参的个数不确定。
可变形参的格式:方法名(参数类型... 参数名)
比如:
public void f(int i, String... strings) {for (String string : strings) {System.out.println(string);}
}
此时,调用方法f
时,可以传入1个或多个字符串,甚至可以不传入字符串。比如:
f(0); // 不传入字符串
f(1, "one"); // 传入1个字符串
f(2, "one", "two"); //入多个字符串
注:
-
可变形参等价于使用数组形参,因此当两个方法一个使用可变形参、一个使用数组且其他参数列表相同时,将会报错。比如下面的语句将会报错:
public void f(int i, String... strings) {}public void f(int i, String[] strings) {}
-
可变形参需要放在方法参数列表的最后
-
在一个方法中,最多只能声明一个可变形参
三、 值传递
3.1 两种传递方式
参数传递有引用传递以及值传递两种方式:
- 引用传递,在调用方法时将参数的地址直接传递到方法中
- 值传递,在调用方法时将参数的值拷贝一份传递到方法中
3.2 Java参数传递类型
Java在传递参数时,只有值传递这一种方式。从JVM层面来看,Java在传递参数时是将栈中变量的值拷贝一份传递给参数。
3.2.1 传递基本类型
当传递基本类型时,将基本类型的数值直接拷贝一份传递给方法,在方法内部对参数做的修改并不会影响到方法外部。比如:
public class Main {public static void main(String[] args) {int i = 10;System.out.println("调用 test 方法前 i 的值:" + i);test(i);System.out.println("调用 test 方法后 i 的值:" + i); // test 方法并不会修改 i 的值}public static void test(int m) {m = 20;}
}
调用test
方法时,main
方法将整形变量i
的值10拷贝赋值给参数m
(这相当于初始化test
方法内部的变量m
),在test
内部对变量m
的改动并不会影响main
方法中的变量i
。
3.2.2 传递引用类型
引用类型与基本类型在内存中的存储方式略有不同:
- 基本类型在栈中存放的是变量具体的值
- 引用类型在栈中存放了一个地址,该地址指向堆中的某块内存区域,而该内存区域才是真正存放对象的地方
public class Main {public static void main(String[] args) {int i = 0;User user = new User(16, "main");}
}class User {public int age;public String name;public User(int age, String name) {this.age = age;this.name = name;}
}
比如,上面这段代码,main
方法中有两个局部变量,一个是整形变量i
,一个是引用类型user
,这两个变量在内存中的分布如下图所示:
引用类型的这种存储特点使得其在参数传递时产生了一些副作用:
-
与传递基本类型一样,当传递引用类型时,将引用类型的数值直接拷贝一份传递给方法,此时不会影响到存放在栈中的值。比如:
public class Main {public static void main(String[] args) {User user = new User(16, "main");System.out.println("调用 f1 方法前 user 的值:" + user);f1(user);System.out.println("调用 f1 方法后 user 的值:" + user); // user的值不会发生改变}public static void f1(User userArg) {userArg = new User(20, "f2");System.out.println("f1:" + userArg);} }class User {public int age;public String name;public User(int age, String name) {this.age = age;this.name = name;} }
从输出结果可以看到,与传递基本类型一样,在调用
f1
方法前后user
的值不会发生改变。 -
虽然不会影响到存放在栈中的值,但是会影响到堆中的值。比如:
public class Main {public static void main(String[] args) {User user = new User(16, "main");System.out.println("调用 f2 方法前 user 的值:" + user.age + user.name);f2(user);System.out.println("调用 f2 方法后 user 的值:" + user.age + user.name); // user所指向的对象发生了改变}public static void f2(User userArg) {userArg.age = 18;userArg.name = "f2";} }class User {public int age;public String name;public User(int age, String name) {this.age = age;this.name = name;} }
从输出结果可以看到,在调用
f2
方法后user
对象的成员变量值发生了改变。这是因为虽然值传递是拷贝,但是user
和userArg
指向的地址是同一个,所以方法内部对引用类型内部的成员变量进行修改时,还是会影响到堆中的值。此时,user
和userArg
在内存中的存储情况如下:
四、 笔/面试题目
-
问:Java是值传递还是引用传递
答:Java是值传递
-
问:下面代码执行完之后,数组
ints
的元素值是多少public class Main {public static void main(String[] args) {int[] ints = new int[]{1, 2, 3};f(ints);}public static void f(int[] intsArg) {for (int i = 0; i < intsArg.length; i++) {intsArg[i] += 10;}} }
答:数组也是一种引用类型,当调用方法
f
时,参入的是数组ints
在栈中的值,该值指向堆中存存放数组具体内容的内存区域。当在方法f
内部对数组内容进行操作时,将会修改堆中的内容,而由于ints
和intsArg
指向同一块堆内存,因此ints
所指向的数组也会被修改。因此最终数组ints
的元素值是11、12、13。 -
问:下面代码执行完之后,数组
ints
的元素值是多少public class Main {public static void main(String[] args) {int[] ints = new int[]{1, 2, 3};f(ints);}public static void f(int[] intsArg) {for (int i : intsArg) {i += 10;}} }
答:这题与上题相识,但在方法
f
内部遍历数组的方式却不同:- 上一题是通过一个递增的变量
i
依次访问数组中的元素并修改元素的值 - 本题是通过
foreach
语法依次取出数组中的元素值并将其赋值给一个新的变量i
,然后修改这个变量i
。修改的是变量i
,并未对数组元素做修改
因此最终数组
ints
的元素值仍然是1、2、3。 - 上一题是通过一个递增的变量