Java 中只有按值传递

"Java 中只有按值传递",初看到这几个字有点不敢相信,无数次通过函数改变过对象,无数次跟同事说 Java 在传对象的时候是按引用传递。后来细细想想,之所以以为 Java 传对象是按引用传递是因为其中有很多概念都没理清楚,与 C++ 中的搞混了。从 C++ 转 Java 的时候将 C++ 中的知识点映射到 Java 没错,这有利于 C++ 转 Java 的人更快的学习 Java。但一旦映射错误就很容易形成固定思维。

在 C++ 和 Java 中都有引用的概念,但他们完全不是同一个东西。Java 中的引用更类似 C++ 的指针,C++ 的引用在 Java 中并无对应概念。在 C++ 中有按值传递、按指针传递和按引用传递三种,而在 Java 中没有 C++ 引用和指针的概念,所以只有按值传递一说。

为了更好的说明 Java 中只有按值传递,先来看看 Java 的数据类型,Java 的数据类型分为基本数据类型和引用类型,其中:

1、基本类型包括 byte/short/int/long/float/double/char/boolean 八种,基本类型在内存中地址中保存的即本身的值,其一般都在栈上分配。

2、引用类型指向一个对象,它与 C++ 的指针非常相似。但 C++ 的指针可以指向基本类型和类对象,而 Java 的引用只能指向类(枚举、接口等)对象。Java 中对象本身在堆上分配,而引用类型在栈上分配,其内存地址中保存的是对象在堆中的地址。两种类型在内存中的布局如下:

上图可以一目了然的看出基本类型与引用类型的区别,基本类型数据即本身,引用类型仅仅是引用。来看下基本类型和引用类型在参数传递中所表现的不同的地方:

 class MyInteger {
    int value;
} public class TestReference {     public static void changeBasic(int arg) {
        arg = 2;
    }     public static void changeReference(MyInteger arg) {
        arg.value = 2;
    }     public static void main(String[] args) {         int basicTypeA = 1;         MyInteger referenceTypeA = new MyInteger();
        referenceTypeA.value = 1;         System.out.println("调用 changeBasic 之前 basicTypeA 的值 "+ basicTypeA);
        changeBasic( basicTypeA);
        System.out.println("调用 changeBasic 之后 basicTypeA 的值 "+ basicTypeA);         System.out.println("调用 changeReference 之前 referenceTypeA 的值 "+ referenceTypeA.value);
        changeReference( referenceTypeA);
        System.out.println("调用 changeReference 之后 referenceTypeA 的值 "+ referenceTypeA.value);
    }
}

运行结果如下:

可以看出基本类型 int 的变量 basicTypeA 在 changeBasic 调用后值并没有发生改变,而引用类型 MyInteger 的变量 referenceTypeA 在调用 changeReference 后发生了改变。这里就比较容易误导读者以为:Java 基本类型是按值传递而引用类型是按引用传递(暂且这么定义)。

其实不然,按值传递的意思想必大家都知道:传递的是值的的拷贝,比如上面代码中的调用 changeBasic(basicTypeA) 时,arg 是 basicTypeA 的一个拷贝,所以无论对 arg 做任何操作都不影响 basicTypeA 变量本身。而调用 changeReference(referenceTypeA) ,arg 也是 referenceTypeA 的一个拷贝,但是由于 arg 和 referenceTypeA 都是引用类型且他们指向同一对象,所以通过 arg 修改对象,referenceTypeA 也能看到。两种类型变量在内存中调用过程如下:

所以可以看出无论是基本类型还是引用类型,都是按值传递。只是由于它们在内存中所表示的内容不同,最后表现出来的结果也有所不同。同理,在 C++ 中的按值传递、按指针传递和按引用传递理论上都可以归为按值传递(其实这个归类在学 C++ 的时候就归纳出来了,只是后来反而忘了)。

对"引用"进行传递的坑

Java 的引用类似于 C++ 的指针,但是 C++ 的对象传递提供了对象本身直接传递和指针传递两种方式(引用方式不谈),而 Java 对象只有对引用进行传递这一种,不存在直接将对象本身进行传递。

1、对象本身进行传递的好处是传递的都是对象的拷贝,在函数中对拷贝的对象做任何修改都不会改变原对象。但是如果对象非常大,而且调用很频繁会影响性能。

2、对象的引用(或者指针)传递的好处是只需要拷贝一个引用(或者指针)大小的数据即可,且可以在调用的函数中改变原对象内容。

在 C++ 中以上(1)和(2)程序员自己还可以选择,Java 里面直接只有第 2 种方式,凡事有利有弊,有时候我们并不想在函数中改变原对象的内容,这里我就踩过一个坑,有个对象通过管道传递的流程如下:

funcA 与 funcB 是两个不同的人负责的,一次升级后 funcB 的负责人发现在函数中获取的对象 X 内容不对,一开始还以为是传递对象 X 的接口出现了错误便是一顿排查,知道最后才发现对象 X 是在升级后在 funcA 中被修改了,浪费了不少时间。当然这个架构的流程设计的不合理是主要原因(只需要在分发的时候讲对象 X 做手动拷贝即可避免上述问题),但是不不影响我们抛出 Java 只能对引用进行传递的弊端。

在调用链较长、各种 for/while 循环中很容易就犯了上述错误,解决方案当然就是手动拷贝对象,Java 中拷贝对象有以下两种方式:

1、实现Cloneable接口并重写Object类中的clone()方法。

2、实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

