Java基础系列文章
Java基础(一):初识Java——发展历程、技术体系与JDK环境搭建
Java基础(五):流程控制全解析——分支(if/switch)和循环(for/while)的深度指南
Java基础(七): 面向过程与面向对象、类与对象、成员变量与局部变量、值传递与引用传递、方法重载与方法重写
Java基础(八):封装、继承、多态与关键字this、super详解
java.lang.Object
数组、集合、自定义类不包括基本类型
)都直接或间接继承自 Object任何对象
都具备这些能力类结构示意:
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj) { return this == obj; }
protected native Object clone() throws CloneNotSupportedException;
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException;
public final void wait() throws InterruptedException;
protected void finalize() throws Throwable { }
}
作用:返回对象的运行时类(Class
对象)
因为Java有多态
现象,所以一个引用数据类型的变量的编译时类型与运行时类型可能不一致
如果需要查看这个变量实际指向的对象的类型,需要用getClass()方法
public static void main(String[] args) {
Object obj = new Person();
System.out.println(obj.getClass());//运行时类型
}
// 输出结果
class com.atguigu.java.Person
关键特性
native
方法:通过JVM本地方法实现.class
语法区别:getClass()
动态获取,MyClass.class
静态获取对象内容
是否逻辑相等(默认实现为==
地址比较,也就是否指向同一个对象)
类型及内容
而不考虑
引用的是否是同一个对象x.equals(x)
返回true
x.equals(y)
⇔ y.equals(x)
x.equals(y) 且 y.equals(z)
⇒ x.equals(z)
x.equals(null)
返回false
正确重写示例:
@Override
public boolean equals(Object o) {
// 如果是同一个对象,直接返回true
if (this == o) return true;
// 如果传入的对象是null或者不是同一个类,直接返回false
if (o == null || getClass() != o.getClass()) return false;
// 如果传入的对象是同一个类,所有属性的做比较
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
哈希码(32位整数)
,用于在哈希表等数据结构中快速查找对象equals()
相等的对象必须有相同哈希码不要求
哈希码不同(但不同能提升哈希表性能)HashMap/HashSet
等作为键/元素
时,此契约极其关键重写规范:
@Override
public int hashCode() {
return Objects.hash(name, age); // 使用JDK工具类
}
Object
类的hashCode方法(默认
的hashCode算法)
内存地址转换为整数
作为哈希值String
类重写hashCode方法
每个字符的ASCII值
public int hashCode() {
int h = 0;
for (int i = 0; i < value.length; i++) {
h = 31 * h + value[i];
}
return h;
}
Integer
类重写hashCode方法
整数的数值
public int hashCode() {
return value;
}
Date
类重写hashCode方法
毫秒数
(自1970年1月1日以来的毫秒数)作为哈希值public int hashCode() {
// ht 是一个 64 位的长整型(long),它表示时间戳
long ht = this.getTime();
// 对低 32 位和高 32 位的哈希值进行异或,得到最终的哈希值
return (int) ht ^ (int) (ht >> 32);
}
31 * 当前结果(初始值1) + 元素哈希(null为0)
的方式计算并返回一个综合所有元素内容的哈希码// Objects类方法
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
// Arrays类方法
public static int hashCode(Object a[]) {
if (a == null){
return 0;
}
int result = 1;
for (Object element : a){
// 31这个数字是为了尽量的减少hash冲突
result = 31 * result + (element == null ? 0 : element.hashCode());
}
return result;
}
为什么使用 31?
31 是一个奇质数
,使用质数有助于减少哈希冲突,使得不同对象组合出相同哈希值的概率更低31 可以被 JVM 优化:31 * i 等价于 (i << 5) - i
,这种位运算在某些情况下 JVM 会自动优化,提高计算效率经验验证
:经过大量实践和实验表明,31 在哈希分布性和计算性能之间取得了较好的平衡,因此被广泛采用(如 Java 的 List、String、HashMap等哈希计算都用了 31)hashCode主要作用
哈希表
(如 HashMap、HashSet、Hashtable等)中快速定位对象
快速确定对象在哈希表中可能存储的“桶(bucket)”位置
,从而提升查找、插入、删除等操作的效率必须
有相同的 hashCode,不同对象尽量
有不同的 hashCode(减少哈希冲突
)两者的关联规则(非常重要!)
一致性
equals 为 true ⇒ hashCode 必须相同
hashCode 相同 ⇏ equals 为 true
总结一句话
hashCode() 用于快速定位对象
(提高哈希表效率),equals() 用于精确判断对象是否逻辑相等
;两者必须配合使用,且遵循“equals为true时hashCode必须相同”的规则。
public class User {
private String name;
private Integer age;
public User(String name,Integer age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
System.out.println("equals...");
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equal(name, user.name);
}
@Override
public int hashCode() {
System.out.println("hashCode...");
return Objects.hashCode(name, age);
}
}
public class Test {
public static void main(String[] args) {
Set<User> set = new HashSet<>();
User u1 = new User("张三",3);
User u2 = new User("张三",3);
set.add(u1);
set.add(u2);
System.out.println("set size:"+ set.size());
}
}
hashCode...
hashCode...
equals...
set size:1
浅拷贝
) protected
,表示只有该类本身、子类或同包中的类
可以调用此方法Cloneable
是一个标记接口
(marker interface),它不包含任何方法。它存在的意义仅仅是告诉 JVM,这个类的对象是允许被克隆的 正确使用clone()方法步骤:
public class Person implements Cloneable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter & Setter 略...
@Override
public Person clone() throws CloneNotSupportedException {
return (Person) super.clone(); // 调用 Object 的 clone() 方法
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + "}";
}
}
类名@十六进制哈希码
)
重写建议:
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
// 输出:Person{name='Alice', age=30}
synchronized
块内使用)方法 | 作用 | 是否释放锁 | 备注 |
---|---|---|---|
wait() | 让当前线程等待,直到其他线程调用 notify()或 notifyAll() | 是 | 无限期等待 |
wait(long timeout) | 最多等指定的毫秒时间,超时自动唤醒(避免永久阻塞) | 是 | 也会被notify()或notifyAll()提前唤醒 |
wait(long timeout, int nanos) | 同上,只是超时时间设置精确到纳秒 | 是 | 很少使用 |
notify() | 唤醒一个等待该对象的线程(具体哪个不确定,由 JVM 决定) 被唤醒线程从等待状态进入锁的竞争状态,需要重新获取对象锁才能运行 | 否 | 可能唤醒不合适的线程 |
notifyAll() | 唤醒所有等待线程 所有被唤醒的线程都会尝试重新去获取对象的锁,但只有一个线程能获取到锁并继续执行,其余线程继续阻塞 | 否 | 推荐使用,更安全 |
简单示例:
class SharedResource {
private boolean ready = false;
// 消费者 线程发现数据没准备好,就调用 wait()等待,直到被叫醒获取锁才能执行
public synchronized void consume() throws InterruptedException {
while (!ready) {
System.out.println("消费者:数据未准备好,等待中...");
wait(); // 等待生产者通知
}
System.out.println("消费者:数据已消费");
ready = false; // 重置状态
}
// 生产者 线程准备好数据后,调用 notify()或 notifyAll()叫醒消费者
public synchronized void produce() {
// 模拟生产
ready = true;
System.out.println("生产者:数据已准备好,通知消费者");
notifyAll(); // 唤醒所有等待的消费者线程
}
}
现代Java开发中,这些方法现在通常被
java.util.concurrent
包中的高级并发工具(如CountDownLatch、CyclicBarrier、Semaphore、Future和 CompletableFuture
等)取代,因为它们更安全、更易用且功能更强大。
回收之前
,提供一个清理资源
的最后机会重要注意事项:
try-with-resources
或手动调用 close()
方法来管理资源(如实现AutoCloseable
接口)Java 9
开始,finalize()方法已被 标记为 deprecated(已废弃)
,官方不建议再使用它