《编程导论(Java)·3.3.2 按值传递语义》

不要受《Java编程思想》的影响,计算机科学中的术语——按引用传递(pass-by-reference)。不要搞成自说自话的个人用语。

这些术语也不是专门针对Java的,你不应该从某一本Java书上学习 不可以用于C、C++或Fortran语言的 特殊的“按引用传递”。

验证按值传递很easy。在方法体中使用一个赋值语句,将形參作为左值

按值传递时,对形參的赋值,不会影响实參。也就是说。那个赋值语句不会有不论什么副作用。

对于foo(A a),注意方法体中你要玩的是 a= new A(),而不是玩还有一个东西。如a.change()。

这段文字秒懂的,《编程导论(Java)·3.3.2 按值传递语义》的内容能够跳过。



通过定义一系列方法,能够将程序分解成小模块,而方法调用将它们联系起来。方法定义时指定了形式參数;而在方法调用时,形式參数由给定的实际參数初始化。

消息传递中的一个重要议题是:消息參数(实參)应该怎样传递给方法的形參?在各种编程语言中。參数传递的方式多种多样[1]。这由语言的设计者和实现者取舍。

经常使用的參数传递的方式有按值传递(pass-by-value)按引用传递(pass-by-reference)

从參数传递机制的渊源上看,C语言中的參数是按值传递的,Fortran语言按引用传递,而C++语言中同一时候採用了两者。Java语言与C语言一样,採用唯一的參数传递方式:按值传递。

參数化机制须要考虑两个问题:

形參初始化。

方法体中对形參的操作是否对实參产生副作用。

 

1. 方法调用栈

按值传递意味着:当调用某个方法时,首先实际參数(或表达式)被求值,(并将结果值进行复制。再)把复制的值存放到形式參数中。

简言之,按值传递就是传递实际參数的一个副本。

原理上看(方法栈帧在[7.4执行时存储管理]中具体解释),Java每调用一次方法,就创建一个新的方法帧。

形式參数(无论是基本类型还是引用类型变量)属于自己的方法帧,形參保存其值的空间在栈上分配

而实际參数(或表达式)既可能在heap中(对象的域),也可能属于还有一个方法帧(还有一个局部变量),两者是独立的。按值传递时,假设被调用的方法改动了形參的值,仅改变了副本,而(实參的)原始值丝毫不受影响。

例程 3?13方法调用
package OO;
import static tips.Print.*;
public class PassByValue{
    private void m(int x){    x +=  5;  }
    private int max(int a,int b){  return ( a>b ? a : b );  }
    public void foo(){
    ?  int i = 1,j =2;//代码前的符号,表示断点
        int max = max(i,j);
        m(max);
        i=max;
    }
}

创建一个对象并运行其foo()方法,foo()的运行过程,如图3-6所看到的。

它反映了两个要点:(1)一个“较大的代码”怎样分解成较多的小片段(方法),而后这些小片段又是怎样构成一个大总体的——假想方法m(int )和max (int,int)有着非常长非常长的代码。

(2)方法调用的运行流程。

图3?6 方法调用流程

foo()的运行过程:(1) 初始化局部变量i和j;(2) int max = max(i,j)。先求方法max(i,j)的(返回)值,然后赋值给局部变量max。

为了求方法max(i,j)的值。JVM创建一个新的方法帧max,将上一帧foo的局部变量i和j的值复制后赋予形參,foo帧处于等待状态。

max运行完成将返回2,max帧被弹出,2赋值给max;(3)运行m(max)。创建新帧m,将帧foo的实參max的值2复制后赋予形參x。m帧尽管改变了x的值。可是不影响实參的值。

假设在学习[2.3.4创建对象]的时候,熟悉了在BlueJ的源码编辑器中设置断点,则能够在如图3-7所看到的的方法帧调用栈中,在两个帧间切换以观察实參与形參分别在各自帧中分配有自己的空间。

图 3?7 在两个帧间切换

2. Java语言中仅仅有按值传递

学习Java语言的參数传递方式,要验证3种情况:

(1)对于基本类型的參数。方法体中对形參的操作不会产生副作用。

(2)以对象的引用作为參数时。实參(引用)相同不会改变

(3)可是将该引用作为消息接收者,可能使它指向的对象的内容发生了变化

