第6章 面向对象入门

第6章 面向对象入门

本章读者可以学到如下实例:

  • 实例043 自定义图书类
  • 实例044 温度单位转换工具
  • 实例045 成员变量的默认初始化值
  • 实例046 单例模式的应用
  • 实例047 汉诺塔问题求解
  • 实例048 编写同名的方法
  • 实例049 构造方法的应用
  • 实例050 统计图书的销售量
  • 实例051 两只完全相同的宠物
  • 实例052 重新计算对象的哈希码
  • 实例053 使用字符串输出对象
  • 实例054 对象的假克隆
  • 实例055 Java对象的浅克隆
  • 实例056 Java对象的深克隆
  • 实例057 序列化与对象克隆
  • 实例058 深克隆效率的比较

实例044 温度单位转换工具

实例说明

目前有两种广泛使用的温度单位,即摄氏度和华氏度。在标准大气压下,沸腾的水可以表示成100摄氏度和212华氏度。
本实例将编写一个简单的温度单位转换工具,它可以将用户输入的摄氏度转换成华氏度

实现过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package com.mingrisoft;

import java.util.Scanner;

public class CelsiusConverter {
/**
* 摄氏度转华氏度
*
* @param celsius 摄氏度
* @return 华氏度
*/
public double celsiusToFahrenheit(double celsius) {
// 计算华氏温度
double fahrenheit = 1.8 * celsius + 32;
// 返回华氏温度
return fahrenheit;
}

/**
* 华氏度转摄氏度
*
* @param fahrenheit 华氏度
* @return 摄氏度
*/
private double fahrenheitToCelsius(double fahrenheit) {
// 计算摄氏度
double celsius = (fahrenheit - 32) / 1.8;
// 计算华氏度
return celsius;
}

public static void main(String[] args) {
// 创建类的对象
CelsiusConverter converter = new CelsiusConverter();
System.out.print("请输入 摄氏度:");
// 获得控制台输入
Scanner in = new Scanner(System.in);
// 获得用户输入的摄氏温度
double celsius = in.nextDouble();
// 转换温度为华氏度
System.out.println(celsius + "摄氏度=" + converter.celsiusToFahrenheit(celsius) + "华氏度");
System.out.print("请输入 华氏度:");
double fahrenheit = in.nextDouble();
System.out.println(fahrenheit + "华氏度=" + converter.fahrenheitToCelsius(fahrenheit) + "摄氏度");
in.close();
}
}

技术要点

实例046 单例模式的应用

单例类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.mingrisoft;

public class Emperor {
// 1 声明一个Emperor类的引用
private static Emperor emperor = null;

// 2 将构造方法私有
private Emperor() {
}

// 3 提供获取实例化引用的方法
public static Emperor getInstance() {
if (emperor == null) {
emperor = new Emperor();
}
return emperor;
}
}

测试类:

1
2
3
4
5
6
7
8
9
package com.mingrisoft;

public class Test {
public static void main(String[] args) {
Emperor emperor1 = Emperor.getInstance();
Emperor emperor2 = Emperor.getInstance();
System.out.println(emperor1 == emperor2);
}
}

技术要点

单例模式的特点在于仅能获得一个对象。为了防止其他用户创建对象,需要将构造方法设置成private的,然后提供一个静态方法,该方法返回这个类的对象。

实例047 汉诺塔问题求解

实例说明

汉诺塔问题的描述如下:有A、B和C 3根柱子,在A上从下往上按照从小到大的顺序放着64个圆盘,以B为中介,把盘子全部移动到C上。移动过程中,要求任意盘子的下面要么没有盘子,要么只能有比它大的盘子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.mingrisoft;

