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

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

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

http://www.xdnf.cn/news/7820.html

相关文章:

  • JavaScript面试题之消息队列
  • 2.4.4-死锁的处理策略-检测和解除
  • origin绘图之【如何将多条重叠、高度重叠的点线图、折线图分开】
  • uni-app使用大集
  • uniapp-商城-64-后台 商品列表(商品修改---页面跳转,深浅copy应用,递归调用等)
  • STM32单片机GUI系统1 GUI基本内容
  • vue3 el-table 行号
  • ubuntu22.04上运行opentcs6.4版本
  • webpack5所用依赖以及对应的版本
  • [Harmony]自定义导航栏
  • 【Java基础笔记vlog】Java中常见的几种数组排序算法汇总详解
  • 算法分析与设计实验:找零钱问题的贪心算法与动态规划解决方案
  • Nginx网站服务
  • AI+MCP 自动发布小红书笔记
  • 【基础】Windows开发设置入门9:WSL 2 上的 Docker 容器
  • 基于Go语言的恶意软件通过Redis配置滥用向Linux主机部署XMRig挖矿程序
  • [论文精读]Ward: Provable RAG Dataset Inference via LLM Watermarks
  • 数据库健康监测器(BHM)实战:如何通过 HTML 报告识别潜在问题
  • Android OkHttp控制链:深入理解网络请求的流程管理
  • 动手学习深度学习V1.1 chapter2 (2.1-2.2)
  • 读一本书第一遍是快读还是细读?
  • 物理机做完bond后network服务重启失败
  • IntelliJ IDEA 接入 DeepSeek帮助你更好编码
  • net Core》》包与库 LibMan、NPM
  • 从加密到信任|密码重塑车路云一体化安全生态
  • 【Redis】二、Redis常用数据类型命令学习
  • 电感在断开的时候会按原来的电流方向流动这是什么定理?
  • Baklib内容中台的构建要点是什么?
  • 【性能测试】jvm监控
  • 前端JavaScript学习-动态编码-基础