乐趣区

关于前端:Java-参数传递到底是按-值传递-还是-引用传递

前言

首先明确,Java 中办法参数传递形式是按值传递 。对于根本类型(int a, long b),参数传递时传递的是值,例如 int a = 5,传递的就是 5。如果是援用类型,传递是指向具体对象内存地址的地址值,例如用 System.out.println(new Object()) 打印进去的 java.lang.Object@7716f4 中 @符号前面的 7716f4 就是 16 进制的内存地址,System.out.println 实际上是默认调用了对象的 toString 办法,

public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

能够看到 7716f4 是由 hashCode()输入的,如果有对象重写了 hashCode 办法,那输入的有可能就不是对象的初始内存地址了,所以如果要精确取得对象的初始地址倡议调用 System.identityHashCode()。

值得一提的是,在 Java 中获取一个对象的内存地址个别没有什么意义,因为它可能在程序运行过程中随着垃圾回收等动作被 JVM 更改。不过在上面咱们能够依据援用的对象地址是否雷同来看看参数传递的各种状况。

举例说明

根本类型作为参数传递

public class ValuePass {public static void main(String[] args) {

        // 值传递举例
        int num = 10;
        System.out.println("改之前的值:" + num);
        modify(num);
        System.out.println("改之后的值:" + num);
    }

    private static void modify(int num2) {num2 = 11;}
}

输入后果为

改之前的值:10
改之后的值:10

通过这个例子,阐明 根本数据类型作为参数传递时,传递的是值的拷贝,无论怎么扭转这个拷贝,原值是不会扭转的。

对象作为参数传递

对象这里能够再划分一下,分为一般对象,汇合类型和数组类型。上面顺次来看一下成果

一般对象

public class ReferenceBasicPass {

输入后果

实参 node 指向的内存地址为:366712642

这阐明,援用对象参数传递时,传递的是指向实在对象的地址,而函数中的形参 node 拿到同样的地址时,通过 node.setVal(11),会通过地址找到实在的对象进行操作。这里 TreeNode 没有重写 hashCode 办法,所以

汇合对象

因为 ArrayList 重写了 hashcode()办法,所以这里应用 System.identityHashCode 拿到地址值。

public class ReferenceBasicPass {

    static class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
        public TreeNode(int x) {val = x;}

        public void setVal(int val) {this.val = val;}

        public int getVal() {return val;}
    }

    public static void main(String[] args) {

        // 一般对象
        TreeNode node = new TreeNode(10);
        System.out.println("实参 node 指向的内存地址为:" + node.hashCode());
        System.out.println("改之前的值:" + node.getVal());
        modify(node);
        System.out.println("改之后的值:" + node.getVal());
    }

    private static void modify(TreeNode node) {System.out.println("形参 node 指向的内存地址为:" + node.hashCode());
        // 援用了同一块地址,操作了同一块堆内存
        node.setVal(11);
    }

}

输入后果为

实参 node 指向的内存地址为:366712642
改之前的值:10
形参 node 指向的内存地址为:366712642
改之后的值:11

对于汇合,传递的也是援用的地址,函数内通过形参失去援用地址的拷贝后再操作实在对象,导致实参拜访实在对象时曾经被批改过了。如果形参指向了新的内存地址,则批改不会影响到原对象的值。

注:JsonUtils 是用 Jackson 实现的。

数组

一般数组,和汇合一样是援用类型

public class ReferencePass {public static void main(String[] args) {

        // 汇合对象
        List<TreeNode> nodes = new ArrayList<>();
        nodes.add(new TreeNode(1));
        nodes.add(new TreeNode(2));
        System.out.println("批改之前实参 node 指向的内存地址为:" + System.identityHashCode(nodes));
        System.out.println("批改之前实参 node 指向地址寄存的对象内容为:" + JsonUtils.toJson(nodes));
        modify(nodes);
        System.out.println("批改之后实参 node 指向的内存地址为:" + System.identityHashCode(nodes));
        System.out.println("批改之后实参 node 指向地址寄存的对象内容为:" + JsonUtils.toJson(nodes));

        System.out.println("\n------------------------------------------------\n");
        modify2(nodes);
        System.out.println("再次批改之后实参 node 指向的内存地址为:" + System.identityHashCode(nodes));
        System.out.println("再次批改之后的实参 nodes 指向地址寄存的对象内容为:" + JsonUtils.toJson(nodes));
    }

    private static void modify(List<TreeNode> nodes) {
        // 援用了同一块地址,操作了同一块堆内存
        nodes.add(new TreeNode(3));
    }

    private static void modify2(List<TreeNode> nodes) {System.out.println("形参 nodes 指向的内存地址:" + nodes.hashCode());
        // 形参 nodes 指向了新的内存地址,对其进行操作然而不影响实参指向的内存地址的实在对象
        nodes = new ArrayList<>();
        nodes.add(new TreeNode(5));
        System.out.println("形参 nodes 指向的新内存地址:" + nodes.hashCode());
        System.out.println("形参 nodes 指向新地址寄存的对象内容为:" + JsonUtils.toJson(nodes));
    }
}

输入

批改之前实参 node 指向的内存地址为:366712642
批改之前实参 node 指向地址寄存的对象内容为:[{"val":1},{"val":2}]
批改之后实参 node 指向的内存地址为:366712642
批改之后实参 node 指向地址寄存的对象内容为:[{"val":1},{"val":2},{"val":3}]

------------------------------------------------

形参 nodes 指向的内存地址:1110478811
形参 nodes 指向的新内存地址:1458540949
形参 nodes 指向新地址寄存的对象内容为:[{"val":5}]
再次批改之后实参 node 指向的内存地址为:366712642
再次批改之后的实参 nodes 指向地址寄存的对象内容为:[{"val":1},{"val":2},{"val":3}]

数组与汇合的状况也是一样的。

根本类型的包装类型

值得注意的是,对于根本类型的包装类型,其参数传递也是属于地址值传递;

public class ReferenceArrayPass {public static void main(String[] args) {

        // 一般数组,和汇合一样是援用类型,数组实质上也是
        int[] ints = new int[3];
        ints[0] = 1;
        ints[1] = 2;
        System.out.println("实参 ints 指向的内存地址为:" + System.identityHashCode(ints));
        System.out.println("批改之前 ints 索引为 2 的值" + ints[2]);
        modify(ints);
        System.out.println("批改之后 ints 索引为 2 的值" + ints[2]);
        // 一般数组的 class 为[I,I 示意 int 型
        System.out.println(ints.getClass());
    }

    private static void modify(int[] ints) {
        // 援用了同一块地址,操作了同一块堆内存
        System.out.println("形参 ints 指向的内存地址为:" + System.identityHashCode(ints));
        ints[2] = 3;
    }

}

输入后果为

实参 ints 指向的内存地址为:366712642
批改之前 ints 索引为 2 的值:0
形参 ints 指向的内存地址为:366712642
批改之后 ints 索引为 2 的值:3

而因为 jdk1.5 以上的主动装箱个性,Integer i = 20 等价于执行 Integer i = Integer.valueOf(20),valueOf()办法参看源码会依据传入的数值 如果在 -128-127 之间 就从常量池中获取一个 Integer 对象返回,如果不在范畴内 会 new Integer(20)返回。

即是说 Integer 的地址会随着值的扭转而扭转,这其实就是援用类型的赋值,指向了新的内存地址了,例如下面 integer = 21 的例子,即等价于 integer = Integer.valueOf(21),不论 21 之前是否有创立过,integer 都指向了新的内存地址,然而并不影响实参,内部仍旧是 20

退出移动版