拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 【JavaSE系列】趁着跨年的时间,学会了Java中常用的比较器和克隆界面

【JavaSE系列】趁着跨年的时间,学会了Java中常用的比较器和克隆界面

白鹭 - 2022-02-12 2122 0 0

??前面的话??

本篇文章带大家认识Java常用的界面——Comparable,Comparator,Cloneable界面,其中Comparable,Comparator界面是比较器,可以用来对物件进行排序,Cloneable界面是克隆界面,用来生成一个物件的副本,


导航小助手

  • 1.Comparable界面
    • 1.1引子:阵列排序
    • 1.2Camparable界面的使用
  • 2.Comparator比较器
  • 3.Cloneable克隆界面
    • 3.1Cloneable的使用
    • 3.2深拷贝与浅拷贝


0


1.Comparable界面

1.1引子:阵列排序

我们知道Arrays类中的·sort方法可以对阵列进行排序,比如对整型阵列进行排序:

import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        int[] arr = {21,3,6,1,42,13,18,85,68};
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));
    }
}

显然我们会得到一个从小到大排好序的阵列,但是实际中往往需要对自定义型别的阵列进行排序,比如我定义一个Person类:

class Person {
    public int age;
    public String name;
    public int score;
    public Person(int age, String name,int score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", score=" + score +
                '}';
    }
}

我们用同样的方法来排序试一试:
1-1
报错了,因为编译器不知道你需要根据物件中哪个元素来进行排序,这时候就需要用到Comparable这个界面来实作对自定义型别的排序,其实有一点像C语言中的qsort函式的用法,

1.2Camparable界面的使用

首先我们来看看呼叫sort方法时发生例外处的原始码:
1-2
我们发现Person进行排序时需要强转Comparable并且呼叫compareTo方法,这就意味着我们需要对Person类实作Comparable界面,并重写该界面中的compareTo方法,

public interface Comparable<T> {
    public int compareTo(T o);
}

我们发现Comparable界面中只有一个方法compareTo方法,还有在界面后面多了一个<T>,这个是泛型的意思,由于泛型概念及其地抽象,晦涩难懂,这里知道他是泛型即可,它相当于方法的自变量一样,可以理解为对一个型别进行传参,在实作Comparable界面时,把型别当做方法自变量一样使用<>传入就可以了,像这样:

class Person implements Comparable<Person>{
    public int age;
    public String name;
    public int score;
    public Person(int age, String name, int score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

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

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }
}

泛型会在后续博文中由浅到深多次介绍,这里知道简单地使用即可,
对于其中的compareTo方法,谁呼叫它谁就是this,如果回传的值大于0,表示this大于o,等于0,两者相等,小于0表示this小于o,如果需要降序排列,将thiso的位置换一换即可,

@Override //升序
    public int compareTo(Person o) {
        return this.age - o.age;
    }
@Override //降序
    public int compareTo(Person o) {
        return this.age - o.age;
    }

如果要进行字符串或者其他类的比较,需要呼叫类中所提供的compareTo方法进行比较,比如字符串:

@Override
    public int compareTo(Person o) {
        return this.name.compareTo(o.name);
    }

同理,降序将thiso反过来,
实作了Comparable界面,我们再来尝试一下能不能排序成功,
根据年龄age进行排序:

import java.util.Arrays;

class Person implements Comparable<Person>{
    public int age;
    public String name;
    public int score;
    public Person(int age, String name, int score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

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

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }
}
public class Test1 {
    public static void main(String[] args) {
        Person[] people = new Person[3];
        people[0] = new Person(32, "001", 92);
        people[1] = new Person(18, "002", 78);
        people[2] = new Person(12, "003", 90);
        Arrays.sort(people);
        System.out.println(Arrays.toString(people));
    }
}

1-3
再来试一试姓名name排序:

import java.util.Arrays;

class Person implements Comparable<Person>{
    public int age;
    public String name;
    public int score;
    public Person(int age, String name, int score) {
        this.age = age;
        this.name = name;
        this.score = score;
    }

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

    @Override
    public int compareTo(Person o) {
        return this.name.compareTo(o.name);
    }
}
public class Test1 {
    public static void main(String[] args) {
        Person[] people = new Person[3];
        people[0] = new Person(32, "zhang", 92);
        people[1] = new Person(18, "wang", 78);
        people[2] = new Person(12, "chen", 90);
        Arrays.sort(people);
        System.out.println(Arrays.toString(people));
    }
}

1-4
好了,上面这些就是Comparable界面的用法,可见该界面有个很大的缺点,那就是对类的侵入性极强,不小心改了compareTo方法后,程序出现bug还不报错,很难排查,因此实际当中常常使用比较器Comparator,下面就来聊一聊Comparator

2.Comparator比较器

Arrays类中实作了许多的sort方法能够对不同的资料型别进行比较,使用Comparator界面就能实作这种形式,你需要哪一个就呼叫哪一个比较器,

public interface Comparator<T> {
    int compare(T o1, T o2);
  ...
}

