Java基础知识点汇总(三)

一、面向对象的特征有哪些方面

Java中面向对象的特征主要包括以下四个核心方面:

  1. 封装(Encapsulation)
    封装是指将对象的属性(数据)和方法(操作)捆绑在一起,隐藏对象的内部实现细节,只通过公共接口与外部交互。通过访问修饰符(如private、public等)控制属性和方法的访问权限,提高了代码的安全性和可维护性。例如,将类的成员变量声明为private,通过public的getter和setter方法访问和修改。

  2. 继承(Inheritance)
    继承允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码复用和扩展。子类可以拥有父类的非私有成员,并可以重写父类的方法以实现特定功能。Java中通过extends关键字实现单继承,一个子类只能有一个直接父类,但支持多层继承。

  3. 多态(Polymorphism)
    多态是指同一操作作用于不同对象时,产生不同的执行结果。它有两种实现方式:

    • 编译时多态:通过方法重载(同一类中方法名相同、参数列表不同)实现。
    • 运行时多态:通过方法重写(子类重写父类方法)和向上转型(父类引用指向子类对象)实现。
      多态提高了代码的灵活性和扩展性。
  4. 抽象(Abstraction)
    抽象是指忽略次要细节,只关注本质特征。在Java中,通过抽象类(abstract class)和接口(interface)实现:

    • 抽象类可以包含抽象方法(无实现)和具体方法,子类必须实现抽象方法。
    • 接口仅包含抽象方法(Java 8后可有默认方法),类通过implements关键字实现接口并必须实现其所有方法。
      抽象简化了复杂系统,便于组件化开发。

这些特征相互配合,使Java代码具有更好的复用性、可扩展性和可维护性,是面向对象编程思想的核心体现。

二、在Java中,如何实现多态?

在Java中,多态的实现主要依赖于方法重写(Override)向上转型(Upcasting) 这两个机制,具体可以分为以下步骤:

1. 定义继承关系
多态通常建立在类的继承体系上,需要一个父类和至少一个子类。子类通过extends关键字继承父类。

2. 子类重写父类方法
子类对父类的方法进行重写(方法名、参数列表、返回值类型必须与父类一致),实现子类特有的逻辑。

3. 向上转型(父类引用指向子类对象)
使用父类类型的引用变量指向子类的实例对象,此时通过该引用调用方法时,会动态绑定到子类的重写方法。

示例代码:

// 1. 定义父类
class Animal {// 父类方法public void makeSound() {System.out.println("动物发出声音");}
}// 2. 定义子类,重写父类方法
class Dog extends Animal {@Override // 注解表示重写public void makeSound() {System.out.println("狗叫:汪汪汪");}
}class Cat extends Animal {@Overridepublic void makeSound() {System.out.println("猫叫:喵喵喵");}
}// 3. 测试多态
public class PolymorphismDemo {public static void main(String[] args) {// 向上转型:父类引用指向子类对象Animal animal1 = new Dog(); Animal animal2 = new Cat();// 调用方法时,实际执行的是子类的重写方法animal1.makeSound(); // 输出:狗叫:汪汪汪animal2.makeSound(); // 输出:猫叫:喵喵喵}
}

多态的其他形式:

  • 接口多态:类通过implements实现接口,多个类可以实现同一个接口并实现其方法,通过接口引用指向不同实现类的对象。

    interface Shape {void draw();
    }class Circle implements Shape {@Overridepublic void draw() {System.out.println("画圆形");}
    }class Rectangle implements Shape {@Overridepublic void draw() {System.out.println("画矩形");}
    }
    
  • 方法重载(Overload):严格来说是“编译时多态”,指同一类中方法名相同但参数列表不同,调用时根据参数自动匹配对应方法。

核心总结:
多态的本质是 “一个接口,多种实现”,通过父类/接口引用指向子类/实现类对象,在运行时动态确定调用的具体方法,从而提高代码的灵活性和可扩展性。

三、abstract class和interface有什么区别?

在Java中,abstract class(抽象类)和interface(接口)都是实现抽象的重要机制,但它们在设计目的和使用方式上有显著区别,主要体现在以下几个方面:

1. 定义与结构

  • 抽象类
    使用abstract class声明,可包含抽象方法(无实现,用abstract修饰)和具体方法(有实现),也可以定义成员变量(包括非静态和非final的变量)。
    示例:

    abstract class Animal {String name; // 成员变量// 具体方法public void breathe() {System.out.println("呼吸空气");}// 抽象方法(无实现)public abstract void makeSound();
    }
    
