java.lang包是Java中最基础和最核心的包,它包含了Java编程中最基本的类,如String、Object、System等。这个包非常特殊,JVM会自动加载它,不需要使用import语句显式导入。
Object 是 Java 中所有类的根类:除了基本类型,任何引用类型都直接或间接继承 Object。所以一个 Object 引用可以指向任意对象。
java
Object o1 = "hello";
Object o2 = Integer.valueOf(1);
Object o3 = new ArrayList<String>();
equals 方法
== 和equals的对比?有何区别?
==是比较运算符,既可以判断基本类型,又可以判断引用类型(引用类型中,变量值存储的是地址,其实是判断地址是否相等)。
equals 是object类中的方法,只能进行引用类型判断。
每个继承Object的子类都有可能重写Object的方法,比如String类重写了Object的equals方法:
java
public boolean equals(Object anObject) {//接受一个string对象
if (this == anObject) {//判断是否是同一个对象,其实比较的是变量中存储的地址是否一样
return true;
}
if (anObject instanceof String) {//判断判断比较的对象是否是同一类型的数据
String anotherString = (String)anObject; //强转
int n = value.length;
if (n == anotherString.value.length) {//比较两个字符串长度是否一致
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) { //比较两个字符串每一个字符是否相等
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

hashCode方法
返回一个对象的哈希码值。这种方法 支持哈希表的好处比如由 HashMap提供
-
调用同一对象,返回的hashcode一致,hashcode具有幂等性。
-
指向同一对象的hashcode一致,指向不同对象的hashcode不一致。
-
hashcode的值是根据jvm内部地址转化来的
-
在集合中,hashcode有需要的话,也会重写。
toString方法
返回一个对象的字符串表示。一般来说, toString方法返回一个字符串,该字符串文本表示这个对象。结果应该是一个简洁但信息表示这是一个人容易阅读。建议所有子类重写这个方法。
类Object toString方法返回一个全类名(路径+类)+"@"+把 hashcode 转化为16进制 的字符串。
换句话说,该方法返回一个字符串的值等于:
getClass().getName() + '@' + Integer.toHexString(hashCode())
一般重写toString就是为了打印对象的属性。
当直接输出一个对象时,toString方法会被默认调用。
finalize方法
当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。一般重写这个方法用于释放其他资源(开发中一般不会重写这个方法)。垃圾回收机制一般是由系统决定什么时候进行垃圾回收的,我们也可以使用System.gc();方法手动触发垃圾回收算法。
clone 方法
clone方法是Object的一个protected方法,这说明你的代码不能直接调用这个方法。
对于clone方法,如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题。但是如果对象包含子对象的引用(比如string实例变量),拷贝域就会得到相同子对象的另一个引用,这样一来,原对象和克隆的对象仍然会共享一些信息。也就是说默认的克隆操作是"浅拷贝",并没有克隆对象中引用的其他对象。
对于每一个类,需要确定:
-
默认的clone方法是否满足要求;
-
是否可以在可变的子对象上调用clone来修补默认的clone方法;
-
是否不该使用clone。
实际上第3个选项是默认选项。如果选择第1项或第2项,类必须:
- 实现
Cloneable接口; - 重新定义
clone方法,并指定public访问修饰符。
在这里,Cloneable接口的出现与接口的正常使用并没有关系。具体来说,它没有指定clone方法,这个方法是从Object类继承的。这个接口只是作为一个标记,指示类设计者了解克隆过程。对象对于克隆很"偏执",如果一个对象请求克隆,但没有实现这个接口,就会生成一个受查异常。也就是说如果执行深拷贝,那么就要实现Cloneable接口。
notify/notifyAll/wait
wait() / notify() / notifyAll() 是 Java 最基础的线程间等待/通知机制 ,定义在 java.lang.Object 上(不是 Thread),配合 synchronized 使用。
都是 final 的,不能被重写。
这三个方法都必须在 synchronized 块/方法中调用,并且锁对象就是调用 wait/notify 的那个对象:
java
Object lock = new Object();
synchronized (lock) {
lock.wait(); // 正确
}
synchronized (lock) {
lock.notify(); // 正确
}
notify
如果有任何线程正在等待此对象,则会任意的选择其中一个被唤醒。被唤醒的线程从 WAITING 变为 BLOCKED,去竞争锁。
notifyAll
如果有任何线程正在等待此对象,则会唤醒所有等待此对象的线程。被唤醒的线程从 WAITING 变为 BLOCKED,然后依次竞争锁。
wait
每个对象都有一个监视器(monitor) ,wait 会把当前线程放入该对象的 wait set。前提是你必须先拥有这个对象的内置锁。
调用 wait() 时:
- 当前线程必须持有
obj的锁 - 释放该对象锁;
- 线程进入
obj的 wait set,状态变为WAITING(带超时的变为TIMED_WAITING); - 等到被
notify/notifyAll唤醒,或者超时,或者被interrupt; - 被唤醒后重新竞争
obj的锁; - 重新拿到锁后,从
wait()之后继续执行。
java
RUNNABLE
│ 调用 wait()
▼
WAITING / TIMED_WAITING (同时释放锁)
│ 被 notify/notifyAll 唤醒 或 超时
▼
BLOCKED (等待重新获取锁)
│ 获取到锁
▼
RUNNABLE
为什么必须用 while 而不是 if
一般我们都会这样写:
java
while (queue.isEmpty()) {
wait(); // 醒来后重新检查条件
}
int value = queue.poll();
主要原因是每次线程被notify唤醒,都是从wait()位置开始执行的。如果是if:
java
if (queue.isEmpty()) { // 错误写法
wait();
}
int value = queue.poll();
如果A从wait醒来后拿走队列之后一个值,处理业务后释放锁。B再从wait醒来,往下执行queue.poll(),会出现异常。
使用while,每次从wait醒来都会判断queue.isEmpty()