你真的理解Java的按引用传递吗?

首先我们来看下面这段代码:

public class Test1 {
    String a = "123";
    public static void change(Test1 test) {
        test.a="abc";
    }
    public static void main(String[] args) {
        Test1 test1=new Test1();
        System.out.println(test1.a);
        change(test1);
        System.out.println(test1.a);
    }
}

结果输出123 abc 相信大家都能做对这道题目。Java是按引用传递的,在函数里面可以修改对象的值。我们再看下面的代码:

public class Test2{
    public static void main(String[] args) {
        String str = "123";
        System.out.println(str);
        change(str);
        System.out.println(str);
    }
    public static void change(String str){
        str = "abc";
    }
}

你认为会输出多少?总之我周围的好几个人都说会输出123 abc。因为在Java中String不是基本数据类型,会传递引用,所以在change方法里面可以修改str的值。 
可是实际的输出确实是123 123,是不是有点毁三观呢?

误区1:

有人说可能是这里String定义的方式有问题,因为这里的定义方式String str = “123”,可能存在问题,java会将其存放在字符串常量池中(当再次声明一个同样内容的字符串时将会使用字符串常量池中原来的那个内存,而不会重新分配) 
写成

JavaString str = new String(“123”);

str是指向堆区的内存会传递引用。可是结果依然打印 123 123

误区2:String是不可变的

还有人查看String文档发现

Strings are constant; their values cannot be changed after they are created. String buffers support mutable strings. Because String objects are immutable they can be shared.For example:

String str = "abc";

is equivalent to:

 char data[] = {‘a‘, ‘b‘, ‘c‘};
 String str = new String(data);

可以看到官方文档解释了因为String是不可以改的。所以有人认为这就是在函数里面无法修改String值得原因。

请看下面的代码

public class Test3{
    public static void main(String[] args) {
        String str = new String("123");
        str = new String("abc");
        System.out.println(str);
    }
}

按照这种理解,这里的String应该还打印123而不是abc。显然这不是瞎扯么。

文档中说StringBuffer是可变的我们用StringBuffer试试

public class Test4{
    public static void main(String[] args) {
        StringBuffer str = new StringBuffer("123");
        System.out.println(str);
        change(str);
        System.out.println(str);
    }
    public static void change(StringBuffer str){
        str = new StringBuffer("abc");
        // str.append(abc);
    }
}

依然打印123 123,可见根本不是这个原因,并且对可变不可变的理解有误。

传递引用?

于是疑问就产生了,传引用到底是什么意思? 
学过C/C++的人理解起来可能会容易一点,在一个函数里面修改一个变量的值就要传递该变量的地址。也就是说通过指针来修改(改变指针变量的值要通过二级指针)。

在Java里面传递引用到底是什么意思?实质上就是将该变量的地址传递了过来,这个角度来看实际上也是按值传递的。

上面的内存图表示new一个变量的时候真正的对象其实在堆区,栈上保存了一个指向堆区存放该对象内存的地址。于是在函数调用的时候传递引用也就是传递这个地址值得一个拷贝。通过这个地址值可以修改这个地址所在堆区的内容。切记,传递的这个地址是按值传递的。在函数里面修改这个值得结果就是失去对堆区对象的指向。函数调用完成后对象没有任何影响。像代码1那样,传递的test1对象的地址,因此test1对象在堆区的值都可以修改,str是他的属性当然也可以修改。

看下面的代码

public class Test5{
    public static void main(String[] args) {
        Object o = new Object();
        System.out.println(o);
        change(o);
        System.out.println(o);
    }
    public static void change(Object o){
        o = null;
    }
}

调用change函数将o置为null,对o对象毫无作用,再次打印o的值照样有值,因为传递的只是o对象地址的一个拷贝,在函数里面修改该变量的值对外面毫无影响。

因此,注意Java中所说的按引用传递实质上是传递该对象的地址,该地址其实是按值传递的,通过这个地址可以修改其指向内存处对象的值。改变该地址的值毫无意义,只会失去对真实对象的掌控。

再来解释一下产生上面误区2的原因。文档中说String不可变StringBuffer可变的意思是堆区的那片内存的可变性。 
对于String类,通过引用无法修改之前在堆区申请的那段内存,大小是固定的,也就是不能修改他的值,因为他的底层是char数组。当再次给变量new一个值时,他会指向另一个堆区内存,从表面上看也是改变了值。 
对于StringBuffer,程序员可以根据实际使用继续分配更多内存,例如调用append方法,这就是可变的意思。比如上面代码4中函数里面的注释那句是可以修改String的值的。




// added at 2015年11月2日10:44:27

附上stackoverflow上关于java传值还是传引用的回答:Is Java “pass-by-reference” or “pass-by-value”?

