形参和实参
在学习值传递和引用传递前,我们需要了解形参/实参的概念。
方法的定义可能会用到 参数(有参的方法),参数在程序语言中分为:
- 实参(实际参数,Arguments):用于传递给函数/方法的参数,必须有确定的值。
- 形参(形式参数,Parameters):用于定义函数/方法,接收实参,不需要有确定的值。
String hello = "Hello!";
// hello 为实参
sayHello(hello);
// str 为形参
void sayHello(String str) {
System.out.println(str);
}
值传递和引用传递
程序设计语言将实参传递给方法(或函数)的方式分为两种:
- 值传递:方法接收的是实参值的拷贝,会创建副本。
- 引用传递:方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。
很多程序设计语言(比如 C++、 Pascal )提供了两种参数传递的方式,不过,在Java中只有值传递,基本数据类型传递的是值的副本,引用数据类型传递的是引用的副本。
1.值传递
1)传递基本数据类型
public class Demo03 {
public static void main(String[] args) {
int a = 1;
System.out.println(a);//a=1
Demo03.change(a);
System.out.println(a);//a=1
}
public static void change(int num1){
num1=10;
}
}
在change
方法中,对num1
的值进行修改,并不会影响到 a
。因为num1
的值只是从 a
的复制过来的。也就是说,num1
相当于 a
的副本,副本的内容无论怎么修改,都不会影响到原件本身。
通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样
2)传递引用数据类型
public class Demo02 {
public static void main(String[] args) {
Perosn perosn = new Perosn();
System.out.println(perosn.name); //nuLL
Demo02.change(perosn);
System.out.println(perosn.name); //NNN
}
public static void change(Perosn perosn1){
//perosn是一个对象:指向的---> Perosn perosn = new Perosn();
//这是一个具体的人, 可以改变属性!
perosn1.name = "NNN";
}
}
//定义了一个Perosn类,有一个属性: name
class Perosn{
String name; //默认值null
}
看了这个案例很多人肯定觉得 Java 对引用类型的参数采用的是引用传递。
实际不然,这里传递的还是值,只是这个值是实参的地址。也就是说 change
方法的参数拷贝的是 perosn
(实参)的地址,因此,它和 person
指向的是同一个对象。这也就说明了为什么方法内部对形参的修改会影响到实参。
为了证明Java对引用数据类型采用的不是引用传递,我们再来看下面这个案例!
public class Person {
private String name;
// 省略构造函数、Getter&Setter方法
}
public static void main(String[] args) {
Person xiaoZhang = new Person("小张");
Person xiaoLi = new Person("小李");
swap(xiaoZhang, xiaoLi);
System.out.println("xiaoZhang:" + xiaoZhang.getName());
System.out.println("xiaoLi:" + xiaoLi.getName());
}
public static void swap(Person person1, Person person2) {
Person temp = person1;
person1 = person2;
person2 = temp;
System.out.println("person1:" + person1.getName());
System.out.println("person2:" + person2.getName());
}
//输出
person1:小李
person2:小张
xiaoZhang:小张
xiaoLi:小李
两个引用类型的形参互换并没有影响实参!swap
方法的参数person1
和person2
只是拷贝的实参xiaoZhang
和xiaoLi
的地址。因此,person1
和person2
的互换只是拷贝的两个地址的互换罢了,并不会影响到实参xiaoZhang
和xiaoLi
。
2.引用传递
以 C++
的代码为例,看一下引用传递的庐山真面目。
#include <iostream>
void incr(int& num)
{
std::cout << "incr before: " << num << "\n";
num++;
std::cout << "incr after: " << num << "\n";
}
int main()
{
int age = 10;
std::cout << "invoke before: " << age << "\n";
incr(age);
std::cout << "invoke after: " << age << "\n";
}
//输出
invoke before: 10
incr before: 10
incr after: 11
invoke after: 11
可以看到,在incr
函数中对形参的修改,可以影响到实参的值。要注意:这里的incr
形参的数据类型用的是int&
才为引用传递,如果是用int
的话还是值传递哦!
3.Java的String类型
public class Test {
public static void main(String[] args) {
String a = "A";
append(a);
System.out.println(a);//A
}
static void append(String str){
str+="is a";
}
}
String毫无疑问不是基本数据类型,因此传递的是引用对象的内存地址。但为什么值没有被修改呢?
答:通过查看源码可知,String类是使用char类型的value数组来存储字符串的,final修饰,所以String类的值(实例对象)不可修改,即final修饰符导致了String一旦定义就是最终形态,任何试图改变String值的操作都只能重新开辟地址。因此当我们使用代码str="xxxx"
的时候,会返回一个新的String对象。