对象与类(三)
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 {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函数十分相似,思想是一样的,感兴趣的读者可以去翻一翻我的博客
引入:假设我们现在手中有一张学生名单,上面有他们的各种信息,那么我们想什么办法给这些学生进行排序呢?我们可以按名字首字母排序,也可以按年龄排序,当然在初中高中,每次月考完,都是按成绩排序。所以在程序清单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. 谢谢观看哟~