package OO;
import tips.Fraction;
import static tips.Print.*;
public class PassByValue{
    /////////////////////////////////////以引用作为參数。仍然按值传递////////
    private void change(Fraction frrr) {
        frrr = new Fraction(11,55);//注意这里。
    }
    private void doubleIt(Fraction f) {        f.add(f);    }
    public void test(){
        Fraction f = new Fraction(1,3);
        p(f+" ");
        change(f);
        pln(f);
        //f = 1/3 Vs 1/5

        Fraction f2 = new Fraction(1,3);
        //Fraction temp = new Fraction(f2);
        doubleIt(f2);
        //doubleIt(temp);
        pln (f2);
    }
}

例程中,change(Fraction)和doubleIt(Fraction) 方法以分数类变量为形參。运行test()代码可知,change(Fraction)对形參的赋值不会影响实參。而doubleIt(Fraction)调用了形參的方法,则导致形參指向的对象(也正是实參指向的对象)的内容改变,因而产生副作用。

为了避免方法调用可能带来的副作用,能够採用例如以下措施:

2       让引用指向的对象属于不变类。不变类的对象(内容)不可改变,如String。

2       克隆一个对象,将它的引用传递给方法。

3. 负负得正

有时候两个错误放在一起,从效果上看是正确的。典型的错误样例“Java中的对象按引用传递”。介绍这个错误的说法有两个目的:(1)说明什么是按引用传递;(2)强调当引用为方法參数时。传值仍然会有副作用。

按引用传递意味着:方法的形式參数不过实际參数的别名——实參不是将自己的值而是地址传递给形參,两者拥有同样的数据存放位置。因此不论什么时候方法改变形式參数的值。其实也就改变了实參的值。

之所以有“Java中的对象按引用传递”这一负负得正的错误来源于一句easy令人误解的话:对象是通过引用传递的(you are passing objects by reference)。其本意是说。Java中的对象不被传递,而是传递其引用。可是不论是英文还是中文的含义,稍不小心就会与pass-by-reference混淆。所谓负负得正,基于:

(1) 对象可以传递。Java中不会传递对象。所以这是错误的如果。根源是由于人们经常混用术语。“把对象传递给方法”毕竟是经常使用的说法,见[2.4.2引用变量、引用和对象]。

(2) 形參和实參拥有同样的位置。

假设形參和实參都是对象。这当然是对的。问题是,形參和实參(不是对象而)是引用,正如左手和右手指向同一个月亮,可是左手不是右手,左手不是右手的别名/外号。

效果正确:的确可以改动对象的内容。

总之,正确的说法是对象的引用按值传递(Object references are passed by value)。


练习3-1.:或许有人说,“对象是通过引用传递。而引用按值传递”这句话太绕口,没有“对象按引用传递”来得明快。你怎样回答?


练习3-2.:为什么说“基本类型按值传递,而引用使用按引用传递”是错误的。


练习3-3.:网络程序中传递序列化的对象,应该採用什么传递机制?提示:传引用语意。


[1] http://www.yoda.arachsys.com/java/passing.html,各种參数传递的语义、按引用传递的目的.

《编程导论(Java)·3.3.2 按值传递语义》

时间: 2024-10-06 21:41:32

《编程导论(Java)·3.3.2 按值传递语义》的相关文章

CI框架源码阅读笔记3 全局函数Common.php

