第12章 传递和返回对象
1.别名问题
”别名“就是多个句柄指向同一个对象,如果有人向对象写入东西,就会产生别名问题。
通常我们调用一个方法是为了产生返回值,或者用它改变为其调用方法的那个对象的状态。
很少需要调用一个方法来处理它的参数;这叫作利用方法的”副作用“。
解决别名的办法是制作副本。
2.制作本地副本
句柄有自己的作用域,而对象没有;不存在本地对象,只有本地句柄。
克隆对象,利用克隆制作本地副本。
Cloneable接口存在的原因:
(1)可能有一个上溯造型句柄指向一个基础类型,而不知道它是否真的能克隆那个对象,
在这种情况下,可以用instanceof关键字调查句柄是否确实同一个能克隆的对象连接。
(2)考虑到我们可能不愿意所有对象类型都能克隆,Object.clone()会验证一个类是否真的
是实现了Cloneable接口。如果没有实现的话,会抛出CloneNotSupportedException违例。
class MyObject implements Cloneable{
int i;
MyObject(int ii){ i=ii;}
public Object clone(){
Object o=null;
try{
o=super.clone();
}catch(CloneNotSupportedException e){
P.rintln("MyObject can't clone");
}
return o;
}
public String toString(){
return Integer.toString(i);
}
}
如上所示:让一个类能够克隆,需要做的工作如下:
(1)实现Cloneable接口
(2)覆盖clone()
(3)在Clone()中调用super.clone()
(4)在clone()中捕获违例;
3.克隆合成对象
下面是书上的例子:注意看 //Must clone handles:这个注释下面的代码
package c12;
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();
}
//Must clone handles:
o.depth=(DepthReading)o.depth.clone();
o.temperature=(TemperatureReading)o.temperature.clone();
return o; //Upcasts back to Object
}
}
public class DeepCopy {
public static void main(String[] args){
OceanReading reading=new OceanReading(33.9,100.5);
OceanReading r=(OceanReading)reading.clone();
System.out.println(reading+" "+r);
}
}
在合成类中,调用完super.clone()后,还需要为合成类内的每个句柄调用clone() 。
对于数组克隆会有一点陷阱,大家可以看我的另一篇Java-克隆数组。
4.通过序列化进行深层复制
序列化比克隆的执行效率低,而且还不稳定,所以还是使用克隆比较好。
5.克隆的控制
下面是书上的例子,主要讲的是控制对象能不能克隆的方法。
package c12;
//Can't clone this because it doesn't
//override clone():
class Ordinary{}
//Overrides clone,but doesn't implement
//Cloneable:
class WrongClone extends Ordinary{
public Object clone() {
Object o=null;
try{
o=super.clone(); //Throws exception
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
//Does all the right things for cloning:
class IsCloneable extends Ordinary implements Cloneable{
public Object clone() {
Object o=null;
try{
o=super.clone(); //Throws exception
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
//Turn off cloning by throwing the exception:
class NoMore extends IsCloneable{
public Object clone() {
Object o=null;
try{
throw new CloneNotSupportedException();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
class TryMore extends NoMore{
public Object clone() {
Object o=null;
o=super.clone(); //Throws exception
return o;
}
}
class BackOn extends NoMore{
private BackOn duplicate(BackOn b){
//Somehow make a copy of b
//and return that copy. This is a dummy
//copy,just to make the point:
return new 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{}
public class CheckCloneable {
static Ordinary tryToClone(Ordinary ord){
String id=ord.getClass().getName();
Ordinary x=null;
if(ord instanceof Cloneable){
System.out.println("Attmpting "+id);
x=(Ordinary)((IsCloneable)ord).clone();
System.out.println("Cloned "+id);
}
return x;
}
public static void main(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 implements Cloneable:
for(int i=0;i<ord.length;i++)
tryToClone(ord[i]);
}
}
6.副本构建器
Java中的副本构建器 不适合我们用,会丢失信息。
7.只读类
处理别名的问题,还可以用只读类,所有的句柄都不能修改对象,所以对于对象来说是安全的,
没有副作用。Java标准库就包含了”封装器“类,可以用于所有基本数据类型,不能修改,只读。
下面是创建只读类的例子:
package c12;
import oypj.tools.*;
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();
P.rintln("i1= "+i1.read());
P.rintln("quad= "+quad.read());
}
public static void main(String[] args){
Immutable1 x=new Immutable1(47);
P.rintln("x="+x.read());
f(x);
P.rintln("x="+x.read());
}
}
8.只读类的弊端
要修改对象的话就要创建新的对象,还会涉及更频繁的垃圾收集,像上面那个只读类的例子一样。
解决这个问题我们可以用”同志“类,使其可以修改。例如:
package c12;
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());
}
}
9.不变字串
String类对象被设计成不可变的,每个方法都创建和返回一个新的String对象。但是当使用”+ ” “+=“运算符连接多个
字符串的时候,要创建很多新类,导致执行效率变低。对于String来说StringBuffer就是它的同志类,利用append()
方法可以连接多个字符串,而且不会创建新的类,提高了执行效率。当我们创建字串的时候,编译器会帮我们调
用StringBuffer,来达到优化的目的。