Comparable,Comparator,Clonable 接口使用剖析

Posted Ischanged

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Comparable,Comparator,Clonable 接口使用剖析相关的知识,希望对你有一定的参考价值。

前言

java中有许多的接口,今天我向大家简单的介绍我们平时常见的三个接口及其用法。接口可以由类通过emplements关键字实现,一个类实现了某一个接口,就具备了该接口的一些功能,就可以完成一些操作。

Comparable接口

排序基本数据类型,如int ,long 等,我们可以使用 Arrays.sort()与Collections.sort(),并且这个排序默认为升序排序是无法改变的,如下面的排序整形数组:

  public static void main(String[] args) {
        int[] array = {19,3,2,18,7,4};
        System.out.println(Arrays.toString(array));
        Arrays.sort(array);
        System.out.println(Arrays.toString(array));
    }

那我们有时候要排列自定义数据类型,不管我们想按升序排列还是降序排列都可以那要如何操作呢????
在下面的代码中,我定义了一个学生类,如果我们也用 Arrays.sort()方法对学生对象进行排序就会出错,这是因为我们在比较对象的时候没有指定比较规则,比较学生时到底是按姓名排序,还是年龄,还是分数,编译器不知道就会报如下的错误了:

class Student  {
    private String name;
    private int age;
    private 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 +
                '}';
    }
}

public class TestDemo {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("gb",28,99.9);
        students[1] = new Student("bit",18,19.9);
        students[2] = new Student("ang",98,69.9);
        System.out.println("排序前:"+Arrays.toString(students));
        Arrays.sort(students);
        System.out.println("排序后:"+Arrays.toString(students));
    }

那么划红线的Comparable是什么呢?其实它是一个接口,里面有比较函数compare,我们只要student实现这个接口,就具备了比较的能力,之后重写一下比较规则,你到底是要按姓名排序,还是分数,还是年龄排序不就行了。

class Student implements Comparable<Student> {//<Student>是泛型的知识,之后讲解,Student表示我们要比较是什么类型的数据
    private String name;
    private int age;
    private 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 +
                '}';
    }

    //比较规则
    @Override
    public int compareTo(Student o) {
        return (int) (this.score - o.score);//分数升序排列
        //return (int) (o.score - this.score);//分数降序排列
        //return this.name.compareTo(o.name);//以姓名进行比较
        //return o.age-this.age;//年龄降序排列
        //=============================================
        //上面代码的分解
        /*if(this.age > o.age) {
            return 1;
        }else if(this.age == o.age) {
            return 0;
        }else {
            return -1;
        }*//*
    }
}*/
 }

}

那如何自定义升序降序排列呢???有什么规律呢,首先我们来来看一下Arrays.sort这个函数的部分源码。

  for (int i=low; i<high; i++)
                for (int j=i; j>low &&
                         ((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
                    swap(dest, j, j-1);
            return;
        }

由源码我们可以大概的知道,对于要排序的数组元素,是前一个元素和后一个元素进行比较时调用compareTo方法,而compareTo方法是我们重写的比较规则,比较规则中我们会返回大于0,等于0,小于0的数字,如果返回大于0的数字,则交换两个元素的位置再由上面的compareTo方法和源码可知,o指向的对象是要比较的两个元素的后面的那一个元素,this指向的元素是要比较的两个元素中前面的一个元素,比较时如果写成(this.score - o.score)也就是说前一个元素大于后一个元素返回大于0的数字,才交换,这样就是升序排列了,相反就是降序排列。
那么我们再来想想Comparable接口有什么不好的地方??从代码可以发现它对类的侵入性比较强,也就是说每次如果更换了比较方式,那么都需要修改类的内部局限性很大,很麻烦,那么有对于比较有更好的,更便捷的比较接口吗?那就是我们接下来,要讲的接口了。

Comparator接口

Comparator接口实际上就是对Comparable的升级完善,有了它可以使我们在比较一些自定义数据的时候更加方便,快捷。例如对于我们上面自定义的学生类的实例,我们可以按年龄,分数,姓名实现我们想要的排序。只要我们实现各种比较器,在排序的时候传一个比较器给 Arrays.sort()即可,这样对类的侵入性就比较弱,不需要频繁地改变类的内部,比较器其实就是比较规则,我们实现一个类实现Comparator接口之后重写接口里面的抽象方法就行了。

class Student {
    private String name;
    private int age;
    private 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 +
                '}';
    }

    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 double getScore() {
        return score;
    }

    public void setScore(double score) {
        this.score = score;
    }
}
//比较器
class AgeComparator implements Comparator<Student> {

    @Override
    public int compare(Student o1, Student o2) {
        return o1.getAge()-o2.getAge();
    }
}
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.getName().compareTo(o2.getName());
    }
}
class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return (int)(o1.getScore()-o2.getScore());
    }
}

 public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("gb",28,99.9);
        students[1] = new Student("bit",18,19.9);
        students[2] = new Student("ang",98,69.9);
        System.out.println("排序前:"+Arrays.toString(students));
        AgeComparator ageComparator = new AgeComparator();
        NameComparator nameComparator = new NameComparator();
        ScoreComparator scoreComparator = new ScoreComparator();
        Arrays.sort(students,scoreComparator);
        System.out.println("排序后:"+Arrays.toString(students));
    }