从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap引导文件都会最先引入全局函数,以便于之后的处理工作). 打开Common.php中,第一行代码就非常诡异: if ( ! defined('BASEPATH')) exit('No direct script access allowed'); 上一篇(CI框架源码阅读笔记2 一切的入口 index

IOS测试框架之:athrun的InstrumentDriver源码阅读笔记

athrun的InstrumentDriver源码阅读笔记 作者:唯一 athrun是淘宝的开源测试项目,InstrumentDriver是ios端的实现,之前在公司项目中用过这个框架,没有深入了解,现在回来记录下. 官方介绍:http://code.taobao.org/p/athrun/wiki/instrumentDriver/ 优点:这个框架是对UIAutomation的java实现,在代码提示.用例维护方面比UIAutomation强多了,借junit4的光,我们可以通过junit4的

Yii源码阅读笔记 - 日志组件

?使用 Yii框架为开发者提供两个静态方法进行日志记录: Yii::log($message, $level, $category);Yii::trace($message, $category); 两者的区别在于后者依赖于应用开启调试模式,即定义常量YII_DEBUG: defined('YII_DEBUG') or define('YII_DEBUG', true); Yii::log方法的调用需要指定message的level和category.category是格式为“xxx.yyy.z

源码阅读笔记 - 1 MSVC2015中的std::sort

大约寒假开始的时候我就已经把std::sort的源码阅读完毕并理解其中的做法了,到了寒假结尾,姑且把它写出来 这是我的第一篇源码阅读笔记,以后会发更多的,包括算法和库实现,源码会按照我自己的代码风格格式化,去掉或者展开用于条件编译或者debug检查的宏,依重要程度重新排序函数,但是不会改变命名方式(虽然MSVC的STL命名实在是我不能接受的那种),对于代码块的解释会在代码块前(上面)用注释标明. template<class _RanIt, class _Diff, class _Pr> in

CI框架源码阅读笔记5 基准测试 BenchMark.php

上一篇博客(CI框架源码阅读笔记4 引导文件CodeIgniter.php)中,我们已经看到:CI中核心流程的核心功能都是由不同的组件来完成的.这些组件类似于一个一个单独的模块,不同的模块完成不同的功能,各模块之间可以相互调用,共同构成了CI的核心骨架. 从本篇开始,将进一步去分析各组件的实现细节,深入CI核心的黑盒内部(研究之后,其实就应该是白盒了,仅仅对于应用来说,它应该算是黑盒),从而更好的去认识.把握这个框架. 按照惯例,在开始之前,我们贴上CI中不完全的核心组件图: 由于BenchMa

CI框架源码阅读笔记2 一切的入口 index.php

上一节(CI框架源码阅读笔记1 - 环境准备.基本术语和框架流程)中,我们提到了CI框架的基本流程,这里这次贴出流程图,以备参考: 作为CI框架的入口文件,源码阅读,自然由此开始.在源码阅读的过程中,我们并不会逐行进行解释,而只解释核心的功能和实现. 1.       设置应用程序环境 define('ENVIRONMENT', 'development'); 这里的development可以是任何你喜欢的环境名称(比如dev,再如test),相对应的,你要在下面的switch case代码块中

Apache Storm源码阅读笔记

欢迎转载,转载请注明出处. 楔子 自从建了Spark交流的QQ群之后,热情加入的同学不少,大家不仅对Spark很热衷对于Storm也是充满好奇.大家都提到一个问题就是有关storm内部实现机理的资料比较少,理解起来非常费劲. 尽管自己也陆续对storm的源码走读发表了一些博文,当时写的时候比较匆忙,有时候衔接的不是太好,此番做了一些整理,主要是针对TridentTopology部分,修改过的内容采用pdf格式发布,方便打印. 文章中有些内容的理解得益于徐明明和fxjwind两位的指点,非常感谢.

CI框架源码阅读笔记4 引导文件CodeIgniter.php

到了这里,终于进入CI框架的核心了.既然是"引导"文件,那么就是对用户的请求.参数等做相应的导向,让用户请求和数据流按照正确的线路各就各位.例如,用户的请求url: http://you.host.com/usr/reg 经过引导文件,实际上会交给Application中的UsrController控制器的reg方法去处理. 这之中,CodeIgniter.php做了哪些工作?我们一步步来看. 1.    导入预定义常量.框架环境初始化 之前的一篇博客(CI框架源码阅读笔记2 一切的入

jdk源码阅读笔记之java集合框架(二)(ArrayList)

关于ArrayList的分析,会从且仅从其添加(add)与删除(remove)方法入手. ArrayList类定义: p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Monaco } span.s1 { color: #931a68 } public class ArrayList<E> extends AbstractList<E> implements List<E> ArrayList基本属性: /** *

dubbo源码阅读笔记--服务调用时序

上接dubbo源码阅读笔记--暴露服务时序,继续梳理服务调用时序,下图右面红线流程. 整理了调用时序图 分为3步,connect,decode,invoke. 连接 AllChannelHandler.connected(Channel) line: 38 HeartbeatHandler.connected(Channel) line: 47 MultiMessageHandler(AbstractChannelHandlerDelegate).connected(Channel) line: