Java是有指针的!事实上,Java中每个对象(除基本数据类型以外)的标识符都属于指针的一种。
但它们的使用受到了严格的限制和防范,不仅编译器对它们有“戒心”,运行期系统也不例外。
或者换从另一个角度说,Java有指针,但没有传统指针的麻烦。
我曾一度将这种指针叫做“句柄”,但你可以把它想像成“安全指针”(对象的实际存储位置)。
“别名”意味着多个句柄都试图指向同一个对象。
一旦准备将句柄作为一个自变量或参数传递——这是Java设想的正常方法——别名问题就会自动出现,因为创建的本地句柄可能修改“外部对象”(在方法作用域之外创建的对象)。
方法的“副作用”(Side Effect)
Java中的所有自变量或参数传递都是通过传递句柄进行的。
也就是说,当我们传递“一个对象”时,实际传递的只是指向位于方法外部的那个对象的“一个句柄”。
所以一旦要对那个句柄进行任何修改,便相当于修改外部对象。此外:
■参数传递过程中会自动产生别名问题
■不存在本地对象,只有本地句柄
■句柄有自己的作用域,而对象没有
■对象的“存在时间”在Java里不是个问题
■没有语言上的支持(如常量)可防止对象被修改(以避免别名的副作用)
本地副本:
Clone是“克隆”的意思,即制作完全一模一样的副本。
“简单复制”或者“浅层复制”:它只复制了一个对象的“表面”部分。
实际对象除包含这个“表面”以外,还包括句柄指向的所有对象,以及那些对象又指向的其他所有对象,由此类推。这便是“对象网”或“对象关系网”的由来。
若能复制下所有这张网,便叫作“全面复制”或者“深层复制”。
尽管克隆方法是在所有类最基本的Object中定义的,但克隆仍然不会在每个类里自动进行。这似乎有些不可思议,因为基础类方法在衍生类里是肯定能用的。
Java确实有点儿反其道而行之;如果想在一个类里使用克隆方法,唯一的办法就是专门添加一些代码,以便保证克隆的正常进行。
克隆时要注意的两个关键问题是:几乎肯定要调用super.clone(),以及注意将克隆设为public。
实现Cloneable接口;
Java不可能在衍生之后反而缩小方法的访问范围。换言之,一旦对象变得可以克隆,从它衍生的任何东西都是能够克隆的,除非使用特殊的机制令其“关闭”克隆能力。
Cloneable interface的实现扮演了一个标记的角色,封装到类的类型中。
两方面的原因促成了Cloneable interface的存在:
首先,可能有一个上溯造型句柄指向一个基础类型,而且不知道它是否真的能克隆那个对象。
第二个原因是考虑到我们可能不愿所有对象类型都能克隆。
通过序列化进行深层复制
安全问题上出现问题:
clone()在Object里被设置成“protected”。必须将其覆盖,并使用“implement Cloneable”,同时解决违例的问题。
只有在准备调用Object的clone()方法时,才没有必要使用Cloneable接口,因为那个方法会在运行期间得到检查,以确保我们的类实现了Cloneable。
但为了保持连贯性(而且由于Cloneable无论如何都是空的),最好还是由自己实现Cloneable。
如果希望一个类能够克隆,那么:
(1) 实现Cloneable接口
(2) 覆盖clone()
(3) 在自己的clone()中调用super.clone()
(4) 在自己的clone()中捕获违例
这一系列步骤能达到最理想的效果。
克隆的另一种替代方案:副本构建器
创建“不变对象”,令其从属于只读类。可定义一个特殊的类,使其中没有任何方法能造成对象内部状态的改变。
Integer类(以及基本的“封装器”类)用简单的形式实现了“不变性”:它们没有提供可以修改对象的方法。
从表面看,不变类的建立似乎是一个好方案。但是,一旦真的需要那种新类型的一个修改的对象,就必须辛苦地进行新对象的创建工作,同时还有可能涉及更频繁的垃圾收集。
“同志”类,并使其能够修改。
这一方法特别适合在下述场合应用:
(1) 需要不可变的对象,而且
(2) 经常需要进行大量修改,或者
(3) 创建新的不变对象代价太高
String的解决方案。
String类的对象被设计成“不可变”。
若查阅联机文档中关于String类的内容,就会发现类中能够修改String的每个方法实际都创建和返回了一个崭新的String对象,新对象里包含了修改过的信息——原来的String是原封未动的。
由于String对象是不可变的,所以能够根据情况对一个特定的String进行多次别名处理。
因为它是只读的,所以一个句柄不可能会改变一些会影响其他句柄的东西。因此,只读对象可以很好地解决别名问题。
对字串来说,这个同志类叫作StringBuffer,编译器可以自动创建一个StringBuffer,以便计算特定的表达式,特别是面向String对象应用覆盖过的运算符+和+=时。
每个String方法都小心地返回了一个新的String对象。另外要注意的一个问题是,若内容不需要改变,则方法只返回指向原来那个String的一个句柄。这样做可以节省存储空间和系统开销。
StringBuffer最常用的一个方法是append()。在计算包含了+和+=运算符的String表达式时,编译器便会用到这个方法。insert()方法采用类似的形式。这两个方法都能对缓冲区进行重要的操作,不需要另建新对象。
String类并非仅仅是Java提供的另一个类。String里含有大量特殊的类。通过编译器和特殊的覆盖或过载运算符+和+=,可将引号字符串转换成一个String。
think in java回顾整理之传递以及返回对象
最新推荐文章于 2024-04-18 23:00:09 发布