只要重写该compare方法就可以实作资料型别大小的比较,

class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.age - o2.age;
    }
}
class NameComparator implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.name.compareTo(o2.name);
    }
}
class ScoreComparator implements Comparator<Person> {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.score - o2.score;
    }
}

如果需要降序排列,同样的道理,将o1o2交换一下位置就好了,那怎么用呢?很简单,实体化比较器物件后,在sort方法中多加一个比较器物件就可以了,因为在原始码中有这样一个sort的多载方法:

    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

不用看懂该方法的实作方式,只需要知道该方法的第二个自变量是比较器就可以了,下面就来实践看看,

public class Test {
    public static void main(String[] args) {
        Person[] people = new Person[3];
        people[0] = new Person(32, "wang", 92);
        people[1] = new Person(18, "zhang", 78);
        people[2] = new Person(12, "chen", 90);
        AgeComparator ageCmp = new AgeComparator();
        NameComparator nameCmp = new NameComparator();
        ScoreComparator scoreCmp = new ScoreComparator();
        Arrays.sort(people, ageCmp);
        System.out.println("按年龄排序:");
        System.out.println(Arrays.toString(people));
        Arrays.sort(people, nameCmp);
        System.out.println("按姓名排序:");
        System.out.println(Arrays.toString(people));
        Arrays.sort(people, scoreCmp);
        System.out.println("按分数排序:");
        System.out.println(Arrays.toString(people));
    }
}

2-1
没有问题,成功排序了,Comparator比起Comparable,对类的侵入性就非常的小了,而且更加的直观,这两个界面是Java当中很常见的比较器工具,在后续资料结构的实作肯定会再次见到他们,最后再来说一说物件的拷贝吧,Cloneable界面是实作拷贝最常用的工具,

3.Cloneable克隆界面

3.1Cloneable的使用

首先,该界面的作用能够帮助生成一个物件的副本,也就是拷贝一个物件,其实其拷贝作用的是clone方法,该界面的作用其实是标志一个物件能够被克隆(拷贝),为什么这幺说呢?我们去看看这个界面的原始码你就知道了,嘻嘻!

public interface Cloneable {
}

我的天啊,它竟然是一个空界面,面试的时候常常会问空界面的作用是什么?空界面的作用是起到一个标志的作用,比如这个Cloneable界面,它是一个空界面,物件实作该界面,这个物件(类)就被标记了,表示该物件(类)能够被克隆,
我们再来看一看真正起作用的clone方法:

protected native Object clone() throws CloneNotSupportedException;

该方法被关键字native修饰,说明它是使用C++代码实作的,并且使用它的时候需要接收例外,
按理说,既然Cloneable界面是一个空界面,实作该界面时不需要重写clone方法,在这里,有点特殊,虽然该界面是空的,也需要实作clone方法,实作方式很简单,呼叫Object(该类是所有类的父类)类中的clone方法即可,

class Person implements Cloneable {
    int age;
    public Person(int age) {
        this.age = age;
    }
    @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) {
        Person p1 = new Person(18);
        try {
            Person p2 = (Person) p1.clone();
            System.out.println(p1);
            System.out.println(p2);
        }catch (CloneNotSupportedException e) {
            e.printStackTrace();
            System.out.println("克隆例外!");
        }
    }
}

3-1
拷贝成功了,上面这些就是Cloneable界面的使用方法,

3.2深拷贝与浅拷贝

深拷贝:当拷贝一个物件时,会新生成一个一模一样的物件,并且如果物件里面还有其他物件,也会新生成一个一模一样的物件,这就是深拷贝,
浅拷贝:当拷贝一个物件时,会新生成一个一模一样的物件,但是如果该物件里面还有其他物件,只会拷贝这个物件的地址,并不会新生成一个物件,

首先深浅拷贝是对代码实作方面来说的,方法的拷贝并没有深浅拷贝这一个说法,如果硬是要有个说法,Java中给出的拷贝方法都是浅拷贝,虽然对于简单资料型别拷贝可以说是深拷贝,是因为拷贝简单资料型别并不能体现出来它到底是浅拷贝还是深拷贝,当去拷贝参考型别(比如阵列,字符串)的时候,只是拷贝了物件的地址,并没有重新生成一个物件,所以说单论方法的拷贝,可以理解为浅拷贝,
所以说,对于一个拷贝到底是深拷贝还是浅拷贝,在于程序员对代码的实作,到底是一个深拷贝还是浅拷贝,
这个深浅拷贝就简单说一下吧,就不举例子了,如果有不懂的欢迎与博主交流~~,

??后面的话??

博主正在参加2021【博客之星】,小伙伴们能否给博主一个五星呢?投票地址:

https://bbs.csdn.net/topics/603955252

投我以桃,报之以李,对于每一个评价,小博主都定当相报!
最后,祝各位小伙伴新年快乐,心想事成,没有烦恼!

下一篇文章是有关集合框架的哦!我们要开始资料结构之旅了!


觉得文章写得不错的老铁们,点赞评论关注走一波!谢谢啦!

1-99

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *