Java笔记 —— 值传递与“引用传递”

在 Java 开发中,关于参数传递的讨论经久不衰。很多初学者(甚至一些有经验的开发者)都会困惑:Java 到底是值传递还是引用传递? 本文将彻底澄清这个问题,通过代码示例和内存模型分析,证明一个事实:Java 只有值传递,不存在真正的"引用传递"。

一、什么是值传递?什么是引用传递?

在深入 Java 之前,我们先明确两个概念:

  • 值传递 :方法调用时,实参将它的传递给形参。形参得到的是实参的副本,在方法内部对形参的修改不会影响实参本身。

  • 引用传递 :方法调用时,实参将它的引用地址传递给形参。形参和实参指向同一个内存地址,对形参的修改会直接影响实参。

很多编程语言(如 C++)同时支持这两种方式,但 Java 只支持值传递

二、Java 中的两种数据类型

要理解 Java 的传递机制,首先要了解 Java 中的两种数据类型:

  • 基本类型byteshortintlongfloatdoublecharboolean。变量本身存储的就是具体的数值。

  • 引用类型 :类、接口、数组等。变量存储的是对象的内存地址(即引用),而不是对象本身。

三、基本类型的传递:值传递的直观体现

先看一个基本类型的例子:

java 复制代码
public class PrimitiveTest {
    public static void main(String[] args) {
        int a = 10;
        System.out.println("调用前 a = " + a); // 10
        change(a);
        System.out.println("调用后 a = " + a); // 10
    }

    public static void change(int x) {
        x = 20;
        System.out.println("方法内 x = " + x); // 20
    }
}

输出:

java 复制代码
调用前 a = 10
方法内 x = 20
调用后 a = 10

分析

  • a 的值是 10。

  • 调用 change(a) 时,将 a 的值(10)复制一份给形参 x

  • 在方法内部修改 x 为 20,仅仅改变了形参副本的值,a 本身不受影响。

  • 这清晰地展示了 值传递:传递的是值的副本。

四、引用类型的传递:表面看起来像引用传递,实则还是值传递

引用类型的例子更容易让人误解。先看代码:

java 复制代码
public class ReferenceTest {
    public static void main(String[] args) {
        Person p = new Person("张三");
        System.out.println("调用前姓名:" + p.getName()); // 张三
        changeName(p);
        System.out.println("调用后姓名:" + p.getName()); // 李四
    }

    public static void changeName(Person person) {
        person.setName("李四");
        System.out.println("方法内姓名:" + person.getName()); // 李四
    }
}

class Person {
    private String name;
    public Person(String name) { this.name = name; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

输出:

java 复制代码
调用前姓名:张三
方法内姓名:李四
调用后姓名:李四

看起来方法内部修改了对象,外部也受到了影响,这难道不是引用传递吗?
不是! 这里传递的依然是"值",只不过这个值是对象的引用地址

内存分析

  1. Person p = new Person("张三");

    • 在堆内存中创建一个 Person 对象(地址假设为 0x123)。

    • 栈中的变量 p 存储了这个地址 0x123

  2. changeName(p);

    • p 的值(地址 0x123复制 一份给形参 person

    • 此时 person 也指向了同一个对象 0x123

  3. person.setName("李四");

    • 通过 person 这个引用找到堆中的对象,修改其属性。

    • 因为 pperson 指向同一个对象,所以通过 p 访问时,看到的是修改后的内容。

关键点:传递的是引用的值(地址),而不是引用本身。形参和实参是两个不同的变量,但它们存储的地址值相同,所以操作的是同一个对象。如果尝试修改形参的指向,外部不会受影响:

java 复制代码
public static void changeReference(Person person) {
    person = new Person("王五"); // 修改形参的引用
    System.out.println("方法内姓名:" + person.getName()); // 王五
}

调用:

java 复制代码
Person p = new Person("张三");
changeReference(p);
System.out.println("调用后姓名:" + p.getName()); // 张三

输出:

java 复制代码
方法内姓名:王五
调用后姓名:张三

可见,虽然形参 person 被重新赋值为一个新对象,但外部的 p 仍然指向原来的对象,因为形参只是实参的副本,改变副本的指向不会影响实参。

五、数组和 String 的特殊性

5.1 数组

数组也是引用类型,传递的是数组引用的副本:

java 复制代码
public class ArrayTest {
    public static void main(String[] args) {
        int[] arr = {1, 2, 3};
        changeArray(arr);
        System.out.println(arr[0]); // 100
    }

    public static void changeArray(int[] a) {
        a[0] = 100;
    }
}

因为 aarr 指向同一个数组对象,所以修改数组内容会反映到外部。

5.2 String 的不可变性

String 虽然是引用类型,但它是不可变的,且设计为常量池优化,可能会让人误解:

java 复制代码
public class StringTest {
    public static void main(String[] args) {
        String s = "hello";
        changeString(s);
        System.out.println(s); // hello
    }

    public static void changeString(String str) {
        str = "world";
    }
}

这里 s 仍然输出 "hello"。因为 str = "world" 只是让形参指向了常量池中的另一个字符串,并没有改变实参 s 的引用。如果尝试修改 String 的内容(String 没有提供修改方法),就更不可能了。

六、常见误区与总结

误区一:"Java 对基本类型是值传递,对引用类型是引用传递"

这是错误的。Java 对所有类型都是值传递,区别仅在于:

  • 基本类型传递的是数据值。

  • 引用类型传递的是引用值(内存地址)。

误区二:"传递对象时,方法内修改对象属性会影响外部,所以是引用传递"

这混淆了"传递机制"和"操作结果"。即使形参和实参指向同一对象,修改对象属性是"通过引用操作对象"的必然结果,但不能因此认为参数传递方式是引用传递。引用传递要求形参和实参本身是同一个变量(即形参是实参的别名),而 Java 中形参只是实参的副本。

误区三:"C/C++ 中可以通过指针实现引用传递,Java 没有指针,所以只能值传递"

Java 的引用本质上就是受限的指针,但它不支持直接取地址或指针运算。参数传递时,引用的值被复制,因此是值传递。

七、总结

  1. Java 只有值传递,没有引用传递。

  2. 基本类型:传递的是数值本身,方法内修改形参不影响实参。

  3. 引用类型 :传递的是引用的副本(地址值),方法内通过形参修改对象属性会影响实参指向的对象;但若修改形参的指向(如 person = new Person()),不影响实参。

  4. 理解这一机制有助于写出更健壮的代码,避免因误以为"引用传递"而犯下的错误。

相关推荐
chushiyunen2 小时前
python语法-继承、方法命名、单例等
开发语言·python
ljt27249606612 小时前
Flutter笔记--事件处理
笔记·flutter
2301_792674862 小时前
java学习day22
java
沐知全栈开发2 小时前
Chart.js 饼图详解
开发语言
于慨2 小时前
spring boot
java·数据库·spring boot
码云数智-大飞2 小时前
迈向 99.99%:高可用系统架构的哲学与实战
开发语言
Amnesia0_02 小时前
类型转换和特殊类
开发语言·c++
always_TT2 小时前
static关键字初探
java·开发语言
飞鸟真人2 小时前
使用netty4写一个UDP的echo服务(笔记)
笔记