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

抽象类和接口

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 抽象类特性

  1. 抽象类本身是抽象的,因此不可以进行实例化
Aniaml animal = new Animal();
// 编译出错
Error:(15, 20) java: Animal是抽象的; 无法实例化
  1. 抽象方法不能是 private 的

private表示仅当前类可见,而abstract要求必须被其他类继承,两者语义冲突。
抽象类的构造方法若为private,子类无法调用父类构造器(super()),导致继承失败 。

  1. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
  2. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
  3. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
  4. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

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();//推荐
}

提示:

  1. 创建接口时, 接口的命名一般以大写字母 I 开头.
  2. 接口的命名一般使用 “形容词” 词性的单词.
  3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性
  4. 接口内部均为抽象方法

2.3 接口的使用

接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。

public class 类名称 implements 接口名称{
// ...
}

注意:子类和父类之间是extends 继承关系,类与接口之间是 implements 实现关系。

2.4 接口的使用实例

请实现笔记本电脑使用USB鼠标、USB键盘的例子

  1. USB接口:包含打开设备、关闭设备功能
  2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
  3. 鼠标类:实现USB接口,并具备点击功能
  4. 键盘类:实现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 接口的特性

  1. 接口类型是一种引用类型,但是不能直接new接口的对象
因为是抽象的,无法实例化
  1. 接口中每一个方法都是public的抽象方法, 即接口中的方法会被隐式的指定public abstract(只能是public abstract,其他修饰符都会报错)
  2. 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现
  3. 重写接口中方法时,不能使用默认的访问权限
  4. 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量
  5. 接口中不能有静态代码块和构造方法
  6. 接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class
  7. 如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类
  8. 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中,==进行比较时:

  1. 如果 == 左右两侧是基本类型变量,比较的是变量中值是否相同

  2. 如果 == 左右两侧是引用类型变量,比较的是引用变量地址是否相同

  3. 如果要比较对象中内容,必须重写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
 */
  1. System.out.println(m == n);true
    因为mn是基本类型int变量,值均为10,直接比较值相等。

  2. System.out.println(p1 == p2);false
    p1p2是两个不同的对象实例(通过new创建),==比较对象的内存地址,因此不相等。

  3. 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()方法,且两个对象的关键字段值(nameage)完全一致。以下是具体分析:

1. hashCode()重写原理
Person1类中,hashCode()通过Objects.hash(name, age)生成哈希码。此方法(JDK7+支持)的底层实现结合了所有传入字段的哈希值:

public static int hash(Object... values) {
    return Arrays.hashCode(values);
}

计算步骤

  1. 对每个字段调用其自身的hashCode()方法(如String类型的name会计算字符串的哈希值)。
  2. 将这些哈希值按固定算法组合成一个整数。

由于p1p2name(“加菲猫”)和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 类的默认实现(基于内存地址),此时 p1p2 的哈希码 必然不同

  • 示例验证
    若移除 Person1 类中的 hashCode() 重写,运行以下代码:

    System.out.println(Objects.hash(p1)); // 输出 p1 的默认哈希码(如 1234567)
    System.out.println(Objects.hash(p2)); // 输出 p2 的默认哈希码(如 7654321)
    

    结果会是两个不同的哈希码,因为默认的 Object.hashCode() 基于对象地址生成。


2. 为什么要在类中重写 hashCode()

  • 哈希一致性要求
    当对象被用于 HashMapHashSet 等依赖哈希码的集合时,必须确保 内容相同的对象返回相同的哈希码。此逻辑应由对象自身定义,而非外部代码。

  • 代码封装性
    哈希码的计算应封装在类的内部,避免外部代码直接依赖对象字段的哈希组合逻辑。例如:

    // 错误写法:外部直接计算哈希码
    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() 时有效,否则无法满足哈希一致性要求。
  • 封装性优先:哈希逻辑应内聚在类中,避免外部代码依赖具体字段。

相关文章:

  • 量子计算驱动的金融衍生品定价革命:突破传统蒙特卡洛模拟的性能边界
  • C++ 中的互斥锁
  • 2通道12bit 10G USB高速示波器采集卡
  • fastapi项目——后端返回前端url
  • layui.table.exportFile 导出数据并清除单元格中的空格
  • 【学习笔记】【SpringCloud】MybatisPlus 基础使用
  • Linux NFS
  • 【用deepseek和chatgpt做算法竞赛】——还得DeepSeek来 -Minimum Cost Trees_5
  • 自学Java-AI结合GUI开发一个石头迷阵的游戏
  • 人工智能丨OCR 的业务场景,实现原理和测试指标
  • HarmonyOS NEXT 全栈开发实战手册(API 12+)
  • 最新本地部署 DeepSeekR1 蒸馏\满血量化版 + WebOpenUI 完整教程(Ubuntu\Linux系统\Ollama)
  • 编译原理基础(1)
  • 4-知识图谱的抽取与构建-4_2实体识别与分类
  • Tesseract OCR使用
  • linux 麒麟安装人大金仓数据库
  • 革新之力:数字科技——重塑未来的超越想象之旅
  • AI基本知识讲解
  • java项目之超市管理系统设计与实现(源码+文档)
  • HTML项目一键打包工具:HTML2EXE 最新版
  • 外交部:反对美方人士发表不负责任谬论
  • “水运江苏”“航运浙江”,江浙两省为何都在发力内河航运?
  • 人民日报访巴西总统卢拉:“巴中关系正处于历史最好时期”
  • 新华时评:中美经贸会谈为全球经济纾压增信
  • 美英贸易协议|不,这不是一份重大贸易协议
  • 马上评丨为护士减负,不妨破除论文“硬指标”