  • 接口
    使用interface声明,Java 8之前只能包含抽象方法(默认public abstract)和常量(默认public static final);Java 8及以后可包含默认方法default修饰,有实现)和静态方法static修饰)。
    示例:

    interface Flyable {int MAX_HEIGHT = 1000; // 常量(默认public static final)// 抽象方法(默认public abstract)void fly();// 默认方法(Java 8+)default void land() {System.out.println("降落");}
    }
    

2. 继承/实现方式

  • 抽象类
    子类通过extends关键字继承抽象类,只能单继承(一个类只能有一个直接父类)。
    子类必须实现抽象类中所有的抽象方法,否则自身也需声明为抽象类。

  • 接口
    类通过implements关键字实现接口,支持多实现(一个类可同时实现多个接口)。
    实现类必须实现接口中所有的抽象方法(默认方法和静态方法除外)。

3. 设计目的

  • 抽象类
    用于表示 “is-a”关系(即继承体系中的“父子关系”),强调类之间的共性和继承性。
    适合封装多个子类的共享实现细节(如通用方法、成员变量),是对类的抽象。

  • 接口
    用于表示 “has-a”关系(即类具备某种能力或行为),强调功能的抽象和实现的多样性。
    适合定义规范或契约(如“可飞行”“可序列化”),是对行为的抽象。

4. 访问修饰符

  • 抽象类中的成员变量和方法可以使用privateprotectedpublic等修饰符。
  • 接口中的抽象方法和常量默认是public的,且不能使用privateprotected修饰(Java 9后允许接口有private方法)。

5. 实例化

  • 抽象类和接口都不能直接实例化,但抽象类的子类(非抽象)和接口的实现类可以被实例化。

选择建议:

  • 若需要定义类的基础结构和共享实现,且类之间是继承关系(is-a),使用抽象类。
  • 若需要定义行为规范,且类可能具备多种独立的行为(has-a),使用接口(支持多实现)。

例如:

  • 用抽象类Animal定义所有动物的共性(如呼吸、进食);
  • 用接口RunnableSwimmable定义“会跑”“会游泳”等独立行为,让不同动物类根据自身能力实现。

四、abstract的method是否可同时是static?是否可同时是native?是否可同时是synchronized?

在Java中,abstract方法与staticnativesynchronized这三个修饰符存在冲突,不能同时使用,具体原因如下:

1. abstractstatic 不能同时使用

  • 原因
    static 方法属于类级别的方法,可通过类名直接调用,其实现是固定的(属于类本身);
    abstract 方法没有具体实现,需要子类重写后才能使用。
    两者语义矛盾:

    • static 要求方法“可直接调用且实现固定”
    • abstract 要求方法“无实现且需子类重写”
  • 错误示例

    abstract class Test {// 编译错误:abstract 与 static 不能同时使用public abstract static void method(); 
    }
    

2. abstractnative 不能同时使用

  • 原因
    native 方法表示方法的实现由非Java代码(如C/C++)提供,其底层有具体实现(只是不在Java代码中);
    abstract 方法要求“完全没有实现”,需要子类重写。
    两者语义矛盾:

    • native 隐含“有实现(非Java代码)”
    • abstract 隐含“无实现”
  • 错误示例

    abstract class Test {// 编译错误:abstract 与 native 不能同时使用public abstract native void method(); 
    }
    

3. abstractsynchronized 不能同时使用

  • 原因
    synchronized 用于修饰方法或代码块,作用是保证多线程环境下的同步(锁定对象或类),其前提是方法有具体实现
    abstract 方法没有实现,同步机制无从谈起(没有可执行的代码需要同步)。
    因此两者不能同时使用。

  • 错误示例

    abstract class Test {// 编译错误:abstract 与 synchronized 不能同时使用public abstract synchronized void method(); 
    }
    

总结:
abstract 方法的核心是 “无实现,需子类重写”,而 staticnativesynchronized 均要求方法“有具体实现”或“与实现细节相关”,因此这三组组合在Java中都是不允许的,会导致编译错误。

五、JDK、JRE、JVM 三者有什么关系?

JDK、JRE、JVM 是 Java 生态系统中三个核心概念,它们的关系可以简单概括为:JDK 包含 JRE,JRE 包含 JVM,三者层层递进,共同支撑 Java 程序的开发与运行。具体关系如下:

1. JVM(Java Virtual Machine,Java 虚拟机)

  • 定义:是运行 Java 字节码的虚拟计算机,负责将字节码翻译成机器码并执行。
  • 作用:实现 Java 跨平台特性的核心(“一次编写,到处运行”),不同操作系统需要安装对应的 JVM 实现(如 Windows、Linux 版 JVM)。
  • 特点:本身不包含任何 Java 类库,仅负责执行字节码。

2. JRE(Java Runtime Environment,Java 运行时环境)

  • 定义:是运行 Java 程序的最小环境。
  • 组成
    • JVM:字节码执行引擎
    • 核心类库:Java 基础类库(如 java.langjava.util 等,位于 rt.jar 中)
    • 其他支持文件:确保 JVM 正常运行的配置文件、资源文件等
  • 作用:供用户运行已编译好的 Java 程序(.class.jar 文件),普通用户只需安装 JRE 即可。

3. JDK(Java Development Kit,Java 开发工具包)

  • 定义:是 Java 开发人员使用的工具包。
  • 组成
    • JRE:包含完整的运行时环境(即 JVM + 类库)
    • 开发工具:编译器(javac)、调试器(jdb)、文档工具(javadoc)、打包工具(jar)等
    • 额外类库:开发时专用的类库(如 tools.jar
  • 作用:供开发人员编写、编译、调试 Java 程序,开发者必须安装 JDK。

三者关系总结:

  • 包含关系JDK ⊇ JRE ⊇ JVM
    (JDK 包含 JRE,JRE 包含 JVM 和运行类库)

  • 功能分工

    • JDK 编写和编译代码(javac Hello.java → 生成 Hello.class 字节码);
    • JRE 中的 JVM 运行字节码(java Hello → JVM 加载并执行 Hello.class)。
  • 使用场景

    • 开发人员:需要安装 JDK(包含开发工具和运行环境);
    • 普通用户:仅需安装 JRE(只需运行 Java 程序)。

简单来说,JVM 是运行核心,JRE 是运行环境,JDK 是开发工具集,三者协同工作完成 Java 程序的开发与执行。

六、JAVA 创建对象有哪些方式?

在Java中,创建对象的方式主要有以下几种,每种方式适用于不同的场景:

  1. 使用new关键字(最常用)
    通过调用类的构造方法创建对象,这是最基本、最直接的方式。

    User user = new User(); // 调用无参构造
    User user2 = new User("张三", 20); // 调用有参构造
    
  2. 使用Class类的newInstance()方法(反射机制)
    通过类的字节码对象创建实例,要求类必须有无参构造方法(已过时,推荐用getDeclaredConstructor())。

    Class<User> clazz = User.class;
    User user = clazz.newInstance(); // 已过时
    
  3. 使用Constructor类的newInstance()方法(反射机制)
    比Class的newInstance()更灵活,支持调用有参构造方法,且可以访问私有构造。

    Constructor<User> constructor = User.class.getDeclaredConstructor(String.class, int.class);
    User user = constructor.newInstance("张三", 20);
    
  4. 使用对象的clone()方法
    通过复制已有对象创建新对象,要求类实现Cloneable接口并重写clone()方法。

    public class User implements Cloneable {@Overrideprotected Object clone() throws CloneNotSupportedException {return super.clone();}
    }
    // 使用
    User user = new User();
    User user2 = (User) user.clone();
    
  5. 通过反序列化创建
    将序列化的对象字节流恢复为对象,要求类实现Serializable接口。

    // 序列化(保存对象到文件)
    ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.txt"));
    oos.writeObject(user);// 反序列化(从文件恢复对象)
    ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.txt"));
    User user2 = (User) ois.readObject();
    
  6. 通过工厂模式创建(设计模式)
    封装对象创建逻辑,通过工厂类统一创建对象(不属于Java语法层面的方式,是设计模式)。

    public class UserFactory {public static User createUser() {return new User();}
    }
    // 使用
    User user = UserFactory.createUser();
    

总结

  • 日常开发中最常用的是 new关键字工厂模式
  • 反射机制(Class/Constructor)常用于框架底层(如Spring IoC)。
  • clone()和反序列化适用于对象复制或跨网络/文件传输场景。

七、值传递和引用传递的区别?

在Java中,值传递和引用传递是关于方法参数传递机制的核心概念,它们的区别主要体现在参数传递的方式和对原数据的影响上:

  1. 值传递(Pass by Value)
  • 定义:方法接收的是实际参数的副本(值的拷贝)。

  • 特点

    • 方法内部对参数的修改,不会影响原数据(因为操作的是副本)。
    • 适用于基本数据类型(如intdoubleboolean等)和字符串(String)(特殊的引用类型,但具有不可变性)。
  • 示例

    public static void main(String[] args) {int num = 10;System.out.println("修改前:" + num); // 输出:10changeValue(num);System.out.println("修改后:" + num); // 输出:10(原数据未变)
    }public static void changeValue(int a) {a = 20; // 仅修改副本,不影响原变量
    }
    
  1. 引用传递(Pass by Reference)
  • 定义:方法接收的是实际参数的引用地址(内存地址),而非值的副本。

  • 特点

    • 方法内部通过引用修改对象的成员变量时,会影响原对象(因为操作的是同一块内存)。
    • 适用于引用数据类型(如对象、数组、集合等)。
  • 示例

    class Person {String name;Person(String name) { this.name = name; }
    }public static void main(String[] args) {Person p = new Person("张三");System.out.println("修改前:" + p.name); // 输出:张三changeName(p);System.out.println("修改后:" + p.name); // 输出:李四(原对象被修改)
    }public static void changeName(Person person) {person.name = "李四"; // 通过引用修改原对象的成员变量
    }
    

关键区别总结:

对比维度值传递引用传递
传递内容实际参数的值副本实际参数的内存地址
对原数据的影响不影响原数据会影响原对象的成员变量
适用类型基本数据类型、String引用数据类型(对象、数组等)

注意点:

  • Java中只有值传递:对于引用类型,传递的是“引用地址的值”(即地址的副本),而非引用本身。这也是为什么在方法中修改引用变量的指向(如person = new Person("王五"))不会影响原对象的原因。
  • String和包装类(如Integer)虽然是引用类型,但因不可变性(值一旦创建无法修改),表现类似值传递。

八、== 和 equals 有什么区别?

在Java中,==equals() 都用于比较数据,但它们的比较逻辑和适用场景有本质区别:

  1. == 运算符
  • 比较内容

    • 对于基本数据类型(如 intdoubleboolean 等):比较的是实际值是否相等。
    • 对于引用数据类型(如对象、数组等):比较的是内存地址是否相同(即是否指向同一个对象)。
  • 示例

    // 基本数据类型比较
    int a = 10;
    int b = 10;
    System.out.println(a == b); // true(值相等)// 引用数据类型比较
    String s1 = new String("hello");
    String s2 = new String("hello");
    System.out.println(s1 == s2); // false(内存地址不同,是两个不同对象)
    
  1. equals() 方法
  • 本质:是 Object 类定义的实例方法,默认实现与 == 相同(比较内存地址)。

    // Object类中equals()的默认实现
    public boolean equals(Object obj) {return (this == obj); // 本质还是比较内存地址
    }
    
  • 特殊情况
    很多类(如 StringIntegerList 等)会重写 equals() 方法,使其比较对象的内容是否相等,而非内存地址。

  • 示例

    String s1 = new String("hello");
    String s2 = new String("hello");// String类重写了equals(),比较内容
    System.out.println(s1.equals(s2)); // true(内容都是"hello")// 自定义类如果不重写equals(),则默认比较地址
    class Person {String name;Person(String name) { this.name = name; }
    }
    Person p1 = new Person("张三");
    Person p2 = new Person("张三");
    System.out.println(p1.equals(p2)); // false(未重写equals(),比较地址)
    

核心区别总结:

对比维度== 运算符equals() 方法
本质运算符实例方法
基本类型比较比较值是否相等不适用(基本类型没有方法)
引用类型比较比较内存地址是否相同默认比较地址,重写后可比较内容
可修改性无法重写,逻辑固定可重写,自定义比较逻辑

常见误区:

  • 认为 String== 比较内容:实际上 String 是引用类型,== 仍比较地址,只是字符串常量池的存在可能造成误解(如 String s1 = "hello"; String s2 = "hello";s1 == s2true,因为指向常量池同一对象)。
  • 自定义类未重写 equals() 却期望比较内容:必须手动重写 equals()(通常需同时重写 hashCode(),遵循“相等的对象必须有相等的哈希码”原则)。

九、hashCode() 的作用?

在Java中,hashCode()Object 类定义的一个 native 方法,主要作用是为对象生成一个哈希值(整数),这个值在哈希表(如 HashMapHashSet 等集合)中用于快速定位对象,是实现高效数据查找的核心机制。

  1. 核心作用:支持哈希表的高效操作
    哈希表(如 HashMap)的底层存储依赖数组 + 链表/红黑树结构,hashCode() 的值会被用来计算对象在数组中的存储索引
  • 当添加对象时,通过 hashCode() 快速定位可能的存储位置,避免遍历整个集合,大幅提升插入和查找效率。
  • 当查找对象时,先通过 hashCode() 定位到候选位置,再用 equals() 精确比较,减少比对次数。
  1. equals() 的关系:必须满足的约定
    Java 规范要求,hashCode()equals() 必须遵守以下规则:
  • 如果两个对象通过 equals() 比较相等,则它们的 hashCode() 必须返回相同的值
  • 如果两个对象的 hashCode() 返回不同的值,则它们通过 equals() 比较一定不相等
  • 反之不成立hashCode() 相同的两个对象,equals() 可能不相等(哈希冲突)。

为什么需要这个约定?
如果两个对象 equals() 相等但 hashCode() 不同,在哈希表中会被存储在不同位置,导致查找时无法找到已存在的对象(如 HashSet 会认为是两个不同对象,出现重复元素)。

  1. 应用场景
  • 哈希集合/映射HashMapHashSetHashTable 等依赖 hashCode() 实现高效存储和查找。
  • 对象去重:通过哈希值快速判断对象是否可能重复,再用 equals() 确认。
  • 缓存机制:利用哈希值作为键存储缓存对象,加速查询。
  1. 自定义类的注意事项
  • 重写 equals() 时必须同时重写 hashCode(),否则会违反上述约定,导致哈希表操作出错。
  • 示例(正确实现):
    class Person {String name;int age;@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return age == person.age && Objects.equals(name, person.name);}@Overridepublic int hashCode() {// 基于equals()中比较的字段生成哈希值return Objects.hash(name, age);}
    }
    

总结:
hashCode() 的核心价值是为哈希表提供快速定位对象的能力,其设计与 equals() 强关联,二者共同保证了哈希集合/映射的正确性和高效性。在自定义类中,重写 equals() 时必须同步重写 hashCode(),这是Java开发的基本规范。

十、为什么要有 hashCode?

hashCode() 存在的核心意义是为哈希表(如 HashMapHashSet 等)提供高效的元素查找和存储机制,解决了“如何在大量数据中快速定位目标”的问题。

具体来说,hashCode() 的必要性体现在以下几点:

  1. 大幅提升哈希表的操作效率
    哈希表的底层是数组结构,当需要插入或查找元素时:
  • 直接遍历数组比对(不用 hashCode()):时间复杂度是 O(n),数据量越大,效率越低。
  • hashCode() 定位:
    • 第一步:通过 hashCode() 计算出一个整数(哈希值),再通过哈希算法转换为数组的索引,直接定位到元素可能存在的位置(时间复杂度接近 O(1))。
    • 第二步:如果该位置有多个元素(哈希冲突),再用 equals() 精确比对。

例如,在 HashMap 中查找一个键时,hashCode() 能快速缩小查找范围,避免全表扫描,这是哈希表高效的核心原因。

  1. 解决“equals() 效率不足”的问题
    equals() 可以精确判断两个对象是否相等,但存在局限性:
  • 对于复杂对象,equals() 可能需要逐个比对成员变量,耗时较长。
  • 如果没有 hashCode(),在集合中查找元素时,必须用 equals() 与集合中所有元素比对,效率极低。

hashCode() 相当于给对象生成一个“摘要信息”,可以先通过哈希值快速排除不可能相等的对象,只对哈希值相同的少数对象进行 equals() 比对,大幅减少不必要的计算。

  1. 保证哈希表的正确性
    哈希表的核心功能(如去重、键值映射)依赖 hashCode()equals() 的配合:
  • 例如 HashSet 要保证元素不重复,当添加新元素时,会先通过 hashCode() 判断是否有“可能重复”的元素(哈希值相同),再用 equals() 确认。
  • 如果没有 hashCode()HashSet 无法高效判断元素是否已存在,可能导致重复元素插入,违背其设计初衷。

一句话总结:
hashCode() 是为了在哈希表中快速缩小查找范围,将原本需要全量比对的操作优化为“先通过哈希值定位,再精确比对”,从而在数据量较大时,显著提升集合的插入、查找、删除效率。没有 hashCode(),哈希表就失去了高效性的基础。

正确重写 hashCode() 的关键原则是:equals() 保持一致,即 equals() 中用于比较的字段,都应参与 hashCode() 的计算。以下是几个不同场景的示例:

十一、hashCode()的示例

  1. 简单类(基于单个字段)

如果 equals() 仅通过一个字段(如 id)判断相等,hashCode() 也应仅基于该字段生成。

import java.util.Objects;class User {private String id;private String name; // 不参与equals比较// 构造方法、getter/setter省略...@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;User user = (User) o;// 仅通过id判断相等return Objects.equals(id, user.id);}@Overridepublic int hashCode() {// 仅基于id生成哈希值(与equals保持一致)return Objects.hash(id);}
}
  1. 复杂类(基于多个字段)

如果 equals() 通过多个字段判断相等,hashCode() 必须包含所有这些字段。

import java.util.Objects;class Student {private String id;private String name;private int age;// 构造方法、getter/setter省略...@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Student student = (Student) o;// 通过id、name、age三个字段判断相等return age == student.age && Objects.equals(id, student.id) && Objects.equals(name, student.name);}@Overridepublic int hashCode() {// 基于所有参与equals比较的字段生成哈希值return Objects.hash(id, name, age);}
}
  1. 包含数组/集合的类

如果类中包含数组或集合,hashCode() 需通过 Arrays.hashCode() 或集合的 hashCode() 处理。

import java.util.Arrays;
import java.util.Objects;class Team {private String teamName;private String[] members; // 数组字段// 构造方法、getter/setter省略...@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Team team = (Team) o;// 比较teamName和members数组return Objects.equals(teamName, team.teamName) && Arrays.equals(members, team.members);}@Overridepublic int hashCode() {// 数组需用Arrays.hashCode()处理int result = Objects.hash(teamName);result = 31 * result + Arrays.hashCode(members);return result;}
}

关键技巧:

  1. 使用 Objects.hash() 简化实现
    该方法会自动处理 null 值(null 的哈希值为 0),并对传入的所有字段计算哈希值。

  2. 为什么用 31 作为乘数?
    在手动计算哈希值时(如示例3),常用 31 * 结果 + 字段哈希值 的公式。31 是一个质数,且 31 * i = (i << 5) - i,可通过移位运算高效计算,减少哈希冲突。

  3. IDE 自动生成
    现代 IDE(如 IDEA、Eclipse)可自动生成 equals()hashCode(),避免手动编写出错(通常在类中右键 → Generate → equals() and hashCode())。

核心原则再强调:

  • equals() 中比较的所有字段,必须全部参与 hashCode() 的计算。
  • equals() 中不比较的字段,不应参与 hashCode() 的计算(否则会增加哈希冲突概率)。

遵循此原则,才能保证哈希表(如 HashMapHashSet)的正确运行。

十二、hashCode()、equals() 的关系?

在Java中,hashCode()equals() 是两个密切相关的方法,它们共同保证了哈希表(如 HashMapHashSet 等集合)的正确性和高效性,二者的关系由Java规范严格定义:

1、核心约定(必须遵守)

  1. 如果两个对象通过 equals() 比较返回 true,则它们的 hashCode() 必须返回相同的整数

    • 原因:哈希表通过 hashCode() 定位对象存储位置,如果 equals() 相等的对象哈希值不同,会被存储在不同位置,导致哈希表无法识别它们是同一个对象(如 HashSet 会允许重复元素)。
  2. 如果两个对象的 hashCode() 返回不同的值,则它们的 equals() 比较必须返回 false

    • 原因:哈希值不同意味着两个对象在哈希表中存储位置不同,逻辑上必然是不同的对象。
  3. 反之不强制

    • 两个对象的 hashCode() 相同(哈希冲突),equals() 可能返回 false(这是允许的,哈希表会通过链表/红黑树处理冲突)。

2、通俗理解
可以把 hashCode() 看作对象的“粗筛”,equals() 看作“精筛”:

  • 先通过 hashCode() 快速排除绝对不同的对象(哈希值不同 → 一定不等)。
  • 再对哈希值相同的对象用 equals() 精确判断是否真的相等。

这种“粗筛+精筛”的机制,让哈希表的查找效率从 O(n) 提升到接近 O(1)

3、违反约定的后果
如果自定义类重写 equals() 时未正确重写 hashCode(),会破坏上述约定,导致哈希表出现逻辑错误:

  • 示例:两个 equals() 相等的对象,hashCode() 不同。
    • 放入 HashSet 会被视为两个不同对象,导致重复存储。
    • HashMap 中查找时,可能找不到已存在的键值对。

正确实践:

  • 重写 equals() 时必须同时重写 hashCode()
  • 两者基于相同的字段计算(equals() 比较哪些字段,hashCode() 就基于哪些字段生成哈希值)。
class Person {private String id;private String name;// equals() 基于 id 和 name 比较@Overridepublic boolean equals(Object o) {if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Person person = (Person) o;return Objects.equals(id, person.id) && Objects.equals(name, person.name);}// hashCode() 也必须基于 id 和 name 生成@Overridepublic int hashCode() {return Objects.hash(id, name); // 与 equals() 保持一致}
}

总结:
hashCode()equals() 是“配套方法”:

  • hashCode() 用于快速定位对象(哈希表高效性的基础)。
  • equals() 用于精确判断对象是否相等。
  • 二者的约定是哈希表正确工作的前提,违反约定会导致集合操作异常。

记住:相等的对象必须有相等的哈希码;哈希码相等的对象不一定相等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96323.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96323.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

GEO优化专家孟庆涛:让AI“聪明”选择,为企业“精准”生长

在生成式AI席卷全球的今天&#xff0c;企业最常遇到的困惑或许是&#xff1a;“为什么我的AI生成内容总像‘模板套娃’&#xff1f;”“用户明明想要A&#xff0c;AI却拼命输出B&#xff1f;”当生成式AI从“能用”迈向“好用”的关键阶段&#xff0c;如何让AI真正理解用户需求…

【交易系统系列04】交易所版《速度与激情》:如何为狂飙的BTC交易引擎上演“空中加油”?

交易所版《速度与激情》&#xff1a;如何为狂飙的BTC交易引擎上演“空中加油”&#xff1f; 想象一下这个场景&#xff1a;你正端着一杯热气腾腾的咖啡&#xff0c;看着窗外我家那只贪睡的橘猫趴在阳光下打着呼噜。突然&#xff0c;手机上的警报开始尖叫&#xff0c;交易系统监…

windows下jdk环境切换为jdk17后,临时需要jdk1.8的处理

近段时间&#xff0c;终于决定把开发环境全面转向jdk17&#xff0c;这不就遇到了问题。 windows主环境已经设置为jdk17了。 修改的JAVA_HOME D:\java\jdk-17CLASSPATH设置 .;D:\java\jdk-17\lib\dt.jar;D:\java\jdk-17\lib\tools.jar;PATH中增加 D:\java\jdk-17\bin但是有些程序…

Android URC 介绍及源码案例参考

1. URC 含义 URC 是 Unsolicited Result Code(非请求结果码)的缩写。 它是 modem(基带)在不需要 AP 主动请求的情况下向上层主动上报的消息。 典型例子:短信到达提示、网络状态变更、来电通知、信号质量变化等。 URC 一般以 AT 命令扩展的形式从 modem 发到 AP,例如串口…

VB.NET发送邮件给OUTLOOK.COM的用户,用OUTLOOK.COM邮箱账号登录给别人发邮件

在VB.NET中通过代码发送邮件时&#xff0c;确实会遇到邮箱服务的身份认证&#xff08;Authentication&#xff09;要求。特别是微软Outlook/Hotmail等服务&#xff0c;已经逐步禁用传统的“基本身份验证”&#xff08;Basic Authentication&#xff09;&#xff0c;转而强制要求…

【网络运维】Shell:变量进阶知识

Shell 变量进阶知识 Shell 中的特殊变量 位置参数变量 Shell 脚本中常用的位置参数变量如下&#xff1a; $0&#xff1a;获取当前执行的 Shell 脚本文件名&#xff08;包含路径时包括路径&#xff09;$n&#xff1a;获取第 n 个参数值&#xff08;n>9 时需使用 ${n}&#xf…

部署Qwen2.5-VL-7B-Instruct-GPTQ-Int3

模型下载 from modelscope import snapshot_download model_dir snapshot_download(ChineseAlpacaGroup/Qwen2.5-VL-7B-Instruct-GPTQ-Int3)相关包导入 import os import numpy as np import pandas as pd from tqdm import tqdm from datetime import datetime,timedelta fro…

sourcetree 拉取代码

提示&#xff1a;文章旨在于教授大家 sourcetree 拉取代码的方式&#xff0c;关于代码的提交合并等操作后续会补充。 文章目录前言一、sourcetree 安装二、http 与 ssh 拉取代码1.http 方式&#xff08;1&#xff09;生成 token&#xff08;2&#xff09;拼接项目的 url&#x…

epoll模型网络编程知识要领

1、程序初始化创建监听socket调用bind函数绑定ip地址、port端口号调用listen函数监听调用epoll_create函数创建epollfd调用epoll_ctrl函数将listenfd绑定到epollfd上&#xff0c;监测listenfd的读事件在一个无限循环中&#xff0c;调用epoll_wait函数等待事件发生2、处理客户端…

15-day12LLM结构变化、位置编码和投机采样

多头机制transformer结构归一化层选择 归一化层位置归一化层类型激活函数Llama2结构MoE架构 混合专家模型DeepSeek MLA为何需要位置编码目前的主流位置编码正余弦位置编码可学习位置编码ROPE旋转位置编码推导参考&#xff1a; https://spaces.ac.cn/archives/8265 https://zhua…

记录 docker容器打包成镜像 在其他服务器快速启动镜像和容器

我有个nginx服务器 需要在其他服务器直接部署使用 里面都是完整的 使用 docker ps 查看容器id 进行打包成镜像docker commit [容器ID或名称] 新镜像名:版本 docker commit 28f60e2206b2 my-nginx-custom:v1镜像保存成文件 docker save -o my-nginx-custom.tar my-nginx-custom:…

使用LLaMA-Factory对大模型进行微调-详解

书接上回 启动llama Factory可视化页面 llamafactory-cli webui 如果想后台运行 使用 nohup llamafactory-cli webui &浏览器访问 http://127.0.0.1:7860/配置项主要参数: 参考: https://docs.coreshub.cn/console/compute_platform/help/llama_factory/ 模型路径 : 解…

【AI】录制自动生成UI自动化脚本

命令行输入&#xff1a;npx playwright codegen https://myerp.dmyc.XXX.com:9443/打开的浏览器上操作&#xff0c;会自动录制&#xff0c;并生成自动化脚本

深度剖析字节跳动VeOmni框架

背景与设计动机 随着推荐系统和AI模型走向多模态、多任务的趋势&#xff0c;字节跳动面临着训练、迭代效率和系统复杂度的双重挑战。一方面&#xff0c;各类业务&#xff08;如新闻推荐、短视频、图文广告、电商带货等&#xff09;都需要处理文本、图像、视频、音频等多种输入模…

OCR库pytesseract安装保姆级教程

本文将介绍使用工具安装OCR库pytesseract的详细流程。 Anaconda安装教程参考Anaconda安装保姆级教程。 目录 一、工具安装 二、创建虚拟环境 三、安装pytesseract 总结 一、工具安装 点击链接前往官网codetou.com&#xff0c;下载安装最新版即可&#xff0c;本篇博客以抠头…

开源im即时通讯软件开发社交系统全解析:安全可控、功能全面的社交解决方案

在即时通讯与社交需求日益增长的今天&#xff0c;一款安全、稳定、功能全面的聊天软件成为不少团队和开发者的刚需。但市面上多数聊天 APP 要么依赖第三方插件&#xff0c;面临数据安全隐患和高额服务费&#xff1b;要么功能单一&#xff0c;难以满足复杂社交场景。今天给大家推…

残差神经网络(ResNet)

残差神经网络&#xff08;Residual Neural Network&#xff0c;简称 ResNet&#xff09;是深度学习领域的里程碑式模型&#xff0c;由何凯明等人在 2015 年提出&#xff0c;成功解决了深层神经网络训练中的梯度消失 / 爆炸问题&#xff0c;使训练超深网络&#xff08;如 152 层…

学习嵌入式之驱动

一、基础搭建1.基础&#xff1a;c语言 软件编程语言 数据结构 软件编程思想2.驱动实现目标如果将Linux系统细致到开发板平台上&#xff1f; Liunx系统与硬件设备的适配3.自我能力的锻炼继续强化C语言锻炼大型代码阅读和分析能力学习大型项目的代码搭建和管理的能力…

在 Golang 中复用 HTTP 连接

问题提出最近在实现一个转发大模型调用请求的中转功能&#xff0c;涉及到要构造client发送请求的内容&#xff0c;一开始我每次都是新建一个client来发送请求&#xff0c;这样的代码实现存在一些问题——每次都要构造新的client&#xff0c;并且要重新建立连接。后面了解到在Go…

前端:el-upload文件上传与FormData 对象

<el-uploadclass"uploadDemo":limit"1"dragaccept".xls,.xlsx" <!-- 只保留Excel格式 -->:on-exceed"handleExceedFileLimit":on-change"handleChangeExcelFile":on-remove"handleRemoveExcelFile":bef…