抽象类和接口
1 抽象类
1.1 抽象类的概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
在动物的例子中, 我们发现, 父类 Animal
中的 bark()
方法好像并没有什么实际工作, 主要的叫都是由 Animal
的各种子类的 bark()
方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod,
包含抽象方法的类我们称为 抽象类(abstract class)
.
1.2 抽象类的语法
//抽象类:被abstract修饰的类
public abstract class Animal {
//抽象方法:被abstract修饰的方法
abstract public void bark();
//但是抽象类里其实亦可以写普通属性和方法
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
}
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
1.3 抽象类特性
- 抽象类本身是抽象的,因此不可以进行实例化
Aniaml animal = new Animal();
// 编译出错
Error:(15, 20) java: Animal是抽象的; 无法实例化
- 抽象方法不能是 private 的
private
表示仅当前类可见
,而abstract
要求必须被其他类继承
,两者语义冲突。
抽象类的构造方法若为private,子类无法调用父类构造器(super()),导致继承失败 。
- 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
- 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
- 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
Animal类(为抽象类)
public abstract class Animal {
abstract public void bark();
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
}
Cat 类 继承了Animal 类
public class Cat extends Animal{
public String color;
public Cat(String name,int age,String color){
super(name,age);
this.color = color;
}
@Override
public void bark() {
System.out.println(this.name+this.color+"正在叫");
}
}
Dog 类继承 Animal 类
public class Dog extends Animal {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void bark() {
System.out.println(this.name+"正在叫");
}
}
Test (进行了向上转型)
public class Test {
public static void func(Animal animal){
animal.bark();
}
public static void main(String[] args) {
func(new Cat("张三",10,"黄"));
func(new Dog("王五",11));
}
}
2 接口
2.1 接口的概念
接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
2.2 语法规则
接口的定义格式与定义类的格式基本相同,将class
关键字换成 interface
关键字,就定义了一个接口。
public interface 抽象方法 {
public abstract void openDevice(); // public abstract 是固定搭配,可以不写
void closeDevice();//推荐
}
提示:
- 创建接口时, 接口的命名一般以大写字母 I 开头.
- 接口的命名一般使用 “形容词” 词性的单词.
- 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性
- 接口内部均为抽象方法
2.3 接口的使用
接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。
public class 类名称 implements 接口名称{
// ...
}
注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。
2.4 接口的使用实例
请实现笔记本电脑使用USB鼠标、USB键盘的例子
- USB接口:包含打开设备、关闭设备功能
- 笔记本类:包含开机功能、关机功能、使用USB设备功能
- 鼠标类:实现USB接口,并具备点击功能
- 键盘类:实现USB接口,并具备输入功能
IUSB接口
public interface IUSB {
void openDevice();
void closeDevice();
}
鼠标类,实现IUSB接口
public class Mouse implements IUSB{
@Override
public void openDevice() {
System.out.println("打开鼠标!");
}
@Override
public void closeDevice() {
System.out.println("关闭鼠标");
}
public void click(){
System.out.println("鼠标点击");
}
}
键盘类,来实现IUSB接口
public class KeyBoard implements IUSB{
@Override
public void openDevice() {
System.out.println("打开键盘");
}
@Override
public void closeDevice() {
System.out.println("关闭键盘");
}
public void input(){
System.out.println("键盘输入");
}
}
电脑类:使用USB设备
public class Computer{
public void powerOn() {
System.out.println("打开电脑");
}
public void powerOff() {
System.out.println("关闭电脑");
}
public void useUSBDevice(IUSB iusb){
iusb.openDevice();
if(iusb instanceof Mouse){
Mouse mouse = (Mouse)iusb;
mouse.click();
} else if (iusb instanceof KeyBoard) {
KeyBoard keyBoard = (KeyBoard)iusb;
keyBoard.input();
}
iusb.closeDevice();
}
}
测试类
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
computer.useUSBDevice(new Mouse());
computer.useUSBDevice(new KeyBoard());
computer.powerOff();
}
}
2.5 接口的特性
- 接口类型是一种引用类型,但是不能直接new接口的对象
因为是抽象的,无法实例化
- 接口中每一个方法都是
public的抽象方法
, 即接口中的方法会被隐式的指定
为public abstract
(只能是public abstrac
t,其他修饰符都会报错) - 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
- 重写接口中方法时,不能使用默认的访问权限
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
- 接口中不能有静态代码块和构造方法
- 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
- 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
- jdk8中:接口中还可以包含default方法。
2.6 实现多个接口
Animal 类
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat(){
System.out.println(this.name+"在吃");
}
}
跑步 接口
public interface IRunning {
void run();
}
游泳 接口
public interface ISwimming {
void swim();
}
猫 类(继承动物类,并调用跑步的接口)
public class Cat extends Animal implements IRunning{
protected String color;
public Cat(String name,String color) {
super(name);
this.color = color;
}
@Override
public void run() {
System.out.println(this.name + this.color+"正在跑");
}
}
鸭类(继承动物类,并调用跑步,飞,游泳的接口)
public class Duck extends Animal implements ISwimming,IFlying,IRunning{
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name+"在飞");
}
@Override
public void run() {
System.out.println(this.name+"在跑步");
}
@Override
public void swim() {
System.out.println(this.name+"在游泳");
}
}
鱼 类(继承动物类,并调用游泳的接口)
public class Fish extends Animal implements ISwimming{
public Fish(String name) {
super(name);
}
@Override
public void swim() {
System.out.println(this.name+"正在游");
}
}
2.7 接口间的继承
两栖接口继承了游泳和跑步接口
public interface IAmphian extends ISwimming,IRunning{
void test();
}
青蛙刚好是两栖
public class Frog extends Animal implements IAmphian{
public Frog(String name) {
super(name);
}
@Override
public void eat() {
}
@Override
public void test() {
}
@Override
public void run() {
}
@Override
public void swim() {
}
}
Test
public class Test {
public static void func(Animal animal){
animal.eat();
}
public static void testFly(IFlying iFlying){
iFlying.fly();
}
public static void testRun(IRunning iRunning){
iRunning.run();
}
public static void testSwim(ISwimming iSwimming){
iSwimming.swim();
}
public static void main(String[] args) {
testFly(new Duck("唐老鸭"));
testRun(new Cat("加菲猫","黄"));
testSwim(new Fish("小金"));
func(new Duck("唐老鸭"));
}
}
2.7 接口使用实例
给对象数组排序
package com.java.d6;
import java.util.Arrays;
class Student{
protected String name;
protected int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' +
"score=" + score +
'}';
}
}
public class mmm {
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("张三",66),
new Student("李四",77),
new Student("王五",88)
};
Arrays.sort(students);
System.out.println(Arrays.toString(students));
}
}
那么你会发现
// 运行出错, 抛出异常.
Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to java.lang.Comparable
和普通的整数不一样, 两个整数是可以直接比较的, 大小关系明确. 而两个学生对象的大小关系 怎么确定? 需要我们额外指定. 让我们的Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法
按照年龄排序
import java.util.Arrays;
class Student implements Comparable<Student>{
protected String name;
protected int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' +
"score=" + score +
'}';
}
@Override
public int compareTo(Student o) {
return this.score - o.score;
}
}
public class mmm {
// 删除自定义的 sort 方法
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("张三",66),
new Student("李四",77),
new Student("王五",88)
};
// 改为使用 Arrays.sort
Arrays.sort(students); // 核心优化点
System.out.println(Arrays.toString(students));
}
}
通过自定义排序
// 导入Arrays工具类用于数组输出
import java.util.Arrays;
// 定义Student类并实现Comparable接口,用于对象比较
class Student implements Comparable<Student>{
protected String name; // 学生姓名
protected int score; // 学生分数
// 构造函数:初始化学生信息
public Student(String name, int score) {
this.name = name;
this.score = score;
}
// 重写toString:定义对象打印格式
@Override
public String toString() {
return "Student{" + "name='" + name + '\'' +
"score=" + score +
'}';
}
// 实现compareTo方法:按分数升序排序
@Override
public int compareTo(Student o) {
// 当前对象分数 - 参数对象分数(结果>0表示当前对象更大)
return this.score - o.score;
}
}
public class Main {
// 自定义排序方法(冒泡排序实现)
public static void sort(Comparable[] comparable){
// 外层循环控制排序轮次(n-1轮)
for (int i = 0; i < comparable.length - 1; i++) {
// 内层循环进行元素比较和交换
for (int j = 0; j < comparable.length - i - 1; j++) {
// 比较相邻元素
if(comparable[j].compareTo(comparable[j+1]) > 0) {
// 交换元素位置
Comparable tmp = comparable[j];
comparable[j] = comparable[j+1];
comparable[j+1] = tmp;
}
}
}
}
public static void main(String[] args) {
// 创建包含3个学生对象的数组(注意初始分数是66,77,88)
Student[] students = new Student[]{
new Student("张三",66),
new Student("李四",77),
new Student("王五",88)
};
// 调用自定义排序方法(实际上数组已经有序)
sort(students);
// 输出排序结果(使用Arrays.toString格式化输出)
System.out.println(Arrays.toString(students));
// 输出内容为按分数升序排列的三个学生对象
}
}
在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象. 然后比较当前对象和参数对象的大小关系(按分数来算).
- 如果当前对象应排在参数对象之前, 返回小于 0 的数字;
- 如果当前对象应排在参数对象之后, 返回大于 0 的数字;
- 如果当前对象和参数对象不分先后, 返回 0;
名字首字母排序
import java.util.Arrays;
class Student implements Comparable<Student>{
protected String name;
protected 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.name.compareTo(o.name);
}
}
public class Test {
public static void mySort(Comparable[] comparables){
for (int i = 0; i < comparables.length-1; i++) {
for (int j = 0; j < comparables.length - i -1; j++) {
if (comparables[j].compareTo(comparables[j+1]) > 0){
Comparable tmp = comparables[j];
comparables[j] = comparables[j+1];
comparables[j+1] = tmp;
}
}
}
}
public static void main(String[] args) {
Student[] students = new Student[]{
new Student("zs",16),
new Student("ls",18),
new Student("ww",20)
};
mySort(students);
System.out.println(Arrays.toString(students));
}
}
2.8 Clonable 接口和深拷贝
2.8.1 Clonable 接口
Java 中内置了一些很有用的接口, Clonable 就是其中之一.
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常.
class Animal implements Cloneable{
private String name;
public Animal(String name) {
this.name = name;
}
@Override
public Animal clone(){
Animal o = null;
try {
o = (Animal)super.clone();
}catch (CloneNotSupportedException e){
e.printStackTrace();
}
return o;
}
}
public class Test {
public static void main(String[] args) {
Animal a1 = new Animal("zhang");
Animal a2 = a1.clone();
System.out.println(a1 == a2);
}
}
/**
* false
*/
2.8.2 浅拷贝
定义
浅拷贝会创建一个新对象,并复制原对象的所有字段:
- 基本类型字段:直接复制值。
- 引用类型字段:仅复制引用地址(新旧对象共享同一份数据)。
实现方式
通过实现Cloneable
接口并重写clone()
方法:
class Person implements Cloneable {
String name;
Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 默认浅拷贝
}
}
特点:
- 修改新对象的引用类型字段(如
address
)会直接影响原对象。 - 默认
Object.clone()
是浅拷贝。
示例问题
package demo3;
class Money {
public double money = 12.5;
}
class Person implements Cloneable{
public String name;
public Money m;
public Person(String name) {
this.name = name;
m = new Money();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person("zhangsan");
Person person2 = (Person) person1.clone();
System.out.println("通过person2修改前的结果");
System.out.println(person1.m.money);
System.out.println(person2.m.money);
person2.m.money = 13.6;
System.out.println("通过person2修改后的结果");
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}
}
2.8.3 深拷贝
定义
深拷贝会创建新对象,并递归复制所有引用对象,使新旧对象完全独立
:
- 基本类型字段:复制值。
- 引用类型字段:创建新对象并复制内容。
实现方式
package demo3;
class Money implements Cloneable{
public double money = 12.5;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public String name;
public Money m;
public Person(String name) {
this.name = name;
m = new Money();
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person tmp = (Person) super.clone();
tmp.m = (Money) this.m.clone();
return tmp;
}
}
public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person("zhangsan");
Person person2 = (Person) person1.clone();
System.out.println("通过person2修改前的结果");
System.out.println(person1.m.money);
System.out.println(person2.m.money);
person2.m.money = 13.6;
System.out.println("通过person2修改后的结果");
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}
}
特点:
- 修改新对象的引用类型字段不会影响原对象
- 性能低于浅拷贝,但保证数据独立性
** 关键对比**
特性 | 浅拷贝 | 深拷贝 |
---|---|---|
引用类型复制逻辑 | 复制引用地址(共享数据) | 递归复制对象(独立数据) |
实现复杂度 | 简单(默认clone() ) | 复杂(需手动处理嵌套对象或序列化) |
性能 | 高 | 低(尤其是序列化方式) |
适用场景 | 无嵌套引用或允许共享数据的场景 | 需要完全独立副本的场景(如并发修改) |
2.9 抽象类和接口的区别
一、语法层面区别
特性 | 抽象类 | 接口 |
---|---|---|
方法实现 | 可包含抽象方法和具体方法(非抽象方法) | 默认只能有抽象方法;可含default 方法(默认实现)和static 方法 |
成员变量 | 普通变量(非final),子类可修改 | 默认public static final 常量,必须初始化且不可变 |
构造方法 | 有构造方法(供子类初始化使用) | 无构造方法 |
继承与实现 | 单继承(类只能继承一个抽象类) | 多实现(类可实现多个接口) |
访问修饰符 | 方法可自定义访问权限(如protected ) | 方法默认public abstract ,不可修改 |
3. Object类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。即所有类的对象都可以使用Object的引用进行接收。
范例:使用Object接收所有类的对象
class Person {
@Override
public String toString() {
return "这是一个 Person 对象";
}
}
class Student {
@Override
public String toString() {
return "这是一个 Student 对象";
}
}
public class Test {
public static void function(Object obj) { // 定义一个静态方法,参数是 Object 类型
System.out.println(obj); // 打印传入的对象
}
public static void main(String[] args) { // 主程序
function(new Person()); // 调用 function 方法,传入一个 Person 对象
function(new Student()); // 调用 function 方法,传入一个 Student 对象
}
}
/**
* 这是一个 Person 对象
* 这是一个 Student 对象
*/
所以在开发之中,Object类是参数的最高统一类型。但是Object类也存在有定义好的一些方法。如下:
3.1 获取对象信息(toString())
其实上面就是
3.2 对象比较equals方法
在Java中,==进行比较时:
如果 == 左右两侧是
基本类型变量
,比较的是变量中值是否相同
如果 == 左右两侧是
引用类型变量
,比较的是引用变量地址是否相同
如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的:
package demo1;
class Person{
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Person p1 = new Person("加菲猫", 10);
Person p2 = new Person("加菲猫", 10);
int m = 10;
int n = 10;
System.out.println(m == n);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
}
}
/**
* true
* false
* false
*/
-
System.out.println(m == n);
→ true
因为m
和n
是基本类型int
变量,值均为10,直接比较值相等。 -
System.out.println(p1 == p2);
→ false
p1
和p2
是两个不同的对象实例(通过new
创建),==
比较对象的内存地址,因此不相等。 -
System.out.println(p1.equals(p2));
→ false
由于Person
类未重写equals()
方法,默认使用Object
类的equals()
(即与==
行为相同),因此结果与p1 == p2
一致。若需比较内容相等,需在Person
类中重写equals()
方法。
package demo1;
class Person{
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (obj == null){
return false;
} else if (this == obj) {
return true;
}
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 p1 = new Person("加菲猫", 10);
Person p2 = new Person("加菲猫", 10);
int m = 10;
int n = 10;
System.out.println(m == n);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
}
}
/**
* true
* false
* true
*/
结论:比较对象中内容是否相同的时候,一定要重写equals方法。
3.3 hashcode方法
我们认为两个名字相同,年龄相同的对象,将存储在同一个位置,如果不重写hashcode()方法,我们可以来看示例代码:
class Person1{
protected String name;
protected int age;
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person1 p1 = new Person1("加菲猫", 10);
Person1 p2 = new Person1("加菲猫", 10);
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
}
}
/**
* 460141958
* 1163157884
*/
那么我们就需要重写hashcode()方法
import java.util.Objects;
class Person1{
protected String name;
protected int age;
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
return Objects.hash(name,age);
}
}
public class Main {
public static void main(String[] args) {
Person1 p1 = new Person1("加菲猫", 10);
Person1 p2 = new Person1("加菲猫", 10);
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
}
}
/**
* 663514834
* 663514834
*/
该程序输出相同哈希码的原因是 Person1
类正确重写了hashCode()
方法,且两个对象的关键字段值(name
和age
)完全一致。以下是具体分析:
1. hashCode()
重写原理
在Person1
类中,hashCode()
通过Objects.hash(name, age)
生成哈希码。此方法(JDK7+支持)的底层实现结合了所有传入字段的哈希值:
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
计算步骤:
- 对每个字段调用其自身的
hashCode()
方法(如String
类型的name
会计算字符串的哈希值)。 - 将这些哈希值按固定算法组合成一个整数。
由于p1
和p2
的name
(“加菲猫”)和age
(10)完全相同,Objects.hash()
会为两者生成相同的哈希码。
哪么这边我犯了一个错误
import java.util.Objects;
class Person1{
protected String name;
protected int age;
public Person1(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person1 p1 = new Person1("加菲猫", 10);
Person1 p2 = new Person1("加菲猫", 10);
System.out.println(Objects.hash(p1));
System.out.println(Objects.hash(p2));
}
}
/**
* 460141989
* 1163157915
*/
在Java中,Objects.hash()
的作用是 基于传入的多个对象生成组合哈希值,但其行为取决于传入对象自身的 hashCode()
实现。而我错误的写法 Objects.hash(p1)
和 Objects.hash(p2)
并不等价于直接在 Person1
类中重写 hashCode()
,原因如下:
1. Objects.hash(p1)
的本质
-
等价于:
Objects.hashCode(p1)
(单参数时),即直接调用p1.hashCode()
。
如果Person1
类未重写hashCode()
,则会使用Object
类的默认实现(基于内存地址),此时p1
和p2
的哈希码 必然不同。 -
示例验证:
若移除Person1
类中的hashCode()
重写,运行以下代码:System.out.println(Objects.hash(p1)); // 输出 p1 的默认哈希码(如 1234567) System.out.println(Objects.hash(p2)); // 输出 p2 的默认哈希码(如 7654321)
结果会是两个不同的哈希码,因为默认的
Object.hashCode()
基于对象地址生成。
2. 为什么要在类中重写 hashCode()
?
-
哈希一致性要求:
当对象被用于HashMap
、HashSet
等依赖哈希码的集合时,必须确保 内容相同的对象返回相同的哈希码。此逻辑应由对象自身定义,而非外部代码。 -
代码封装性:
哈希码的计算应封装在类的内部,避免外部代码直接依赖对象字段的哈希组合逻辑。例如:// 错误写法:外部直接计算哈希码 int hash = Objects.hash(p1.name, p1.age); // 正确写法:类内部定义哈希规则 @Override public int hashCode() { return Objects.hash(name, age); }
风险:若字段名或逻辑变更,外部代码会直接崩溃。
3. Objects.hash()
的适用场景
Objects.hash()
的典型用途是 在类内部重写 hashCode()
时,便捷地组合多个字段的哈希值,例如:
@Override
public int hashCode() {
return Objects.hash(name, age); // 内部调用字段的 hashCode()
}
若直接在 main
方法中写 Objects.hash(p1)
,则:
- 行为等价于:
p1.hashCode()
,即依赖对象自身的哈希实现。 - 未解决问题:若
Person1
未重写hashCode()
,结果仍基于内存地址,无法实现逻辑相等的哈希一致性。
4. 正确与错误写法对比
场景 | 代码示例 | 结果与问题 |
---|---|---|
正确写法 | p1.hashCode() (类已重写) | 基于字段值生成哈希码,相同内容的对象哈希码一致。 |
错误写法 | Objects.hash(p1) (类未重写) | 基于默认哈希码(内存地址),相同内容的对象哈希码不同。 |
错误替代方案 | Objects.hash(p1.name, p1.age) | 直接暴露字段,破坏封装性,且无法保证与类的 equals() 逻辑同步。 |
5. 总结
- 必须在类中重写
hashCode()
:确保对象自身能正确生成基于内容的哈希码。 - 不要外部拼凑哈希码:
Objects.hash(p1)
仅在类已正确实现hashCode()
时有效,否则无法满足哈希一致性要求。 - 封装性优先:哈希逻辑应内聚在类中,避免外部代码依赖具体字段。