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

标签: 理解 java 引用 | 发表时间:2015-10-11 21:04 | 作者:cauchyweierstrass
出处:http://blog.csdn.net

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

  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会将其存放在字符串常量池中(当再次声明一个同样内容的字符串时将会使用字符串常量池中原来的那个内存,而不会重新分配)
写成

java String 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的值的。

作者:cauchyweierstrass 发表于2015/10/11 13:04:08 原文链接
阅读:81 评论:0 查看评论

相关 [理解 java 引用] 推荐:

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

- - CSDN博客推荐文章
首先我们来看下面这段代码:. 结果输出123 abc 相信大家都能做对这道题目. Java是按引用传递的,在函数里面可以修改对象的值. 总之我周围的好几个人都说会输出123 abc. 因为在Java中String不是基本数据类型,会传递引用,所以在change方法里面可以修改str的值. 可是实际的输出确实是123 123,是不是有点毁三观呢.

Java基础—ClassLoader的理解

- - ZJD'S NOTES
} ``` 其余两个ClassLoader都是继承自`ClassLoader`这个类. Java的类加载采用了一种叫做“双亲委托”的方式(稍后解释),所以除了`Bootstarp ClassLoader`其余的ClassLoader都有一个“父”类加载器, 不是通过集成,而是一种包含的关系. ``` ##“双亲委托” 所谓“双亲委托”就是当加载一个类的时候会先委托给父类加载器去加载,当父类加载器无法加载的时候再尝试自己去加载,所以整个类的加载是“自上而下”的,如果都没有加载到则抛出`ClassNotFoundException`异常.

java中的强引用、软引用、弱引用、 虚引用

- - 编程语言 - ITeye博客
     今晚接触到一种说法,就是软引用,查了一下,原来还有其他引用. 各种百度和谷歌总算有点头绪,虽然不是很懂,但是总结一下. ⑴强引用(StrongReference). 强引用是使用最普遍的引用. 如果一个对象具有强引用,那垃圾回收器绝不会回收它. 当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题.

JAVA基础之理解JNI原理

- shuangxi - 博客园-首页原创精华区
JNI是JAVA标准平台中的一个重要功能,它弥补了JAVA的与平台无关这一重大优点的不足,在JAVA实现跨平台的同时,也能与其它语言(如C、C++)的动态库进行交互,给其它语言发挥优势的机会. 有了JAVA标准平台的支持,使JNI模式更加易于实现和使用. 环境说明:ubuntu 10.4.2 LTS系统.

深入理解Java:内省(Introspector)

- - 博客园_首页
  内省(Introspector) 是Java 语言对 JavaBean 类属性、事件的一种缺省处理方法.   JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则. 如果在两个模块之间传递信息,可以将信息封装进JavaBean中,这种对象称为“值对象”(Value Object),或“VO”.

理解Java对象序列化

- - 博客 - 伯乐在线
来源: jiangshapub 的博客( @jiangshapub). 关于Java序列化的文章早已是汗牛充栋了,本文是对我个人过往学习,理解及应用Java序列化的一个总结. 此文内容涉及Java序列化的基本原理,以及多种方法对序列化形式进行定制. 在撰写本文时,既参考了 Thinking in Java, Effective Java,JavaWorld,developerWorks中的相关文章和其它网络资料,也加入了自己的实践经验与理解,文、码并茂,希望对大家有所帮助.

Java序列化理解与总结

- - CSDN博客编程语言推荐文章
Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长. 但在现实应用中,就可能要求在JVM停止运行之后能够保存指定的对象,并在将来重新读取被保存的对象. Java对象序列化就能够帮助我们实现该功能.

理解Java虚拟机体系结构

- - ImportNew
众所周知,Java支持平台无关性、安全性和网络移动性. 而Java平台由Java虚拟机和Java核心类所构成,它为纯Java程序提供了统一的编程接口,而不管下层操作系统是什么. 正是得益于Java虚拟机,它号称的“一次编译,到处运行”才能有所保障. 1.1 Java程序执行流程. Java程序的执行依赖于编译环境和运行环境.

Java常量池理解与总结

- - 移动开发 - ITeye博客
用final修饰的成员变量表示常量,值一旦给定就无法改变. final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量. Class文件中的常量池. 在Class文件结构中,最头的4个字节用于存储魔数 Magic Number,用于确定一个文件是否能被JVM接受,再接着4个字节用于存储版本号,前2个字节存储次版本号,后2个存储主版本号,再接着是用于存放常量的常量池,由于常量的数量是不固定的,所以常量池的入口放置一个U2类型的数据(constant_pool_count)存储常量池容量计数值.