分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
45.java编程思想——传递和返回对象 克隆的控制
为消除克隆能力,大家也许认为只需将clone()方法简单地设为private(私有)即可,但这样是行不通的,因为不能采用一个基础类方法,并使其在衍生类中更“私有”。所以事情并没有这么简单。此外,我们有必要控制一个对象是否能够克隆。对于我们设计的一个类,实际有许多种方案都是可以采取的:
(1) 保持中立,不为克隆做任何事情。也就是说,尽管不可对我们的类克隆,但从它继承的一个类却可根据实际情况决定克隆。只有Object.clone()要对类中的字段进行某些合理的操作时,才可以作这方面的决定。
(2) 支持clone(),采用实现Cloneable(可克隆)能力的标准操作,并覆盖clone()。在被覆盖的clone()中,可调用super.clone(),并捕获所有违例(这样可使clone()不“掷”出任何违例)。
(3) 有条件地支持克隆。若类容纳了其他对象的句柄,而那些对象也许能够克隆(集合类便是这样的一个例子),就可试着克隆拥有对方句柄的所有对象;如果它们“掷”出了违例,只需让这些违例通过即可。举个例子来说,假设有一个特殊的Vector,它试图克隆自己容纳的所有对象。编写这样的一个Vector 时,并不知道客户程序员会把什么形式的对象置入这个Vector 中,所以并不知道它们是否真的能够克隆。
(4) 不实现Cloneable(),但是将clone()覆盖成protected,使任何字段都具有正确的复制行为。这样一来,从这个类继承的所有东西都能覆盖clone(),并调用super.clone() 来产生正确的复制行为。注意在我们实现方案里,可以而且应该调用super.clone()——即使那个方法本来预期的是一个Cloneable 对象(否则会掷出一个违例),因为没有人会在我们这种类型的对象上直接调用它。它只有通过一个衍生类调用;对那个衍生类来说,如果要保证它正常工作,需实现Cloneable。
(5) 不实现Cloneable 来试着防止克隆,并覆盖clone(),以产生一个违例。为使这一设想顺利实现,只有令从它衍生出来的任何类都调用重新定义后的clone()里的suepr.clone()。
(6) 将类设为final,从而防止克隆。若clone()尚未被我们的任何一个上级类覆盖,这一设想便不会成功。若已被覆盖,那么再一次覆盖它,并“掷”出一个CloneNotSupportedException(克隆不支持)违例。为担保克隆被禁止,将类设为final 是唯一的办法。除此以外,一旦涉及保密对象或者遇到想对创建的对象数量进行控制的其他情况,应该将所有构建器都设为private,并提供一个或更多的特殊方法来创建对象。采用这种方式,这些方法就可以限制创建的对象数量以及它们的创建条件。
class Ordinary {
}
// Overrides clone, but doesn't implement
// Cloneable:
class WrongCloneextends Ordinary {
public Object clone()throws CloneNotSupportedException {
returnsuper.clone(); // Throws exception
}
}
// Does all the right things for cloning:
class IsCloneableextends Ordinary implements Cloneable {
public Object clone()throws CloneNotSupportedException {
returnsuper.clone();
}
}
// Turn off cloning by throwing the exception:
class NoMoreextends IsCloneable {
public Object clone()throws CloneNotSupportedException {
thrownew CloneNotSupportedException();
}
}
class TryMoreextends NoMore {
public Object clone()throws CloneNotSupportedException {
// Calls NoMore.clone(), throws exception:
returnsuper.clone();
}
}
class BackOnextends NoMore {
private BackOn duplicate(BackOnb) {
// Somehow make a copy of b
// and return that copy. This is a dummy
// copy, just to make the point:
returnnew BackOn();
}
public Object clone() {
// Doesn't call NoMore.clone():
return duplicate(this);
}
}
// Can't inherit from this, so can't override
// the clone method like in BackOn:
final class ReallyNoMore extends NoMore {
}
publicclass CheckCloneable {
static Ordinary tryToClone(Ordinaryord) {
String id =ord.getClass().getName();
Ordinary x =null;
if (ordinstanceofCloneable) {
try {
System.out.println("Attempting "+id);
x = (Ordinary) ((IsCloneable)ord).clone();
System.out.println("Cloned "+id);
} catch (CloneNotSupportedExceptione) {
System.out.println("Could not clone "+id);
}
}
returnx;
}
publicstaticvoidmain(String[]args){
// Upcasting:
Ordinary[] ord = {new IsCloneable(), new WrongClone(),new NoMore(), new TryMore(),new BackOn(),
new ReallyNoMore(), };
Ordinary x =new Ordinary();
// This won't compile, since clone() is
// protected in Object:
// ! x = (Ordinary)x.clone();
// tryToClone() checks first to see if
// a class implementsCloneable:
for (inti = 0; i < ord.length; i++)
tryToClone(ord[i]);
}
} /// :~
AttemptingIsCloneable
ClonedIsCloneable
AttemptingNoMore
Couldnot clone NoMore
AttemptingTryMore
Couldnot clone TryMore
AttemptingBackOn
ClonedBackOn
AttemptingReallyNoMore
Couldnot clone ReallyNoMore
第一个类Ordinary:不支持克隆,但在它正式应用以后,却也不禁止对其克隆。但假如有一个指向Ordinary对象的句柄,而且那个对象可能是从一个更深的衍生类上溯造型来的,便不能判断它到底能不能克隆。
WrongClone 类揭示了实现克隆的一种不正确途径。它确实覆盖了Object.clone(),并将那个方法设为public,但却没有实现Cloneable。所以一旦发出对super.clone()的调用(由于对Object.clone()的一个调用造成的),便会无情地掷出CloneNotSupportedException 违例。
在IsCloneable 中,大家看到的才是进行克隆的各种正确行动:先覆盖clone(),并实现了Cloneable。但是,这个clone()方法以及本例的另外几个方法并不捕获CloneNotSupportedException违例,而是任由它通过,并传递给调用者。随后,调用者必须用一个try-catch 代码块把它包围起来。在我们自己的clone()方法中,通常需要在clone()内部捕获CloneNotSupportedException 违例,而不是任由它通过。正如大家以后会理解的那样,对这个例子来说,让它通过是最正确的做法。
类NoMore 试图按照Java 设计者打算的那样“关闭”克隆:在衍生类clone()中,我们掷出
CloneNotSupportedException 违例。TryMore 类中的clone()方法正确地调用super.clone() ,并解析成NoMore.clone(),后者掷出一个违例并禁止克隆。
但在已被覆盖的clone()方法中,假若程序员不遵守调用super.clone()的“正确”方法,又会出现什么情况呢?在BackOn 中,可看到实际会发生什么。这个类用一个独立的方法duplicate()制作当前对象的一个副本,并在clone()内部调用这个方法,而不是调用super.clone()。违例永远不会产生,而且新类是可以克隆的。因此,我们不能依赖“掷”出一个违例的方法来防止产生一个可克隆的类。唯一安全的方法在ReallyNoMore 中得到了演示,它设为final,所以不可继承。这意味着假如clone()在final 类中掷出了一个违例,便不能通过继承来进行修改,并可有效地禁止克隆(不能从一个拥有任意继承级数的类中明确调用Object.clone();只能调用super.clone(),它只可访问直接基础类)。因此,只要制作一些涉及安全问题的对象,就最好把那些类设为final。
在类CheckCloneable 中,我们看到的第一个类是tryToClone(),它能接纳任何Ordinary 对象,并用instanceof 检查它是否能够克隆。若答案是肯定的,就将对象造型成为一个IsCloneable,调用clone(),并将结果造型回Ordinary,最后捕获有可能产生的任何违例。请注意用运行期类型鉴定打印出类名,使自己看到发生的一切情况。
在main()中,我们创建了不同类型的Ordinary 对象,并在数组定义中上溯造型成为Ordinary。在这之后的头两行代码创建了一个纯粹的Ordinary 对象,并试图对其克隆。然而,这些代码不会得到编译,因为clone()是Object 中的一个protected(受到保护的)方法。代码剩余的部分将遍历数组,并试着克隆每个对象,分别报告它们的成功或失败。
总之,如果希望一个类能够克隆,那么:
(1) 实现Cloneable 接口
(2) 覆盖clone()
(3) 在自己的clone()中调用super.clone()
(4) 在自己的clone()中捕获违例
这一系列步骤能达到最理想的效果。
克隆看起来要求进行非常复杂的设置,似乎还该有另一种替代方案。一个办法是制作特殊的构建器,令其负责复制一个对象。在C++中,这叫作“副本构建器”。刚开始的时候,这好象是一种非常显然的解决方案(如果你是C++程序员,这个方法就更显亲切)。下面是一个实际的例子:
class FruitQualities {
privateintweight;
privateintcolor;
privateintfirmness;
privateintripeness;
privateintsmell;
// etc.
FruitQualities() { // Default constructor
// do something meaningful...
}
// Otherconstructors:
// ...
// Copy constructor:
FruitQualities(FruitQualities f) {
weight =f.weight;
color =f.color;
firmness =f.firmness;
ripeness =f.ripeness;
smell =f.smell;
// etc.
}
}
class Seed {
// Members...
Seed() {
/* Default constructor */ }
Seed(Seed s) {
/* Copy constructor */ }
}
class Fruit {
private FruitQualitiesfq;
privateintseeds;
private Seed[]s;
Fruit(FruitQualities q, int seedCount) {
fq = q;
seeds =seedCount;
s = new Seed[seeds];
for (inti = 0; i < seeds; i++)
s[i] =newSeed();
}
// Otherconstructors:
// ...
// Copyconstructor:
Fruit(Fruit f) {
fq = new FruitQualities(f.fq);
seeds =f.seeds;
// Call all Seed copy-constructors:
for (inti = 0; i < seeds; i++)
s[i] =newSeed(f.s[i]);
// Other copy-construction activities...
}
// To allowderived constructors (or other
// methods) toput in different qualities:
protectedvoidaddQualities(FruitQualities q) {
fq = q;
}
protected FruitQualities getQualities() {
returnfq;
}
}
class Tomatoextends Fruit {
Tomato() {
super(new FruitQualities(), 100);
}
Tomato(Tomato t) {// Copy-constructor
super(t);// Upcastfor base copy-constructor
// Other copy-construction activities...
}
}
class ZebraQualitiesextends FruitQualities{
privateintstripedness;
ZebraQualities() { // Default constructor
// do something meaningful...
}
ZebraQualities(ZebraQualities z) {
super(z);
stripedness =z.stripedness;
}
}
class GreenZebraextends Tomato {
GreenZebra() {
addQualities(new ZebraQualities());
}
GreenZebra(GreenZebra g) {
super(g);// CallsTomato(Tomato)
// Restore the right qualities:
addQualities(new ZebraQualities());
}
void evaluate() {
ZebraQualities zq = (ZebraQualities)getQualities();
// Do something with the qualities
// ...
}
}
publicclass CopyConstructor {
publicstaticvoidripen(Tomatot) {
// Use the "copy constructor":
t = new Tomato(t);
System.out.println("In ripen, t is a " +t.getClass().getName());
}
publicstaticvoidslice(Fruitf) {
f = new Fruit(f);// Hmmm...will this work?
System.out.println("In slice, f is a " +f.getClass().getName());
}
publicstaticvoidmain(String[]args){
Tomato tomato =new Tomato();
ripen(tomato);// OK
slice(tomato);// OOPS!
GreenZebra g =new GreenZebra();
ripen(g);// OOPS!
slice(g);// OOPS!
g.evaluate();
}
} /// :~
这个例子第一眼看上去显得有点奇怪。不同水果的质量肯定有所区别,但为什么只是把代表那些质量的数据
成员直接置入Fruit(水果)类?有两方面可能的原因。第一个是我们可能想简便地插入或修改质量。注意Fruit 有一个protected(受到保护的)addQualities()方法,它允许衍生类来进行这些插入或修改操作(大家或许会认为最合乎逻辑的做法是在Fruit 中使用一个protected 构建器,用它获取FruitQualities 参数,但构建器不能继承,所以不可在第二级或级数更深的类中使用它)。通过将水果的质量置入一个独立的类,可以得到更大的灵活性,其中包括可以在特定Fruit 对象的存在期间中途更改质量。之所以将FruitQualities 设为一个独立的对象,另一个原因是考虑到我们有时希望添加新的质量,或者通过继承与多形性改变行为。注意对GreenZebra 来说(这实际是西红柿的一类——我已栽种成功,它们简直令人难以置信),构建器会调用addQualities(),并为其传递一个ZebraQualities 对象。该对象是从FruitQualities 衍生出来的,所以能与基础类中的FruitQualities句柄联系在一起。当然,一旦GreenZebra 使用FruitQualities,就必须将其下溯造型成为正确的类型(就象evaluate()中展示的那样),但它肯定知道类型是ZebraQualities。
大家也看到有一个Seed (种子)类,Fruit(大家都知道,水果含有自己的种子)包含了一个Seed 数组。最后,注意每个类都有一个副本构建器,而且每个副本构建器都必须关心为基础类和成员对象调用副本构建器的问题,从而获得“深层复制”的效果。对副本构建器的测试是在CopyConstructor 类内进行的。方法ripen()需要获取一个Tomato 参数,并对其执行副本构建工作,以便复制对象:
t = new Tomato(t);
而slice()需要获取一个更常规的Fruit 对象,而且对它进行复制:
f = new Fruit(f);
它们都在main()中伴随不同种类的Fruit 进行测试。
在slice()内部对Tomato 进行了副本构建工作以后,结果便不再是一个Tomato 对象,而只是一个Fruit。它已丢失了作为一个Tomato(西红柿)的所有特征。此外,如果采用一个GreenZebra,ripen()和slice()会把它分别转换成一个Tomato 和一个Fruit。所以非常不幸,假如想制作对
象的一个本地副本,Java 中的副本构建器便不是特别适合我们。
1. 为什么在C++的作用比在Java 中大?
副本构建器是C++的一个基本构成部分,因为它能自动产生对象的一个本地副本。但前面的例子确实证明了它不适合在Java 中使用,为什么呢?在Java 中,我们操控的一切东西都是句柄,而在C++中,却可以使用类似于句柄的东西,也能直接传递对象。这时便要用到C++的副本构建器:只要想获得一个对象,并按值传递它,就可以复制对象。所以它在C++里能很好地工作,但应注意这套机制在Java 里是很不通的,所以不要用它。