《编程导论(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 安装并启动 ...
随机推荐
- nodejs全局安装路径的位置
一般nodejs安装在默认的C盘,如果不知道安装在哪里,可以打开控制面板-系统和安全-系统-高级配置中找到 所谓全局安装: 是指安装在node中node_module的根目录里,可以在电脑的任何位置调 ...
- 关于debug.keystore文件用法以及错误处理
在开发过程中需要频繁的为测试的同事签名apk,非常很麻烦,把默认debug.keystore文件替换成发布用(生产环境)的签名文件,不用频繁地签名apk文件了. 如果直接使用生产keysto ...
- MVC5+EasyUI+EF6+Linq通用权限系统出炉(1)
1.先晒一下结构吧,
- 【sqli-labs】 less50 GET -Error based -Order By Clause -numeric -Stacked injection(GET型基于错误的整型Order By从句堆叠注入)
报错没有关闭,直接可以用UpdateXml函数 http://192.168.136.128/sqli-labs-master/Less-50/?sort=1 and UpdateXml(1,conc ...
- python os os.path模块学习笔记
#!/usr/bin/env python #coding=utf-8 import os #创建目录 os.mkdir(r'C:\Users\Silence\Desktop\python') #删除 ...
- (转)Hibernate框架基础——在Hibernate中java对象的状态
http://blog.csdn.net/yerenyuan_pku/article/details/52760627 在Hibernate中java对象的状态 Hibernate把对象分为4种状态: ...
- PHP 之递归遍历目录与删除
/** * @Description: 递归查询目录文件 * @Author: Yang * @param $path * @param int $level * @return array */ f ...
- CAD从二制流数据中加载图形(com接口)
主要用到函数说明: _DMxDrawX::ReadBinStream 从二制流数据中加载图形,详细说明如下: 参数 说明 VARIANT varBinArray 二制流数据,是个byte数组 BSTR ...
- java静态变量、实例变量和局部变
实例变量又称成员变量: 1⃣️成员变量定义在类中,在整个类中都可以被访问 2⃣️成员变量随着对象的建立而建立,随对象的消失而消失,存在于对象所在的对内存中 3⃣️成员变量有默认初始值 局部变量: 1⃣ ...
- Nginx反向代理WebSocket(WSS)
1. WebSocket协议 WebSocket 协议提供了一种创建支持客户端和服务端实时双向通信Web应用程序的方法.作为HTML5规范的一部分,WebSockets简化了开发Web实时通信程序的难 ...