第十二章的标题是传递和返回对象,起初看的时候我认为这章会深入讲解如何在方法里改变参数的方法和需要注意的地方这方面的内容,但其实这章主要讲解的反而是如何传递句柄参数给方法,并且在方法内部改变对象而不影响原对象的解决办法。最后将问题引申到了如何创建一个非基础类型的副本,或者称为如何拷贝一个非基础类型对象。书中给出的方法是调用object类里面就定义了的clone方法,clone虽然在基础类里面就已经定义,clone确是 protected的。在新定义的类里面覆盖clone方法的话应该将其改为public,否则这个方法不能被其他程序使用。而且clone方法还有对于类是否继承Cloneable接口做出判断,如果类没有继承Cloneable接口的话,调用clone方法会抛出一个 CloneNotSupportedException违例。然而Cloneable接口其实没有任何内容,声明只有interface Cloneable {}对于他的继承大概只是为了能让clone方法中类似于 if (myHandle instanceof Cloneable) 这样的条件判断能通过而已。
clone方法在新定义的类里面覆盖方法如下:
import java.util.*;
class MyObject implements Cloneable
{
int i;
MyObject(int ii) { i = ii; }
public Object clone()
{
Object o = null;
try
{
o = super.clone();
}
catch (CloneNotSupportedException e)
{
System.out.println("MyObject can't clone");
}
return o;
}
}
当然使用完clone方法以后得到的是指向对象的Object型的句柄,我们必须将它下塑成MyObject才能使用类MyObject中新定义的方法和字段。
如果就这样看来复制一个对象似乎很简单,因为object类里的clone方法可以完全将一个对象按位复制一遍,这样的确可以保证得到一个只包含了基础类型的对象的一个完整副本。但是如果对象的属性里面包含了非基础类型会怎么样呢,这样的clone方法可行么?显然,这样做是不行的,这样做对象属性里面的非基础类型得不到完整的复制,只能得到那个非基础类型的句柄。所以遇到这种问题必须进行深层复制。
以下给出一个深层复制的例子:
class DepthReading implements Cloneable
{
private double depth;
public DepthReading(double depth)
{
this.depth = depth;
}
public Object clone()
{
Object o = null;
try
{
o = super.clone();
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
return o;
}
}
class TemperatureReading implements Cloneable
{
private long time;
private double temperature;
public TemperatureReading(double temperature)
{
time = System.currentTimeMillis();
this.temperature = temperature;
}
public Object clone()
{
Object o = null;
try
{
o = super.clone();
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
return o;
}
}
class OceanReading implements Cloneable
{
private DepthReading depth;
private TemperatureReading temperature;
public OceanReading(double tdata, double ddata)
{
temperature = new TemperatureReading(tdata);
depth = new DepthReading(ddata);
}
public Object clone()
{
OceanReading o = null;
try
{
o = (OceanReading)super.clone();
}
catch (CloneNotSupportedException e)
{
e.printStackTrace();
}
// 句柄必须被复制
o.depth = (DepthReading)o.depth.clone();
o.temperature = (TemperatureReading)o.temperature.clone();
return o;
}
}
public class DeepCopy
{
public static void main(String[] args)
{
OceanReading reading = new OceanReading(33.9, 100.5);
OceanReading r = (OceanReading)reading.clone();
}
}
这个作为深层复制的参考例子对我们很有帮助,但是这也只是一个简单的深层复制而已,说它简单不是说它的层数不够深,只是说这个程序使得深层复制容易实现,因为在现实编程中我们不一定能知道该对象属性中的所有句柄是否都有clone功能。如果对没有继承Cloneable接口类型的句柄调用 clone方法的话将会得到CloneNotSupportedException违例。所以在实际编程中深层复制并不是那么简单的工作。
之后书中对于创建对象副本提出了类似于C++中的副本构建器的方法。就是在构建器中重载一种传入同种类型对象实例进行创建对象的构建起,举个例子:
class FruitQualities {
private int weight;
private int color;
private int firmness;
private int ripeness;
private int smell;
// etc.
FruitQualities() { // Default constructor
// do something meaningful...
}
// Other constructors:
// ...
// Copy constructor:
FruitQualities(FruitQualities f) {
weight = f.weight;
color = f.color;
firmness = f.firmness;
ripeness = f.ripeness;
smell = f.smell;
// etc.
}
}
其中的FruitQualities(FruitQualities f)就是一个副本构建器
这种进行对象复制的办法从感觉上来说比clone方法直观而且代码的可读性也更好,个人也认为clone方法并不是很好用的方法,但是think in java的作者告诉我们副本构建器在java里工作并不像C++中的那么好用。
给出个例子
class FruitQualities
class Seed
class Fruit
class ZebraQualities extends FruitQualities
class GreenZebra extends Tomato
//以上类 代码省略 每个类都有一个副本构建器
public class CopyConstructor
{
public static void ripen(Tomato t)
{
// Use the "copy constructor":
t = new Tomato(t);
System.out.println("In ripen, t is a " +
t.getClass().getName());
}
public static void slice(Fruit f)
{
f = new Fruit(f); // Hmmm... will this work?
System.out.println("In slice, f is a " +
f.getClass().getName());
}
public static void main(String[] args)
{
Tomato tomato = new Tomato();
ripen(tomato); // OK
slice(tomato); // OOPS!
GreenZebra g = new GreenZebra();
ripen(g); // OOPS!
slice(g); // OOPS!
g.evaluate();
}
}
该程序的输出为:
In ripen, t is a Tomato
In slice, f is a Fruit
In ripen, t is a Tomato
In slice, f is a Fruit
对于这个结果,作者的分析为“从中可以看出一个问题。在slice()内部对Tomato 进行了副本构建工作以后,结果便不再是一个Tomato 对象,而只是一个Fruit。它已丢失了作为一个Tomato(西红柿)的所有特征。此外,如果采用一个GreenZebra,ripen()和 slice()会把它分别转换成一个Tomato 和一个Fruit。所以非常不幸,假如想制作对象的一个本地副本,Java 中的副本构建器便不是特别适合我们。”
所以副本对象问题还是得回到clone方法上面。至于书中提到的byvalue关键字(byvalue:java对于“传值”保留但未实现的关键字)在作者所处的1.1版本中没有实现,但是即使到了现在java1.6中似乎也并未实现(之所以说似乎是因为我在网上并没有查到关于byvalue的任何资料,但也可能只是没有查到。)
关于对象复制问题,书中还提到Doug lea提出的只需为每个类都创建一个名为duplicate()的函数即可。但是并没有具体讲解,所以暂时先摆在一边,以后有空再研究。
最后书中提到创建只读类的方法,只读类的实现具体来说就是将类的属性都设为私有,构建器以及不会改变对象的方法与一般类的方法一样,会改变对象属性的方法全部改成创建一个对象副本,然后对对象的副本进行改变,然后返回该对象。例如:
public class Immutable1
{
private int data;
public Immutable1(int initVal)
{
data = initVal;
}
public int read() { return data; }
public boolean nonzero() { return data != 0; }
public Immutable1 quadruple()
{
return new Immutable1(data * 4);
}
static void f(Immutable1 i1)
{
Immutable1 quad = i1.quadruple();
System.out.println("i1 = " + i1.read());
System.out.println("quad = " + quad.read());
}
public static void main(String[] args)
{
Immutable1 x = new Immutable1(47);
System.out.println("x = " + x.read());
f(x);
System.out.println("x = " + x.read());
}
}
这样创建只读类也可以解决对象的别名问题,但是这种只读类对于需要频繁改变属性的类来说代价太大,因为每次改变都必须生成一个对象的实例。
解决办法是创建一个“同志”类,具体例子为:
class Mutable
{
private int data;
public Mutable(int initVal)
{
data = initVal;
}
public Mutable add(int x)
{
data += x;
return this;
}
public Mutable multiply(int x)
{
data *= x;
return this;
}
public Immutable2 makeImmutable2()
{
return new Immutable2(data);
}
}
public class Immutable2
{
private int data;
public Immutable2(int initVal)
{
data = initVal;
}
public int read() { return data; }
public boolean nonzero() { return data != 0; }
public Immutable2 add(int x)
{
return new Immutable2(data + x);
}
public Immutable2 multiply(int x)
{
return new Immutable2(data * x);
}
public Mutable makeMutable()
{
return new Mutable(data);
}
public static Immutable2 modify1(Immutable2 y)
{
Immutable2 val = y.add(12);
val = val.multiply(3);
val = val.add(11);
val = val.multiply(2);
return val;
}
// This produces the same result:
public static Immutable2 modify2(Immutable2 y)
{
Mutable m = y.makeMutable();
m.add(12).multiply(3).add(11).multiply(2);
return m.makeImmutable2();
}
public static void main(String[] args)
{
Immutable2 i2 = new Immutable2(47);
Immutable2 r1 = modify1(i2);
Immutable2 r2 = modify2(i2);
System.out.println("i2 = " + i2.read());
System.out.println("r1 = " + r1.read());
System.out.println("r2 = " + r2.read());
}
}
这样可以一定程度提高只读类的效率。