数据传递与对象复制

Java中,8种类型的数据类型(byte、short、int、long、char、float、double、char)是值类型,而类(class)、枚举(enum)等则是引用类型。

值类型与引用类型在内存管理方式、数据传递方式等方面有所不同,下面,我们通过一些示例来了解。

引用类型

下面的代码,我们使用CAuto类来演示引用类型的赋值与参数传递操作。

Java
public class Hello {
public static void main(String[] args) throws Exception{
		CAuto auto1 = new CAuto("auto1", 4);
		System.out.printf("%s %d 门 \n", auto1.model, auto1.getDoorCount());
		// 对象赋值
		CAuto auto2 = auto1;
		auto2.model = "auto2";
		auto2.setDoorCount(5);
		System.out.println("----对象赋值----");
		System.out.printf("%s %d 门 \n", auto1.model, auto1.getDoorCount());
		System.out.printf("%s %d 门 \n", auto2.model, auto2.getDoorCount());
		// 对象参数
		autoFactory(auto1);
		System.out.println("----对象参数----");
		System.out.printf("%s %d 门 \n", auto1.model, auto1.getDoorCount());
		System.out.printf("%s %d 门 \n", auto2.model, auto2.getDoorCount());
	}
		public static void autoFactory(CAuto auto) throws Exception {
		auto.model = auto.model + " RS";
		auto.setDoorCount(2);
	}
}

先看执行效果,如下图。

本例,首先定义了一个CAuto实例,即auto1,并设置其型号(model)为auto1、车门数量为4。然后,定义了另一个CAuto实例auto2,这次并没有使用new关键字创建新的实例,而是将auto1对象赋值给auto2;由于类是引用类型,赋值时只是传递了对象的引用,即对象在内存位置,这样,auto1和auto2对象实际就指向的是同一对象区域;这样一来,下面的代码就比较容易理解了,当修改auto2对象的型号和车门数量时,实际上也会从auto1对象反映出来。

autoFactory()方法的参数也是CAuto类类型,当引用类型作为参数传递时,同样是传递引用,所以,通过autoFactory()方法修改后的auto1对象,其数据会实际发生变化,同时也反映到auto2对象中。

值类型

值类型在传递时会复制数据,所以,对副本的修改不会改变原有的数据,如下面的代码。

Java
public class Hello {
	public static void main(String[] args) {
		int x = 10;
		int y = x;
		System.out.printf("x=%d, y=%d \n",x,y);
		y = 99;
		System.out.printf("x=%d, y=%d",x,y);
	}
}

代码执行结果如下图。

引用类型的特殊情况

前一示例中,我们使用的int类型变量x和y,在将x的值赋给y时传递了数据副本,所以,修改y的值时是不会影响x的。那么,基本数据类型的包装类是不是按引用传递呢?答案是否定的,这些包装类的表现与基本数据类型是相同的,如下面的代码。

Java
public class Hello {
	public static void main(String[] args) {
		Integer x = 10;
		Integer y = x;
		System.out.printf("x=%d, y=%d \n",x,y);
		y = 99;
		System.out.printf("x=%d, y=%d",x,y);
	}
}

代码执行结果如下图。

引用类型中,另一个需要注意的类型是String类,它表示一个不可变字符串。当一个String类型的实例创建后,就不能修改它的内容了,任何改变字符串内容的操作都会创建新的String实例。如下面的代码。

Java
public class Hello {
	public static void main(String[] args) {
		String str1 = "abc";
		String str2 = str1;
		System.out.println(str1);
		System.out.println(str2);
		str2 = "def";
		System.out.println(str1);
		System.out.println(str2);
	}
}

代码执行结果如下图。

本例,首先定义了字符串对象str1,然后将其赋值为str2对象,其它的类类型此时会传递引用,而字符串则只能使用独立的对象内容;前两个输出显示了str1和str2对象的初始内容。然后,我们修改了str2的内容,实际上其本身指向的字符串对象也已经发生了变化,后两个输入显示了修改str2内容后的结果,可以看到,修改字符串str2的内容与str1对象无关。后续课程还有更多关于字符串操作的内容。

见于引用类型的特殊性,对象作为常量时,实际上是不能保证它的数据不被修改的,如下面的代码。

Java
public class Hello {
	public static final CDictItem<String,String> MARS = 
			new CDictItem<String,String>("mars","火星");
	public static void main(String[] args) {
		System.out.printf("%s : %s \n",MARS.key,MARS.value);
		MARS.key = "2rd earth";
		MARS.value = "第二地球";
		System.out.printf("%s : %s",MARS.key,MARS.value);
	}
}

代码执行结果如下图。

同样的道理,如果引用类型只想作为只读属性,也是不能保存对象数据不被修改的,如下面的代码,我们定义了一个简单的C5类。

Java
public class C5 {
	public int value;
}

下面的代码,我们创建的C6类中使用了C5类型的私有字段,并且只能通过getC5()方法读取字段对象。

Java
public class C6 {
	// 构造函数
	public C6(C5 obj) {
		c5 = obj;
	}
	//
	private C5 c5;
	public C5 getC5() {
		return c5;
	}
}

下面,在main()方法中测试这两个类。

Java
public class Hello {
	public static void main(String[] args) {
		C5 c5 = new C5();
		c5.value = 10;
		//
		C6 c6 = new C6(c5);
		System.out.println(c6.getC5().value);
		c6.getC5().value = 99;
		System.out.println(c6.getC5().value);
	}
}

代码执行结果如下图。

如果要保证传递对象不会影响原有的对象,可以通过对象的复制来完成。

对象复制

对象复制的第一种方法是实现Cloneable接口,关键是其中的clone()方法,如下面的代码,我们创建C5A类,它会实现Cloneable接口。

Java
public class C5A implements Cloneable {
	public int value;
	//
	public Object clone() {
		C5A obj = new C5A();
		obj.value = this.value;
		return obj;
	}
}

下面的代码,我们来测试对象复制是否有效。

Java
public class Hello {
	public static void main(String[] args) {
		C5A obj1 = new C5A();
		obj1.value = 10;
		C5A obj2 = (C5A)obj1.clone();
		System.out.println(obj1.value);
		System.out.println(obj2.value);
		//
		obj2.value = 99;
		System.out.println(obj1.value);
		System.out.println(obj2.value);
	}
}

代码执行结果如下图。

通过实现Cloneable接口复制对象的关键在于,在clone()方法实现上创建了一个新的对象,并将原对象中的所有数据都一一复制到新对象中;clone()方法的返回值类型是Object,所以,在获取复制后的对象时,应用使用相应的类型进行强制转换。

需要注意的是,如果对象的数据是引用类型,却没有实现Cloneable接口,那么复制的对象依然只是引用,如下面的代码,我们创建C6A类进行测试。

Java
public class Hello {
	public static void main(String[] args) {
		C5 c5 = new C5();
		c5.value = 10;
		C6A obj1 = new C6A(c5);
		C6A obj2 = (C6A)obj1.clone();
		System.out.println(c5.value);
		System.out.println(obj1.getC5().value);
		System.out.println(obj2.getC5().value);
		//
		obj1.getC5().value = 99;
		System.out.println(c5.value);
		System.out.println(obj1.getC5().value);
		System.out.println(obj2.getC5().value);
	}
}

代码执行结果如下图。

实际上,当我们观察整个代码时会发现,C5类只有一个实例,就是代码开始的c5;所以,即使C6A类实现了Cloneable接口,但对于C5类实例的操作就只有一个;这种情况下,是不能实现真正地复制对象的。

对象复制的另一种操作是序列化,即实现Serializable接口,它定义在java.io包中。下面的代码,首先修改C5类的定义。

Java
import java.io.Serializable;
public class C5 implements Serializable {
	public int value;
}

我们可以看到,Serializable接口并不需要实现什么成员,只需要在类的定义中添加实现就行;C6A类也需要做一些修改,如下面的代码。

Java
import java.io.Serializable;
public class C6A implements Cloneable, Serializable {
	private static final long serialVersionUID = 1L;
	// 构造函数
	public C6A(C5 obj) {
		c5 = obj;
	}
	//
	private C5 c5;
	public C5 getC5() {
		return c5;
	}
	// Cloneable接口
	public Object clone() {
		C6A obj = new C6A((C5)CObj.clone(this.c5));
		return obj;
	}
}

只添加实现Serializable接口的标识还不够,对于序列化的操作还需要单独编码完成,如下面的代码,我们在CObj类中封装了clone(Object)静态方法来通过序列化复制对象。

Java
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
public class CObj {
	// 对象复制
	public static Object clone(Object obj) {
		try
		{
			// 序列化
			ByteArrayOutputStream byteOutput = 
					new ByteArrayOutputStream();
			ObjectOutputStream objOutput = 
					new ObjectOutputStream(byteOutput);
			objOutput.writeObject(obj);
			objOutput.close();
			// 反序列化
			ByteArrayInputStream byteInput = 
					new ByteArrayInputStream(byteOutput.toByteArray());
			ObjectInputStream objInput= 
					new ObjectInputStream(byteInput);
			return objInput.readObject();
		}
		catch(Exception ex)
		{
			ex.printStackTrace();
			return null;
		}
	}
}

下面,在main()方法测试通过序列化实现对象复制。

Java
public class Hello {
	public static void main(String[] args) {
		C5 c5 = new C5();
		c5.value = 10;
		C6A obj1 = new C6A(c5);
		C6A obj2 = (C6A)CObj.clone(obj1);
		System.out.println(obj1.getC5().value);
		System.out.println(obj2.getC5().value);
		//
		obj1.getC5().value = 99;
		System.out.println(obj1.getC5().value);
		System.out.println(obj2.getC5().value);
	}
}

代码执行结果如下图。

本站内容均为原创作品,转载请注明出处,本页面网址为:http://caohuayu.com/chy/article/Article.aspx?code=cc001011