通过JVM内存模型深入理解值传递和引用传递两种方式

值传递和引用传递分析
Java中数据类型分为两大类:基本类型和引用类型(也就是对象类型)。
基本类型:boolean、char、byte、short、int、long、float、double
引用类型:类、接口、数组
因此,变量类型也可分为两大类:基本类型和引用类型。
在分析值传递和引用传递之前,建议了解下以上变量类型在Java内存管理模型中的位置,如果对此有所了解,将更加有助于理解两种传递的方式^_^

在Java内存中,基本类型变量存储在Java栈(VM Stack)中,引用变量存储在堆(Heap)中,模型如下:

通过JVM内存模型深入理解值传递和引用传递两种方式_第1张图片

值传递和引用传递的定义:

这里要用实际参数和形式参数的概念来帮助理解

值传递:
方法调用时,实际参数把它的值传递给对应的形式参数,函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法中的操作都是对形参这个值的修改,不影响实际参数的值。

引用传递:
也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址;在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象。


Demo代码:


package cn.roc.other;

/**
 * @author Roc
 * @desc Java中参数传递方式分为值传递和引用传递
 * 1)基本类型变量
 * @date created on 2017/12/31
 */
public class ArgumentsPassTypeTest {
    public static void main(String[] args) {
        System.out.println(" 值传递测试 ");
        int a = 10;
        int b = 20;
        System.out.println("before swap " + "a = " + a + " b = " + b);
        swap(a, b);
        System.out.println("after swap " + "a = " + a + " b = " + b);

        System.out.println("-------------------------------------------------------------");

        System.out.println(" 引用传递测试 ");
        ReferenceObj obj = new ReferenceObj();
        System.out.println("before swapByReference: count = " + obj.count);
        swapByReference(obj);
        System.out.println("after swapByReference: count = " + obj.count);

        System.out.println("-------------------------------------------------------------");

        //StringCharByteShortIntegerLongFloatDoublefinal修饰的类
        //对形参修改时实参不受影响
        System.out.println(" final修饰的类-特殊的引用传递测试 ");
        String str = "我是final我不变";
        swapByFinalClass(str);

        System.out.println("after swapByFinalClass,  str = " + str);
    }

    /**
     * 值传递方式    基本类型
     * @param a
     * @param b
     */
    public static void swap(int a, int b) {
        int temp = a;
        a = b;
        b = temp;
        System.out.println("swaping " + "a = " + a + " b = " + b);
    }

    /**
     * 引用传递方式   类、数组、接口
     * @param obj
     */
    public static void swapByReference(ReferenceObj obj) {
        obj.count = 0;
        System.out.println("swaping : count = " + obj.count);
    }

    /**
     * final修饰的类做形参时, 修改形参不影响实参
     * @param str
     */
    public static void swapByFinalClass(String str) {
        str = "我是形参";
        System.out.println("swapByFinalClassing : str = " + str);
    }
}

class ReferenceObj{
    int count = 99;
}


输出结果:
值传递测试
before swap a = 10 b = 20
swaping a = 20 b = 10
after swap a = 10 b = 20
-------------------------------------------------------------
 引用传递测试 
before swapByReference: count = 99
swaping : count = 0
after swapByReference: count = 0
-------------------------------------------------------------
 final修饰的类-特殊的引用传递测试 
swapByFinalClassing : str = 我是形参
after swapByFinalClass,  str = 我是final我不变



1)使用基本类型的变量a、b通过swap方法进行的是值传递,对形参修改但实参未改变,利用内存模型详解原理:
  通过JVM内存模型深入理解值传递和引用传递两种方式_第2张图片


2)使用类ReferenceObj的实例变量obj,通过swapByReference()进行的是引用传递的方式,具体的内存模型如下:

通过JVM内存模型深入理解值传递和引用传递两种方式_第3张图片

 
通过上面的分析,对于传递方式应该很好理解了^_^

注意:这里要特殊考虑String,以及Integer、Double等基本类型包装类,它们的类前面都有final修饰,为不可变的类对象,每次操作(new或修改值)都是新生成一个对象,对形参的修改时,实参不受影响,与值传递的效果类似,但实际上仍是引用传递。

总结:

1)基本类型变量作为方法中的参数,进行的值传递,对形参的修改不影响实参的原来的值;
2)非final修饰的类、数组、接口作为方法中的参数,进行的引用传递(地址传递),对形参修改后实参也会改变,因为二者指向的是同一个实例;
3)final修饰的类作为方法中的参数,因为final的存在初始化后值不可变,每次操作都相当于产生一个新的实例对象,因此对形参修改时,实参也不受影响。

你可能感兴趣的:(Java)