刚开始接触JAVA的时候,从书上看到的JAVA中参数的传递机制,包括了很多概念,比如:形参,实参,传值调用,传引用调用等等,花了很长时间弄明白这其中的关系.直到我看了一本叫《JAVA编程的逻辑》的书,里面这样写的:
关于参数传递,简单总结一下,定义函数时声明参数,实际上就是定义变量,只是这些变量的值是未知,调用函数时传递参数,实际上就是给函数中的变量赋值.
既然调用函数时传递参数,实际上就是给函数中的变量赋值 ,那我们不妨先来看看在JAVA中给变量赋值的原理,再来对照理解参数传递.
1.基本类型
int a = 10;
int b = a;
b = 5;
a = ?
毫无疑问,这里a = 10,值不会被改变,画个简单的图示意一下:
![38f97138b6322ee8ae6abd98d15a577f.png](https://img-blog.csdnimg.cn/img_convert/38f97138b6322ee8ae6abd98d15a577f.png)
这里a赋值给b,是把a的值(10)给了b,a和b同样是分别保存在栈上,互相不影响,所以改变b的值,a并不会被改变.
那么同样的道理,基本类型的参数传递,也不会改变原来的值:
public static void main(String[] args) {
int a = 10;
System.out.println("a:before = "+a);
reset(a);
System.out.println("a:after = "+a);
}
public static void reset(int b){
System.out.println("b:before = "+b);
b = 5;
System.out.println("b:after = "+b);
}
输出结果:
a:before = 10
b:before = 10
b:after = 5
a:after = 10
2.数组
int[] a = {1, 2};
int[] b = a;
b[1] = 3;
a[1] = ?
在这里,a[1] = 3.这是因为一个数组变量有两块空间,一块用于存储数组内容本身,另一块用于存储内容的位置,变量a记录的就是这个存储内容的位置.这里a对b赋值,就是把这个内容的位置信息交给了b,所以a和b都保存了这么一个相同的位置,而数组内容本身只有一份数据.所以当b去修改数组内容的时候,a也会同样受到影响.
int[] a = {1, 2}
![73c75dd8a2b50e9df8a93af2f8b26e5d.png](https://img-blog.csdnimg.cn/img_convert/73c75dd8a2b50e9df8a93af2f8b26e5d.png)
int[] a = {1, 2};
![0121d994ec31f3b886e5e3e155197341.png](https://img-blog.csdnimg.cn/img_convert/0121d994ec31f3b886e5e3e155197341.png)
b[1] = 3;
![5ca55de3dad8f5afcfcd93f760eda6b7.png](https://img-blog.csdnimg.cn/img_convert/5ca55de3dad8f5afcfcd93f760eda6b7.png)
这样再来看数组作为参数时的情况就很清楚了
public static void main(String[] args) {
int[] arr = {10,20,30,40};
reset(arr);
for(int i=0; i<arr.length; i++){
System.out.println(arr[i]);
}
}
public static void reset(int[] arr){
for(int i=0; i<arr.length; i++){
arr[i] = i;
}
}
输出:
0
1
2
3
main方法中的数组arr被修改了.
3.对象
因为数组本身就是一种对象,所以如果是传入一个对象到方法中,然后再修改这个对象,那么和数组没有什么区别,变量保存的内容和参数传递的内容同样是对象在堆中的位置,所以在方法中的修改同样会影响到方法外.我就不再重复讲也不画图了.
这个地方我们讲点别的情况
public static void main(String[] args) {
User teacher = new User();
teacher.name = "cxk";
changeUser(teacher);
System.out.println("teacher = " + teacher);
}
public static void changeUser(User user){
//user.name = "Owen"; //如果在这里修改后直接返回的话, 那么显然main方法中的teacher.name是会被改变的
user = new User();
user.name = "Owen";
}
static class User{
public String name;
@Override
public String toString() {
return name;
}
}
这里打印出来的结果是:
teacher = cxk
这是因为changeUser方法中的new User() 在堆上重新创建了一个User对象 , user = new User() 则把这对象的地址赋值给了变量user.但是这仅仅是修改了changeUser方法内的引用指向,对main方法中的teacher的引用指向没有任何影响.
![731cd35231de0abe98764180b029be72.png](https://img-blog.csdnimg.cn/img_convert/731cd35231de0abe98764180b029be72.png)
user =newUser();
user.name ="Owen";
![ce5eb7903d0c88c0f603a5e42742f195.png](https://img-blog.csdnimg.cn/img_convert/ce5eb7903d0c88c0f603a5e42742f195.png)
可以看到,main方法中的teacher此时是不会被changeUser方法中的user影响了,因为他们已经指向了不同的对象.
在这种情况下,如果我们就是需要将另外的对象赋值给传递进来的参数,或者有时候,我们想把这个对象置为null,改怎么办呢?我们可以时候Holder类,他的源码非常简单,就是一个对泛型value的包装:
package javax.xml.ws;
import java.io.Serializable;
/**
* Holds a value of type <code>T</code>.
*
* @since JAX-WS 2.0
*/
public final class Holder<T> implements Serializable {
private static final long serialVersionUID = 2623699057546497185L;
/**
* The value contained in the holder.
*/
public T value;
/**
* Creates a new holder with a <code>null</code> value.
*/
public Holder() {
}
/**
* Create a new holder with the specified value.
*
* @param value The value to be stored in the holder.
*/
public Holder(T value) {
this.value = value;
}
}
还是用刚才老师和学生的例子来测试一下:
public static void main(String[] args) {
User teacher = new User();
teacher.name = "cxk";
Holder<User> teacherHolder = new Holder<>();
changeUser(teacherHolder);
System.out.println("teacher = " + teacherHolder.value.name);
}
public static void changeUser(Holder<User> holder){
User student = new User();
student.name = "Owen";
holder.value = student;
}
static class User{
public String name;
@Override
public String toString() {
return name;
}
}
输出是:teacher = Owen
相信看到这里上面的原理大家应该很清楚了,我就不再画图了(好的好的,我就是懒O(∩_∩)O)
4.String
String稍微有点特殊,他本身是一个引用类型,那么我们先来看看String在赋值的时候会发生什么:
String teacher = "cxk";
String student = teacher;
student = "Owen";
System.out.println("teacher = "+teacher);
System.out.println("student = "+student);
输出为:
teacher = cxk
student = Owen
虽然teacher 和 student 都同时指向了"cxk",但是在修改student的时候,并不会影响到teacher.那么同样的道理,在把String作为参数传递时,也不会影响到函数外原本的变量的值.
这是因为String虽然是引用类型的数据,但是它的底层是一个字符数组,String被设计为不可变的,每次改变他的值,都是会重新生成一个新的String,然后把这个新String的地址指给变量.所以当执行student = "Owen"时,其实是产生了一个新的String,然后student指向了这个新的地址.
String teacher = "cxk";
![8caa2cb3294937718a2acf570805959d.png](https://img-blog.csdnimg.cn/img_convert/8caa2cb3294937718a2acf570805959d.png)
String student = teacher;
![5cb20642b9ff60c93a75158de16e097d.png](https://img-blog.csdnimg.cn/img_convert/5cb20642b9ff60c93a75158de16e097d.png)
student = "Owen";
![8d08aa54f9558c018527fb83b29a8598.png](https://img-blog.csdnimg.cn/img_convert/8d08aa54f9558c018527fb83b29a8598.png)
总结
这里大致讲了一下对JAVA参数传递的理解,但是有的地方沒有展开或者深入的去说.比方说,String的底层是一个字符数组和String不可变有什么关系?String为什么要设计成不可变的?还可以了解数组扩容会改变地址,字符串常量池等等引申出去很多内容.希望大家可以对不太了解的点去深入的学习.这样才能形成自己的知识体系.其实很多时候面试官在问问题时也是这样一个逻辑,当一个问题你回答的不错时,他就会顺着这个问题的某一个点继续去深入挖掘,所以在平时学习时也保持这样一个习惯是非常好的.
扯淡内容又开始了:
其实我想过是不是应该把标题改为《JAVA的参数传递,看这一篇就够了!》或者《再有人问你JAVA的参数传递,就把这一篇丢给他!》,后来觉得还是算了.因为我也只能算个初学者,这个专栏也是我自己在学习的过程中有什么感想,就随手记录一下.所以我不会写一些系列文章,那种在网上一搜就会有一大把,我只会在自己对某些知识点真的有些自己的思考的时候才会写下来.如果能偶然帮助到一些和我一样在学习JAVA的同学,帮助大家在某一个小的知识点上产生了自己的理解,而不是把它简单的背下来,那我就非常开心了.