public class HanoiTower {
public static void moveDish(int level, char from, char inter, char to) {
// 如果只有一个盘子就退出迭代
if (level == 1) {
// System.out.println("从 " + from + " 移动盘子 1 号到 " + to);
System.out.println("从 " + from + " 移动盘子 " + level + " 号到 " + to);
}
// 如果有大于一个盘子就继续迭代
else {
moveDish(level - 1, from, to, inter);
System.out.println("从 " + from + " 移动盘子 " + level + " 号到 " + to);
moveDish(level - 1, inter, from, to);
}
}

public static void main(String[] args) {
// 设置汉诺塔为3阶
int nDisks = 3;
// int nDisks = 4;
// 实现移动算法
moveDish(nDisks, 'A', 'B', 'C');
}
}

运行效果

1
2
3
4
5
6
7
从 A 移动盘子 1 号到 C
从 A 移动盘子 2 号到 B
从 C 移动盘子 1 号到 B
从 A 移动盘子 3 号到 C
从 B 移动盘子 1 号到 A
从 B 移动盘子 2 号到 C
从 A 移动盘子 1 号到 C

技术要点

为了将N个盘子从A移动到C,需要先将第N个盘子上面的N-1个盘子移动到B上,这样才能将第N个盘子移动到C上。
同理,为了将第N-1个盘子从B移动到C上,需要将N-2个盘子移动到A上,这样才能将第N-1个盘子移动到C上。
通过递归就可以实现汉诺塔问题的求解。

实例051 两只完全相同的宠物

实例说明

由于生命的复杂性,寻找两只完全相同的宠物(类的对象)是不可能的。Java中,可以通过比较对象的成员变量来判断对象是否相同。本实例将创建3只宠物猫,通过比较它们的名字、年龄、重量和颜色属性来看它们是否相同。

实现过程

Cat.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.mingrisoft;

import java.awt.Color;

