根据一道经典的面试题来探讨Java中的值传递与Java的内存分配机制

Java中的“值传递”机制分析

起因:最近面试时遇到这样一道面试题,这道题能够很好的考察对Java中的参数传递和内存分配机制的理解,虽然当时我回答对了,但是仍然有瑕疵,特此记录总结。

public static void main(String[] args) {
        User a = new User();
        a.setName("Hollis");
        a.setGender("Male");
        f(a);
        System.out.println("print in main , user is " + a);
        String s = "111"
        f2(s)
        System.out.println("string is " + s);
    }
    public static void f(User user) {
        //user=new USer()
        user.setName("hollischuang");
        user.setGender("Male");
    }
    public static void f2(String s) {
        s = "333"
    }

从上面的代码中,我不难看出代码的意图,给一个User对象和一个String对象赋初值后,分别在函数里调用该对象并重新赋值(但没有return)。
面试官当时的问题是:程序运行完毕后,User和String的输出的值应该是多少?即两者的初值是否发生了变化。
正确答案是:User的值变了,变成函数里赋的值,String的值没变
为什么会这样呢? 这就涉及到了Java中的值传递机制以及String类的特殊。

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

基本数据类型与引用数据类型

在Java方法中参数列表有两种类型的参数,基本类型和引用类型。
基本类型:值存放在局部变量表中,无论如何修改只会修改当前栈帧的值,方法执行结束对方法外不会做任何改变;此时需要改变外层的变量,必须返回主动赋值。
引用数据类型:指针存放在局部变量表中,调用方法的时候,副本引用压栈,赋值仅改变副本的引用。但是如果通过操作副本引用的值,修改了引用地址的对象,此时方法以外的引用此地址对象当然被修改。(两个引用,同一个地址,任何修改行为2个引用同时生效)。
这两种类型都是将外面的参数变量拷贝一份到局部变量中,基本类型为值拷贝,引用类型就是将引用地址拷贝一份。
此处只放结论,具体分析过程见 翎野的博客——辨析Java方法参数中的值传递和引用传递

但是为什么String同样是引用数据类型但却其值没有发生改变呢?
这个问题真正原因是因为String类的存储是通过final修饰的char[]数组来存放结果的。不可更改。所以每次当外部一个String类型的引用传递到方法内部时候,只是把外部String类型变量的引用传递给了方法参数变量。对的。外部String变量和方法参数变量都是实际char[]数组的引用而已。所以当我们在方法内部改变这个参数的引用时候,因为char[]数组不可改变,所以每次新建变量都是新建一个新的String实例。很显然外部String类型变量没有指向新的String实例。所以也就不会获取到新的更改。具体分析过程见关于JAVA中String类以形参传递到函数里面,修改后外面引用不能获取到更改后的值

与之类似的,还有Integer,Boolean等对基本数据类型的装箱,
当传入参数为包装类型时,为对象的引用地址拷贝。那么既然是引用拷贝为什么还是没有更改原来的包装类型的变量值呢?
这是因为Java中的自动装箱机制,当在方法中执行 flg = true 时,实际在编译后执行的是 flg = Boolean.valueOf(true),即又会产生一个新的Boolean对象。同理Integer num也是如此。
具体分析过程见 翎野的博客——辨析Java方法参数中的值传递和引用传递

浅谈Java的内存分配机制

Java 把内存划分成两种:一种是栈内存,另一种是堆内存。在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配,当在一段代码块定义一个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作用域后(比如,在函数A中调用函数B,在函数B中定义变量a,变量a的作用域只是函数B,在函数B运行完以后,变量a会自动被销毁。分配给它的内存会被回收),Java 会自动释放掉为该变量分配的内存空间,该内存空间可以立即被另作它用。
堆内存用来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或者对象之后,还可以在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象起的一个名称。引用变量是普通的变量,定义时在栈中分配,引用变量在程序运行到其作用域之外后被释放。而数组和对象本身在堆中分配,即使程序运行到使用 new 产生数组或者对象的语句所在的代码块之外,数组和对象本身占据的内存不会被释放,数组和对象在没有引用变量指向它的时候,才变为垃圾,不能在被使用,但仍然占据内存空间不放,在随后的一个不确定的时间被垃圾回收器收走(释放掉)。这也是 Java 比较占内存的原因,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!
具体分析可见xwdreamer——Java的内存机制

PS: 本想着好好做一个全面的分析,但是Java的内存机制相关的内容太多了,最近时间安排比较紧,画图写程序来说明等等流程比较繁琐,因此记录下了结论,分析的过程则留下了查资料过程中看到的写的不错的博客的链接,这样做一个暂时的内容的总结,方便后续回顾。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值