文章目录
一、抽象类
1.1 什么是抽象类
在Java中,抽象类是一种特殊的类,用来表示具有一般特征和行为的抽象概念。它不能够直接实例化,而是作为其他类的基类或者父类来使用。
抽象类通过在类声明中使用关键字abstract
来定义。它可以包含抽象方法和非抽象方法。抽象方法是没有具体实现的方法,只有方法签名,用关键字abstract
关键字修饰;非抽象方法则具有具体的实现。
抽象类的主要目的是提供一种通用的基类,将相关的子类归类在一起,以便共享通用的属性和行为。它可以定义一些通用方法,供子类继承和实现。
1.2 抽象类的语法
抽象类的基本语法如下面的代码所示:
abstract class AbstractClass {
// 抽象方法
public abstract void abstractMethod();
// 非抽象方法
public void nonAbstractMethod() {
// 方法实现
}
// 构造方法
public AbstractClass() {
// 构造方法实现
}
}
抽象类的语法要点如下:
-
使用关键字
abstract
来声明抽象类,将类定义为抽象类。抽象类不能被实例化。 -
抽象类可以包含抽象方法和非抽象方法。
-
抽象方法使用
abstract
关键字进行声明,并且没有方法体。它们只有方法签名,用于定义方法的名称、参数列表和返回类型。子类必须实现(覆盖、重写)抽象方法,否则子类就应该声明为抽象类。 -
非抽象方法是具有实现的方法,可以在抽象类中直接定义方法体。子类可以直接继承非抽象方法的实现。
-
抽象类可以包含构造方法。构造方法在子类实例化时调用父类的构造方法。
需要注意的是,如果一个类包含一个或多个抽象方法,那么该类必须被声明为抽象类。另外,抽象类可以被继承,但不能被标记为final
,因为它需要被子类扩展和实现。
以下是一个更完整的示例,展示了抽象类的语法和使用:
abstract class AbstractClass {
// 抽象方法
public abstract void abstractMethod();
// 非抽象方法
public void nonAbstractMethod() {
System.out.println("This is a non-abstract method.");
}
// 构造方法
public AbstractClass() {
System.out.println("AbstractClass constructor");
}
}
class ConcreteClass extends AbstractClass {
// 实现抽象方法
public void abstractMethod() {
System.out.println("Abstract method implementation");
}
}
public class Main {
public static void main(String[] args) {
ConcreteClass concrete = new ConcreteClass();
concrete.abstractMethod();
concrete.nonAbstractMethod();
}
}
在上面的示例中,AbstractClass
是一个抽象类,定义了一个抽象方法abstractMethod()
和一个非抽象方法nonAbstractMethod()
。ConcreteClass
继承了AbstractClass
并实现了抽象方法。在Main
类的main()
方法中,我们实例化了ConcreteClass
的对象,并调用了抽象方法和非抽象方法。
输出结果为:
AbstractClass constructor
Abstract method implementation
This is a non-abstract method.
以上是抽象类的基本语法和用法。通过使用抽象类,我们可以定义一些共享的行为和属性,并要求子类实现特定的方法,从而实现更灵活和可扩展的类设计。
1.3 抽象类的特性
抽象类具有以下特性:
-
无法实例化:抽象类不能直接实例化,也就是说,不能使用
new
关键字来创建抽象类的对象。只能通过继承抽象类并实现其中的抽象方法,创建其子类的对象。 -
可以包含抽象方法:抽象类可以包含一个或多个抽象方法。抽象方法是没有具体实现的方法,只有方法的声明,没有方法体。抽象方法使用
abstract
关键字进行声明,并且必须在抽象类中定义。子类必须实现(覆盖)抽象方法,否则子类也必须声明为抽象类。 -
可以包含非抽象方法:抽象类可以包含非抽象方法,这些方法具有具体的实现。子类可以直接继承和使用这些非抽象方法。
-
可以包含成员变量和常量:抽象类可以包含成员变量和常量。这些成员变量和常量可以被子类继承和使用。
-
可以包含构造方法:抽象类可以有构造方法,用于初始化抽象类的实例。子类在实例化时会调用父类的构造方法。
-
可以被继承:抽象类可以作为其他类的基类或父类,被其他类继承。子类继承抽象类后,必须实现(覆盖)抽象类中的所有抽象方法,除非子类自身也是抽象类。
-
用于抽象概念建模:抽象类常用于对具有相似特征和行为的类进行建模。它们可以捕捉类之间的共同特征,并提供一种通用的基类,以便共享通用的属性和行为。
-
不能被标记为
final
或static
:抽象类不能被标记为final
或static
,因为它需要被其他类继承和实现。
抽象类的主要目的是作为类继承层次结构中的一个抽象概念,将相关的子类归类在一起,并为子类提供共享的行为和属性。通过使用抽象类,可以实现代码的重用性和可扩展性,同时还能够强制要求子类实现特定的方法。
1.4 抽象类的作用
抽象类在Java中有多种作用和用途,包括以下几个方面:
-
定义类层次结构:抽象类可以用于定义类的继承层次结构。它可以作为其他类的基类或父类,用于将一组相关的子类进行分类和组织。通过抽象类,可以将具有共同特征和行为的类进行归类,并将通用的属性和方法提取到抽象类中。
-
提供默认实现:抽象类可以包含非抽象方法的实现。这些非抽象方法提供了默认的行为,可以被子类继承和使用。通过在抽象类中定义通用的实现,可以减少子类中重复的代码,提高代码的重用性。
-
强制子类实现特定方法:抽象类可以包含抽象方法,这些方法没有具体的实现,只有方法签名。子类继承抽象类时,必须实现(覆盖)抽象方法,以提供特定的实现逻辑。通过抽象类,可以强制子类实现一些必要的方法,确保它们在正确的上下文中被调用。
-
提供模板方法:抽象类可以定义模板方法,这些方法提供了算法的骨架,而具体的实现可以在子类中完成。模板方法模式通过抽象类的模板方法和具体子类的实现方法,实现了算法的固定结构和可变部分的分离。这样可以方便地扩展和定制算法的实现。
-
提供共享的成员变量和常量:抽象类可以包含成员变量和常量,这些成员可以被子类继承和使用。通过在抽象类中定义共享的属性,可以避免在每个子类中重复定义相同的属性,提高代码的可维护性和一致性。
总的来说,抽象类提供了一种机制,用于定义类之间的共同特征和行为,并要求子类实现特定的方法。它促进了代码的重用性、可扩展性和模块化设计。通过使用抽象类,可以更好地组织和管理类的继承关系,并确保类在正确的上下文中被使用和实现。
二、接口
2.1 什么是接口
接口(Interface)是一种抽象的参照类型(Reference Type),它定义了一组方法的规范,但没有具体的实现。接口提供了一种契约机制,用于描述类应该具有的行为,而不涉及具体的实现细节。
在Java中,接口具有以下特点:
- 接口使用
interface
关键字来定义。接口可以包含方法的声明、常量的定义和默认方法的实现。 - 接口中的方法默认为抽象方法,即没有方法体。在接口中声明方法时,不需要使用
abstract
关键字进行标识。 - 接口中的常量默认为
public static final
,即常量的值不能够修改。 - 接口可以继承其他接口,使用
extends
关键字进行声明,一个接口可以继承多个接口,实现接口的继承层次结构。 - 一个类可以实现一个或多个接口,使用
implements
关键字进行声明。一个类实现接口时,必须实现接口中声明的所有方法,提供具体的实现。 - 接口可以用于实现多态性。通过接口引用,可以引用实现该接口的任意对象,并调用接口中的方法。
接口的主要目的是定义类应该具备的行为和能力,实现了接口的类必须提供接口中定义的方法的具体实现。通过使用接口,可以实现代码的解耦和模块化设计,提高代码的可维护性和可扩展性。接口还促进了面向接口编程的思想,实现了更松散耦合的系统架构。
2.2 接口的语法规则
接口(Interface)在Java中遵循以下语法规则:
- 声明接口
interface 接口名 {
// 常量声明(可选)
// 方法声明(抽象方法)
}
使用 interface
关键字声明一个接口,接口名应该符合标识符的命名规则。
- 声明方法
接口中的方法默认为抽象方法,没有具体的实现。方法声明的语法如下:
[访问修饰符] 返回类型 方法名(参数列表);
其中,访问修饰符可以是 public 或者省略(默认为 public)。方法的返回类型可以是任意合法的数据类型,或者是另一个接口类型。
- 声明常量
接口中的常量默认为public static final
,即常量的值不能修改。常量声明的语法如下:
[访问修饰符] static final 数据类型 常量名 = 值;
其中,访问修饰符可以是 public 或者省略(默认为 public)。常量的数据类型可以是任意合法的数据类型。
- 继承接口
接口可以继承其他接口,使用extends
关键字进行声明。一个接口可以继承多个接口,形成接口的继承层次结构。
interface 子接口名 extends 父接口1, 父接口2, ... {
// ...
}
- 实现接口
类可以实现一个或多个接口,使用implements
关键字进行声明。一个类实现接口时,必须提供接口中声明的所有方法的具体实现。
class 类名 implements 接口1, 接口2, ... {
// ...
}
如果一个类实现了接口,那么它必须实现接口中的所有抽象方法,否则类本身必须声明为抽象类。
2.3 接口的使用
接口不能直接使用,必须要有一个 实现类
来实现
该接口,并且还有实现接口中的所有抽象方法。需要注意的是,子类和父类之间是extends
继承关系,类与接口之间是 implements
实现关系。
以下是实现笔记本电脑使用USB鼠标、USB键盘的例子:
- USB接口:包含打开设备、关闭设备功能
- 笔记本类:包含开机功能、关机功能、使用USB设备功能
- 鼠标类:实现USB接口,并具备点击功能
- 键盘类:实现USB接口,并具备输入功能
// USB接口
interface USB {
// 打开设备
void openDevice();
// 关闭设备
void closeDevice();
}
// 鼠标类,实现USB接口
class Mouse implements USB {
@Override
public void openDevice() {
System.out.println("打开鼠标");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
public void click() {
System.out.println("鼠标点击");
}
}
// 键盘类,实现USB接口
class KeyBoard implements USB {
@Override
public void openDevice() {
System.out.println("打开键盘");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
public void inPut() {
System.out.println("键盘输入");
}
}
// 笔记本类:使用USB设备
class Computer {
public void powerOn() {
System.out.println("打开笔记本电脑");
}
public void powerOff() {
System.out.println("关闭笔记本电脑");
}
public void useDevice(USB usb) {
usb.openDevice();
if (usb instanceof Mouse) {
Mouse mouse = (Mouse) usb;
mouse.click();
} else if (usb instanceof KeyBoard) {
KeyBoard keyBoard = (KeyBoard) usb;
keyBoard.inPut();
}
usb.closeDevice();
}
}
// 测试类:
public class Test2 {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
// 使用鼠标设备
computer.useDevice(new Mouse());
// 使用键盘设备
computer.useDevice(new KeyBoard());
computer.powerOff();
}
}
上述代码展示了一个使用USB设备的示例。
首先,定义了一个USB接口,其中包含了openDevice()
和closeDevice()
两个方法,表示打开和关闭设备的操作。
接着,定义了鼠标类Mouse
和键盘类KeyBoard
,它们都实现了USB接口,并重写了接口中的方法。除此之外,鼠标类还有一个额外的方法click()
表示鼠标的点击操作,键盘类也有一个额外的方法inPut()
表示键盘的输入操作。
然后,定义了一个笔记本类Computer
,它具有开机和关机的功能,以及一个useDevice()
方法用于使用USB设备。useDevice()
方法接收一个USB接口的实例作为参数,并根据具体的实例类型执行相应的操作。如果是鼠标设备,就调用鼠标的点击方法;如果是键盘设备,就调用键盘的输入方法。
最后,在测试类Test2
中创建了一个笔记本电脑对象,并依次执行了打开电脑、使用鼠标设备、使用键盘设备和关闭电脑的操作。
运行测试类,输出如下结果:
打开笔记本电脑
打开鼠标
鼠标点击
关闭鼠标
打开键盘
键盘输入
关闭键盘
关闭笔记本电脑
通过接口的使用,我们可以通过统一的方法来操作不同的USB设备,提高了代码的灵活性和可扩展性。同时,也实现了多态性的特性,使得可以根据具体的设备类型进行不同的操作。
2.4 实现多个接口
在Java中,一个类可以实现多个接口,以获得多个接口所定义的行为和能力。实现多个接口的语法如下:
class MyClass implements Interface1, Interface2, Interface3 {
// 实现接口中的抽象方法
// ...
}
在上述示例中,MyClass
类同时实现了 Interface1
、Interface2
和 Interface3
这三个接口。接口之间使用逗号进行分隔。
实现多个接口时,需要确保实现类提供了每个接口中声明的所有抽象方法的具体实现。如果某个接口的方法在实现类中没有被实现,则该实现类必须声明为抽象类。
实现多个接口使得类能够同时具备这些接口所定义的行为和能力。通过多态性,可以使用接口类型的引用来引用实现类的对象,并调用接口中定义的方法。这样可以在编程中实现灵活性和扩展性,同时允许代码以接口为基础进行设计和组织。
需要注意的是,当多个接口中存在同名的默认方法时,实现类必须重写这个方法来消除冲突。否则,编译器会报错,要求显式地在实现类中提供方法的具体实现。
class MyClass implements Interface1, Interface2 {
// 实现接口1中的抽象方法
// 实现接口2中的抽象方法
// 重写接口1和接口2中的同名默认方法
}
实现多个接口是一种强大的工具,可以使类具备多重行为和能力,同时遵循面向接口编程的原则,提高代码的灵活性和可维护性。
2.5 接口间继承
在Java中,接口之间可以进行继承,这样一个接口可以继承自另一个或多个接口,形成接口的继承层次结构。接口继承的语法如下:
interface SubInterface extends SuperInterface {
// ...
}
在上述示例中,SubInterface
接口继承了 SuperInterface
接口。这表示 SubInterface
接口继承了 SuperInterface
接口所声明的方法和常量,并且可以在 SubInterface
接口中添加新的方法和常量。
接口继承的特点如下:
- 一个接口可以继承多个接口,使用逗号进行分隔。例如:
interface SubInterface extends Interface1, Interface2 { ... }
- 接口继承可以形成多层继承关系,类似于类的继承结构。
- 继承的接口可以拥有自己的抽象方法和常量,并且可以在子接口中添加新的抽象方法和常量。
- 子接口可以继承父接口的默认方法,并可以选择性地重写或继承这些默认方法。
- 子接口继承了父接口的类型,可以作为父接口类型的引用。
通过接口的继承,可以实现更加抽象和通用的接口定义,促进代码的模块化和可复用性。接口继承还可以构建更丰富和灵活的接口层次结构,使得接口之间的关系更加清晰和易于管理。
2.6 Comparable 接口
现在有一个Student类,其包含的属性有名字name和年龄age,要求对其对象按照年龄进行升序排序:
class Student{
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
再给定一个学生对象数组, 对这个对象数组中的元素按照年龄进行升序排序。
Student[] students = new Student[] {
new Student("张三", 25),
new Student("李四", 16),
new Student("王五", 14),
new Student("赵六", 20),
};
在学习数组的时候,我们可以使用Arrays
中的sort
方法对数组中的元素进行排序,那么是否也可以对学生数组进行排序呢?
Arrays.sort(students);
System.out.println(Arrays.toString(students));
运行代码可以发现抛出异常:ClassCastException
,即不能将Student
类型转换为java.lang.Comparable
类型。
当我们思考一下,就会发现:如果是整数类型,当然可以直接比较出大小,而Student类型更加复杂,如果直接比较是按照什么来进行比较呢?因此需要我们自己指定比较的方式。
在Java中,如果需要实现类对象之间的比较,需要让该类实现Comparable
接口,并实现其中的compateTo
方法。Comparable
接口是在对象自身类中实现的,表示该类的对象具备自然顺序。
class Student implements Comparable<Student>{
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
return this.age - o.age;
// return o.age - this.age;
}
}
在 sort
方法中会自动调用 compareTo
方法 compareTo
的参数是 Student
类型的对象。然后比较当前对象和参数对象的大小关系(按年龄来算)。
- 如果是当前对象的年龄减去比较对象的年龄,则实现的是升序排序;
- 如果是比较对象的年龄减去当前对象的年龄,则实现的是降序排序。
当再次运行上面的代码逻辑的时候,我们就能够实现按照Student的年龄进行升序排序了。
但是,如果是使用Comparable
接口实现对象之间的比较,如果要在后面改变对象之间的比较规则,并且如果项目已经发布一段时间,那么显然就很难修改了,因此Comparator
接口的作用就体现出来了。
2.7 Comparator 接口
Comparator
接口是一个独立的比较器,可以在对象类外部实现,用于对不同类的对象进行比较。通过实现其中的 compare()
方法来定义对象之间的比较规则。
compare()
方法接收两个参数,表示要比较的两个对象,返回一个整数值表示比较结果。Comparator
接口常用于对某个类的对象进行临时的比较和排序,而不需要修改该类的代码。
对于自定义的类,如果需要按照不同的属性进行排序,可以通过实现 Comparator
接口来定义不同的比较器。例如,对上述的学生类,分别通过实现Comparator
接口,完成对姓名和年龄的比较:
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
// return o2.age - o1.age;
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
// return o2.name.compareTo(o1.name);
}
}
通过对年龄的比较,进行升序排序:
public static void main(String[] args) {
Student[] students = new Student[] {
new Student("张三", 25),
new Student("李四", 16),
new Student("王五", 14),
new Student("赵六", 20),
};
AgeComparator ageComparator = new AgeComparator();
Arrays.sort(students, ageComparator);
System.out.println(Arrays.toString(students));
}
通过对姓名的比较进行排序:
public static void main(String[] args) {
Student[] students = new Student[] {
new Student("张三", 25),
new Student("李四", 16),
new Student("王五", 14),
new Student("赵六", 20),
};
NameComparator nameComparator = new NameComparator();
Arrays.sort(students, nameComparator);
System.out.println(Arrays.toString(students));
}
2.8 Cloneable 接口和深拷贝
Object
类中存在一个 clone
方法,调用这个方法可以创建一个对象的 拷贝
。但是要想合法调用 clone
方法,必须要先实现 Clonable
接口,否则就会抛出 CloneNotSupportedException
异常。
例如下面的代码:
class Person implements Cloneable{
public int id;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
'}';
}
}
在上面的代码中,Person
类实现了Cloneable
接口,并且完成了对clone
方法的重写。
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
Person person1 = (Person) person.clone();
Person person2 = person;
System.out.println(person == person1); // 运行结果:false
System.out.println(person == person2); // 运行结果:true
}
通过运行这段代码,就能够发现是否完成了克隆的功能。
关于深浅拷贝的问题
在创建一个Money
类,并且让其作为Student
类的一个成员变量:
class Money {
public double m = 0.00;
@Override
public String toString() {
return "Money{" +
"m=" + m +
'}';
}
}
class Person implements Cloneable {
public int id;
public Money money = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", money=" + money +
'}';
}
}
然后再执行以下代码:
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
Person person1 = (Person) person.clone();
person.money.m = 10.00;
person.id = 1;
person1.money.m = 12.00;
person1.id = 2;
System.out.println(person);
System.out.println(person1);
}
运行结果:
通过对比可以发现,其实对Money
对象实现的是浅拷贝,其内存结构大概是:
即引用类型变量person和person1指向的Money
对象为同一个,这就是浅拷贝。如果要实现对Money
的深拷贝,Money对象也需要实现Cloneable
接口。
class Money implements Cloneable {
public double m = 0.00;
@Override
public String toString() {
return "Money{" +
"m=" + m +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable {
public int id;
public Money money = new Money();
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person) super.clone();
tmp.money = (Money) this.money.clone();
return tmp;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", money=" + money +
'}';
}
}
此时,再次运行main
方法,其结果如下:
由此,便实现了对Money
的深拷贝。
2.9 抽象类和接口的区别
抽象类和接口是面向对象编程中的两个重要概念,它们有以下区别:
-
继承关系:
- 抽象类使用类的继承机制,子类通过继承抽象类来获得共享的属性和方法,并可以进行扩展和重写。
- 接口使用实现机制,类通过实现接口来声明自己能够提供某些行为,一个类可以实现多个接口。
-
构造方法:
- 抽象类可以有构造方法,可以用于初始化共享的属性和执行一些公共逻辑。
- 接口不能有构造方法,因为接口主要用于定义行为而不是实现。
-
成员变量和常量:
- 抽象类可以包含实例变量、静态变量和常量。
- 接口只能包含常量,即接口中的变量默认为
public static final
。
-
方法实现:
- 抽象类可以包含抽象方法和具体方法的实现。
- 接口只能包含抽象方法,即方法声明而没有具体实现。
- Java 8 之后,接口也可以包含默认方法和静态方法的实现。
-
被实现/继承的数量:
- 一个类可以实现多个接口,从而获得多个接口提供的行为。
- 一个类只能继承一个抽象类。
-
设计目的:
- 抽象类用于表示一种类的继承关系和层次结构,它体现了"is-a"的关系。
- 接口用于表示一种类的能力或行为,它体现了"has-a"的关系。
选择抽象类还是接口取决于具体的需求和设计目的。如果需要共享一些通用的属性和方法,并且涉及到类的继承关系,可以选择抽象类。如果需要定义一组行为或能力,并且与类的继承关系无关,可以选择接口。有时候也可以将抽象类和接口结合使用,以满足不同的需求。
三、Object 类
3.1 认识 Object 类
Object
是Java默认提供的一个类。Java里面除了Object
类,所有的类都是存在继承关系的。默认会继承Object
父类。即所有类的对象都可以使用Object
的引用进行接收。
class A{}
class B{}
public class Test {
public static void main(String[] args) {
function(new A());
function(new B());
}
public static void function(Object obj) {
System.out.println(obj);
}
}
运行结果:
3.2 获取对象信息
如果要打印对象中的内容,可以直接重写Object
类中的toString()方法,Object
类中的toString()
方法实现:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
3.3 对象比较 equals 方法
在Java中,使用==
进行比较时:
- 如果
==
左右两侧是基本类型变量,比较的是变量中值是否相同。 - 如果
==
左右两侧是引用类型变量,比较的是引用变量地址是否相同。 - 如果要比较对象中内容,必须重写
Object
中的equals
方法,因为equals
方法默认也是按照地址比较的。
Object
类中的equals
方法:
public boolean equals(Object obj) {
return (this == obj); // 使用引用中的地址直接来进行比较
}
Student
类重写equals
方法:
class Student {
public String name;
public int age;
@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);
}
}
因此在比较对象中内容是否相同的时候,一定要重写equals
方法。
3.4 hashcode 方法
回忆Object
类中的toString方法:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
发现其中有一个hashCode()
方法,它的作用是为我们算一个具体的对象位置,即内存地址。然后调用Integer.toHexString()方法,将这个地址以16进制输出。
继续查看hashCode
源码:
public native int hashCode();
该方法是一个native
方法,底层是由C/C++
代码实现的。
在Student
类中,通常认为name和age相同的两个对象是同一个人,如果不重写hashCode
方法,其结果如下:
class Student implements Comparable<Student>{
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test1 {
public static void main(String[] args) {
Student student1 = new Student("张三", 18);
Student student2 = new Student("张三", 18);
System.out.println(student1.hashCode());
System.out.println(student2.hashCode());
}
}
运行结果如下,表现为两个不同的对象:
在Student
类中重写hashCode
方法:
@Override
public int hashCode() {
return Objects.hash(name, age);
}
再次运行,此时就表现为一个对象了。