《编程导论(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)调用了形參的方法,则导致形參指向的对象(也正是实參指向的对象)的内容改变,因而产生副作用。
为了避免方法调用可能带来的副作用,能够採用例如以下措施:
² 让引用指向的对象属于不变类。不变类的对象(内容)不可改变,如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 按值传递语义》的更多相关文章
- 简单物联网:外网访问内网路由器下树莓派Flask服务器
最近做一个小东西,大概过程就是想在教室,宿舍控制实验室的一些设备. 已经在树莓上搭了一个轻量的flask服务器,在实验室的路由器下,任何设备都是可以访问的:但是有一些限制条件,比如我想在宿舍控制我种花 ...
- 利用ssh反向代理以及autossh实现从外网连接内网服务器
前言 最近遇到这样一个问题,我在实验室架设了一台服务器,给师弟或者小伙伴练习Linux用,然后平时在实验室这边直接连接是没有问题的,都是内网嘛.但是回到宿舍问题出来了,使用校园网的童鞋还是能连接上,使 ...
- 外网访问内网Docker容器
外网访问内网Docker容器 本地安装了Docker容器,只能在局域网内访问,怎样从外网也能访问本地Docker容器? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Docker容器 ...
- 外网访问内网SpringBoot
外网访问内网SpringBoot 本地安装了SpringBoot,只能在局域网内访问,怎样从外网也能访问本地SpringBoot? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装Java 1 ...
- 外网访问内网Elasticsearch WEB
外网访问内网Elasticsearch WEB 本地安装了Elasticsearch,只能在局域网内访问其WEB,怎样从外网也能访问本地Elasticsearch? 本文将介绍具体的实现步骤. 1. ...
- 怎样从外网访问内网Rails
外网访问内网Rails 本地安装了Rails,只能在局域网内访问,怎样从外网也能访问本地Rails? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Rails 默认安装的Rails端口 ...
- 怎样从外网访问内网Memcached数据库
外网访问内网Memcached数据库 本地安装了Memcached数据库,只能在局域网内访问,怎样从外网也能访问本地Memcached数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装 ...
- 怎样从外网访问内网CouchDB数据库
外网访问内网CouchDB数据库 本地安装了CouchDB数据库,只能在局域网内访问,怎样从外网也能访问本地CouchDB数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动Cou ...
- 怎样从外网访问内网DB2数据库
外网访问内网DB2数据库 本地安装了DB2数据库,只能在局域网内访问,怎样从外网也能访问本地DB2数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动DB2数据库 默认安装的DB2 ...
- 怎样从外网访问内网OpenLDAP数据库
外网访问内网OpenLDAP数据库 本地安装了OpenLDAP数据库,只能在局域网内访问,怎样从外网也能访问本地OpenLDAP数据库? 本文将介绍具体的实现步骤. 1. 准备工作 1.1 安装并启动 ...
随机推荐
- Java中的overload(方法的覆写)
方法覆写(overload)与方法的重载非常相似,它在 Java的继承中也有很重要的应用. 写程序可能会碰到下面的情况,在父类中已经实现的方法可能不够精确,不能满足子类 的需求.例如在前面的 Anim ...
- Java&Xml教程(六)使用JDOM解析XML文件
JDOM 提供了非常优秀的Java XML API来更方便的读取.修改.生成XML文档.JDOM还提供了包装类供用户从SAX.DOM.STAX事件解析.STAX流解析中选择具体的实现. 在本教程中,我 ...
- c++ 以二进制和以文本方式读写文件的区别
在c++项目开发中,时常涉及到文件读写操作.因此在这里先简单梳理和回顾一下文本模式和二进制模式在进行文件读写上的区别. 1.linux平台下文本文件和二进制文件的读写 在linux平台下进行文件读写时 ...
- python2升级成python3
系统环境centos 6.5原python版本2.6.6目标python版本3.5.11.下载python3.5wget --no-check-certificate http://www.pyth ...
- (三)Python 学习第三天--GUI桌面项目
(代码参考了别人的代码,只做学习用途!!!最近因为写论文,好久没有记录,好内疚...今天学习了一个小案例,做一下) 主要使用模块:tkinter 代码如下: from tkinter import * ...
- 文件下载之ServletOutputStream
使用response.getOutputStream可以获取ServletOutputStream,从而实现向页面发送流数据.但是需要注意的是,不能使用ajax进行请求,因为这样页面不会有任何反应,可 ...
- IOS7 APP 升级的10个TIP 建议
There is no way to preserve the iOS 6 style status bar layout. The status bar will always overlap yo ...
- C#---EF映射MySQL
使用EF CodeFirst连接MySql数据库 如何使用EF CodeFirst连接MySql数据库? 我们这篇文章介绍怎么使用EF连接MySql 作者的环境 VS2017.Win10.MySql5 ...
- Linux添加FTP用户并设置权限
在linux中添加ftp用户,并设置相应的权限,操作步骤如下: 1.环境:ftp为vsftp.被限制用户名为test.被限制路径为/home/test 2.建用户,命令行状态下,在root用户下: 运 ...
- CF176E Archaeology(set用法提示)
题目大意: 给一棵树,每次激活或熄灭一个点,每次问这些点都联通起来所需的最小总边权 分析: 若根据dfs序给所有点排序,为$v1,v2,v3....vk$,那么答案就是$(dis(v1,v2)+dis ...