Java 的对象与类(三)
对象与类(三)
1. 理解多态
在说多态之前,我们一起来看一下什么是向上转型和动态绑定。
(1)向上转型
向上转型的三种情况:
① 直接赋值
② 方法传参
③ 方法返回
① 直接赋值
在程序清单1中,父类的引用 引用了 子类对象 这一操作被称为向上转型。
程序清单1:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
public class Test {
public static void main(String[] args) {
Animal animal1 = new Dog("哈士奇"); //父类的引用 引用了 子类对象
System.out.println(animal1.name);
Dog dog = new Dog("阿拉斯加");
Animal animal2 = dog; //父类的引用 引用了 子类对象
System.out.println(animal2.name);
}
}
输出结果:
② 方法传参
程序清单2:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(){
System.out.println(name + " 正在吃烤肉");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
public class Test {
public static void func(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog("阿拉斯加");
func(dog); //方法传参
}
}
输出结果:
在程序清单2中,我们创建一个 func 方法,接收的参数类型是 Animal,接着我们在主函数调用 func(dog),奇怪的是,我们传参传的却是 Dog 类型,然而却不会报错。
也就是说传参传的是子类类型的变量,接收的是父类类型变量,那么这个行为也称为向上转型。
这个时候我们思考一下,如果我们传参传的是父类,接收的是子类,可以通过编译吗?答案是否,因为不符合继承的原则,编译器会报错。
③ 方法返回
程序清单3:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
public class Test {
public static Animal func(){
Dog dog = new Dog("阿拉斯加");
return dog;
}
public static void main(String[] args) {
System.out.println(func().name);
}
}
输出结果:
在程序清单3中,我们创建一个 func 方法,在创建方法的时候,定义的返回类型是 Animal 类型,然而,可以看见我们实际返回的却是 Dog 类型,接着,我们在主函数调用 func( ).name,这就等价于 dog.name,最终也可以得出想要的结果,那么这个操作也称为向上转型。
(2)动态绑定
在说动态绑定之前,我们先来看程序清单4和程序清单5.
程序清单4:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(){
System.out.println("某个动物正在吃东西");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog("小柴犬");
animal.eat();
}
}
输出结果:
程序清单5:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(){
System.out.println("某个动物正在吃东西");
}
}
class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void eat(){
System.out.println(name + " 正在吃东西");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Dog("小柴犬");
animal.eat();
}
}
输出结果:
我们发现程序清单5中,在父类和子类中,出现了同名的方法,而当通过父类的引用调用 eat( ) 方法时,输出结果竟然是子类的方法。那么我们就来总结几个概念。
动态绑定:
① 父类的引用 引用子类的对象
② 通过这个父类的引用 调用父类和子类同名的覆盖方法
重写:
重写表示,在继承的关系下,子类创建了父类已有的某个方法。
重写的规则:
① 方法名相同
② 参数的个数和类型都必须相同
③ 返回值相同
④ 方法名不能被 static 关键字修饰,方法名同时不能被 final 修饰
⑤ 子类方法的修饰限定符的范围必须大于父类方法的修饰限定符范围
如:子类方法使用 protected,父类方法使用 public,这就会使编译器报错。当然,如果使用 private,也是无法重写的,因为 private 修饰的方法只能在同一个类下才能使用。
对比重载的规则:
① 方法名相同
② 方法的参数个数不同 / 方法的参数类型不同
③ 方法的返回值类型不作要求
在程序清单6中,有一行代码被编译器报错,我已通过注释标明出来了。
程序清单6:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
}
class Bird extends Animal {
public int a = 10;
public Bird(String name) {
super(name);
}
public void fly(){
System.out.println(name + " 正在飞翔");
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Bird("杜鹃");
animal.fly(); //error
System.out.println(animal.a);; //error
}
}
结论:通过父类的引用,只能访问父类自己的成员变量和成员方法,而不能访问任何子类特有的成员变量和成员方法。
举个例子,我们可以看到,在上面的程序中,我们可以看到不是所有的动物都会飞,那么也就是说,父类存在的意义,就是对共性的提取,而发生动态绑定时,多数是有 [ 子类重写父类的方法 ] 的情况存在的。
程序清单7:
class A {
public A() {
func();
}
public void func() {
System.out.println("abc");
}
}
class B extends A {
@Override
public void func() {
System.out.println("xyz");
}
}
public class Test {
public static void main(String[] args) {
B d = new B();
}
}
输出结果:
在程序清单7中,程序也发生了动态绑定,因为子类重写了父类的成员方法,这就会导致我们在父类中遇到 func( ) 方法的时候,会直接选择子类的 func( )方法,详见下面的代码运行顺序。
代码运行顺序:
当然,我们在程序清单7 中,如果在类 B 中没有进行对 func( ) 方法的重写,那么输出的结果为 [ abc ].
请看程序清单8,比较复杂但并不难,我们一起看一下:
程序清单8:
class X{
Y y=new Y();
public X(){
System.out.print("X");
}
}
class Y{
public Y(){
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();
public Z(){
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
输出结果:
代码运行顺序:
(3)多态
多态含义: 一个引用,能表现出多种不同的形态
引入一个例子,比方说:我现在要画画,分别画圆形,矩形,三角形。不管是什么几何图案,它们都是某个形状,那么我们现在归一个类,这三个几何图案都归为形状 Shape 这个类。接下来,我来阐明代码思路:
在程序清单9中,我们创建一个父类 Shape 表示形状,另外创建三个子类 圆形 Cycle,矩形 Rect,花形 Flower。我们的目的是:通过三个子类继承父类的 draw( ) 方法来画出圆形,矩形,花形这三个形状,而我把 draw( ) 方法这个功能又通过 drawShape( ) 方法来实现,如果说:draw( ) 方法的意义就是某事物拥有的一个画画特性,那么drawShape( ) 方法的意义就是如何实现画画的这个特性,并把这个特性无限扩展给别的事物!
在程序中,我们要注意两个地方,这也是多态的核心要点:
-
我们在三个子类中分别对 draw( ) 方法进行了重写
-
我们在主函数中通过父类的引用 “引用了” 子类的对象(在这里,如果我们传参的传是子类对象也可以达到目的,我已通过注释表明出来了。)
程序清单9:
class Shape{
public void draw(){
}
}
class Cycle extends Shape{
@Override
public void draw() {
System.out.println("○");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("□");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("♣");
}
}
//分割线//
public class Test {
public static void drawShape(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape shape1 = new Cycle();
Shape shape2 = new Rect();
Shape shape3 = new Flower();
drawShape(shape1); //drawShap(new Cycle())
drawShape(shape2); //drawShap(new Rect())
drawShape(shape3); //drawShap(new Flower())
}
}
输出结果:
解析:
在程序清单9中,分割线上方的代码是 [ 类的实现者 ] 编写的,分割线下方的代码是 [ 类的调用者 ] 编写的。
当类的调用者在编写 drawShape 这个方法的时候, 参数类型为父类 Shape,此时在该方法内部并不知道,也不关注当前的 shape 这个引用指向的是 [ 哪个子类 ] 的实例。此时 shape 这个引用调用 draw( ) 方法可能会有多种不同的表现形式,而这种表象形式就称为多态。
但是我必须得额外声明一个点:子类引用 没必要 引用父类的对象,因为不是所有的形状都是圆形,不是所有的形状都是花形…
而圆形,花形,矩形,三角形,长方形等等都是归为了形状,希望读者能明白这个逻辑!
(4)多态的意义
在我第一次接触多态的时候,我认为这个语法是多余的、无用的。比方说:在程序清单9 中,我们大可不必用父类的引用 “引用” 子类的对象,直接通过子类创建一个子类的对象引用,其实也可以做到访问子类的成员。这样一想,连继承这个语法也是无用的了。但后来慢慢接触到更多的代码时,我发现了一个问题:就拿程序清单9 这个例子来说,比方说我现在要画画,但在实际生活中可不止是我上面的三种形状(圆形、矩形、花形),假设我现在需要在我的程序中添加更多的形状(三角形,菱形,长方形…),那么此时的多态的意义就体现出来了。
比方说:现在我可以将 Shap 类的引用全部放在一个数组中,而这些引用都是通过父类 Shap 引用到了不同的对象,而我每添加一个新的形状时,不用再为新的形状创建一个不同的画画方法,直接通过这个数组中的引用变量来执行画画这一操作即可。这样一来,我们可以发现这个多态所支持的拓展能力是很强的,因为当我们需要某个行为和状态,都可以通过父类来表象出来。可以继续往下看,程序清单10 就体现出来了多态的方便之处。
程序清单10:
class Shape{
public void draw(){
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("○");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("□");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("♣");
}
}
public class Test {
public static void drawShape(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape shape1 = new Cycle();
Shape shape2 = new Rect();
Shape shape3 = new Flower();
Shape[] shapes ={shape1,shape3,shape1,shape2,shape3};
for (Shape shape:shapes) {
drawShape(shape);
}
}
}
输出结果:
在程序清单10中,我们创建了一个 Shape 类型的数组,数组中放的是 Shape 类型(当然,也可以直接放子类的对象)这样一来,我们不但可以打印所有形状,也可以多次打印每个形状。所以说多态的行为拓展能力很强,给我们带来了效率的提升。
2. 抽象类和抽象方法
程序清单11:
abstract class Shape{
public int a;
public void func(){
}
abstract public void draw();
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("♣");
}
}
public class Test {
public static void drawShape(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Shape shape3 = new Flower();
drawShape(shape3);
}
}
输出结果:
在程序清单11 中,我对程序清单9 进行了简化,我添加了 abstract 关键字,可以发现我将父类变成了抽象类,父类中的一个成员方法我也变成了抽象方法。其写法变成了下面的形式:
abstract public void draw();
(1)抽象类存在的意义
为什么会有抽象类存在呢?抽象类,顾名思义,它就是一个很抽象,不具象的类,换句话说:抽象类的本身没有具体实现。它除了被子类继承外,没有用途、没有值、没有目的。我们也可以通俗地将抽象类理解为子类的空模板。
(2)总结 abstract 关键字
那么我们来总结一下被 abstract 修饰的类和方法:
- 被 abstract 修饰的类叫做 [ 抽象类 ]
- 被 abstract 修饰的方法叫做 [ 抽象方法 ]
① 抽象类不可以通过 new 实例化对象,抽象类的存在只是为了被子类继承
② 抽象类中依然可以包含普通的成员变量和非抽象的成员方法
③ 如果一个普通类继承了一个抽象类,那么普通类必须重写抽象类的所有抽象方法
④ 抽象方法没有具体实现,只是为了被子类重写方法再调用
⑤ 一个抽象类可以继承另一个抽象类,此时的情况与上述概述有些不同,由于用的少,读者可以自己通过编译器实现一下细节部分
⑥ abstract 修饰父类的意义只有一个,那就是父类被继承,其实际工作不应该由父类完成,而应由子类完成
3. 接口
接口从某种意义上来说,就是完完全全的抽象类。
接口:由 interface 实现,如:
interface IA {
}
(1)接口的语法规则
① 接口中的普通方法,不能有具体实现,如果非要实现,需要被 default 修饰这个方法
② 接口中的所有方法的限定符都是 public,那么所有的方法都可以省略 public,而接口中的所有抽象方法的限定符都是 abstract public,那么所有的抽象方法同样可以省略 abstract public
③ 接口中的成员变量默认是被 public static final 修饰的,也就是说,接口中的成员变量必须被初始化
④ 接口不可以通过 new 实例化对象
⑤ 如果一个类实现了一个接口,那么在这个类中,必须重写接口中所有的抽象方法,而且重写的抽象方法必须被 public 修饰(因为接口中的抽象方法默认是 public abstract)
⑥ 如果一个类 A 实现了接口 B,那么对应的代码格式为:
interface IB{
}
class A implements IB{
}
⑦ 一个类可以实现多个接口,然而一个类只能继承一个类
interface IX{
}
interface IY{
}
class Z{
}
class A extends Z implements IX,IY{
}
⑧ 两个接口之间使用 extends,表示一个接口拓展另一个接口。
注意下面代码,当类 A 实现了接口 IY ,类 A 必须重写两个方法。
interface IX{
void func1();
}
interface IY extends IX{
void func2();
}
class A implements IY{
@Override
public void func2() {
}
@Override
public void func1() {
}
}
在程序清单12中,我通过注释标明了接口使用时的一些语法规则,这十分重要!
程序清单12:
interface IShape{
public int a; //error
public static final int b = 10; //right
int c = 20; //right
public void func1(){ //error
}
default public void func2(){ //right
}
public static void write(){ //right
}
abstract public void draw1(){ //error
}
abstract public void draw2(); //right
void draw3(); //right
}
public class Test {
public static void main(String[] args) {
IShape iShape = new IShape(); //error
}
}
在程序清单13中,我利用类实现了接口,和前面的动态绑定思想一样,但实现方式不同,我模拟了形状打印
程序清单13:
interface IShape{
abstract public void draw();
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("○");
}
}
class Rect implements IShape {
@Override
public void draw() {
System.out.println("□");
}
}
class Flower implements IShape {
@Override
public void draw() {
System.out.println("♣");
}
}
public class Test {
public static void drawShape(IShape ishape){
ishape.draw();
}
public static void main(String[] args) {
IShape iShape1 = new Cycle();
IShape iShape2 = new Rect();
IShape iShape3 = new Flower();
drawShape(iShape1); //drawShape(new Cycle());
drawShape(iShape2); //drawShape(new Rect());
drawShape(iShape3); //drawShape(new Flower());
}
}
输出结果:
程序清单14:
class Animal{
String name;
public Animal(String name) {
this.name = name;
}
}
interface IFly{
void fly();
}
class Bird extends Animal implements IFly{
public Bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(name + " 正在飞");
}
}
public class Test {
public static void main(String[] args) {
Bird bird = new Bird("蜜蜂");
bird.fly();
}
}
输出结果:
在程序清单14中,我们可以看到类 Bird 继承了类 Animal,并实现了接口 IFly,因为我们知道,不是所有的动物都会飞,而在 Java 中,一个类最多只能继承一个类,所以我们可以让鸟类飞的功能放在接口 IFly 中。这样看来,从某种意义上来说,接口语法存在的意义,或许就是弥补类不能多个继承的短板。
(2)继承与接口的综合
程序清单15:
class Animal{
String name;
public Animal(String name) {
this.name = name;
}
public void eat(){
System.out.println(name + " 正在吃东西");
}
}
interface IFlying{
void fly();
}
class Bird extends Animal implements IFlying{
public Bird(String name) {
super(name);
}
//重写了接口中的方法
@Override
public void fly() {
System.out.println(name + " 正在飞");
}
//重写了父类中的方法
@Override
public void eat() {
System.out.println(name + " 正在喝蜂蜜");
}
}
public class Test {
public static void fly(IFlying iFlying){
iFlying.fly();
}
public static void main(String[] args) {
fly(new Bird("老鹰"));
new Bird("蜜蜂").eat();
}
}
输出结果:
对程序清单15进行分析:
在程序清单15中,我们演示了继承和接口,有几个语法点很关键:
① 在父类构造带参数的方法的同时,子类也需要构造同样的带参数方法。
public Bird(String name) {
}
② 在父类已有的普通成员方法情况下,子类却重写了父类的此方法,那么在编译时,系统直接使用子类重写的方法。
public void eat(){
}
③ 接口中的抽象方法是被 abstract public 修饰的,那么在一个类实现接口的时候,我们就必须对此抽象方法进行重写。
@Override
public void fly() {
System.out.println(name + " 正在飞");
}
4. 三个常用接口
(1)Comparable 接口
[ Comparable 接口、Comparator 接口 ] 这两个接口和我之前写的C语言博客qsort函数十分相似,思想是一样的,感兴趣的读者可以去翻一翻我的博客,这是链接:
qsort函数
引入:假设我们现在手中有一张学生名单,上面有他们的各种信息,那么我们想什么办法给这些学生进行排序呢?我们可以按名字首字母排序,也可以按年龄排序,当然在初中高中,每次月考完,都是按成绩排序。所以在程序清单16中,我们创建一个类 Student,表示一个学生,类中有其名字、年龄、成绩。此时我们生成一个带三个参数的构造方法。我们先让类实现接口 Comparable,在类 Student 中,我们重写方法 compareTo.
在主函数中,我们创建两个对象,然后通过对象变量 student1 调用这个 compareTo方法,进行比较 student2,我们先来将学生按名字进行排序吧!那么在 compareTo 中,this.age 就等价于 student1.age,o.age 就等价于 student2.age,也就是说:我们拿学生1和学生2的年龄进行排序,而实现这一功能,就需要使用 Comparable 接口。如果学生1的年龄大于学生2,那么就返回正数,反之,返回负数。
在程序清单1中,我们发现,类 Student 实现了 Comparable 接口,那么在类中必须重写 Comparable 接口的方法 compareTo( ). 当然,Comparable 接口就是为了比较而设计的,而其对应的 compareTo( ) 方法也是如此。
程序清单16:
class Student implements Comparable<Student>{
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student("A",21,90);
Student student2 = new Student("C",28,75);
System.out.println(student1.compareTo(student2));
}
}
输出结果:
在程序清单17中,我们选择让学生以年龄升序:
使用Arrays.sort() 方法的时候,编译器不知道我们是基于什么方式进行排序的,
因为 students 数组中,有三个不同的类型。
那么我们就实现比较接口,告诉编译器,以学生名字进行怕排序。
程序清单17:
class Student implements Comparable<Student>{
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("A",21,90);
students[1] = new Student("C",28,75);
students[2] = new Student("B",24,70);
System.out.println(Arrays.toString(students));
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
输出结果:
如果我们让学生以名字降序,那么我们就改变 compareTo 方法中的代码,代码如下:
public int compareTo(Student o) {
return o.age - this.age;
}
同样地,如果我们让学生以成绩升序,那么代码如下:
public int compareTo(Student o) {
return (int)(this.score - o.score);
}
如果我们让学生以名字升序,那么代码如下:
public int compareTo(Student o) {
return this.name.compareTo(o.name);
}
读者可以自己去试一下这些操作,了解不同类型的比较方式。
(2)Comparator 接口
我们来看一下 Comparator 接口来排序两个学生年龄的大小,与 Comparable 接口的思想相同,接下来我主要说明一下 Comparator 接口的语法。
在程序清单18中,我们需要额外创建一个类 AgeComparator 来实现 Comparator 接口,并在这个类中,我们创建一个重写方法 compare( ),那么在主函数中,我们传参只需要传两个对象变量 student1 和 student2 就可以了,那么回过头来看重写方法 compare( ),student1.age 就等价于 o1.age;student2.age 就等价于 o2.age.
与上面的 Comparable 接口思想相同,不管哪个类实现了接口,那么一定要重写对应接口中的抽象方法。接下来,让我们看一下 Comparator 接口是怎么进行实现的吧。
程序清单18:
class Student {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
}
class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
public class Test {
public static void main(String[] args) {
Student student1 = new Student("Z",11,85);
Student student2 = new Student("Y",18,73);
AgeComparator ageComparator = new AgeComparator();
System.out.println(ageComparator.compare(student1, student2));
}
}
输出结果:
在程序清单19中,我将一个学生的三个参数分别进行了排列。
程序清单19:
class Student {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
class ScoreComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return (int)(o1.score - o2.score);
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class Test {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student("Z",11,85);
students[1] = new Student("Y",18,73);
students[2] = new Student("X",14,80);
System.out.println(Arrays.toString(students));
System.out.println();
AgeComparator ageComparator = new AgeComparator();
Arrays.sort(students, ageComparator);
System.out.println(Arrays.toString(students));
System.out.println();
ScoreComparator scoreComparator = new ScoreComparator();
Arrays.sort(students,scoreComparator);
System.out.println(Arrays.toString(students));
System.out.println();
NameComparator nameComparator= new NameComparator();
Arrays.sort(students,nameComparator);
System.out.println(Arrays.toString(students));
System.out.println();
}
}
输出结果:
(3)Cloneable 接口
注意几个要点:
① 一个对象要被克隆,需要使用一个类实现 Cloneable 接口。
② 虽然通过一个对象调用了 Clone( ) 方法,但是必须在类中重写 Clone( ) 方法,这一点是 Cloneable 接口 特有的性质。
③ 这里 Clone( ) 方法 返回的是 Object 类型,所以我们要强制转换成 Person 类型。
④ Cloneable 是一个空接口,空接口又被称为标志接口,代表当前这个类是可以被克隆的。
程序清单20:( 代码实现的方式为深拷贝 )
class Person implements Cloneable{
public int age = 10;
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person) person1.clone();
System.out.println(person1);
System.out.println(person2);
System.out.println("------------------");
person2.age = 99;
System.out.println(person1);
System.out.println(person2);
}
}
输出结果:
分析内部结构图:
程序清单21:( 代码实现为浅拷贝 )
class Money{
public int dollar = 17;
@Override
public String toString() {
return "Money{" +
"dollar=" + dollar +
'}';
}
}
class Person implements Cloneable{
public int age = 10;
public Money money = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person) person1.clone();
System.out.println(person1.money);
System.out.println(person2.money);
System.out.println("------------------");
person2.money.dollar = 99;
System.out.println(person1.money);
System.out.println(person2.money);
}
}
输出结果:
注意:
这里是通过对象变量 person1,而只拷贝了 Person 类中的成员变量,而 Person 类之外的变量,没有进行拷贝。
分析内部结构图:
程序清单22:( 代码实现为深拷贝 )
class Money implements Cloneable{
public int dollar = 17;
@Override
public String toString() {
return "Money{" +
"dollar=" + dollar +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public int age = 10;
public Money money = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
Person temp = (Person) super.clone();
this.money = (Money) this.money.clone();
return temp;
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person();
Person person2 = (Person) person1.clone();
System.out.println(person1.money);
System.out.println(person2.money);
System.out.println("------------------");
person2.money.dollar = 99;
System.out.println(person1.money);
System.out.println(person2.money);
}
}
输出结果:
注意:
虽然这里通过对象变量 person1,只拷贝了 Person 类中的成员变量,然而在 Clone( ) 方法中,我们也进行了对 Money 类中成员进行了拷贝。
分析内部结构图:
Over. 谢谢观看哟~