Java接口的应用

  1. 目标:总结Comparable接口以及compareTo方法、comparator接口中compare方法比较器、toString方法、equals方法、hashCode方法、Cloneable接口以及深浅拷贝

  2. 比较对象中内容的大小【Comparable接口以及compareTo方法】
    例如:学生类:成员有姓名、年龄、分数
    要求:分别按照姓名、年龄、分数排序
    初步编写:
    学生类:

package demo2;

public class Student {
    public String name;
    public int age;
    public int score;

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

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

测试类:
package demo2;

import java.util.Arrays;

public class TestStudent {
public static void main(String[] args) {
Student[] students = new Student[3];
students[0] = new Student(“王王王”,21,98);
students[1] = new Student(“帅帅帅”,22,66);
students[2] = new Student(“哈哈哈”,23,99);

    Arrays.sort(students);
    System.out.println(Arrays.toString(students));
}

}

运行结果:抛出异常
Java接口的应用_第1张图片
分析:Arrays.sort(students);不知道怎么比较大小
根据报错提示:
Java接口的应用_第2张图片
在这里插入图片描述
作用:取到数组的某一个值,把这个值转换为一个Comparable接口类型,然后调用compareTo方法
Java接口的应用_第3张图片

因此在Student类中实现Comparable接口,并重写compareTo方法。

package demo2;

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

    @Override
    public int compareTo(Object o) {
    //向下转型,因为Object是所有类的父类,将Object父类转成Studnet子类
        Student s = (Student) o;
        if (this.age > s.age) {
            return 1;
        }else if (this.age < s.age) {
            return  -1;
        }else {
            return 0;
        }
    }

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

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

运行结果:
在这里插入图片描述
分析:此时按照年龄从小到大排序。

        System.out.println(students[0].compareTo(students[1]));

让0下标的学生去调用compareTo方法去和1下标的学生进行比较,所以在compareTo方法中,this代表0下标的学生,传入的Object o,以及强制类型转换成Student s代表的是1下标的学生。
Java接口的应用_第4张图片
假如根据姓名比较,重写的compareTo方法相当于写了一个比较规则,从而sort也有用了

    public int compareTo(Object o) {
        Student s = (Student) o;
        if (this.name.compareTo(s.name) > 0) {
            return 1;
        }else if (this.name.compareTo(s.name) < 0) {
            return -1;
        }else {
            return 0;
        }
    }

运行结果:
在这里插入图片描述
分析:此处的name是String类型,String类型比较大小不能通过数学符号(>和<等)比较,要通过String类中的compareTo方法(String类也实现了Comparable接口,则必定也实现了compareTo方法),equals只能比较字符串相不相同(并且equals返回值的类型是boolean类型)

自己实现sort【采用冒泡排序】

    public static void sort(Comparable[] array) {
        for (int i = 0; i < array.length - 1; i++) {
            for (int j = 0; j < array.length - 1 -i; j++) {
                if (array[j].compareTo(array[j+1]) > 0) {
                    Comparable t = array[j];
                    array[j] = array[j+1];
                    array[j+1] = t;
                }
            }
        }
    }

注意:形参是Comparable接口类型,在if语句中的比较规则用到实现接口中的compareTo方法。

但是,目前这样写,用Comparable接口不好,因为代码不够灵活,一旦写死了拿年龄比较,以后默认都是拿年龄比较,那我要想拿名字或者分数比较就不行了。

  1. 比较对象中内容的大小【比较器Comparator接口中compare方法】
    刚刚是Student类实现Comparable接口重写compareTo方法,现在是新创建一个毫不相干的类实现Comparator接口。
    Comparator接口里面的抽象方法只有一个,所以新建一个AgeComparator类实现Comparator接口重写compare方法。
    Java接口的应用_第5张图片
    新建一个类实现接口并重写方法:
package demo4;

import java.util.Comparator;

public class AgeComparator implements Comparator <Student>{
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}

实例化新建类的对象,并作为sort的第二个参数

    public static void main(String[] args) {
        Student[] students = new Student[3];

        students[0] = new Student("王王王",23,98);
        students[1] = new Student("帅帅帅",22,66);
        students[2] = new Student("哈哈哈",24,99);

        //既然是一个类,就在这里面实例化这个对象
        AgeComparator ageComparator = new AgeComparator();
        //把他传给sort的第二个参数
        Arrays.sort(students,ageComparator);
        System.out.println(Arrays.toString(students));
    }

分析: AgeComparator ageComparator = new AgeComparator(); 叫做比较器,这样实现比较就比较灵活。
比如想通过分数比较,可以新建一个分数的比较器:

package demo4;

import java.util.Comparator;

public class ScoreComparator implements Comparator <Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.score - o2.score;
    }
}
        ScoreComparator scoreComparator = new ScoreComparator();
        Arrays.sort(students,scoreComparator);

通过姓名比较:

package demo4;

import java.util.Comparator;

public class NameCompartor implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return (o1.name.compareTo(o2.name));
    }
}
        NameCompartor nameCompartor = new NameCompartor();
        Arrays.sort(students,nameCompartor);

以上就是非常灵活的比较器,想怎么写就怎么写。

  1. Object是Java默认提供的一个类,Java中Object类是所有类的父类,默认会继承Object父类,即所有类的对象都可以使用Object的引用进行接受
    Object类中有的方法
    Java接口的应用_第6张图片
  2. Object类中的toString方法
    在这里插入图片描述
    如果我们要打印对象中的内容,则需要在对象中重写toString方法。
    因为可以认为要重写的那个对象的父类一定是Object,因此重写toString
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

