当前位置: 首页 > news >正文

Java SE 抽象类和接口(下)

注!!!

本文章所使用的编写代码和示例软件为:

IntelliJ IDEA Community Edition 2021.3.2

本文学习目标:

  1. 知道什么是Object 类。
  2. 知道Object 类的 equals 方法和 hashCode 方法,以及怎么去用。
  3. 知道 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

 我们发现:哈希值一样。

总结:

  1. hashcode方法用来确定对象在内存中存储的位置是否相同。
  2. 事实上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是两个引用变量,它们存的是对象的地址,不能直接进行比较,从这里我们也得到两个疑问:

  1.  当前自定义类,要根据什么样的规则进行比较?
  2. 这个规则如何定义?

这里我们可以用到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 接口可以实现灵活的排序规则,我们不必每次都去比较的类中修改,只要定义我们需要的比较器即可,这样子就实现了解耦。

到此,“抽象类和接口”的内容已完结,若本文有不对的地方还请指出,多谢!!!

相关文章:

  • 【题解-洛谷】B4240 [海淀区小学组 2025] 最短字符串
  • NIFI的处理器:ExecuteGroovyScript 2.4.0
  • C# AI(Trae工具+claude3.5-sonnet) 写前后端
  • A1-A2 英语学习系列 第五集
  • Java枚举详解
  • 抽象:C++命名作用域与函数调用
  • IO pin的transition约束从哪来?
  • 高级认知型Agent
  • dedecms织梦全局变量调用方法总结
  • 如何在电脑上登录多个抖音账号?多开不同IP技巧分解
  • 广东省省考备考(第十六天5.21)—言语:语句排序题(听课后强化)
  • React中 lazy与 Suspense懒加载的组件
  • git合并多次commit提交
  • CentOS:搭建国内软件repository,以实现自动yum网络安装
  • JUC高并发编程
  • 自动化软件如何确保高可用性和容错性?
  • 云蝠智能大模型呼叫动态情感共情能力上线!
  • 大语言模型 17 - MCP Model Context Protocol 介绍对比分析 基本环境配置
  • 双活数据中心解决方案
  • 如何在Java中处理PDF文档(教程)
  • 马上评|当众猥亵女演员,没有任何开脱理由
  • 中公教育:现阶段在全力解决学员退费问题,部分地区历史退费已逐步出清
  • 人民日报评论员观察:稳就业,抓好存量、增量、质量
  • 纽约市长称墨西哥海军帆船撞桥事故已致2人死亡
  • 信俗与共:清代新疆回疆儒释道庙宇的中华政教
  • 浙江演艺集团7部作品组团来沪,今夏开启首届上海演出季