1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00
Java-Interview-Guide/docs/java/basis/why-there-only-value-passing-in-java.md
2023-12-30 17:14:13 +08:00

220 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: Java 值传递详解
category: Java
tag:
- Java基础
---
开始之前,我们先来搞懂下面这两个概念:
- 形参&实参
- 值传递&引用传递
## 形参&实参
方法的定义可能会用到 **参数**(有参的方法),参数在程序语言中分为:
- **实参实际参数Arguments**:用于传递给函数/方法的参数,必须有确定的值。
- **形参形式参数Parameters**:用于定义函数/方法,接收实参,不需要有确定的值。
```java
String hello = "Hello!";
// hello 为实参
sayHello(hello);
// str 为形参
void sayHello(String str) {
System.out.println(str);
}
```
## 值传递&引用传递
程序设计语言将实参传递给方法(或函数)的方式分为两种:
- **值传递**:方法接收的是实参值的拷贝,会创建副本。
- **引用传递**:方法接收的直接是实参所引用的对象在堆中的地址,不会创建副本,对形参的修改将影响到实参。
很多程序设计语言(比如 C++、 Pascal )提供了两种参数传递的方式,不过,在 Java 中只有值传递。
## 为什么 Java 只有值传递?
**为什么说 Java 只有值传递呢?** 不需要太多废话,我通过 3 个例子来给大家证明。
### 案例 1传递基本类型参数
代码:
```java
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}
```
输出:
```plain
a = 20
b = 10
num1 = 10
num2 = 20
```
解析:
`swap()` 方法中,`a``b` 的值进行交换,并不会影响到 `num1``num2`。因为,`a``b` 的值,只是从 `num1``num2` 的复制过来的。也就是说a、b 相当于 `num1``num2` 的副本,副本的内容无论怎么修改,都不会影响到原件本身。
![](https://oss.javaguide.cn/github/javaguide/java/basis/java-value-passing-01.png)
通过上面例子,我们已经知道了一个方法不能修改一个基本数据类型的参数,而对象引用作为参数就不一样,请看案例 2。
### 案例 2传递引用类型参数 1
代码:
```java
public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}
public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}
```
输出:
```plain
1
0
```
解析:
![](https://oss.javaguide.cn/github/javaguide/java/basis/java-value-passing-02.png)
看了这个案例很多人肯定觉得 Java 对引用类型的参数采用的是引用传递。
实际上,并不是的,这里传递的还是值,不过,这个值是实参的地址罢了!
也就是说 `change` 方法的参数拷贝的是 `arr` (实参)的地址,因此,它和 `arr` 指向的是同一个数组对象。这也就说明了为什么方法内部对形参的修改会影响到实参。
为了更强有力地反驳 Java 对引用类型的参数采用的不是引用传递,我们再来看下面这个案例!
### 案例 3传递引用类型参数 2
```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());
}
```
输出:
```plain
person1:小李
person2:小张
xiaoZhang:小张
xiaoLi:小李
```
解析:
怎么回事???两个引用类型的形参互换并没有影响实参啊!
`swap` 方法的参数 `person1``person2` 只是拷贝的实参 `xiaoZhang``xiaoLi` 的地址。因此, `person1``person2` 的互换只是拷贝的两个地址的互换罢了,并不会影响到实参 `xiaoZhang``xiaoLi`
![](https://oss.javaguide.cn/github/javaguide/java/basis/java-value-passing-03.png)
## 引用传递是怎么样的?
看到这里,相信你已经知道了 Java 中只有值传递,是没有引用传递的。
但是,引用传递到底长什么样呢?下面以 `C++` 的代码为例,让你看一下引用传递的庐山真面目。
```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";
}
```
输出结果:
```plain
invoke before: 10
incr before: 10
incr after: 11
invoke after: 11
```
分析:可以看到,在 `incr` 函数中对形参的修改,可以影响到实参的值。要注意:这里的 `incr` 形参的数据类型用的是 `int&` 才为引用传递,如果是用 `int` 的话还是值传递哦!
## 为什么 Java 不引入引用传递呢?
引用传递看似很好,能在方法内就直接把实参的值修改了,但是,为什么 Java 不引入引用传递呢?
**注意:以下为个人观点看法,并非来自于 Java 官方:**
1. 出于安全考虑,方法内部对值进行的操作,对于调用者都是未知的(把方法定义为接口,调用方不关心具体实现)。你也想象一下,如果拿着银行卡去取钱,取的是 100扣的是 200是不是很可怕。
2. Java 之父 James Gosling 在设计之初就看到了 C、C++ 的许多弊端,所以才想着去设计一门新的语言 Java。在他设计 Java 的时候就遵循了简单易用的原则,摒弃了许多开发者一不留意就会造成问题的“特性”,语言本身的东西少了,开发者要学习的东西也少了。
## 总结
Java 中将实参传递给方法(或函数)的方式是 **值传递**
- 如果参数是基本类型的话,很简单,传递的就是基本类型的字面量值的拷贝,会创建副本。
- 如果参数是引用类型,传递的就是实参所引用的对象在堆中地址值的拷贝,同样也会创建副本。
## 参考
- 《Java 核心技术卷 Ⅰ》基础知识第十版第四章 4.5 小节
- [Java 到底是值传递还是引用传递? - Hollis 的回答 - 知乎](https://www.zhihu.com/question/31203609/answer/576030121)
- [Oracle Java Tutorials - Passing Information to a Method or a Constructor](https://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html)
- [Interview with James Gosling, Father of Java](https://mappingthejourney.com/single-post/2017/06/29/episode-3-interview-with-james-gosling-father-of-java/)
<!-- @include: @article-footer.snippet.md -->