目标:总结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接口不好,因为代码不够灵活,一旦写死了拿年龄比较,以后默认都是拿年龄比较,那我要想拿名字或者分数比较就不行了。
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);
以上就是非常灵活的比较器,想怎么写就怎么写。
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
①如果没有重写toString方法,运行结果:
②如果重写了toString方法,运行结果:
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;
}
@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);
}
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方法用来确定对象在内存中存储的位置是否相同
@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);
}
}
运行结果:
分析:目前是可以对自定义类型进行拷贝了,但是拷贝分为浅拷贝和深拷贝。
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也拷贝一份。
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对象。
所以如要要实现深拷贝,当前对象的每一个对象都要克隆。
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,使用场景是只用一次的时候。