Java is always pass-by-value. Unfortunately, they decided to call pointers references, thus confusing newbies. Because those references are passed by value.

Java一直是值传递。不幸的是,他们决定把指针叫做引用,因此新人总是被搞晕。因为这些引用也是通过值传递的

原文转自 http://blog.csdn.net/cauchyweierstrass/article/details/49047217

原作者为 weiers. 请尊重原作者版权

时间: 2024-08-10 19:11:25

你真的理解Java的按引用传递吗?的相关文章

测试下你是否真的理解JAVA运行机以及执行顺序

java代码如下所示:class Name {Name() {   Screen.print("8", "Name instance constructor!");}Name(String value) {   Screen.print("9", "Name instance constructor with: " + value);}static {   Screen.print("10", "

别翻了,这篇文章绝对让你深刻理解java类的加载以及ClassLoader源码分析【JVM篇二】

目录 1.什么是类的加载(类初始化) 2.类的生命周期 3.接口的加载过程 4.解开开篇的面试题 5.理解首次主动使用 6.类加载器 7.关于命名空间 8.JVM类加载机制 9.双亲委派模型 10.ClassLoader源码分析 11.自定义类加载器 12.加载类的三种方式 13.总结 14.特别注意 @ 前言 你是否真的理解java的类加载机制?点进文章的盆友不如先来做一道非常常见的面试题,如果你能做出来,可能你早已掌握并理解了java的类加载机制,若结果出乎你的意料,那就很有必要来了解了解j

深入理解Java注解类型(@Annotation)

"-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 深入理解Java注解类型(@Annotation) - zejian的博客 - 博客频道 - CSDN.NET zejian的博客 目录视图 摘要视图 订阅 [活动]2017 CSDN博客专栏评选 &nbsp [5月书讯]流畅的Python,终于等到你!    &am

《深入理解Java虚拟机 JVM高级特性...》核心笔记

深入理解Java虚拟机 JVM高级特性与最佳实践(第二版) 核心笔记 JAVA 环境: JAVA虚拟机高级特性: 一:java内存区域与内存异常 一):运行数据区     1:程序计数器(Program Counter Register),也称"PC寄存器" A:用来指示需要执行哪条指令的.(在汇编语言中,CPU在得到指令之后,程序计数器便自动加1或者根据                    转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令.) B:由于在JVM中,多线程

步步理解 JAVA 泛型编程

步步理解 JAVA 泛型编程 转载自: 隔叶黄莺 Unmi Blog------步步理解 JAVA 泛型编程(一) 隔叶黄莺 Unmi Blog------步步理解 JAVA 泛型编程(二) 隔叶黄莺 Unmi Blog------步步理解 JAVA 泛型编程(三) 步步理解 JAVA 泛型编程(一) JDK 1.5 相对于 JDK 1.4 来说变化最大的部分就是泛型,甚至可以说自 Java 1.0 发布以来也是最大的一次语言变化,因为要涉及到编译器的大改动.很早的时候大家对泛型的呼声很大,正如

深入理解Java引用类型

深入理解Java引用类型 在Java中类型可分为两大类:值类型与引用类型.值类型就是基本数据类型(如int ,double 等),而引用类型,是指除了基本的变量类型之外的所有类型(如通过 class 定义的类型).所有的类型在内存中都会分配一定的存储空间(形参在使用的时候也会分配存储空间,方法调用完成之后,这块存储空间自动消失), 基本的变量类型只有一块存储空间(分配在stack中), 而引用类型有两块存储空间(一块在stack中,一块在heap中),在函数调用时Java是传值还是传引用,这个估

深入理解Java:注解(Annotation)基本概念

转自 竹子-博客:http://www.cnblogs.com/peida/archive/2013/04/23/3036035.html 什么是注解(Annotation): Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法.Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据. Annotation(注解)是JDK5.0及以后版

[1] 注解(Annotation)-- 深入理解Java:注解(Annotation)基本概念

转载 http://www.cnblogs.com/peida/archive/2013/04/23/3036035.html 深入理解Java:注解(Annotation)基本概念 什么是注解(Annotation): Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和着任何元数据(metadata)的途径和方法.Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据. Ann

深入理解Java 8 Lambda(类库篇——Streams API,Collectors和并行)

转:http://zh.lucida.me/blog/java-8-lambdas-inside-out-library-features/ 关于 深入理解 Java 8 Lambda(语言篇--lambda,方法引用,目标类型和默认方法) 深入理解 Java 8 Lambda(类库篇--Streams API,Collector 和并行) 深入理解 Java 8 Lambda(原理篇--Java 编译器如何处理 lambda) 本文是深入理解 Java 8 Lambda 系列的第二篇,主要介绍