《编程导论(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 安装并启动 ...
随机推荐
- Spring Cloud (12) 服务网关-基础
通过前几篇介绍,已经可以构建一个简单的微服务架构了,如下图: 通过eureka实现服务注册中心以及服务注册发现,通过ribbon或feign实现服务的消费以及负载均衡,通过spring cloud c ...
- java 多线程并发系列之 生产者消费者模式的两种实现
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题.该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度. 为什么要使用生产者和消费者模式 在线程世界里,生产者就是生产数据 ...
- Ajax——模板引擎
模板介绍 1.必要性:ajax请求从服务器接收到大量数据,此时再用普通的字符串拼接是很耗费时间的,这时候模板就有其必要性 2.便利性:插件套用,现在有很多主流的模板插件:BaiduTemplate(百 ...
- 170925_2 Python socket 创建UDP的服务器端和客户端
[python版本]3.6 UDP服务器端: from socket import * from time import ctime host = '' port = 21567 buf_size = ...
- CAD实现自定义实体夹点移动(com接口VB语言)
主要用到函数说明: MxDrawXCustomEvent::MxDrawXCustomEntity::moveGripPointsAt 自定义实体事件,自定义实体夹点被移动,详细说明如下: 参数 说明 ...
- BZOJ 4561: [JLoi2016]圆的异或并 扫描线 + set
看题解看了半天...... Code: #include<bits/stdc++.h> #define maxn 200010 #define ll long long using nam ...
- ModelBinder 请求容错性
代码 //using System.Web.Mvc; public class TrimToDBCModelBinder : DefaultModelBinder { public override ...
- 【数据结构】C语言栈的基本操作
#include<stdio.h> #include<stdlib.h> #include<malloc.h> //定义节点 struct Node { int d ...
- 图的连通性问题之连通和最小环——Floyd算法
Floyd 判断连通性 d[i][j]仅表示i,j之间是否联通 ;k<=n;k++) ;i<=n;i++) ;j<=n;j++) dis[i][j]=dis[i][j]||(dis[ ...
- jdk编译安装及tomcat编译安装
这里我安装的jdk版本为1.8版本,tomcat版本为8.5(请上官网下载) 运维开发技术交流群欢迎大家加入一起学习(QQ:722381733) jdk部署: 1.前往软件所在路径 [root@web ...