经典接口

发布 | 2024-08-07 | JAVA

经典接口介绍

java.lang.Comparable

我们知道基本数据类型的数据(除boolean类型外)需要比较大小的话,之间使用比较运算符即可,但是引用数据类型是不能直接使用比较运算符来比较大小的。那么,如何解决这个问题呢?

Java给所有引用数据类型的大小比较,指定了一个标准接口,就是java.lang.Comparable接口:

package java.lang;

public interface Comparable{
    int compareTo(Object obj);
}

那么我们想要使得我们某个类的对象可以比较大小,怎么做呢?步骤:

第一步:哪个类的对象要比较大小,哪个类就实现java.lang.Comparable接口,并重写方法

  • 方法体就是你要如何比较当前对象和指定的另一个对象的大小

第二步:对象比较大小时,通过对象调用compareTo方法,根据方法的返回值决定谁大谁小。

  • this对象(调用compareTo方法的对象)大于指定对象(传入compareTo()的参数对象)返回正整数
  • this对象(调用compareTo方法的对象)小于指定对象(传入compareTo()的参数对象)返回负整数
  • this对象(调用compareTo方法的对象)等于指定对象(传入compareTo()的参数对象)返回零

代码示例:

package com.atguigu.api;

public class Student implements Comparable {
    private int id;
    private String name;
    private int score;
    private int age;

    public Student(int id, String name, int score, int age) {
        this.id = id;
        this.name = name;
        this.score = score;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", score=" + score +
                ", age=" + age +
                '}';
    }

    @Override
    public int compareTo(Object o) {
        //这些需要强制,将o对象向下转型为Student类型的变量,才能调用Student类中的属性
        //默认按照学号比较大小
        Student stu = (Student) o;
        return this.id - stu.id;
    }
}

测试类

package com.atguigu.api;

