详解 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);
    }
}

输出结果:

out

② 方法传参

程序清单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); //方法传参
    }
}

输出结果:

out
在程序清单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);
    }
}

输出结果:
out
在程序清单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();
    }
}

输出结果:

out

程序清单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();
    }
}

输出结果:

out

我们发现程序清单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();
    }
}

输出结果:

out

在程序清单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();
    }
}

输出结果:

out
代码运行顺序:
分析

(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())
    }
}

输出结果:
out

解析:

在程序清单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);
        }
    }
}

输出结果:
out
在程序清单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);
    }
}

输出结果:
out

在程序清单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());
    }
}

输出结果:

out

程序清单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();
    }
}

输出结果:
out

在程序清单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();
    }
}

输出结果:
out

对程序清单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));
    }
}

输出结果:
out
在程序清单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));
    }
}

输出结果:
out

如果我们让学生以名字降序,那么我们就改变 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));
    }
}

输出结果:
out
在程序清单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();

    }
}

输出结果:
out

(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);
    }
}

输出结果:
out

分析内部结构图:
分析

程序清单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);
    }
}

输出结果:
out
注意:
这里是通过对象变量 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);
    }
}

输出结果:
out
注意:
虽然这里通过对象变量 person1,只拷贝了 Person 类中的成员变量,然而在 Clone( ) 方法中,我们也进行了对 Money 类中成员进行了拷贝。

分析内部结构图:
分析
请添加图片描述
Over. 谢谢观看哟~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十七ing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值