警惕数组的浅拷贝

来源:互联网 发布:php 抽象工厂模式 编辑:程序博客网 时间:2024/05/22 04:43

有这样一个例子,第一个箱子里有赤橙黄绿青蓝紫7色气球,现在希望在第二个箱子中也放入7个气球,其中最后一个气球改为蓝色,也就是赤橙黄绿青蓝蓝7个气球,那我们很 容易就会想到第二个箱子中的气球可以通过拷贝第一个箱子中的气球来实现,毕竟有6个气 球是一样的嘛,来看实现代码:

public class Balloon {//编号private int id;//颜色private Color color;public Balloon(Color color,int id){ this.color = color;this.id = id;}public Balloon() {// TODO Auto-generated constructor stub}/*id、color 的 getter/setter方法省略 */public int getId() {return id;}public void setId(int id) {this.id = id;}public Color getColor() {return color;}public void setColor(Color color) {this.color = color;}//apache-common 包下的 ToStringBuilder重写 toString 方法 @Overridepublic String toString() {// TODO Auto-generated method stubreturn new ToStringBuilder(this).append ("编号 " , id).append ("颜色 ", color).toString();}

public enum Color {Red, Orange, Yellow, Green, Indigo,Blue, Violet;}

public class Client {   public static void main(String[] args) {int ballonNum=7;Balloon[] boxl=new Balloon[ballonNum];for(int i=0;i<ballonNum;i++){boxl[i]=new Balloon(Color.values()[i],i);}Balloon[] box2=ElongateArray.expandCapacity(boxl, boxl.length);box2[6].setColor(Color.Blue);for(Balloon b2:boxl){System.out.println(b2.toString());}}}


第二个箱子里最后一个气球的颜色毫无疑问是被修改成蓝色了,不过我们是通过拷贝第一个箱子里的气球然后再修改的方式来实现的,那会对第一个箱子的气球颜色有影响吗?我 们看输出:

Balloon®b2fd8f [编号=0,颜色=Red]

Balloon@a2 0892 [编号=1,颜色=0range]

Balloon@158b649[编号=2,颜色=Yellow]

Balloon@1037c71 [编号=3,颜色=Green]

Balloon®1546e25 [编号=4,颜色=Indigo]

Balloon@8a0d5d [编号=5,颜色=Blue]

Balloon@a470b8 [编号=6,顏色=Blue]

最后一个气球颜色竟然也被修改了,我们只是希望修改第二个箱子的气球啊,这是为何?这是很典型的浅拷贝(Shallow Clone)问题,前面第1章的序列化中也介绍过,但是这 里与之有一点不同:数组中的元素没有实现Serializable接口。

确实如此,通过copyOf方法产生的数组是一个浅拷贝,这与序列化的浅拷贝完全相同:基本类型是直接拷贝其他都是拷贝引用地址。需要说明的是,数组的clone方法也是与 此相同的,同样是浅拷贝,而且集合的clone方法也都是浅拷贝,这就需要大家在拷贝时多 留心了。


该方法用得最多的地方是在使用集合(如List)进行业务处理时,比如发觉需要拷贝集 合中的元素,可集合没有提供拷贝方法,如果自己写会很麻烦,所以干脆使用LisUoArray 方法转换成数组,然后通过Arrays.copyOf拷贝,再转换回集合,简单便捷!但是,非常遗 憾的是,这里我们又撞到浅拷贝的枪口上了,虽然很多时候浅拷贝可以解决业务问题,但更多时候会留下隐患,需要我们提防又提防。


问题找到了,解决方案也很简单,遍历boxl的每个元素,重新生成一个气球(Ballon) 对象,并放置到box2数组中,

public class ElongateArray {    public static<T> T[] expandCapacity(T[] datas,int newLen){newLen=newLen<0?0:newLen;//不能是负值    return copyOf(datas, newLen);//生成一个新数组,并拷贝原值    }            public static <T> T[] copyOf(T[] original, int newLength) {        return (T[]) copyOf(original, newLength, original.getClass());    }             public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {         T[] copy = ((Object)newType == (Object)Object[].class)                ? (T[]) new Object[newLength]                : (T[]) Array.newInstance(newType.getComponentType(), newLength);    try {    for(int i=0;i<newLength;i++){    copy[i]=(T) ReflectTester.copy(original[i]);    }        } catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}            return copy;    }}

copy[i]=(T) ReflectTester.copy(original[i]);为反射拷贝对象的方法 代码如下

public class ReflectTester {  public static Object copy(Object o) throws Exception{  Class<?> c=o.getClass();Object objectCopy=c.newInstance(); //获得对象的所有成员变量Field[] fields=c.getDeclaredFields();for(Field field:fields){//获取成员变量的名字String name=field.getName();//获取get和set方法的名字String firstLetter=name.substring(0,1).toUpperCase();//将属性的首字母转换为大String getMethodName="get"+firstLetter+name.substring(1);String setMethodName = "set" + firstLetter + name.substring(1);  System.out.println(getMethodName + "," + setMethodName);//获取方法对象Method getMethod=c.getMethod(getMethodName, new Class[]{});Method setMethod=c.getMethod(setMethodName, new Class[]{field.getType()});//注意set方法需要传入参数类型//调用get方法获取旧的对象的值Object value=getMethod.invoke(o, new Object[]{});//调用set方法将这个值复制到新的对象中去setMethod.invoke(objectCopy, new Object[]{value});}  return objectCopy;  }}


0 0
原创粉丝点击