其中 2 能避免深浅拷贝的问题,但调用比较耗时。1 也能避免深浅拷贝但是需要自己手动去写相应的代码,如果嵌套较深,代码将非常复杂。至于深浅拷贝的问题可以自行百度,其本质还是因为只是将对象的引用进行了传递而导致的一些问题。如果觉得有同感,记得关注下哦~


 

Java 中的按值传递的更多相关文章

  1. Java中只有按值传递,没有按引用传递!

      今天,我在一本面试书上看到了关于java的一个参数传递的问题: 写道 java中对象作为参数传递给一个方法,到底是值传递,还是引用传递? 我毫无疑问的回答:“引用传递!”,并且还觉得自己对java ...

  2. 【转】Java中只有按值传递,没有按引用传递!

    原文链接:http://guhanjie.iteye.com/blog/1683637 今天,我在一本面试书上看到了关于java的一个参数传递的问题: 写道 java中对象作为参数传递给一个方法,到底 ...

  3. JAVA中的按值传递

    Java中只有按值传递,没有按引用传递! 方法参数共有两种类型: 基本数据类型 对象引用 一:基本数据类型 首先看一个小例子: package chuandi; public class Test1 ...

  4. Java中只有按值传递,没有按引用传递!(两种参数情况下都是值传递)

    今天,我在一本面试书上看到了关于java的一个参数传递的问题: 写道 java中对象作为参数传递给一个方法,到底是值传递,还是引用传递? 我毫无疑问的回答:“引用传递!”,并且还觉得自己对java的这 ...

  5. java中数据的传递方式到底是怎样的!

    今天早上我了一道有关java的题.主要考点是考java中值得传递方式. 之前我在javaoo里总结的是:基本数据类型中保存的是实际的值,引用数据类型保存的是被引用的内存地址,那么基本数据类型就是按值传 ...

  6. JAVA中只有值传递

    今天,我在一本面试书上看到了关于java的一个参数传递的问题: 写道 java中对象作为参数传递给一个方法,到底是值传递,还是引用传递? 我毫无疑问的回答:“引用传递!”,并且还觉得自己对java的这 ...

  7. Java中不得不谈的值传递和地址传递

    个人的一些认识,希望能对初学Java的你,或者困惑于方法参数传递的你祈祷一丝帮助! 下面是一些作者的个人观点,如果有错,欢迎各位大牛指出错误,灰常感谢您的观看与支持... -------------- ...

  8. 你真的理解Java中的try/catch/finally吗?

    看几个例子,回顾一下执行顺序 例子1 无异常,finally中的return会导致提前返回 public static String test() {    try {        System.o ...

  9. java中的参数传递是按引用传递还是按值传递

    最近去面试,有一个面试官问到java中参数传递的问题,感觉自己对于这一块还是理解的不够深.今天我们就一起来学习一下Java中的接口和抽象类.下面是本文的目录大纲: 一 . 什么是按值传递,什么是按引用 ...

随机推荐

  1. vue 重构项目第一步(vue-cli跟bootstrap)

    一.先安装jq跟bootstrap 1.因为bootstrap依赖jq,先安装jq----->npm install jquery@1.11.3 --save-dev   ----->可以 ...

  2. 2017-12-26--mysql(5.6.15),linux下安装使用

    本文档的目的是,指导使用者安装.配置.还原所需要用到的mysql数据库.仅提供linux版本服务器的安装说明,且linux版本为64位的Centos6.4.同时,会提供的mysql安装包(MySQL- ...

  3. leetcode 66.加一 python3

    class Solution: def plusOne(self, digits): """ :type digits: List[int] :rtype: List[i ...

  4. 类成员(static)和final修饰符

    在Java类里只能包含成员变量.方法.构造器.初始化块.内部类(包括接口.枚举)5种成员,类成员是用static来修饰的,其属于整个类. 当使用实例来访问类成员时,实际上依然是委托给该类来访问类成员, ...

  5. opencv学习之路(29)、轮廓查找与绘制(八)——轮廓特征属性及应用

    一.简介 HSV颜色空间(hue色调,saturation饱和度,value亮度) 二.HSV滑动条 #include "opencv2/opencv.hpp" #include ...

  6. 异常处理与网络基础中的tcp,udp协议

    # 异常处理: # 什么是异常?异常和错误的区别 # Error 语法错误 比较明显的错误 在编译代码阶段就能检测出来 # Iteration 异常 在执行代码的过程中引发的异常 # 异常发生之后的效 ...

  7. Android中粗字体

    前言 最近UI大牛出了一版新的效果图,按照IOS的效果做的,页面里面有普通字体.中粗字体.加粗字体.对于IOS的小伙伴,分分钟搞定,但是对于Android开发的我,瞬间懵逼了.WTF! 安卓只有粗和不 ...

  8. java中的构造器

    构造器是什么 1.构造器,也称构造方法.构造函数.作用是构造出来一个类的实例,确保对象得到初始化. 2.构造器的格式: 权限修饰符 类名(无参/有参){}. 3.根据有无参数,可分为无参构造 和有参构 ...

  9. Dockerfile详解及优化

    Dockerfile详解 0. Dockerfile的作用 docker可以根据Dockerfile中的指令来构建docker镜像.Dockerfile是一个文本文件,其应当包含用户想要构建一个镜像的 ...

  10. linux新手记录;可执行文件直接运行

    下载meshlab $sudo apt-get install meshlab 查看meshlab位置 $ whereis meshlab\meshlab: /usr/bin/meshlab /usr ...