Java 方法参数的值传递和引用传递

时间:2021-12-29 21:27:44

Java 方法参数的值传递和引用传递

在Java中,实际上都是值传递,并不存在引用传递。人们所说的引用传递,实则为指针间地址的传递。

要理解此,我们必须明白一个概念,形如

Object o;

这样的定义,实际上我们在定义一个指针。当我们把此指针传入方法形参,实则是把此指针所指向的地址传递给了方法形参,而不是对象本体的传入,也就是没有进行真正意义上的引用传递了。关于此问题的更多分析,请参阅笔者之前的一篇译文《我靠!Java就是值传递!》

举例分析

最常见的例子


class Student {
    int id;

    public Student(int id) {
        super();
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Student [id=" + id + "]";
    }
}

public class Main {

    public static void changeId(Student student, int newId) {
        student.setId(newId);
        student = new Student(-1);
    }

    public static void main(String[] args) {
        Student student = new Student(10);
        System.out.println(student);
        changeId(student, 0x7fffffff);
        System.out.println(student);
    }
}

控制台输出:

Student [id=10]
Student [id=2147483647]

传入的student将自身持有的地址赋给形参student。方法的功能为改变学号,那么方法会利用形参改变形参所指向的Student对象的Id。在方法的第二句中,方法改变了形参的指向,将一个新对象的地址赋值给形参student。但注意实参student并没有改变指向,最后的输出依旧是原来的那个对象, 且ID为为未改变形参指向时新赋予的0x7fffffff。此案例再次证明了Java中仅有“值传递”的概念。

一个关于Integer的例子

猜猜下面的输出:

public class Main {

    public static void inc(Integer integer) {
        integer+=1;
        System.out.println("inc: " + integer.toString());
    }

    public static void main(String[] args) {
        Integer integer = new Integer(10);
        inc(integer);
        System.out.println(integer);
    }
}

相信一定会有人认为最后这个Integer对象会改为11,实际上并非如此。控制台输出如下:

inc: 11
10

在inc方法中形参已经改为11了,但是实参并没有,什么鬼?首先我们应该理解Integer是一个典型的不可变类。Integer中没有对值的set相关方法,如果为一个Integer赋予新的值,那么它实际上会指向一块新的内存区域,而非将原有指向的区域的值改变。回头看着inc方法,首先实参integer把自己的地址传给形参integer,然后在方法体中对形参integer进行加一操作。正如上所述,integer实际上没有改变原来指向10,而是指向了新的地址,这个地址存有11这个数。这也就是为什么在最后依旧输出10的原因了。

那么如何解决无法改变外部Integer的这个问题呢?在未来遇到这个需求时我们该作何处理?

一个可行的方法就是定义自己的整型包装类,内部封装一个int值,然后为这个值添加setter和gatter。

总结

明白Java中只有值传递的概念,但是注意对不可变类(如Integer)的特别分析。