`
student_lp
  • 浏览: 428498 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

解析java传值和传址以及对象的Clone

阅读更多

       在讨论上面问题之前,我们先来看看函数的实参为形参赋值时,传递的到底是什么东西?实际上实参赋值给形参时,是将自己的一份拷贝传递到函数内部。这就不难理解,不管是“传址”还是“传值”,本质上都是传值,但传递值的类型是不相同的。对于普通基本类型,就是这个数值的拷贝,所以函数内部对其进行修改,不会影响传递的实参的值;而对于指针来说,函数内部对其修改,影响的是指针指向的那片内存区域,但实参指针的值也没有发生变化(指针还是指向那片内存区域)。

        java中不存在指针,但实际上java中对象的引用(句柄)就是指针。 由此可见,java中所谓的“传址”其实也是一种传值(或者说java中没有传址)。我们给方法“传址”时实际上是传递的是实参的地址的一个拷贝,它跟我们的实参(这里把他们暂时都理解为指针)所指向的地址虽然相同,但他们却是两个不同的实体。所以当我们在方法中对形参进行重新赋值时,改变的只是形参所指向的地址,而实参所指向的地址没有被改变,所以其内容不变。

      上面两段是整体总结,下面我们来看一下设计到的知识点:

      1.Java中的参数传递有传值和传址两种; 
      2.基本类型和String型作为参数时,为传值方式,只把值传入方法,不管在方法中怎么处理这个参数,原值不变; 
       3.其他引用类型作为参数时,为传址方式,将指向内存中的地址传入方法,方法中此内存地址中的值发生变化时,原值也会改变; 
       4.例外: 
       (1)如果引用类型的对象通过传址方式将其指向内存中的地址传入方法后,方法中使用new关键字重新给参数赋值时,会在内存中重新开辟空间,参数指向新的内存空间,此时参数和原对象指向的就不是同一个地址了,参数值的变化不会改变原值; 
       (2)String型是引用类型,但是String型作为参数,是传值方式,可以通过以下两种方式来理解: 
           <1>String本质上是基本类型的char[],基本类型作为参数时,为传值方式; 
           <2> 字符串在内存中是存储在堆中的一个常量,String对象指向内存中这个常量的地址,通过传址方式将地址传入方法后,方法中如果通过字符串给参数赋值,则会重新在堆中创建一个字符串常量,并指向这个地址,原值依然指向原来的字符串常量地址,参数值的变化不会改变原值,如果通过new关键字给参数赋值,参见 (1)中的解释。 

        下面为大家一一详解:

一、别名的问题

      “别名”意味着多个句柄都试图指向同一个对象,若有人向那个对象写入一点什么东西,就会产生别名问题。如果其他句柄所有者不希望那个对象改变,那就要失望了。在编写程序的过程中,我们可以避免多个句柄指向同一个对象,但是一旦准备将句柄作为一个自变量或参数传递,别名问题就会自动重现。下面就是讲如何克服这个问题。

二、制作本地副本

     在讲制作本地副本之前我需要强调一下,我们无须在传值和传址上纠结,如果知道他们的区别后,即可以灵活的运用,这个在实际的编程的过程中为我们带来的好处远远大于弊端。只有我们明确的需要,句柄传递后的使用过程中,不能对外面的对象产生影响,才考虑下面的方式。

    对象的克隆就是我们这里要讲解的,这也是本地副本最常见的一种用途。只需要简单的使用clone()方法即可。这个方法在基础类object中定义成了protected模式,但在希望克隆的任何衍生类中,必须将其覆盖为public模式。

    采用clone()方法复制对象,我们通常把这种方法叫做“简单复制”或者“浅层复制”。因为他只是复制了一个对象的表面部分。实际对象除了包含这个表面以外,还包括句柄指向的所有对象,以及那些对象又指向的其他所有对象。由此类推,这便是对象网或对象关系网。如果可以复制下所有这张网,便叫做全面复制。

三、如何实现clone()

    为了使一个对象的克隆能力成功圆满,需要实现Cloneable接口。这个接口使人感觉奇怪,因为他是一个空的! interface Cloneable{}

    之所以要实现这个空接口,显然不是因为准备上塑造成一个Cloneable,以及调用其他的某个方法。而是为了实现一种标记的功能。首先,可能有一个上塑造句柄指向一个基础类型,而且不知道他是否真的能克隆那个对象,在这种情况下,可用instanceof调查句柄是否确实实现克隆功能;其次,我们可能不愿意所有对象类型都能克隆,所以Object.clone()会验证一个类是否真的实现了Cloneable接口,判断类是否真的可以Clone()。

    在Object.clone()正式开始操作之前,首先会检查一个类是否实现Cloneable。如果已经实现,则Object.clone()会检查原先的对象有多大,在为新对象腾出足够大的内存,将所有二进制位从原来的对象复制到新的对象,这就是“按位复制”。

     注意:java对“是否等价”的测试并不对所对比对象的内部进行检查,从而核实他们的值是否相同。==和!=运算符知识简单的对比句柄内容。若句柄的地址相同,就认为句柄指向同样的对象,所以认为他们是“等价”的。所以运算符真正检测的是“由于别名问题,句柄是否指向同一个对象?”。

     在克隆的过程中,第一个部分都应该调用super.clone()。这样做的目的就是希望复制整条对象链,达到深层复制的目的。试图深层复制合成对象时会遇到一个问题。必须假定成员对象中的 clone()方法也能依次对自己的句柄进行深层复制,以此类推。这使我们的操作变得复杂。为了能正常实现深层复制,必须对所有类中的代码进行控制,或者至少全面掌握深层复制中需要涉及的类,确保它们自己的深层复制能正确进行。

四、再论String类

       String 类的对象被设计成“不可变”。若查阅联机文档中关于 String 类的内容,就会发现类中能够修改 String 的每个方法实际都创建和返回了一个崭新的 String 对象,新对象里包含了修改过的信息——原来的 String 是原封未动的。

       由于 String 对象是不可变的,所以能够根据情况对一个特定的 String 进行多次别名处理。因为它是只读的,所以一个句柄不可能会改变一些会影响其他句柄的东西。因此,只读对象可以很好地解决别名问题。通过修改产生对象的一个崭新版本,似乎可以解决修改对象时的所有问题,就象 String 那样。但对某些操作来讲,这种方法的效率并不高。一个典型的例子便是为 String 对象覆盖的运算符“+”。为了解决这个问题,java又引入了StringBuffer(可变字符串)。无须像string对象那样,在变动的过程中,需要差生很多垃圾中间字符串对象。

       

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics