`
wangminshe89
  • 浏览: 703634 次
文章分类
社区版块
存档分类
最新评论

传值与引用

 
阅读更多

JAVA中的传值与传引用

首先,推荐对Java有一定理解的同仁一本书《PracticalJava》。在《PracticalJava》中也有一个章节介绍Java中关于传值和传引用的问题,堪称经典。

《PracticalJava》

在Java中,事实上底层工作原理不存在传引用的概念,这也象《PracticalJava》中所说的那样,Java中只有传值。这句话理解起来需要费一定的周折。

熟悉C的程序员都用过指针,对指针可谓爱之深恨之切。指针是指向一块内存地址的内存数据(有些拗口),也就是说指针本身是一个占用4字节内存的int(32位系统内),而这个int值恰恰又是另一块内存的地址。比如"hello"这个字串,存放在@0x0000F000这个地址到@0x0000F005这段内存区域内(包括0x00的结束字节)。而在@0x0000FFF0到@0x0000FFF03这四个字节内存放着一个int,这个int的值是@0x0000F000。这样就形成了一个指向"hello"字串的指针。

在Java中,很多人说没有指针,事实上,在Java更深层次里,到处都是大师封装好的精美绝伦的指针。为了更容易的讲解Java中关于类和类型的调用,Java中出现了值与引用的说法。浅显的来说,我们可以认为Java中的引用与C中的指针等效(其实差别非常非常大,但是为了说明我们今天的问题,把他们理解为等效是没有任何问题的)。

所谓传引用的说法是为了更好的讲解调用方式。基于上面对指针的理解,我们不难看出,指针其实也是一个int值,所谓传引用,我们是复制了复制了指针的int值进行传递。为了便于理解,我们可以姑且把指针看作一种数据类型,透明化指针的int特性,从而提出传引用的概念。

重申一遍:Java中只有传值。

Java中一切都是值传递。你可以通过方法来改变被引用的对象中的属性值,却无法改变这个对象引用本身.也就是当一个对象的实例被创建的时候,like this:Apple a = new Apple(); a 存的就是这个对象实例的地址。而这个地址,即使是a的值作为参数传到某个函数中的时候,a本身也是不会改变的。也就是说传递的是引用的拷贝,即不是引用本身,更不是对象。


1所谓传值和传引用

传值和传引用的问题一直是Java里争论的话题。与C++不同的,Java里面没有指针的概念,Java的设计者巧妙的对指针的操作进行了管理。事实上,在懂C++的Java程序员眼中,Java到处都是精美绝伦的指针。
下面举个简单的例子,说明什么是传值,什么是传引用。
//例1
voidmethod1(){
intx=0;
this.change(x);
System.out.println(x);
}

voidintchange(inti){
i=1;
}

很显然的,在mothod1中执行了change(x)后,x的值并不会因为change方法中将输入参数赋值为1而变成1,也就是说在执行change(x)后,x的值z依然是0。这是因为x传递给change(inti)的是值。这就是最简单的传值。
同样的,进行一点简单的变化。
//例2
voidmethod1(){
StringBufferx=newStringBuffer("Hello");
this.change(x);
System.out.println(x);
}

voidintchange(StringBufferi){
i.append("world!");
}
看起来没什么变化,但是这次mothed1中执行了change(x)后,x的值不再是"Hello"了,而是变成了"Helloworld!"。这是因为x传递给change(i)的是x的引用。这是最经典的传引用。
似乎有些奇怪了,两段程序没有特别的不同,可是为什么一个传的是值而另一个传的是引用呢?......

2非要搞清楚传值还是传引用的问题吗?

搞清楚这自然是有必要的,不然我也不需要写这么多了,不过的确没有到"非要"的地步。
首先,如果我们不太关心什么是传值什么是传引用,我们一样能写出漂亮的代码,但是这些代码在运行过程中可能会存在着极大的隐患。
全局变量是让大家深恶痛绝(又难以割舍)的东西,原因就是使用全局变量要特别注意数据的保护。如果在多线程的程序里使用全局变量简直就等于跟自己过不去。不了解传值和传引用的问题,跟使用全局变量不考虑数据保护的罪过是不相上下下的,甚至有时候比它还要糟。你会莫名其妙,为什么我的返回参数没有起作用,为什么我传进去的参数变成了这样......?
一个例子:
//例3
voidmothed1(){
intx=0;
inty=1;
switchValue(x,y);
System.out.println("x="+x);
System.out.println("y="+y);
}
voidswitchValue(inta,intb){

intc=a;
a=b;
b=c;
}
上面是一个交换a,b值的函数,看起来似乎蛮正确的,但是这个函数永远也不会完成你想要的工作。
还有一个例子:
//例4
StringBuffera=newStringBuffer("Iama");
StringBufferb=a;
a.append("afterappend");
a=b;
System.out.println("a="+a);
在编程过程中,经常会遇到这种情况,一个变量的值要被临时改变一下,等用完之后再恢复到开始的值。就好像上面的例子,a为了保持它的值,使用b=a做赋值,之后a被改变,再之后a把暂存在b里面的值取回来。这是我们一厢情愿的想法,而事实上,这段代码执行后,你会发现a的值已经改变了。
以上是两个最简单的例子,真正的程序开发过程中,比这要复杂的情况每天都会遇到。

3类型和类

Java提出的思想,在Java里面任何东西都是类。但是Java里面同时还有简单数据类型:int,byte,char,boolean,与这些数据类型相对应的类是Integer,Byte,Character,Boolean,这样做依然不会破坏Java关于任何东西都是类的提法。

这里提到数据类型和类似乎和我们要说的传值和传引用的问题无关,但这是我们分辨传值和传引用的基础。

4试图分辨传值还是传引用

为什么是"试图分辨"呢?很简单,传值和传引用的问题无处不在,但是似乎还没有人能正统的给出标准,怎样的就是值拷贝调用,怎样的就是引用调用。面对这个问题,我们更多的应该是来自平时积累对Java的理解。
回过头来,我们分析一下上面的几个例子:
先看例1,即使你不明白为什么,但是你应该知道这样做肯定不会改变x的值。为了方便说明,我们给例子都加上行号。

//例1
1voidmethod1(){
2intx=0;
3this.change(x);
4}
5
6voidintchange(inti){
7i=7;
8}
让我们从内存的存储方式看一下x和I之间到底是什么关系。
在执行到第2行的时候,变量x指向一个存放着int0的内存地址。

变量x---->[存放值0]

执行第3行调用change(x)方法的时候,内存中是这样的情形:x把自己值在内存中复制一份,然后变量i指向这个被复制出来的0。

变量x---->[存放值0]
↓进行了一次值复制
变量i---->[存放值0]

这时候再执行到第7行的时候,变量i的被赋值为7,而这一步的操作已经跟x没有任何关系了。

变量x---->[存放值0]

变量i---->[存放值7]

说到这里应该已经理解为什么change(x)不能改变x的值了吧?因为这个例子是传值的。
那么,试着分析一下为什么例三中的switchValue()方法不能完成变量值交换的工作?
再看例2。

//例2
1voidmethod1(){
2StringBufferx=newStringBuffer("Hello");
3this.change(x);
4}
5
6voidintchange(StringBufferi){
7i.append("world!");
8}
例2似乎和例1从代码上看不出什么差别,但是执行结果却是change(x)能改变x的值。依然才从内存的存储角度来看看例2的蹊跷在哪里。
在执行到第2行时候,同例1一样,x指向一个存放"Hello"的内存空间。

变量x---->[存放值"Hello"]

接下来执行第三行change(x),注意,这里就与例1有了本质的不同:调用change(x)时,变量i也指向了x指向的内存空间,而不是指向x的一个拷贝。(x COPY了一份引用给 i)

变量x\
-->[存放值"Hello"]
变量i/

于是,第7行对i调用append方法,改变i指向的内存空间的值,x的值也就随之改变了。

变量x\
-->[追加为"HelloWorld!"]
变量i/

为什么x值能改变呢?因为这个例子是传引用的。

这几个例子是明白了,可是很多人会开始有另一个疑问了:这样看来,到底什么时候是传的值什么时候是传得引用呢?于是,我们前面讲到的类型和类在这里就派上了用场:对于参数传递,如果是简单数据类型,那么它传递的是值拷贝,对于类的实例它传递的是类的引用。需要注意的是,这条规则只适用于参数传递。为什么这么说呢?我们看看这样一个例子:

//例5
Stringstr="abcdefghijk";
str.replaceAll("b","B");
这两句执行后,str的内容依然是"abcdefghijk",但是我们明明是对str操作的,为什么是这样的呢?因为str的值究竟会不会被改变完全取决于replaceAll这个方法是怎么实现的。类似的,有这样一个例子:

//例6
1voidmethod1(){
2StringBufferx=newStringBuffer("Hello");
3change1(x);
4System.out.println(x);
5}
6
7voidmethod2(){
8StringBufferx=newStringBuffer("Hello");
9change2(x);
10System.out.println(x);
11}
12
13voidchange1(StringBuffersb){
14sb.append("world!");
15}
16
17voidchange2(StringBuffersb){
18sb=newStringBuffer("hi");
19sb.append("world!");
20}

调用method1(),屏幕打印结果为:"Helloworld!"
调用method2(),我们认为结果应该是"hiworld",因为sb传进来的是引用。可是实际执行的结果是"Hello"!

难道change2()又变成传值了?!其实change1()和change2()的确都是通过参数传入引用,但是在方法内部因为处理方法的不同而使结果大相径庭。我们还是从内存的角度分析:

执行method1()和change1()不用再多说了,上面的例子已经讲解过,这里我们分析一下method2()和change2()。
程序执行到第8行,x指向一个存放着"Hello"的内存空间。

变量x---->[存放值"Hello"]

第9行调用change2,将sb指向x指向的内存空间,也就是传入x的引用。

变量x\
-->[存放值"Hello"]
变量sb/

到这里为止还没有什么异样,接下来执行18行,这里就出现了类似传入值拷贝的变化:new方法并没有改变sb指向内存的内容,而是在内从中开辟了一块新的空间存放串"hi",同时sb指向了这块空间。

变量x---->[存放值"Hello"]
×原有的引用被切断
变量sb---->[另一块存放"hi"的空间]

接下来再对sb进行append已经和x没有任何关系了。

所以,还有一条不成规则的规则:对于函数调用,最终效果是什么完全看函数内部的实现。比较标准的做法是如果会改变引用的内容,则使用void作为方法返回值,而不会改变引用内容的则在返回值中返回新的值。

虽然已经说了这么多,但是感觉传值还是传引用的问题依然没有完全说清楚。因为这个问题本身就是很难归纳总结的问题,所以更多的理解要靠平时的积累和形成。下面几个例子,给大家尝试进行分析。

//例7,打印结果是什么?
publicstaticvoidmain(String[]args){
inta;
intb;
StringBufferc;
StringBufferd;
a=0;
b=a;
c=newStringBuffer("Thisisc");
d=c;

a=2;
c.append("!!");

System.out.println("a="+a);
System.out.println("b="+b);
System.out.println("c="+c);
System.out.println("d="+d);
}

//例8,打印结果是什么?
publicclassTest{
publicstaticvoidmain(String[]args){
StringBuffersb=newStringBuffer("Hello");
System.out.println("Beforechange,sb="+sb);
changeData(sb);
System.out.println("AfterchangeData(n),sb="+sb);
}

publicstaticvoidchangeData(StringBufferstrBuf){
StringBuffersb2=newStringBuffer("Hi");
strBuf=sb2;
sb2.append("World!");
}

}

打印结果:

Before change, sb = Hello

After changeData(n), sb = Hello


分享到:
评论

相关推荐

    JAVA中传值与引用问题

    ### JAVA中传值与引用问题详解 #### 一、引言 在计算机编程领域,特别是对于面向对象语言如Java而言,“传值”与“传引用”的概念是理解数据传递机制的关键所在。Java作为一种广泛使用的高级编程语言,在设计之初就...

    Java传值还是引用

    根据标题和描述,我们将深入探讨Java中传值与引用的区别,以及它们在实际编程中的应用。 首先,Java是一种“总是按值传递”的语言。这意味着无论是基本类型还是引用类型,当作为参数传递时,都会有一个副本被创建并...

    赋值传值,引用传值,指针传值的区别

    构造函数,借助构造函数 解释 【赋值传值】,【引用传值】,【指针传值】

    labview主VI和子VI的传值、全局变量传值、引用传值

    理解和掌握主VI与子VI之间的传值方法对于编写高效且可维护的LabVIEW程序至关重要。本文将详细探讨三种常见的传值方式:直接传值、全局变量传值和引用传值。 1. 直接传值 直接传值是最基础的通信方式,通过连线在主...

    C++传值调用与引用调用区别实例代码

    ### C++中的传值调用与引用调用 在C++编程语言中,函数调用是一种常见的编程结构,用于执行特定任务或计算结果。当一个函数被调用时,可以通过不同的方式传递参数,其中最常见的是传值(call by value)和传引用(call...

    引用类型传值方法

    引用类型包括类(classes)、接口(interfaces)、数组以及委托(delegates),它们在内存中的存储方式与值类型截然不同。对于引用类型,变量存储的是一个指向对象内存地址的引用,而非对象本身。因此,当我们将一个...

    java中传值与传引用

    在Java编程语言中,函数调用时的参数传递方式有两种:传值(Passing by Value)和传引用(Passing by Reference)。虽然Java官方文档中并未明确指出有传引用这一概念,但在实际操作中,Java的行为类似于传引用,尤其...

    java的传值与传引用详解

    ### Java的传值与传引用详解 #### 一、简单类型是按值传递的 Java方法的参数如果是简单类型(例如基本数据类型如`int`, `boolean`等),那么是按照值传递的方式进行的。简单来说,就是当我们把一个简单类型的变量...

    java的传值与传值后的改变

    理解Java中的传值与传引用对于编写正确且预期的行为代码至关重要。在编写函数时,应清楚地知道参数如何被传递,以及这将如何影响函数的可读性和行为。在实际编程中,合理运用这些知识能帮助避免许多常见的错误和困惑...

    JAVA传值与传引用[整理].pdf

    JAVA传值与传引用[整理].pdf

    传值调用与引用调用.cs

    传值调用与引用调用.cs

    java中传值还是传引用的的认识

    "java中传值还是传引用的认识" Java 中的参数传递是值传递还是引用传递?这是一个经常引发讨论的问题。在 Java 中,参数传递是按值传递的,也就是说,传递给方法的参数是一个副本,而不是原始值本身。 当一个对象...

    Java是传值还是传址引用

    ### Java是传值还是传址引用 #### 一、简单类型是按值传递的 Java在处理简单数据类型(如int、boolean等)时采用的是按值传递的方式。这意味着当你将一个简单类型的值作为参数传递给一个方法时,实际上传递的是这...

    in.ref.out C#传值调用与引用调用

    ### in.ref.out C#传值调用与引用调用 #### 概述 在C#编程语言中,方法参数的传递方式对理解程序的行为至关重要。根据传递的方式不同,方法内部的操作可能会影响方法外部的变量,也可能完全独立。本文将详细介绍C#...

    Java中的传值与传引用实现过程解析

    "Java中的传值与传引用实现过程解析" Java中的传值与传引用是Java编程语言中的一种基础概念,它们是Java函数中参数传递的两种方式。 Java中的传值是指函数参数的值被复制到函数内部,在函数内部对参数的修改不会...

    vue prop属性传值与传引用示例

    vue组件在prop里根据type决定传值还是传引用。 简要如下: 传值:String、Number、Boolean 传引用:Array、Object 若想将数组或对象类型也以值形式传递怎么办呢?如下方式可以实现: // component-A 引用component-...

    传值赋值与引用赋值的区别[参考].pdf

    在软件开发过程中,尤其是使用PHP这种动态类型的编程语言时,理解传值赋值与引用赋值的区别至关重要。这两种赋值方式决定了变量之间的关联性以及值的传递方式。 **传值赋值** 是指将一个变量的当前值完整地复制给另...

    php传值和传引用的区别点总结

    与传值不同,传引用并不复制变量的值,而是传递变量的内存地址。这意味着,当函数内部对引用的变量进行操作时,实际上是在操作原始变量。这种机制使得函数内部和外部的变量共享同一个内存空间。 ```php $param2 = 1...

    php引用传值实例详解学习

    PHP引用传值是一种将一个变量标识符与另一个变量标识符绑定在一起的方式,使得这两个标识符能够访问同一个变量的值。通过使用引用操作符(&),PHP允许开发者创建指向同一数据的变量别名,而不是创建变量的副本。...

Global site tag (gtag.js) - Google Analytics