`
qindongliang1922
  • 浏览: 2180761 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
7265517b-f87e-3137-b62c-5c6e30e26109
证道Lucene4
浏览量:117397
097be4a0-491e-39c0-89ff-3456fadf8262
证道Hadoop
浏览量:125814
41c37529-f6d8-32e4-8563-3b42b2712a50
证道shell编程
浏览量:59779
43832365-bc15-3f5d-b3cd-c9161722a70c
ELK修真
浏览量:71223
社区版块
存档分类
最新评论

聊聊Java里面的引用传递

    博客分类:
  • JAVA
阅读更多

长久以来,在Java语言里面一直有一个争论,就是Java语言到底是值传递(pass-by-value)还是引用传递(pass-by-reference),有的人说是值传递,有的人说是引用传递,两边各执一词,从而误导了很多开发者,更有甚者告诉开发者说不必纠结Java到底是值传递还是引用传递,只要能用就行了,但事实真的是这样吗? 答案是否定的。

为了弄清这两个概念,我们先要理解这两个概念本身的意思:

首先值传递本身这个名字,我感觉就误导了不少人,对于值传递你可以理解成是数据的内存地址传递,并非是数据本身,或者叫它是指针。在维基百科里面,关于指针有清晰的描述:

In computer science, a pointer is a programming language object that stores the memory address of another value located in computer memory. A pointer references a location in memory。

然后,我们看引用传递中的引用的在维基百科上的描述:

In computer science, a reference is a value that enables a program to indirectly access a particular datum, such as a variable's value or a record, in the computer's memory or in some other storage device.

简单的说,引用本身就代表了数据,改变引用相当于改变了数据本身。


根据概念的定义再回到Java语言里面,就会发现对Java本身来说,它只有指针传递也就是值传递,并非是引用传递。到这里,我相信有一部分读者可能已经接受不了,因为在Java里面大多数时候,我们都是讲基本类型,引用类型,从没听过什么指针的概念。导致这样的原因其实跟早期的Sun公司宣传策略有关系,我们知道Java语言,其实是从C语言借鉴改进过来的,其中一个主要的优点就是不允许我们像C那样可以通过指针直接操作内存区域,为了这个事情,Sun公司替换了概念称Java的指针为引用,从而在Java语言的发展历程中导致了更大的困惑,持续演变至今。事实就是Sun公司命名错误,当然Java已经发展了很多年,Oracle接盘后也犯不上纠正命名的问题。结果就是懂的人永远都知道是怎么回事,不懂的人一直分不清这两个概念。


只有认清了Java里面存在指针,承认指针,我们才能更加自信的理解Java语言。

我们来看一个简单的例子:

```
class Dog{
       public String name;

       public Dog() {
       }

       public Dog(String name) {
            this.name = name;
        }
	}
```

然后,我们试着创建一个Dog对象:
```
Dog dog=null; //1
System.out.println(dog.name);
dog=new Dog();//2

```

然后运行一下,我们会看到一个异常:
```
Exception in thread "main" java.lang.NullPointerException
```

注意这个异常,叫空指针异常,在Java里面任何对象没有初始化的时候,如果我们使用其内部属性,就会抛出上面的信息,这也从侧面反映了dog这个变量的作用,其实就是指针,而并非引用。对于已经实例化的对象,如果我们没有重写toString方法,打印指针会得到一串类名组合+内存地址转换的十六进制字符串。

我们接着来看一个例子:


```
    public static void change(String point){
       point="new value";
    }

    public static void swapString(){
        String orgin="orgin";
        System.out.println(orgin);
        change(orgin);
        System.out.println("==================after==================");
        System.out.println(orgin);


    }
```


如果在main方法里面,执行swapString()方法,输出的结果都是orgin。

对于Dog对象也一样,如下:
```
    public static void change(Dog dog){
       dog=new Dog("CAT");
       dog.name="cat";
    }


    public static void swapDog(){
        Dog dog=new Dog("tom");
        System.out.println(dog.name);
        change(dog);
        System.out.println("==================after==================");
        System.out.println(dog.name);
    }
```


最终输出的结果都是tom,好像从未执行过change方法一样。
```
tom
==================after==================
tom
```



这是为什么? 你可能要说很简单啊,方法里面的作用域,只在方法里生效,出了方法就无效了。真的是这样吗? 接着看下面,我们稍微改动一下change方法:

```
    public static void change(Dog dog){
	   dog.name="new_tom";
       dog=new Dog("CAT");
       dog.name="cat";
    }
```

同样,在main方法执行,输出的结果是:
```
tom
==================after==================
new_tom
```


这里面,如果不理解值传递(指针传递)和引用传递的区别,其实是很难明白原因的:


```
    public static void change(Dog dog){
       dog=new Dog("CAT");//3 memory location:8888
       dog.name="cat";//4
    }


    public static void swapDog(){
        Dog dog=new Dog("tom");//1  memory location:7777
        System.out.println(dog.name);
        change(dog);//2
        System.out.println("==================after==================");
        System.out.println(dog.name);
    }
```


我们加上序号之后,来分析一下,首先在第一步,我们创建了一个Dog对象,其中dog变量是指针(或者按Java的习惯叫引用,但其实是不正确的),指针存的是内存地址,而并非是内存中的数据本身,我们假设内存地址是7777,然后在第二步,将dog(指针)引用,传给了change方法,在第三步,因为我们新创建了一个Dog,并把dog指针指向了新的对象,这里假设其在堆上的内存地址是8888,然后并给内存地址=8888的Dog对象的name赋值了cat,然后方法执行结束,最后回到swapDog方法,继续执行,此时swapDog方法dog对象的指针仍然是7777,所以并没有任何变化。

图示如下:





注意change方法执行后,没有指针指向内存地址8888的对象,故会在下一次gc时回收。

接着我们分析下,微调后的change方法:
```
    public static void change(Dog dog){
	   dog.name="new_tom";// 2.1
       dog=new Dog("CAT");
       dog.name="cat";
    }