public class TestStudent {
    public static void main(String[] args) {
        Student[] arr = new Student[5];
        arr[0] = new Student(3,"张三",90,23);
        arr[1] = new Student(1,"熊大",30,22);
        arr[2] = new Student(5,"王五",75,25);
        arr[3] = new Student(4,"李四",85,24);
        arr[4] = new Student(2,"熊二",85,18);

        //单独比较两个对象
        System.out.println(arr[0].compareTo(arr[1]));
        System.out.println(arr[1].compareTo(arr[2]));
        System.out.println(arr[2].compareTo(arr[2]));

        System.out.println("所有学生:");
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("按照学号排序:");
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(arr[j].compareTo(arr[j+1])>0){
                    Student temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

java.util.Comparator

思考:

(1)如果一个类,没有实现Comparable接口,而这个类你又不方便修改(例如:一些第三方的类,你只有.class文件,没有源文件),那么这样类的对象也要比较大小怎么办?

(2)如果一个类,实现了Comparable接口,也指定了两个对象的比较大小的规则,但是此时此刻我不想按照它预定义的方法比较大小,但是我又不能随意修改,因为会影响其他地方的使用,怎么办?

JDK在设计类库之初,也考虑到这种情况了,所以又增加了一个java.util.Comparator接口。

package java.util;

public interface Comparator{
    int compare(Object o1,Object o2);
}

那么我们想要比较某个类的两个对象的大小,怎么做呢?步骤:

第一步:编写一个类,我们称之为比较器类型,实现java.util.Comparator接口,并重写方法

  • 方法体就是你要如何指定的两个对象的大小

第二步:比较大小时,通过比较器类型的对象调用compare()方法,将要比较大小的两个对象作为compare方法的实参传入,根据方法的返回值决定谁大谁小。

  • o1对象大于o2返回正整数
  • o1对象小于o2返回负整数
  • o1对象等于o2返回零

代码示例:定义定制比较器类

package com.atguigu.api;

import java.util.Comparator;

public class StudentScoreComparator implements Comparator {
    @Override
    public int compare(Object o1, Object o2) {
        Student s1 = (Student) o1;
        Student s2 = (Student) o2;
        int result = s1.getScore() - s2.getScore();
        return result != 0 ? result : s1.getId() - s2.getId();
    }
}

代码示例:测试类

package com.atguigu.api;

public class TestStudent {
    public static void main(String[] args) {
        Student[] arr = new Student[5];
        arr[0] = new Student(3,"张三",90,23);
        arr[1] = new Student(1,"熊大",30,22);
        arr[2] = new Student(5,"王五",75,25);
        arr[3] = new Student(4,"李四",85,24);
        arr[4] = new Student(2,"熊二",85,18);

        //单独比较两个对象
        System.out.println(arr[0].compareTo(arr[1]));
        System.out.println(arr[1].compareTo(arr[2]));
        System.out.println(arr[2].compareTo(arr[2]));

        System.out.println("所有学生:");
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("按照学号排序:");
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(arr[j].compareTo(arr[j+1])>0){
                    Student temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }

        System.out.println("按照成绩排序");
        StudentScoreComparator sc = new StudentScoreComparator();
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < arr.length-i; j++) {
                if(sc.compare(arr[j],arr[j+1])>0){
                    Student temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

Cloneable接口基础详解

一、引言

Cloneable接口是Java开发中常用的一个接口, 它的作用是使一个类的实例能够将自身拷贝到另一个新的实例中,注意,这里所说的“拷贝”拷的是对象实例,而不是类的定义,进一步说,拷贝的是一个类的实例中各字段的值。

在开发过程中,拷贝实例是常见的一种操作,如果一个类中的字段较多,而我们又采用在客户端中逐字段复制的方法进行拷贝操作的话,将不可避免的造成客户端代码繁杂冗长,而且也无法对类中的私有成员进行复制,而如果让需要具备拷贝功能的类实现Cloneable接口,并重写clone()方法,就可以通过调用clone()方法的方式简洁地实现实例拷贝功能。

二、源码分析

打开JDK源码找到Coloneable接口,发现Cloneable接口竟然没有定义任何的接口方法,这是为什么呢?

/*
JDK1.8的Cloneable接口源代码
*/
public interface Cloneable {
}
1.2.3.4.5.

Cloneable接口之所以没有定义任何的接口的原因其实很简单,那就是在Java中,Object类已经将clone()方法定义为所有类都应该具有的基本功能,只是将该方法声明为了protected类型。该方法定义了逐字段拷贝实例的操作。

/*
Object类中clone()是一个native本地方法,因此没有实现体,而且在拷贝字段时,除了Object类的字段外,其子类的新字段也将被拷贝到新的实例中
*/
protected native Object clone() throws CloneNotSupportedException;
1.2.3.4.

既然Object类中既然已经有了一个定义实例拷贝操作的方法,那为什么还是需要让想具备实力拷贝功能的类实现Cloneable接口呢?其实,Cloneable接口在这里起到了一种标识的作用,表明实现它的类具备了实例拷贝功能,在Cloneable接口的官方javadoc文档中有这样一段话:

   "Invoking Object's clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown. JDK1.8"
   
   "在不实现可克隆接口的实例上调用对象的克隆方法会导致引发异常CloneNotSupportedException。JDK1.8 "
1.2.3.

也就是说,要想使一个类具备拷贝实例的功能,那么除了要重写Object类的clone()方法外,还必须要实现Cloneable接口。下面的代码即可以使一个CloneableTest具备了浅拷贝实例的功能。

class CloneableTest implements Cloneable {
     
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
 
}
1.2.3.4.5.6.7.8.

三、浅拷贝与深拷贝

浅拷贝

前面提到了浅拷贝与深拷贝的概念,这是任何一种面向对象的编程语言中都必须要讨论的内容。在java中,对象创建后需要有一个引用变量来指向该对象实际的地址空间,也就是说引用变量与对象实体是两个不同的数据体。在Object类的clone()方法中,对对象字段进行复制时,如果字段是基本数据类型(如int、double等),则会复制字段的值到一个新的变量中,而字段是引用类型,则仅会将引用值复制给新对象中的相应字段中,也就是说,两个字段指向了同一个对象实例。

案例分析:

一、先定义一个用户自定义的类Dog,该类中有一个字符串类型的成员变量,该类将用于验证浅拷贝的性质。

public class Dog {
    public String name = null;

    public Dog(String name) {
        this.name = name;
    }

}
1.2.3.4.5.6.7.8.

二、定义一个具有拷贝实例功能的CloneablePerson类实现Cloneable接口并重写Clone()方法,并在重写方法中调用Object类的本地clone()方法。

public class CloneablePerson implements Cloneable {

    public String name = null;// 引用数据类型
    public Dog dog = null;// 引用数据类型
    public int age = 0;// 基本数据类型

    public CloneablePerson(String name, Dog dog, int age) {
        this.name = name;
        this.dog = dog;
        this.age = age;
    }


    public void show() {
        System.out.println("name=" + name);
        System.out.println("dog=" + dog.name);
        System.out.println("age=" + age);
    }

    /*
      重写clone()方法并调用父类Object的本地clone()方法,实现浅拷贝功能
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

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.

三、在Test类中测试代码:
1.创建一个CloneableClass类对象作为初始实例,该实例的各字段值如构造函数所示。
2.在调用初始实例的clone()方法创建一个拷贝实例,显示初始实例与拷贝实例各字段的值并判断他们是不是指向了同一个对象实例。

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {


        Dog black = new Dog("小黑");

        // src 源对象
        CloneablePerson src = new CloneablePerson("jh", black, 21);
        // dest 拷贝目标对象
        CloneablePerson dest = null;

        Object o = src.clone();
        if (o instanceof CloneablePerson) {
            dest = (CloneablePerson) o;
        }

        System.out.println("==================浅拷贝测试==================");
        System.out.println("dest == src? " + (dest == src));
        System.out.println("------------------------------------------");
        System.out.println("src的所有数据:");
        src.show();
        System.out.println("------------------------------------------");
        System.out.println("dest的所有数据:");
        dest.show();
        System.out.println("------------------------------------------");
        System.out.println("src.name == dest.name? " + (src.name == dest.name));
        System.out.println("src.dog == dest.dog? " + (src.dog == dest.dog));
        System.out.println("src.age == dest.age? " + (src.age == dest.age));


    }
}
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.

执行结果:

==================浅拷贝测试==================
dest == src? false
------------------------------------------
src的所有数据:
name=jh
dog=小黑
age=21
------------------------------------------
dest的所有数据:
name=jh
dog=小黑
age=21
------------------------------------------
src.name == dest.name? true
src.dog == dest.dog? true
src.age == dest.age? true

Process finished with exit code 0
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.

从执行结果来看 dest == src? false * 说明dest和src是两个不同的对象实例(对于引用类型的比变量,“==”判断的是对象地址是否相同,也就是是不是指向了同一个对象),但是src.xxx== dest.xxx? true* 说明他们字段的值却是相同的,也证明了本地Object本地clone()对实例引用型字段进行的是浅拷贝。

那么浅拷贝会造成什么样的后果呢?由于浅拷贝仅将字段的引用值复制给了新的字段,但是却并没有创建新的相应对象,也就是说dest和src中的两个字段都指向了同一个对象实例。这样,我们对dest中各字段所指向对象的属性进行了修改,src中的成员对象也会随之改变

测试代码和运行结果如下所示。

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {


        Dog black = new Dog("小黑");

        CloneablePerson src = new CloneablePerson("jh", black, 21);
        CloneablePerson dest = null;

        Object o = src.clone();
        if (o instanceof CloneablePerson) {
            dest = (CloneablePerson) o;
        }


        // 修改dest中各字段指向对象的属性
        dest.name = "update";
        dest.dog.name= "小白";
        dest.age = 18;


        System.out.println("==================浅拷贝==================");
        System.out.println("dest == src? " + (dest == src));
        System.out.println("------------------------------------------");
        System.out.println("src的所有数据:");
        src.show();
        System.out.println("------------------------------------------");
        System.out.println("dest的所有数据:");
        dest.show();
        System.out.println("------------------------------------------");
        System.out.println("src.name == dest.name? " + (src.name == dest.name));
        System.out.println("src.dog == dest.dog? " + (src.dog == dest.dog));
        System.out.println("src.age == dest.age? " + (src.age == dest.age));


    }
}
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.

运行结果:

==================浅拷贝==================
dest == src? false
------------------------------------------
src的所有数据:
name=jh
dog=小白
age=21
------------------------------------------
dest的所有数据:
name=update
dog=小白
age=18
------------------------------------------
src.name == dest.name? false
src.dog == dest.dog? true
src.age == dest.age? false
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.

从运行结果 src.dog == dest.dog? true 可以看出,由于CloneablePerson的dog字段为引用型变量,所以由src浅拷贝出来的dest实例中的dog字段与src中的dog字段实际上指向的是同一个对象,因此dest对成员对象的修改也就导致了src 中相应成员对象的随之改变。

深拷贝

所谓深拷贝就是对于引用型变量,深拷贝会开辟一块新的内存空间,将被复制引用所指向的对象实例的各个属性复制到新的内存空间中,然后将新的引用指向块内存。

( 个人狭隘的理解就是 深拷贝把 对象以及对象属性,普通属性都拷贝一次,放入到新的内存空间。)

要想实现深拷贝的功能,在重写clone()方法的时候,就不能再简单地调用Object的本地clone()方法,而是要对上文中的CloneablePerson做如下修改:

public class CloneablePerson implements Cloneable {

    public String name = null;
    public Dog dog = null;
    public int age = 0;

    public CloneablePerson(String name, Dog dog, int age) {
        this.name = name;
        this.dog = dog;
        this.age = age;
    }


    public void show() {
        System.out.println("name=" + name);
        System.out.println("dog=" + dog.name);
        System.out.println("age=" + age);
    }

    /* 浅拷贝
    @Override
    protected Object clone() throws CloneNotSupportedException {
       return super.clone();
    }*/

    // 深拷贝
    @Override
    public Object clone() throws CloneNotSupportedException {
        String name = this.name;
        Dog      dog = new Dog(this.dog.name);
        int       age = this.age;
        CloneablePerson dest = new CloneablePerson(name,dog,age);
        return dest;
    }
}
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.

按着浅拷贝的测试思路再走一遍:

public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {


        Dog black = new Dog("小黑");

        CloneablePerson src = new CloneablePerson("jh", black, 21);
        CloneablePerson dest = null;

        Object o = src.clone();
        if (o instanceof CloneablePerson) {
            dest = (CloneablePerson) o;
        }


        // 修改dest中各字段指向对象的属性
        dest.name = "update";
        dest.dog.name= "小白";
        dest.age = 18;


        System.out.println("==================深拷贝==================");
        System.out.println("dest == src? " + (dest == src));
        System.out.println("------------------------------------------");
        System.out.println("src的所有数据:");
        src.show();
        System.out.println("------------------------------------------");
        System.out.println("dest的所有数据:");
        dest.show();
        System.out.println("------------------------------------------");
        System.out.println("src.name == dest.name? " + (src.name == dest.name));
        System.out.println("src.dog == dest.dog? " + (src.dog == dest.dog));
        System.out.println("src.age == dest.age? " + (src.age == dest.age));


    }
}
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.

运行结果如下:

==================深拷贝==================
dest == src? false
------------------------------------------
src的所有数据:
name=jh
dog=小黑
age=21
------------------------------------------
dest的所有数据:
name=update
dog=小白
age=18
------------------------------------------
src.name == dest.name? false
src.dog == dest.dog? false
src.age == dest.age? false
1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.

根据以上结果 src.xxx == dest.xxx? false 可以看出 src和dest 所有相同字段都指向了不同的实例,无论怎么修改 dest对象都不会影响src对象,这就是深拷贝。

© 著作权归作者所有

本文由 趣代码Blog 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。

评论关闭