上面在比较名字的时候也是要用compareTo()方法,名字是字符串源码底层说的比较抽象,大体总结白话说出来,String 是字符串,它的比较用compareTo方法,首先比较两个字符串的长度,当长度不同时返回的是字符串的长度差,长度相同时,它从第一位开始往后比较, 如果遇到不同的字符,则马上返回这两个字符的ASCII值差值.返回值是int类型。

Comparable,Comparator两者简单比较:
Comparable 是排序接口;若一个类实现了 Comparable 接口,就意味着 “该类支持排序”。而 Comparator 是比较器;我们若需要控制某个类的次序,可以建立一个 “该类的比较器” 来进行排序。前者应该比较固定,对类的侵入性也比较强,它和一个具体类相绑定,而后者比较灵活,对类的侵入性也比较弱,它可以被用于各个需要比较功能的类使用。

Clonable 接口和深拷贝

Object 类中存在一个 clone方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.

class Person implements Cloneable{
    public String name;
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\\'' +
                '}';
    }

    public Person(String name) {
        this.name = name;
    }
    //Object类的克隆方法。所有的类  默认继承与Object类
    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
        return  super.clone();
    }
}
public class TestDemo1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person=new Person("lmm");
        System.out.println(person);
        Person person2 = (Person)person.clone();//注意强制类型转换
        System.out.println(person2);
    }
}

实现了这个接口还没有完事,还要重写这个接口里面的方法clone(),因为由源码可知,这个接口里面没有任何方法,之后还要注意类型转换,方法的返回值类型是Object ,下面调用的时候,我们要把它转换为克隆对象的类型之后就可以打印出来我们克隆的对象了。

接着讲接口,我们来说一下有关Clonable接口实现深浅拷贝的问题。如下面的代码我克隆出了一个对象person2,通过这个对象去访问里面的成员变量,并且改动了它,原来父本里面的内容是不会被改变的,像这样的拷贝就是一个深拷贝

class Person implements Cloneable {
    public String name;
    // public Money money = new Money();
    public int a = 10;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\\'' +
                '}';
    }
    //Object类的克隆方法。所有的类  默认继承与Object类

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
        return super.clone();
        /*Person personClone = (Person) super.clone();
        personClone.money = (Money)this.money.clone();
        return personClone;*/
    }

}

public  class TestDemo1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("lmm");
        Person person2 = (Person)person.clone();

        System.out.println(person.a);//10
        System.out.println(person2.a);//10
        System.out.println("=========================");
        person2.a = 99;
        System.out.println(person.a);//10
        System.out.println(person2.a);//99
    }
}

如像下面的代码实现就是一个浅拷贝了,下面的代码只是Person成员变量多了一个Money类的实例,Money类里面有成员变量money = 12.5;之后克隆出了一个对象person2,通过这个对象去访问里面的成员变量,并且改动了它,原来父本里面的内容也被改变了。究其原因就是因为,Person里面的成员变量有一个是引用数据类型,它也指向堆区的一个对象,我们克隆person对象的时候,没有把这个引用指向的对象也克隆一分,导致父本和克隆的对象里面的money都指向了同一个对象,那么我们通过克隆的对象修改它成员所指向的对象,也会改变父本里面的内容。

class Money {
    public double money = 12.5;
  /*  @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }*/
}
class Person implements Cloneable {
    public String name;
     public Money money = new Money();
    public int a = 10;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\\'' +
                '}';
    }
    //Object类的克隆方法。所有的类  默认继承与Object类

    @Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
        return super.clone();
       /* Person personClone = (Person) super.clone();
        personClone.money = (Money)this.money.clone();
        return personClone;*/
    }

}

public  class TestDemo1 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person = new Person("lmm");
        Person person2 = (Person)person.clone();
        System.out.println(person.money.money);
        System.out.println(person2.money.money);
        System.out.println("=========================");
        person2.money.money = 99;
        System.out.println(person.money.money);
        System.out.println(person2.money.money);
    }
}

也可以在看看图理解下:

所以如何修改实现深拷贝,对于这种情况,首先拷贝出person2,再把 person的成员里面的那个引用成员变量所指向的对象再拷贝一份之后person2的money指向拷贝出的引用指向的对象简单的说就是拷贝的对象和父本各自拥有各自的成员变量,不能共用成员变量,特别是当成员变量里面有引用数据类型的时候。代码如下:

class Money implements Cloneable{
    public double money = 12.5;
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
@Override
    protected Object clone() throws CloneNotSupportedException {
        //return super.clone();
       // return super.clone();
        Person personClone = (Person) super.clone();
        personClone.money = (Money)this.money.clone();
        return personClone;
    }

最后实现下面的结果:

我的第一次1024,大家节日快乐呀呀。谢谢支持

以上是关于Comparable,Comparator,Clonable 接口使用剖析的主要内容,如果未能解决你的问题,请参考以下文章

数组对象排序:Comparable和Comparator

Comparable与Comparator的区别

Comparable与Comparator的区别

Comparable与Comparator的区别

comparable 和comparator的区别

Comparable 与 Comparator的区别