本文分析下java对象当做参数传递时在方法内部是否可改变的问题


目录

  • 1、值传递 VS 引用传递
  • 2、基本类型作为参数传递
  • 3、引用对象作为参数传递



java中对象作为参数传递时只有值传递,没有引用传递,即在方法内部传入参数对象的引用永远不会改变,改变的只有可能是值

1、值传递 VS 引用传递

首先,我们必须要搞清楚,到底什么是值传递,什么是引用传递,否则,讨论 Java 到底是值传递还是引用传递就显得毫无意义。

当一个参数按照值的方式在两个方法之间传递时,调用者和被调用者其实是用的两个不同的变量——被调用者中的变量(原始值)是调用者中变量的一份拷贝,对它们当中的任何一个变量修改都不会影响到另外一个变量。

而当一个参数按照引用传递的方式在两个方法之间传递时,调用者和被调用者其实用的是同一个变量,当该变量被修改时,双方都是可见的。

Java 程序员之所以容易搞混值传递和引用传递,主要是因为 Java 有两种数据类型,一种是基本类型,比如说 int,另外一种是引用类型,比如说 String。

基本类型的变量存储的都是实际的值,而引用类型的变量存储的是对象的引用——指向了对象在内存中的地址。值和引用存储在 stack(栈)中,而对象存储在 heap(堆)中。

2、基本类型作为参数传递

传递的参数仅仅只是基本类型对象的一个值副本,因为基本类型没有引用可言,所以在方法内改变基本类型的值不会对原有对象做任何改变

public class Test3 {
    
    public void add(int value) {
    	// 调用进入后在栈中创建了变量value值为a1的值(变量和值都存储在栈),
    	// 所以无论怎么修改value都不会对传进来的变量造成影响
        value = 2;
    }

    public static void main(String[] args) {
        Test3 test3 = new Test3();
        int a1 = 0;
        test3.add(a1);
        System.out.println("a1:" + a1);//不改变原有对象值 a1:0
    }

}

3、引用对象作为参数传递

可以改变对象的值,不能改变对象的引用

public class Test22 {

    void add(List<String> list1) {
        // 进入后相当于在栈中创建了一个变量list1并且指向了
        // 调用方传进来的list在堆中的内存地址
        // 即此时list1和调用的客户端的list变量指向堆内存中的同一个对象
        // 那么修改堆中的对象会同时影响list和list1变量
        // 重新赋值了list和list1不会改变堆中的对象
        list1.add("list2");
        list1 = new ArrayList<>();
    }

    void change(List<String> list) {
        // 修改了list的引用
        list = new ArrayList<>();
    }

    public static void main(String[] args) {
        Test22 test22 = new Test22();
        List<String> list = new ArrayList<>();
        list.add("list1");
        test22.add(list);
        System.out.println(list); // [list1, list2]
        test22.change(list);
        System.out.println(list); // [list1, list2] 引用不会改变
        // list传递了多个方法后,最后引用还是不可变的
    }
}

下面再看一个面试题,如下如何在最后输出a1=2

public static void main(String[] args) {
        Test3 test3 = new Test3();
        Integer a1 = 1;
        test3.add(a1);
        System.out.println("a1:" + a1);
    }

由上规则可得,Integer a1在方法内部的值可以改变,引用不可改变,那么我们只能修改a1的值。

public void add(Integer value) {
	// 这里可以理解为在方法内部栈上创建了一个变量value指向了堆内存中a1的内存地址
	// 所以此时value==a1,因为都指向了堆中的同一个对象
    value = 2;// 等同于Integer.valueOf(2),此时变量value指向堆中的另外一个内存地址,所以value!=a1
}

如下代码看似修改了a1的值,但是实际是改变了a1的引用(因为Integer是不可变类)实际代码等同于value = new Integer(2);那么应该怎么修改Integer的值呢,我们看下Integer的源码

public final class Integer extends Number implements Comparable<Integer> {
    public static final int MIN_VALUE = -2147483648;
    public static final int MAX_VALUE = 2147483647;
    public static final Class<Integer> TYPE = Class.getPrimitiveClass("int");
    static final char[] digits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
    static final char[] DigitTens = new char[]{'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '1', '1', '1', '1', '1', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '2', '2', '2', '2', '2', '3', '3', '3', '3', '3', '3', '3', '3', '3', '3', '4', '4', '4', '4', '4', '4', '4', '4', '4', '4', '5', '5', '5', '5', '5', '5', '5', '5', '5', '5', '6', '6', '6', '6', '6', '6', '6', '6', '6', '6', '7', '7', '7', '7', '7', '7', '7', '7', '7', '7', '8', '8', '8', '8', '8', '8', '8', '8', '8', '8', '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'};
    static final char[] DigitOnes = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
    static final int[] sizeTable = new int[]{9, 99, 999, 9999, 99999, 999999, 9999999, 99999999, 999999999, 2147483647};
    // 不可变的value
    private final int value;
    public static final int SIZE = 32;
    public static final int BYTES = 4;
    private static final long serialVersionUID = 1360826667806852920L;
	.......
}

由上可以看出Integer的value被设计成了final,那么我们要怎么改变呢?
我们可以使用无所不能的反射

public void add(Integer value) {
        try {
            Class czz = value.getClass();
            Field field = czz.getDeclaredField("value");
            // 暴力访问,及时是私有变量也可访问
            field.setAccessible(true);
            field.set(value,2);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

最后总结一句话:java只有值传递,没有引用传递