不要受《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)调用了形參的方法,则导致形參指向的对象(也正是实參指向的对象)的内容改变,因而产生副作用。

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

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

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

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 按值传递语义》的更多相关文章

  1. 简单物联网:外网访问内网路由器下树莓派Flask服务器

    最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...

  2. 利用ssh反向代理以及autossh实现从外网连接内网服务器

    前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...

  3. 外网访问内网Docker容器

    外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...

  4. 外网访问内网SpringBoot

    外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...

  5. 外网访问内网Elasticsearch WEB

    外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...

  6. 怎样从外网访问内网Rails

    外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...

  7. 怎样从外网访问内网Memcached数据库

    外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...

  8. 怎样从外网访问内网CouchDB数据库

    外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...

  9. 怎样从外网访问内网DB2数据库

    外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...

  10. 怎样从外网访问内网OpenLDAP数据库

    外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...

随机推荐

  1. html用过标签记录

    nowrap="nowrap" //用于列表中td不许换行 maxlength="100" //用于输入框的长度限制 colspan="2" ...

  2. python--8、面向对象的深入知识

    面向对象的三大特性 上一篇我们讲的主要内容都符合面向对象的封装特性.那么问题来了?面向对象难道只有封装性么?当然不是,作为一个这么难理解的东西,要是只有封装性都对不起我们死了这么多脑细胞!所以,晴天霹 ...

  3. html5——css选择器

    复习 div>p: 子代 div+p:div后面相邻的第一个p div~p: div后面所有的兄弟p 属性选择器 标志:[]:区别于id选择器:#,区别于类名选择器:. 特殊符号:^:开头    ...

  4. JS——标记

    continue 语句(带有或不带标签引用)只能用在循环中.break 语句(不带标签引用),只能用在循环或 switch 中.通过标签引用,break 语句可用于跳出任何 JavaScript 代码 ...

  5. OpenCV:使用OpenCV3随机森林进行统计特征多类分析

    原文链接:在opencv3中的机器学习算法练习:对OCR进行分类 本文贴出的代码为自己的训练集所用,作为参考.可运行demo程序请拜访原作者. CNN作为图像识别和检测器,在分析物体结构分布的多类识别 ...

  6. 计组_IEEE754_练习题

    IEEE754   阶码:移码:尾数:原码 一个规格化的32位浮点数x的真值可表示为:          x=(-1)^s×(1. M) × 2^(E-127)       e=E-127 其中尾数域 ...

  7. Eclipse中自动生成get/set时携带注释给get/set

    Eclipse中自动生成get/set时携带注释给get/set   编码的时候通常要用到 JavaBean ,而在我们经常把注释写在字段上面,但生成的Get/Set方法不会生成,通过修改Eclips ...

  8. c# 图片资料

  9. 数据结构与算法(4) -- list、queue以及stack

    今天主要给大家介绍几种数据结构,这几种数据结构在实现原理上较为类似,我习惯称之为类list的容器.具体有list.stack以及queue. list的节点Node 首先介绍下node,也就是组成li ...

  10. 8.1.1 Connection 对象

    Connect是sqllite3模块中最基本的也是最重要的一个类,其主要方法如下表所示: 方法 说明 execute(sql[,parameters]) 执行一条SQL语句 executemany(s ...