2018年7月2日19:57:06
【前言】
基础很重要,如果基础不扎实,那么它会成为你前进的绊脚石。
【前菜】
复习一下C语言关于传值与引用传递(指针)的使用:
- 传递就是最普通的传递方式,比如函数定义为fun(int a)
,在调用的地方有int x=6
, 使用fun(x)
就可以了。这种方式在fun(int a)
函数内部的对a的修改 不能 导致外部x的变化。
- 指针传递其实也就是地址传递,函数定义为fun(int *a)
,形参为指针,这就要求调用的时候传递进去一个参数的地址,例如int x=6;
fun(&x)
。 这种方式在fun(int a)
函数内部的对a的修改 能 导致外部x的变化。
- 引用传递相比前两种方式用的比较少,但也非常有用。引用传递函数定义为fun(int &a)
,这里&符号是引用而不是取地址的意思,调用方式和值传递类似,例如int x=6;
fun(x)
。 但是这种方式在fun(int a)函数内部的对a的修改 能 导致外部x的变化。
传递方式 | 函数定义 | 函数调用 | 函数内对a修改的影响 |
---|---|---|---|
值传递 | fun(int a) | fun(x) | 外部x不变 |
指针传递 | fun(int *a) | fun(&x) | 外部x同步更改 |
引用传递 | fun(int &a) | fun(x) | 外部x同步更改 |
【正菜】
Java的函数参数传递相较于C语言,
1)传值-基本类型
package test01;
public class test01 {
public static void main(String[] args) {
/*
* 这是java值(基本类型)的传递,那么在堆栈中创建的便是这个参数的简单拷贝。
* x,y这2个参数是基本类型,所以存储在堆栈中。当调用modifyPrimitiveTypes()方法时,
* 在堆栈中创建了这2个参数的拷贝(我们就叫它们w,z),
* 实际上是w,z被传递到了方法中。
* 所以原始的参数并没有被传递到方法中,在方法中的任何修改都只作用于参数的拷贝w,z
* */
int x = 1;
int y = 2;
System.out.print("Values of x & y before primitive modification: ");
System.out.println(" x = " + x + " ; y = " + y );
modifyPrimitiveTypes(x,y);
System.out.print("Values of x & y after primitive modification: ");
System.out.println(" x = " + x + " ; y = " + y );
}
private static void modifyPrimitiveTypes(int x, int y)
{
x = 5;
y = 10;
}
}
输出结果:
Values of x & y before primitive modification: x = 1 ; y = 2
Values of x & y after primitive modification: x = 1 ; y = 2
2)传值-包装类
package test01;
public class test02 {
public static void main(String[] args) {
/*
* 包装类,值传递
* 包装类存储在堆中,在堆栈中有一个指向它的引用。
* 当调用modifyWrappers()方法时,在堆栈中为每个引用创建了一个拷贝,
* 这些拷贝被传递到了方法里。
* 任何在方法里面的修改都只是改变了引用的拷贝,而不是原始的引用。
*
* P.S: 如果方法中的表达式为x += 2,x值得改变也不会影响到方法外部,
* 因为包装类是immutable类型的。
* 当他们的state变化时,他们就会创建一个新的实例。
* 如果你想了解更多关于immutable类,可以阅读How to create an immutable class in Java。
* 字符串类型和包装类相似,所以以上的规则对于字符串也有效。
* */
Integer obj1 = new Integer(1);
Integer obj2 = new Integer(2);
System.out.print("Values of obj1 & obj2 before wrapper modification: ");
System.out.println("obj1 = " + obj1.intValue() + " ; obj2 = " + obj2.intValue());
modifyWrappers(obj1, obj2);
System.out.print("Values of obj1 & obj2 after wrapper modification: ");
System.out.println("obj1 = " + obj1.intValue() + " ; obj2 = " + obj2.intValue());
}
private static void modifyWrappers(Integer x, Integer y)
{
x = new Integer(5);
y = new Integer(10);
}
}
输出结果:
Values of obj1 & obj2 before wrapper modification: obj1 = 1 ; obj2 = 2
Values of obj1 & obj2 after wrapper modification: obj1 = 1 ; obj2 = 2
3)传递引用-集合类型,引用传递
package test01;
import java.util.ArrayList;
import java.util.List;
public class test03 {
/*
* 集合类型,引用传递
*
* 当我们创建一个ArrayList或任意集合,在堆栈中便会创建一个指向堆中多个对象的引用。
* 当modifyList()被调用时,一个引用的拷贝被创建中传递到了方法中。
* 现在有2个引用指向了真正的对象数据,其中任何一个引用的数据改变会影响到另一个。
* 在方法中,当我们调用lstParam.add(2)时,一个Integer对象在堆中被创建,添加到了现有的list对象。
* 所以原始的list引用可以看见这次修改,因为2个引用都指向了内存中的同一个对象。
* */
public static void main(String[] args) {
List<Integer> lstNums = new ArrayList<Integer>();
lstNums.add(1);
System.out.println("Size of list before List modification = " + lstNums.size());
modifyList(lstNums);
System.out.println("Size of list after List modification = " + lstNums.size());
}
private static void modifyList(List<Integer> lstParam)
{
lstParam.add(2);
}
}
输出结果:
Size of list before List modification = 1
Size of list after List modification = 2
4)传递引用 -自定义对象,引用传递
package test01;
public class test04 {
public static void main(String[] args) {
/*
* 自定义对象,引用传递。
* student对象在堆中被创建,在堆栈中存储着指向它的引用。
* 当调用calling modifyStudent(),在堆栈中创建了这个引用的拷贝,传递到了方法中。
* 所以任何对这个对象属性的修改会影响原始的对象引用。
* */
Student student = new Student();
System.out.println("Value of name before Student modification = " + student.getName());
modifyStudent(student);
System.out.println("Value of name after Student modification = " + student.getName());
}
private static void modifyStudent(Student student)
{
student.setName("Alex");
}
}
package test01;
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Student(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Student() {
super();
}
}
输出结果:
Value of name before Student modification = null
Value of name after Student modification = Alex
【结论】
在Java中,参数都是按值传递的。被传递到方法中的拷贝值,要不就是一个引用或一个变量,取决于原始参数的类型。从现在开始,下面的几条规则将帮助你理解方法中对于参数的修改怎么影响原始参数变量。
在方法中,修改一个基础类型的参数永远不会影响原始参数值。
在方法中,改变一个对象参数的引用永远不会影响到原始引用。然而,它会在堆中创建了一个全新的对象。(译者注:指的是包装类和immutable对象)
在方法中,修改一个对象的属性会影响原始对象参数。
在方法中,修改集合和Maps会影响原始集合参数。
阅读衍伸(了解具体的JVM内存操作):
1、 https://mp.weixin.qq.com/s/G2gzBGJ7zV0leo0tVPuCGw