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

不要受《Java编程思想》的影响。计算机科学中的术语——按引用传递(pass-by-reference),不要搞成自说自话的个人用语。这些术语也不是专门针对Java的,你不应该从某一本Java书上学习 不能够用于C、C++或Fortran语言的 特殊的“按引用传递”。

验证按值传递非常简单,在方法体中使用一个赋值语句,将形参作为左值。按值传递时,对形参的赋值,不会影响实参,也就是说,那个赋值语句不会有任何作用。

对于foo(A a),注意你要玩的是 a= new A(),而不是玩另一个东西,如a.change()。

这段文字秒懂的,本节的内容可以跳过。

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

消息传递中的一个重要议题是:消息参数(实参)应该如何传递给方法的形参?在各种编程语言中,参数传递的方式多种多样。这由语言的设计者和实现者取舍。常用的参数传递的方式有按值传递(pass-by-value)和按引用传递(pass-by-reference)。

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

参数化机制需要考虑两个问题:

形参初始化,

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

1. 方法调用栈

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

原理上看(方法栈帧在[7.4运行时存储管理]中详解),Java每调用一次方法,就创建一个新的方法帧。形式参数(不管是基本类型还是引用类型变量)属于自己的方法帧,形参保存其值的空间在栈上分配。而实际参数(或表达式)既可能在heap中(对象的域),也可能属于另一个方法帧(另一个局部变量),两者是独立的。按值传递时,如果被调用的方法修改了形参的值,仅改变了副本,而(实参的)原始值丝毫不受影响。

例程 313方法调用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)方法调用的执行流程。

图36 方法调用流程

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所示的方法帧调用栈中,在两个帧间切换以观察实参与形参分别在各自帧中分配有自己的空间。

图 37 在两个帧间切换

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

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

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

(2)以对象的引用作为参数时,实参(引用)同样不会改变;

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

例程 314 pass-by-value

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)3.3.2 按值传递语义》

相关文章:

你感兴趣的文章:

标签云: