-
目标:总结Comparable接口以及compareTo方法、comparator接口中compare方法比较器、toString方法、equals方法、hashCode方法、Cloneable接口以及深浅拷贝
-
比较对象中内容的大小【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));
}
}
运行结果:抛出异常
分析:Arrays.sort(students);
不知道怎么比较大小
根据报错提示:
作用:取到数组的某一个值,把这个值转换为一个Comparable接口类型,然后调用compareTo方法
因此在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下标的学生。
假如根据姓名比较,重写的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接口不好,因为代码不够灵活,一旦写死了拿年龄比较,以后默认都是拿年龄比较,那我要想拿名字或者分数比较就不行了。
- 比较对象中内容的大小【比较器Comparator接口中compare方法】
刚刚是Student类实现Comparable接口重写compareTo方法,现在是新创建一个毫不相干的类实现Comparator接口。
Comparator接口里面的抽象方法只有一个,所以新建一个AgeComparator类实现Comparator接口重写compare方法。
新建一个类实现接口并重写方法:
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);
以上就是非常灵活的比较器,想怎么写就怎么写。
- Object是Java默认提供的一个类,Java中Object类是所有类的父类,默认会继承Object父类,即所有类的对象都可以使用Object的引用进行接受
Object类中有的方法
- Object类中的toString方法
如果我们要打印对象中的内容,则需要在对象中重写toString方法。
因为可以认为要重写的那个对象的父类一定是Object,因此重写toString
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
①如果没有重写toString方法,运行结果:
②如果重写了toString方法,运行结果:
- 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,运行结果:
分析:因为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;
}
运行结果:
编译器自动重写的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);
}
- Object类中的hashCode方法
在上例中创建的2个学生类对象,我们认为2个对象是指同一个人(对象),所有在打印2个对象的地址的时候,理论上的运行结果应该是在内存中存同一个地址。
System.out.println(student1);
System.out.println(student2);
①没有重写hashCode方法的时候,运行结果:
②重写hashCode方法的运行结果:
@Override
public int hashCode() {
return Objects.hash(name, age);
}
分析:通过name和age去返回哈希地址,因为student1和student2的name和age都相同,因此打印出来的地址也是相同的。
总结:hashcode方法用来确定对象在内存中存储的位置是否相同
- 要去克隆,则一定要实现Clonable接口
分析:克隆接口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);
}
}
运行结果:
分析:目前是可以对自定义类型进行拷贝了,但是拷贝分为浅拷贝和深拷贝。
- 关于浅拷贝和深拷贝
浅拷贝:修改拷贝的对象(副本),之前的本身也会改变。
深拷贝: 修改拷贝的对象(副本),之前的本身不受影响。
如图:本身是蓝色,拷贝后也是蓝色,如果用红色修改拷贝后的,原本身的也被会红色修改则是浅拷贝,原本身的不会被红色修改就是深拷贝。
- 浅拷贝例子
新建一个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);
}
}
运行结果:
分析:修改拷贝对象student2的值money,原来的student1的值也发生了改变,则是浅拷贝,因为只对student1拷贝,并没有把money也拷贝一份。
- 将上述例子改成深拷贝:即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;
}
分析:此时实现了深拷贝,Student student = (Student) super.clone();
只是克隆了student对象, student.m = (Money) this.m.clone();
克隆了student对象里面的money对象。
所以如要要实现深拷贝,当前对象的每一个对象都要克隆。
- 使用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,使用场景是只用一次的时候。