①如果没有重写toString方法,运行结果:
Java接口的应用_第7张图片
②如果重写了toString方法,运行结果:
Java接口的应用_第8张图片

  1. Object类中的equals方法
    public boolean equals(Object obj) {
        return (this == obj);
    }

如果要比较对象中内容是否相同,必须重写Object中的equals方法,因为equals方法默认是按照地址比较的。
在创建2个对象,分别为(姓名和年龄都一样):
姓名:小王,年龄22;姓名:小王,年龄22

    public static void main(String[] args) {
        Student student1 = new Student("小王",22);
        Student student2 = new Student("小王",22);

        System.out.println(student1.equals(student2));
    }

①没有重写equals,运行结果:
Java接口的应用_第9张图片
分析:因为2个对象的姓名和年龄都相同,在主观现实世界中,我们会认为这2个对象是同一个对象,但是程序的运行结果却是flase,因此我们要重写Objec类中的equals方法。

②重写equals,运行结果:

   @Override
    public boolean equals(Object o) {

        if (this == o) {
            return true;
        }
        if (o == null) {
            return false;
        }

        if (! (o instanceof Student)) {
            return false;
        }

        Student s = (Student) o;
        if (this.name == s.name && this.age == s.age) {
            return true;
        }
        return false;
    }

运行结果:
Java接口的应用_第10张图片
编译器自动重写的equals方法:

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age && Objects.equals(name, student.name);
    }
  1. Object类中的hashCode方法
    在上例中创建的2个学生类对象,我们认为2个对象是指同一个人(对象),所有在打印2个对象的地址的时候,理论上的运行结果应该是在内存中存同一个地址。
        System.out.println(student1);
        System.out.println(student2);

①没有重写hashCode方法的时候,运行结果:
Java接口的应用_第11张图片
②重写hashCode方法的运行结果:

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

Java接口的应用_第12张图片
分析:通过name和age去返回哈希地址,因为student1和student2的name和age都相同,因此打印出来的地址也是相同的。
总结:hashcode方法用来确定对象在内存中存储的位置是否相同

  1. 要去克隆,则一定要实现Clonable接口
    Java接口的应用_第13张图片
    分析:克隆接口Cloneable时一个空接口,也叫标记接口,作用是:表示当前对象是可以被克隆的。
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public static void main(String[] args) throws CloneNotSupportedException  {
        Student student1 = new Student("小帅");
        Student student2 = (Student) student1.clone();
        System.out.println(student1);
        System.out.println(student2);

    }

整体代码:

package demo5;

class Student implements Cloneable{
    public String name;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

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

public class Test {
    //学习自定义类型克隆拷贝
    public static void main(String[] args) throws CloneNotSupportedException  {
        Student student1 = new Student("小帅");
        Student student2 = (Student) student1.clone();
        System.out.println(student1);
        System.out.println(student2);

    }
}

运行结果:
Java接口的应用_第14张图片
分析:目前是可以对自定义类型进行拷贝了,但是拷贝分为浅拷贝和深拷贝。

  1. 关于浅拷贝和深拷贝
    浅拷贝:修改拷贝的对象(副本),之前的本身也会改变。
    深拷贝: 修改拷贝的对象(副本),之前的本身不受影响。
    如图:本身是蓝色,拷贝后也是蓝色,如果用红色修改拷贝后的,原本身的也被会红色修改则是浅拷贝,原本身的不会被红色修改就是深拷贝。
    Java接口的应用_第15张图片
  2. 浅拷贝例子
    新建一个Money类
class Money {
    public double money = 12.5;
}

组合:每个学生都有钱,在Student类中实例化Money对象

class Student implements Cloneable{
    public String name;
    public Money m = new Money();

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

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

测试:

public class Test {
    //学习自定义类型克隆拷贝
    public static void main(String[] args) throws CloneNotSupportedException  {
        Student student1 = new Student("小帅");
        Student student2 = (Student) student1.clone();
        System.out.println(student1.m.money);
        System.out.println(student2.m.money);
        student2.m.money = 99;
        System.out.println("修改拷贝对象的money之后的打印结果");
        System.out.println(student1.m.money);
        System.out.println(student2.m.money);
    }
}

运行结果:
Java接口的应用_第16张图片
分析:修改拷贝对象student2的值money,原来的student1的值也发生了改变,则是浅拷贝,因为只对student1拷贝,并没有把money也拷贝一份。
Java接口的应用_第17张图片

  1. 将上述例子改成深拷贝:即m所指的money对象也要克隆
    ①Money类实现Cloneable接口
class Money implements  Cloneable{
    public double money = 12.5;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

②Student类重写clone方法

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone(); //得到一个浅拷贝对象
        student.m = (Money) this.m.clone(); //浅拷贝的对象的m要再次拷贝,从而实现深拷贝
        //this 谁调用这个方法就指谁,这里指student1
        return student;
    }

Java接口的应用_第18张图片
分析:此时实现了深拷贝,Student student = (Student) super.clone();只是克隆了student对象, student.m = (Money) this.m.clone();克隆了student对象里面的money对象。
Java接口的应用_第19张图片
所以如要要实现深拷贝,当前对象的每一个对象都要克隆。

  1. 使用Objetct接受所有类的对象,以及匿名对象的使用
package demo6;

class  Dog {
    
}


public class Test {
    public static void func(Object o) {
        System.out.println("wow");
    }
    
    public static void main(String[] args) {
      func(new Dog());
      Dog dog = new Dog();
      func(dog);
    }
}

所有类的对象都可以使用Object的引用进行接受public static void func(Object o)
当没有给其他引用的时候,这些都是匿名对象,形如: func(new Dog());即为匿名对象,缺点是每次使用都需要new,使用场景是只用一次的时候。

你可能感兴趣的:(JavaSE,java)