public class Cat {
// 表示猫咪的名字
private String name;
// 表示猫咪的年龄
private int age;
// 表示猫咪的重量
private double weight;
// 表示猫咪的颜色
private Color color;

// 初始化猫咪的属性
public Cat(String name, int age, double weight, Color color) {
this.name = name;
this.age = age;
this.weight = weight;
this.color = color;
}

// 利用属性来判断猫咪是否相同
@Override
public boolean equals(Object obj) {
// 1.如果是同一个对象,则相同
if (this == obj) {
return true;
}
// 2.如果有一个为null,则不同
if (obj == null) {
return false;
}
// 3.如果类型不同,则不同
if (getClass() != obj.getClass()) {
return false;
}
// 4.强制类型转换
Cat cat = (Cat) obj;
// 5.比较属性
return name.equals(cat.name) && (age == cat.age) && (weight == cat.weight) && (color.equals(cat.color));
}

@Override
public String toString() {// 重写toString()方法
StringBuilder sb = new StringBuilder();
sb.append("名字:" + name + ",\t");
sb.append("年龄:" + age + ",\t");
sb.append("重量:" + weight + ",\t");
sb.append("颜色:" + color + "");
return sb.toString();
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.mingrisoft;

import java.awt.Color;

public class Test {
public static void main(String[] args) {
// 创建猫咪1号
Cat cat1 = new Cat("Java", 12, 21, Color.BLACK);
// 创建猫咪2号
Cat cat2 = new Cat("C++", 12, 21, Color.WHITE);
// 创建猫咪3号
Cat cat3 = new Cat("Java", 12, 21, Color.BLACK);
// 输出猫咪1号
System.out.println("猫咪1号:" + cat1);
// 输出猫咪2号
System.out.println("猫咪2号:" + cat2);
// 输出猫咪3号
System.out.println("猫咪3号:" + cat3);
// 比较是否相同
System.out.println("猫咪1号是否与猫咪2号相同:" + cat1.equals(cat2));
// 比较是否相同
System.out.println("猫咪1号是否与猫咪3号相同:" + cat1.equals(cat3));
}

}

运行效果

1
2
3
4
5
猫咪1号:名字:Java,    年龄:12,       重量:21.0,     颜色:java.awt.Color[r=0,g=0,b=0]
猫咪2号:名字:C++, 年龄:12, 重量:21.0, 颜色:java.awt.Color[r=255,g=255,b=255]
猫咪3号:名字:Java, 年龄:12, 重量:21.0, 颜色:java.awt.Color[r=0,g=0,b=0]
猫咪1号是否与猫咪2号相同:false
猫咪1号是否与猫咪3号相同:true

技术要点

Java中的类都是Object类的直接或间接子类。在Object类中定义了equals方法用于比较类的两个对象是否相同。该方法的默认实现仅能比较两个对象是否是同一个对象。通常在定义类时推荐重写这个方法。

重点 equals方法的规范定义描述

  • 1.如果是同一个对象,则相同
  • 2.如果有一个为null,则不同
  • 3.如果类型不同,则不同
  • 4.强制类型转换,比较属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public boolean equals(Object obj) {
// 1.如果是同一个对象,则相同
if (this == obj) {
return true;
}
// 2.如果有一个为null,则不同
if (obj == null) {
return false;
}
// 3.如果类型不同,则不同
if (getClass() != obj.getClass()) {
return false;
}
// 4.强制类型转换
Cat cat = (Cat) obj;
// 5.比较属性
return name.equals(cat.name) && (age == cat.age) && (weight == cat.weight) && (color.equals(cat.color));
}

实例052 重新计算对象的哈希码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.mingrisoft;

import java.awt.Color;

public class Cat {
// 表示猫咪的名字
private String name;
// 表示猫咪的年龄
private int age;
// 表示猫咪的重量
private double weight;
// 表示猫咪的颜色
private Color color;

// 初始化猫咪的属性
public Cat(String name, int age, double weight, Color color) {
this.name = name;
this.age = age;
this.weight = weight;
this.color = color;
}

// 利用属性来判断猫咪是否相同
@Override
public boolean equals(Object obj) {
// 1.如果是同一个对象,则相同
if (this == obj) {
return true;
}
// 2.如果有一个为null,则不同
if (obj == null) {
return false;
}
// 3.如果类型不同,则不同
if (getClass() != obj.getClass()) {
return false;
}
// 4.强制类型转换
Cat cat = (Cat) obj;
// 5.比较属性
return name.equals(cat.name) && (age == cat.age) && (weight == cat.weight) && (color.equals(cat.color));
}

@Override
// 重写hashCode()方法
public int hashCode() {
return 7 * name.hashCode()
+ 11 * Integer.valueOf(age).hashCode()
+ 13 * Double.valueOf(weight).hashCode()
+ 17 * color.hashCode();
}

@Override
public String toString() {// 重写toString()方法
StringBuilder sb = new StringBuilder();
sb.append("名字:" + name + ",\t");
sb.append("年龄:" + age + ",\t");
sb.append("重量:" + weight + ",\t");
sb.append("颜色:" + color + "");
return sb.toString();
}

}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.mingrisoft;

import java.awt.Color;

public class Test {
public static void main(String[] args) {
// 创建猫咪1号
Cat cat1 = new Cat("Java", 12, 21, Color.BLACK);
// 创建猫咪2号
Cat cat2 = new Cat("C++", 12, 21, Color.WHITE);
// 创建猫咪3号
Cat cat3 = new Cat("Java", 12, 21, Color.BLACK);
// 输出猫咪1号的哈希码
System.out.println("猫咪1号的哈希码:" + cat1.hashCode());
// 输出猫咪2号的哈希码
System.out.println("猫咪2号的哈希码:" + cat2.hashCode());
// 输出猫咪3号的哈希码
System.out.println("猫咪3号的哈希码:" + cat3.hashCode());
// 比较是否相同
System.out.println("猫咪1号是否与猫咪2号相同:" + cat1.equals(cat2));
// 比较是否相同
System.out.println("猫咪1号是否与猫咪3号相同:" + cat1.equals(cat3));
}

}

运行效果

1
2
3
4
5
猫咪1号的哈希码:849794130
猫咪2号的哈希码:1119356584
猫咪3号的哈希码:849794130
猫咪1号是否与猫咪2号相同:false
猫咪1号是否与猫咪3号相同:true

简单的计算哈希码的方式

一种简单的计算哈希码的方式是:
将重写equals方法时使用到的成员变量,乘以不同的质数然后求和,以此作为新的哈希码

实例054 对象的假克隆

实例说明

对象的克隆是Java中的一项高级技术,它可以根据给定的对象,获得与其完全相同的另个对象。在克隆过程中,有些注意事项。本实例将演示常见的错误,其运行效果如图6.12所示。

技术要点

Java中,对于基本类型可以使用“==”来进行克隆,此时两个变量除了相等是没有任何关系的。而对于引用类型却不能简单地使用“==”进行克隆,这与Java的内存空间使用有关。Java将内存空间分成两块,即栈和堆。

  • 在栈中保存基本类型和引用变量;
  • 在堆中保存对象。

对于引用变量而言,使用“==”将修改引用,而不是复制堆中的对象。此时两个引用变量将指向同一个对象。因此,如果一个变量对其进行修改则会改变另一个变量。

实例055 Java对象的浅克隆

克隆相关内容

实例说明

在克隆对象时,

  • 如果对象的成员变量是基本类型,则使用浅克隆即可完成。
  • 如果对象的成员变量包括可变引用类型,则需要使用深克隆。

本实例将演示如何使用浅克隆

实现过程

Address.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.mingrisoft;

public class Address {
// 表示员工所在的国家
private String state;
// 表示员工所在的省
private String province;
// 表示员工所在的市
private String city;

// 利用构造方法进行初始化
public Address(String state, String province, String city) {
this.state = state;
this.province = province;
this.city = city;
}

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public String getProvince() {
return province;
}

public void setProvince(String province) {
this.province = province;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

// 重写toString()方法
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("国家:" + state + ", ");
sb.append("省:" + province + ", ");
sb.append("市:" + city);
return sb.toString();
}
}

Employee.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.mingrisoft;

public class Employee implements Cloneable {
// 表示员工的姓名
private String name;
// 表示员工的年龄
private int age;
// 表示员工的地址
private Address address;

// 利用构造方法进行初始化
public Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

// 重写toString()方法
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("姓名:" + name + ", ");
sb.append("年龄:" + age + "\n");
sb.append("地址:" + address);
return sb.toString();
}

// 实现浅克隆
@Override
public Employee clone() {
Employee employee = null;
try {
// 直接调用Object的clone方法
employee = (Employee) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return employee;
}
}

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.mingrisoft;

public class Test {
public static void main(String[] args) {
System.out.println("---------------------克隆之前--------------------------");
// 创建address对象
Address address = new Address("中国", "吉林", "长春");
// 创建employee1对象
Employee employee1 = new Employee("张三", 30, address);
System.out.println("员工1的信息:");
// 输出employee1对象
System.out.println(employee1);
System.out.println("---------------------克隆之前--------------------------");
// 使用克隆创建employee2对象
Employee employee2 = employee1.clone();
// 修改员工地址
employee2.getAddress().setState("中国");
// 修改员工地址
employee2.getAddress().setProvince("四川");
// 修改员工地址
employee2.getAddress().setCity("成都");
// 修改员工名字
employee2.setName("李四");
// 修改员工年龄
employee2.setAge(24);
System.out.println("员工1的信息:");
// 输出employee1对象
System.out.println(employee1);
System.out.println("员工2的信息:");
// 输出employee2对象
System.out.println(employee2);
}
}

运行效果:

1
2
3
4
5
6
7
8
9
10
11
---------------------克隆之前--------------------------
员工1的信息:
姓名:张三, 年龄:30
地址:国家:中国, 省:吉林, 市:长春
---------------------克隆之前--------------------------
员工1的信息:
姓名:张三, 年龄:30
地址:国家:中国, 省:四川, 市:成都
员工2的信息:
姓名:李四, 年龄:24
地址:国家:中国, 省:四川, 市:成都

从运行结果上看,使用浅克隆得来的对象和原来的对象之间的

  • 基本类型的属性和不可变引用类型的属性不会相互影响
  • 可变引用类型的属性会相互影响

多学两招

如果引用类型是不可变的,如String类的对象,则不必进行深克隆

技术要点

当需要克隆对象时,需要使用clone()方法,该方法的声明如下:

需要注意的是,该方法是一个受保护的方法,通常需要重写该方法并将访问权限限定符改成public。该方法将类中各个域进行复制,如果对于引用类型的域,这种操作就会有问题,因此称作浅克隆
提供克隆功能的类需要实现Cloneable接口,否则使用cloned方法时会抛出CloneNotSupportedException异常。

实例056 Java对象的深克隆

实例说明

如果类的成员变量中包括可变引用类型,则在克隆时就需要使用深克隆。本实例将演示如何使用深克隆

实现过程

Address.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.mingrisoft;

public class Address implements Cloneable {
// 表示员工所在的国家
private String state;
// 表示员工所在的省
private String province;
// 表示员工所在的市
private String city;

// 利用构造方法进行初始化
public Address(String state, String province, String city) {
this.state = state;
this.province = province;
this.city = city;
}

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public String getProvince() {
return province;
}

public void setProvince(String province) {
this.province = province;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

// 重写toString()方法
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("国家:" + state + ", ");
sb.append("省:" + province + ", ");
sb.append("市:" + city);
return sb.toString();
}

@Override
// 实现浅克隆
protected Address clone() {
Address address = null;
try {
address = (Address) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return address;
}
}

脚下留神:Address类的域不是基本类型就是不可变类型,所以可以直接使用浅克隆

Employee.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.mingrisoft;

public class Employee implements Cloneable {
// 表示员工的姓名
private String name;
// 表示员工的年龄
private int age;
// 表示员工的地址
private Address address;

// 利用构造方法进行初始化
public Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

@Override
// 重写toString()方法
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("姓名:" + name + ", ");
sb.append("年龄:" + age + "\n");
sb.append("地址:" + address);
return sb.toString();
}

@Override
// 实现浅克隆
public Employee clone() {
Employee employee = null;
try {
employee = (Employee) super.clone();
// 可变引用类型也调用clone方法
employee.address = address.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return employee;
}
}

多学两招:在Java5.0版中,支持重写方法时返回协变类型,因此可以返回Employee对象。

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.mingrisoft;

public class Test {
public static void main(String[] args) {
System.out.println("---------------------克隆之前--------------------------");
Address address = new Address("中国", "吉林", "长春");// 创建address对象
Employee employee1 = new Employee("张三", 30, address);// 创建employee1对象
System.out.println("员工1的信息:");
System.out.println(employee1);// 输出employee1对象
System.out.println("---------------------克隆之后--------------------------");
Employee employee2 = employee1.clone();// 使用克隆创建employee2对象
employee2.getAddress().setState("中国"); // 修改员工地址
employee2.getAddress().setProvince("四川"); // 修改员工地址
employee2.getAddress().setCity("成都"); // 修改员工地址
employee2.setName("李四"); // 修改员工名字
employee2.setAge(24);// 修改员工年龄
System.out.println("员工1的信息:");
System.out.println(employee1);// 输出employee1对象
System.out.println("员工2的信息:");
System.out.println(employee2);// 输出employee2对象
}

}

运行效果

1
2
3
4
5
6
7
8
9
10
11
---------------------克隆之前--------------------------
员工1的信息:
姓名:张三, 年龄:30
地址:国家:中国, 省:吉林, 市:长春
---------------------克隆之后--------------------------
员工1的信息:
姓名:张三, 年龄:30
地址:国家:中国, 省:吉林, 市:长春
员工2的信息:
姓名:李四, 年龄:24
地址:国家:中国, 省:四川, 市:成都

从运行结果可以看出,拷贝得来的员工2不再和员工1共享同一个地址对象了,修改员工2的地址,不会影响到员工1的地址.

技术要点

通常情况下,需要克隆对象时都需要使用深克隆。但需要注意的是,如果引用类型中还有可变的引用类型成员变量,则它也需要进行克隆。例如本实例中Address类如果增加了一个Date成员变量表示开始在此居住的时间,则其也需要被克隆。

实例057 序列化与对象克隆

实例说明

如果类的成员变量比较复杂,例如使用了多个可变引用类型,使用clone()方法来克隆是非常麻烦的。此时,也可以考虑使用序列化的方式完成克隆。本实例将演示其用法,实例的运行效果如图6.15所示。

实现过程

Address.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package com.mingrisoft;

import java.io.Serializable;

public class Address implements Serializable {
private static final long serialVersionUID = 4983187287403615604L;
// 表示员工所在的国家
private String state;
// 表示员工所在的省
private String province;
// 表示员工所在的市
private String city;

// 利用构造方法初始化各个域
public Address(String state, String province, String city) {
this.state = state;
this.province = province;
this.city = city;
}

public String getState() {
return state;
}

public void setState(String state) {
this.state = state;
}

public String getProvince() {
return province;
}

public void setProvince(String province) {
this.province = province;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public static long getSerialversionuid() {
return serialVersionUID;
}

@Override
// 使用地址属性表示地址对象
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("国家:" + state + ", ");
sb.append("省:" + province + ", ");
sb.append("市:" + city);
return sb.toString();
}

}

Employee.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.mingrisoft;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Employee implements Serializable {
private static final long serialVersionUID = 3049633059823371192L;
// 表示员工的姓名
private String name;
// 表示员工的年龄
private int age;
// 表示员工的地址
private Address address;

// 利用构造方法初始化各个域
public Employee(String name, int age, Address address) {
this.name = name;
this.age = age;
this.address = address;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}

@Override
// 重写toString()方法
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(" 姓名:" + name + ", ");
sb.append("年龄:" + age + "\n");
sb.append(" 地址:" + address);
return sb.toString();
}

/**
* 反序列化Employee.
*
* @return 反序列化得到的Employee对象.
*/
public static Employee readEmployee() {
Employee employee = null;
ObjectInputStream in = null;
try {
in = new ObjectInputStream(new FileInputStream("employee.dat"));
employee = (Employee) in.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return employee;
}

/**
* 序列化Employee.
*
* @param employee 需要序列化的Employee对象.
*/
public static void writeEmployee(Employee employee) {
ObjectOutputStream out = null;
// 对象输出流
try {
out = new ObjectOutputStream(new FileOutputStream("employee.dat"));
// 序列化对象1
out.writeObject(employee);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.mingrisoft;

public class Test {
public static void main(String[] args) {
System.out.println("----------------------------序列化之前----------------------------");
// 创建address对象
Address address = new Address("中国", "吉林", "长春");
// 创建employee1对象
Employee employee1 = new Employee("张三", 30, address);
System.out.println("员工1的信息:");
// 输出employee1对象
System.out.println(employee1);
System.out.println("----------------------------序列化之后----------------------------");
// 序列化employee1对象
Employee.writeEmployee(employee1);
// 反序列化得到employee2对象
Employee employee2 = Employee.readEmployee();
System.out.println("员工2的信息:");
System.out.println(employee2);
System.out.println("----------------------------员工2修改之后----------------------------");
if (employee2 != null) {
// 修改员工地址
employee2.getAddress().setState("中国");
// 修改员工地址
employee2.getAddress().setProvince("四川");
// 修改员工地址
employee2.getAddress().setCity("成都");
// 修改员工名字
employee2.setName("李四");
// 修改员工年龄
employee2.setAge(24);

System.out.println("员工1的信息:");
// 输出employee1对象
System.out.println(employee1);
System.out.println("员工2的信息:");
// 输出employee2对象
System.out.println(employee2);
}
}
}

运行效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
----------------------------序列化之前----------------------------
员工1的信息:
姓名:张三, 年龄:30
地址:国家:中国, 省:吉林, 市:长春
----------------------------序列化之后----------------------------
员工2的信息:
姓名:张三, 年龄:30
地址:国家:中国, 省:吉林, 市:长春
----------------------------员工2修改之后----------------------------
员工1的信息:
姓名:张三, 年龄:30
地址:国家:中国, 省:吉林, 市:长春
员工2的信息:
姓名:李四, 年龄:24
地址:国家:中国, 省:四川, 市:成都

从运行结果可以看出,修改员工2的成员变量,不会影响到员工1的成员变量.

技术要点

进行序列化的类需要实现Serializable接口,该接口中并没有定义任何方法,是一个标识接口。如果类中有可变的引用类型成员变量,则该变量也需要实现Serializable接口,依此类推。
本实例中采用将对象写入本地文件的方式完成序列化,读者也可以将其写入到内存中再读取。

实例058 深克隆效率的比较

实例说明前面介绍了两种实现深克隆的方式,本实例将分别使用这两种方式克隆10000个对象,并输出花费的时间,这样读者可以对它们的效率差异有个直观的认识。

实现过程

Employee.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.mingrisoft;

import java.io.Serializable;

public class Employee implements Cloneable, Serializable {
private static final long serialVersionUID = 5022956767440380940L;
// 表示员工的姓名
private String name;
// 表示员工的年龄
private int age;

// 利用构造方法初始化各个域
public Employee(String name, int age) {
this.name = name;
this.age = age;
}

@Override
// 重写toString()方法
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("姓名:" + name + ", ");
sb.append("年龄:" + age + "\n");
return sb.toString();
}

@Override
// 使用父类的clone()方法实现深克隆
protected Employee clone() {
Employee employee = null;
try {
employee = (Employee) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return employee;
}
}

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.mingrisoft;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public class Test {

public static void main(String[] args) {
// 创建Employee类的对象
Employee employee = new Employee("李四", 25);
// 获得当前系统时间
long currentTime = System.currentTimeMillis();
// 创建列表保存对象
List<Employee> employeeList1 = new ArrayList<Employee>();
// 循环次数
int times = 100000;
for (int i = 0; i < times; i++) {
// 使用克隆方式获得对象
Employee employee2 = employee.clone();
// 添加到List中
employeeList1.add(employee2);
}
System.out.println("克隆花费时间:" + (System.currentTimeMillis() - currentTime) + "毫秒");
// 获得当前时间
currentTime = System.currentTimeMillis();
// 创建列表保存对象
List<Employee> employeeList2 = new ArrayList<Employee>();
for (int i = 0; i < times; i++) {
// 序列化对象到内存
ByteArrayOutputStream byteArrayOut = witeEmployee(employee);
// 从内存中读取对象
Employee employee2 = readEmployee(byteArrayOut);
// 添加到List中
employeeList2.add(employee2);
}
System.out.println("序列化花费时间:" + (System.currentTimeMillis() - currentTime) + "毫秒");
}

/**
* 反序列化对象,从内存中读取出Employee对象.
*
* @param byteArrayOut 保存Empoyee对象的输出流.
* @return Employee对象.
*/
private static Employee readEmployee(ByteArrayOutputStream byteArrayOut) {
Employee employee = null;
// 获得字节数组输出流内容
ByteArrayInputStream bais = new ByteArrayInputStream(byteArrayOut.toByteArray());
ObjectInputStream in = null;
try {
// 创建对象输入流
in = new ObjectInputStream(bais);
// 读取对象
employee = (Employee) in.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
// 释放资源
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return employee;
}

/**
* 序列化对象,将Employee对象写入字节数组输出流
*
* @param employee 要序列化的对象.
* @return 字节数组输出流.
*/
private static ByteArrayOutputStream witeEmployee(Employee employee) {
// 创建字节数组输出流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = null;
try {
// 创建对象输出流
out = new ObjectOutputStream(baos);
// 将对象写入到输出流中
out.writeObject(employee);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
// 释放资源
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return baos;
}
}

运行效果

1
2
克隆花费时间:13毫秒
序列化花费时间:1992毫秒

可以看到使用序列化实现的深拷贝所需要的时间比深拷贝的时间要长的多

技术要点

使用ByteArrayOutputStreamByteArrayInputStream可以将对象保存在内存中,这样就不必产生一个本地文件来完成序列化的功能。