Java SE 抽象类和接口(下)
注!!!
本文章所使用的编写代码和示例软件为:
IntelliJ IDEA Community Edition 2021.3.2
本文学习目标:
- 知道什么是Object 类。
- 知道Object 类的 equals 方法和 hashCode 方法,以及怎么去用。
- 知道 Comparable接口和 Comparator接口,以及怎么去使用。
1. Object 类
1.1 什么是 Object 类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object 父类,简单来说:Object 类是所有类的父类,即所有类的对象都可以使用Object的引用进行接收。
举个例子:使用Object 接收所有类的对象
class Person {}class Student {} public class Test {public static void main(String[] args) {Person person = new Person();Student student = new Student();func(person);func(student);}public static void func(Object obj) {System.out.println(obj);} }//运行结果
Demo1.Person@41629346
Demo1.Student@404b9385
1.2 Object 类提供的方法
Object 类提供了一些定义好的方法,接下来介绍两个:equals() 方法,hashCode() 方法。
1.2.1 对象比较 equals 方法
在Java中,使用 == 进行比较时:
a. 如果 == 左右两边是基本类型变量,则比较的是变量中的值是否相同。
b. 如果 == 左右两边是引用类型变量,则比较的是引用变量地址是否相同。
c. 如果要比较对象中内容,必须重写Object中的equals 方法,因为equals 方法默认也是按照地址比较的。
// Object类中的equals方法 public boolean equals(Object obj) {return (this == obj); // 使用引用中的地址直接来进行比较 }
我们可以写个代码来证明:
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;} }public class Test {public static void main(String[] args) {Person person1 = new Person("小明",12);Person person2 = new Person("小明",12);System.out.println(person1 == person2);System.out.println(person1.equals(person2));} }//运行结果
false
false
Person 类重写 equals 方法后重新比较
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}public boolean equals(Object obj) {if(obj == null) {return false;}if(this == obj) {return true;}//检测是不是Person类对象if (!(obj instanceof Person)) {return false ;}Person person = (Person) obj; // 向下转型 比较属性值return this.name.equals(person.name) && this.age == person.age;} }public class Test {public static void main(String[] args) {Person person1 = new Person("小明",12);Person person2 = new Person("小明",12);System.out.println(person1 == person2);System.out.println(person1.equals(person2));} }//运行结果
false
true
结论: 比较对象中内容是否相同时,一定要重写equals 方法。
1.2.2 hashCode 方法
hashCode 方法的功能简单来说就是帮忙算一个具体的对象地址。
hashCode方法的源码:
public native int hashCode();
显然,这是一个native 方法,底层是由C/C++代码写的,我们看不见。
我们认为两个名字,年龄相同的对象,将储存在同一个位置,逻辑上是这样的,但实际上不是。
如果不重写hashCode()方法,我们可以来看示例代码:
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;} }public class Test {public static void main(String[] args) {Person person1 = new Person("小明",12);Person person2 = new Person("小明",12);System.out.println(person1.hashCode());System.out.println(person2.hashCode());} }//运行结果
990368553
1096979270
我们发现:两个对象的hash值不一样,但两个对象的名字、年龄是一样,这结果与我们期望的不符。
像重写 equals 方法,我们也可以重写 hashCode 方法。
class Person {public String name;public int age;public Person(String name, int age) {this.name = name;this.age = age;}public int hashCode() {return Objects.hash(name,age);} }public class Test {public static void main(String[] args) {Person person1 = new Person("小明",12);Person person2 = new Person("小明",12);System.out.println(person1.hashCode());System.out.println(person2.hashCode());} }//运行结果
23458766
23458766
我们发现:哈希值一样。
总结:
- hashcode方法用来确定对象在内存中存储的位置是否相同。
- 事实上hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的 散列码,进而确定该对象在散列表中的位置。
2. 接口使用实例
给对象数组排序(不灵活)
现在我们创建一个学生类,并且实例化两个学生对象如下:
public class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';} }
public class Test {public static void main(String[] args) {Student student1 = new Student("小明",12);Student student2 = new Student("小红",13);}}
现在我们打算比较两个对象,
public class Test {public static void main(String[] args) {Student student1 = new Student("小明",12);Student student2 = new Student("小红",13); System.out.println(student1 <student2);}}
显然这种写法是错误的,student1与student2是两个引用变量,它们存的是对象的地址,不能直接进行比较,从这里我们也得到两个疑问:
- 当前自定义类,要根据什么样的规则进行比较?
- 这个规则如何定义?
这里我们可以用到Java提供的 Comparable 接口,定义一个规则,以便去实现比较。
Comparable 本身就有“比较”的意思,这个接口因此也是用于比较的。
我们进入到 Comparable 接口,可以看到
这里的 Comparable<T> 中的 T 指的是 泛型参数,就是你要比较哪个类就写哪个类,这里我们要比较两个学生对象,就写 Student 类, 接着它里面有一个抽象方法 compareTo() ,这个就是比较的规则,我们可以在子类中重写需要的规则。
我们使Student 类实现这个接口,如下:
public class Student implements Comparable<Student> {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {if(this.age > o.age) {return 1;} else if(this.age == o.age) {return 0;} else {return -1;}} }
注意,这里的 this 指的是 谁调用这个方法就是谁。
这里是按年龄进行比较,这下子我们就能比较了,我们设想输出结果为 -1
public class Test {public static void main(String[] args) {Student student1 = new Student("小明",12);Student student2 = new Student("小红",13);System.out.println(student1.compareTo(student2));}}//运行结果
-1
student1 小于 student2,因此输出-1,这与我们设想的一样。
那么我们如果想按照名字首字母比较,又该怎么样呢?首先,我们明确一件事,名字的数据类型为 String 是引用数据类型,它是一个类,既然是类那么它也会有很多方法
在它的目录底下,我们发现了 compareTo()方法 ,因此根据名字首字母进行比较我们可以这样写
public class Student implements Comparable<Student> {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}@Overridepublic int compareTo(Student o) {return this.name.compareTo(o.name);} }//这里调用的是 String 类的compareTo()方法。
既然两个对象我们已经知道如何去比较了,那么多个对象呢?对于多个对象,我们不得不想到数组,Student类 也是一个类型,那么可以创建一个 Studnet数组把多个 Student对象存起来。
public class Test {public static void main(String[] args) {Student student1 = new Student("李明",12);Student student2 = new Student("小红",9);Student student3 = new Student("王刚",11);Student[] stu = {student1,student2,student3};} }
当初我们在学习数组排序时,曾使用过 Arrays 类的 sort()方法,只不过那会数组的类型是基本数据类型,那么现在是引用数据类型,还能行吗?我们试试
public class Test {public static void main(String[] args) {Student student1 = new Student("李明",12);Student student2 = new Student("小红",9);Student student3 = new Student("王刚",11);Student[] stu = {student1,student2,student3};System.out.println("排序前:" + Arrays.toString(stu));Arrays.sort(stu);System.out.println("排序后:" + Arrays.toString(stu));} }//运行结果
排序前:[Student{name='李明', age=12}, Student{name='小红', age=9}, Student{name='王刚', age=11}]
排序后:[Student{name='小红', age=9}, Student{name='王刚', age=11}, Student{name='李明', age=12}]
显然是可以进行排序的,并且是按照年龄升序进行排序的,这是为什么呢?
这里需要明白一件事,就是sort()方法排序自定义类数组时会依赖 Comparable 接口,比较前,sort()方法会检测自定义类是否实现了 Comparable 类,如果没有实现就会报错,如果实现了,则会通过 Comparable 接口中的 compareTo()方法进行排序,而 compareTo方法我们在Student 类中已经重写了,就是按年龄进行排序。当一个类实现了 Comparable 接口,就意味着这个类的对象之间有了自然的比较方法。
写到这,我们的两个疑问基本解决了,但是我们有发现一个问题:当根据不同的属性进行比较时,不得不对 Student 类中重写的 compareTo()方法进行修改,这是很不方便的,那么有没有更方便的方法呢? 其实是有的,就是换一个接口。
给对象数组排序(灵活)
我们可以通过 Comparator 接口实现,简单讲就是设置一个比较器,让 sort()方法按照这个比较器去实现排序。
我们先定义一个学生类
public class Student {private String name;private int age;public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public int getAge() {return age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';} }
再定义一个类,作为比较器,我们姑且设置它为“按年龄升序排序”比较器。这个类需要实现 Comparator 接口,并且重写 compare()方法。
public class AgeRise implements Comparator<Student> {@Override //这里如果o1大于o2,则返回一个正数,否则返回一个负数。public int compare(Student o1, Student o2) {return o1.getAge() - o2.getAge();} }
接着创建一个测试类,创建一个学生类数组,并且放入三个对象,创建一个比较器对象,接着把比较器对象作为 sort()方法的第二个参数,那么sort()方法再对数组进行排序时,就会按照比较器的规则进行排序。
public class Test {public static void main(String[] args) {Student student1 = new Student("黎明",12);Student student2 = new Student("王昂",14);Student student3 = new Student("李白",9);AgeRise ageRise = new AgeRise();Student[] stu = {student1,student2,student3};System.out.println("排序前:" + Arrays.toString(stu));Arrays.sort(stu,ageRise);System.out.println("排序后:" + Arrays.toString(stu));} }//运行结果
排序前:[Student{name='黎明', age=12}, Student{name='王昂', age=14}, Student{name='李白', age=9}]
排序后:[Student{name='李白', age=9}, Student{name='黎明', age=12}, Student{name='王昂', age=14}]
注意: 被比较的类可以不实现 Comparator 接口。
我们发现 sort()方法确实是按照比较器的规则进行比较了,那么我们想让它按名字排序,再定义一个比较器
public class NameCompare implements Comparator<Student> {@Overridepublic int compare(Student o1, Student o2) {return o1.getName().compareTo(o2.getName());} }
在测试类中运行一下
public class Test {public static void main(String[] args) {Student student1 = new Student("黎明",12);Student student2 = new Student("王昂",14);Student student3 = new Student("李白",9);NameCompare nameCompare = new NameCompare();Student[] stu = {student1,student2,student3};System.out.println("排序前:" + Arrays.toString(stu));Arrays.sort(stu,nameCompare);System.out.println("排序后:" + Arrays.toString(stu));} }//运行结果
排序前:[Student{name='黎明', age=12}, Student{name='王昂', age=14}, Student{name='李白', age=9}]
排序后:[Student{name='李白', age=9}, Student{name='王昂', age=14}, Student{name='黎明', age=12}]
可以看到,确实是按照名字进行排序了。
总结: 从上述两个例子,我们可以知道使用 Comparator 接口可以实现灵活的排序规则,我们不必每次都去比较的类中修改,只要定义我们需要的比较器即可,这样子就实现了解耦。
到此,“抽象类和接口”的内容已完结,若本文有不对的地方还请指出,多谢!!!