```


在第2.1步,我们通过dog指针=7777的数据,重新改变了其名称,这意味着内存地址7777的数据,被修改了,后面的两行改的是内存地址=8888的数据,所以最终结果是改变后的。


注意,如果Java语言是引用传递的话,那么最终的结果name肯定是cat,因为引用传递的修改,指的就是数据本身,而并非地址。



上面关于值传递(指针传递)和引用传递,说的有点抽象,我打个比方:

指针指的是你的名字,通过指针可以找到数据本身,然后操作数据,但如果指针本身(非数据本身)变了,也就是你名字变了,但其实跟你没有关系,你自己还是你自己,你可以重新在换个名字代表你。

引用指的是你本身,如果本身变了,比如你换了个发型,这个时候,无论你换多个名字(指针),你都一样是你,改变不了你发型换了的事实。

所以,这个时候如果按照值传递(指针传递)的理解,来看上面的例子,你就会恍然大悟。在change方法里面dog的指针已经被替换成了8888,而8888地址代表的是新的对象
所以不会改变7777的对象的内容,在微调后的版本中,我们直接改变了7777地址数据的name,所以最终的结果也是改变后的。


总结:

Java语言本身是值传递,也叫做指针传递,虽然我们一直叫引用类型,但其实它实际上是一个指针,而真正的引用传递改变的是数据本身的内容,如Lisp和Fortran语言,无论哪种方式,我们只要理解了其本质,就可以掌握的更好。




  • 大小: 13.9 KB
0
0
分享到:
评论

相关推荐

    聊聊Java中的方法.docx

    - **形式参数列表**:即方法声明时定义的参数列表,用以接收调用该方法时传递过来的实际参数值。 #### 方法的调用 定义好方法后,需要在程序中进行调用才能发挥作用。调用方法的语法格式如下: ```java [对象/...

    JAVA面试题解惑系列合集

    在方法中传递变量时,如果是基本类型,是按值传递的,即传递的是值的副本;如果是引用类型,则是按引用传递,即传递的是引用的副本,但副本指向同一对象。 6. 字符串(String)杂谈 字符串是Java中非常特殊的对象...

    C_C++程序员Java编程

    9. **语法差异**:例如,Java中的方法定义没有C++中的返回类型,Java使用`this`关键字引用当前对象,而C++使用`.`或`->`操作符访问成员。 10. **学习资源**:Java拥有庞大的开发者社区和丰富的开源项目,学习资料...

    各大互联网公司常见java面试题

    9. **值传递与引用传递**:Java中参数传递总是按值传递,对于基本类型,传递的是值本身;对于对象,传递的是对象引用的副本,但实际对象不变。 10. **final, finally, finalize的区别**:final修饰变量表示不可改变...

    java引用传递笔试题abc-2019-js-Interview:2019年前端面试都聊啥?一起来看看

    java引用传递笔试题abc 2019-js-Interview 2019年前端面试都聊啥?一起来看看 原文链接: 前言 笔者只是一个从事前端开发不足三年的小前端,看到上面的面试题发现自己还有很多很多需要学习的。于是就萌生了写答案的...

    java做的简单聊天程序

    5. **消息传递机制**:尽管没有实现文件传输,但私聊和公聊功能需要一种消息传递机制。可能是通过队列(如`LinkedList`)存储待发送的消息,然后逐一处理和发送。如果是多用户环境,可能涉及网络编程,如Socket编程...

    java基础知识

    - CSS语法:选择器+属性+值。 - CSS选择器:标签选择器、类选择器、ID选择器等。 2. **盒模型**: - 盒模型的组成部分:content、padding、border、margin。 - 盒模型的兼容性问题。 - 盒模型的应用。 3. **...

    javaOA办公系统模块设计方案.pdf

    此外,系统还包含了通讯模块,涵盖单聊、群聊、发图片、文件发送、离线消息等功能,以及好友管理和群组管理,提升内部沟通效率。整个系统集成了多种技术,为实现高效、安全的办公环境提供了全面的解决方案。

    Android应用源码之百度推聊.zip

    这使得不同组件可以相互传递信息而无需直接引用对方。 10. **依赖注入**:源码中可能使用了如Dagger或Hilt这样的依赖注入框架,以提高代码的可测试性和可维护性。 11. **测试**:在`app/src/androidTest`目录下...

    caf-com-java:dicas sobre javascript

    首先,我们来聊聊JavaScript的基础。JavaScript是一种广泛使用的解释型、弱类型、动态类型的脚本语言,主要应用于Web开发,用于实现客户端交互、网页动态效果以及服务器端运算。它由Brendan Eich在1995年创建,最初...

    synt.docx

    - **USER_CHAT (7)**:单聊消息,通过`rIds`来确认是否是批量发送。 - **GROUP_CHAT (8)**:群聊消息,通过`rIds`来确认是否是群内的私聊。 #### 五、服务消息类型 - **SERVICE_CHAT (9)**:官方客服消息,用于客户...

    COMPSCI-355-CppReview:有关讨论点,请参见源文件中的文档。

    按值,指针值和引用传递参数。 第3部分。数组的部分初始化,通过引用传递的数组参数以及2D数组的释放。 第4部分。析构函数,断言宏,const方法以及前缀与后缀增量。 第5部分。单独的编译和名称空间。 第6部分。...

    spring从入门到精通精简笔记

    在构造注入方式下,Bean 的依赖关系通过构造函数传递。这种方式的好处在于它能确保所有必需的依赖都被正确地初始化。改造后的 `HelloWorld.java` 和 `config.xml` 文件应该如下所示: ```java public class Hello...

    Cucumber_DataTables

    DataTable是由多行多列组成的结构,可以用来传递复杂的数据到你的测试步骤中。在Gherkin脚本中,你可以用竖线(|)分隔列,换行分隔行。例如: ```gherkin Given 表格数据如下: | 列1 | 列2 | | 值1 | 值2 | ...

    仿QQ详细设计

    【仿QQ详细设计】是一个IT项目,旨在模仿QQ的功能,包括注册、登录、单群聊、网络协议的设计以及数据库管理。下面将详细阐述这些关键知识点。 1. **注册**: - 注册流程中,客户端收集用户信息,如昵称和密码,...

    android-PieChartAndList

    开发者需要将解析出的数据转换为适合饼图显示的格式,如每块饼图的值和对应的颜色。饼图可以用来直观地展示各部分所占比例,对于数据可视化非常有用。 5. **列表(List)展示**: 在Android中,通常使用`...

Global site tag (gtag.js) - Google Analytics