1. == 和 equals()的区别
public boolean equals(Object obj) {
return (this == obj);
}
Object源码中equals方法和"==“并没有区别,它就是用” ==" 写的。
可以知晓
- 如果是基本数据类型,也就是byte,int,long, double之类的数据类型 "== "是比较数值的
- 如果是对象,"=="是比较对象的地址是否相同,其实就是比较是否是同一个对象。
但是float,double这两种浮点类型,如果用来比较大小的话,可能会出现精度丢失的问题。
float a = 1.0f - 0.9f;
float b = 0.9f - 0.8f;
if (a == b) {
System.out.println("true");
} else {
System.out.println("false");
}
这一段代码的输出结果是false,因为出现了精度丢失
即使是他们的包装类型Float以及Double也一样会出现精度丢失
Float a = Float.valueOf(1.0f - 0.9f);
Float b = Float.valueOf(0.9f - 0.8f);
if (a.equals(b)) {
System.out.println("true");
} else {
System.out.println("false");
}
输出结果也是false,所以在阿里巴巴编码规约里规定,浮点型的基本数据类型不能用"=="比较,包装数据类型不能用equals()比较。
hashCode() 和 equals()
public native int hashCode();
这是一个native方法,返回的是一个int。这个方法是用来获取哈希码的,就是对象在堆中的一个特殊值。
类似于HashMap中的hash。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
通过传进来的对象,生成一个独特的hash值,来代表它存储在HashMap底层的数组下标。
所以如果需要判断对象是否相等,就一定需要覆盖hashCode()方法以及equals()方法。
- 首先进行判断它们的hashCode 是否相等,如果不相等,那么这两个对象就一定不相等。
- 如果这两个对象的hashCode相等,那么就会使用equals()方法去判断这两个对象是否真的相等。
关于hashCode以及equals的一些规则
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
3. toString()
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
返回当前对象的类名,以及使用16进制转换完后的hashCode值
这个toString()方法于直接使用System.out.println()输出对象是一样的结果
Object object = new Object();
System.out.println(object);
System.out.println(object.toString());
输出结果为
java.lang.Object@1540e19d
java.lang.Object@1540e19d
4. clone()
如何实现对象克隆?
- 实现cloneable接口
- 重写Object的clone()方法
直接赋值
Object o1 = new Object();
Object o2 = o1;
在Java中,这种直接赋值方式,就是赋值引用,也就是说它们指向的是同一个对象
需要注意的是一个类覆盖clone方法需要实现Cloneable接口,不然会抛出CloneNotSupportedException异常
浅拷贝
拷贝但不拷贝引用的对象,即创建一个对象,如果对象中的是值类型,则进行拷贝,如果是引用类型,则拷贝对象的引用但不拷贝引用的对象。
public class FatherClass implements Cloneable {
private String name;
private int age;
private ChildClass child;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
FatherClass father = new FatherClass();
father.name = "张三";
father.age = 30;
father.child = new ChildClass();
father.child.setName("小张三");
father.child.setAge(20);
FatherClass father1 = (FatherClass) father.clone();
//false
System.out.println(father == father1);
//356573597
System.out.println(father.hashCode());
//1735600054
System.out.println(father1.hashCode());
//张三
System.out.println(father.name);
//张三
System.out.println(father1.name);
//true
System.out.println(father.child == father1.child);
//21685669
System.out.println(father1.child.hashCode());
//21685669
System.out.println(father.child.hashCode());
}
}
这是一个浅拷贝的例子,它们输出的结果在语句的上方,方便查看。
浅拷贝说明,clone()方法确实创建了一个对象,因为它们的地址以及hashCode均不相同。
而且,它们的值类型都相等,引用类型childClass 也都指向同一个引用,因为它们相等,并且hashCode也相等。
深拷贝
深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。
如何实现一个深拷贝?
- 序列化这个对象,再序列化回来
- 修改clone()方法的内容,将引用类型也使用clone()
首先将ChildClass也覆盖clone方法
public class ChildClass implements Cloneable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
接着修改FatherClass里的clone方法
@Override
protected Object clone() throws CloneNotSupportedException {
FatherClass fatherClass = (FatherClass) super.clone();
fatherClass.child = (ChildClass) this.child.clone();
return fatherClass;
}
public static void main(String[] args) throws CloneNotSupportedException {
FatherClass father = new FatherClass();
father.name = "张三";
father.age = 30;
father.child = new ChildClass();
father.child.setName("小张三");
father.child.setAge(20);
FatherClass father1 = (FatherClass) father.clone();
//false
System.out.println(father == father1);
//356573597
System.out.println(father.hashCode());
//1735600054
System.out.println(father1.hashCode());
//张三
System.out.println(father.name);
//张三
System.out.println(father1.name);
//false
System.out.println(father.child == father1.child);
//21685669
System.out.println(father1.child.hashCode());
//2133927002
System.out.println(father.child.hashCode());
}
可以看到child比较也是false,并且hashCode也不相同,说明引用类型指向的也不是同一个引用了。
深拷贝和浅拷贝也是相对的,如果一个类中只有基本数据类型,那么执行clone方法就是深拷贝,如果还包括引用数据类型,那么就是浅拷贝了。
5. notify()和notifyAll()
这两个方法都是用来唤醒线程的,只不过notify()是随机唤醒一个线程,notifyAll()是唤醒所有的线程。
一般来说notify(), notifyAll()都是基于wait()方法以及synchronized 关键字而言。
因为notify(),notifyAll() 是基于wait()方法,是用来唤醒等待中得线程得,而wait()方法又是基于synchronized关键字,只能在synchronized方法或块中使用,因为只有获得了锁,才能释放锁。
wait()方法又引出锁池和等待池的概念
锁,锁池,等待池都是对于对象而言,synchronized也是锁住对象而不是锁住代码。
锁池:当线程A已经获得了某个对象的锁得拥有权,而线程B想要进入这个对象得synchronized方法或块得时候,就需要获取到这个对象的锁得拥有权,但是因为线程A已经获得了,所以线程B就会被阻塞,进入到一个地方去等待线程A释放锁,这个地方就是锁池。
等待池:线程A调用了某个对象得wait()方法,那么线程A就会使用这个对象得锁,同时线程A就进入到了这个对象得等待池中了,进入等待池中得线程不会去竞争锁。
notify(),notifyAll()就是在等待池中唤醒线程进入到锁池中去竞争锁。不同的是前者是随机唤醒一个,而后者是全部唤醒。
public static void main(String[] args) {
final Object lock = new Object();
new Thread(new Runnable() {
public void run() {
System.out.println("thread A is waiting to get lock");
synchronized (lock) {
try {
System.out.println("thread A get lock");
Thread.sleep(20);
System.out.println("thread A do wait method");
//不填参数会进入无限期的等待
lock.wait();
System.out.println("thread A is done");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(new Runnable() {
public void run() {
System.out.println("thread B is waiting to get lock");
synchronized (lock) {
try {
System.out.println("thread B get lock");
System.out.println("thread B is sleeping 10ms");
Thread.sleep(10);
System.out.println("thread B is done");
//能够唤醒线程
lock.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
运行结果如下:
thread A is waiting to get lock
thread A get lock
thread B is waiting to get lock
thread A do wait method
thread B get lock
thread B is sleeping 10ms
thread B is done
thread A is done
结合代码分析:
①线程A在获取到了锁之后
②线程B被阻塞,进入lock对象的锁池当中,
③接着线程A调用lock对象的wait方法,进入lock对象的等待池中,
④直到线程B中调用lock的notify()方法唤醒,
⑤然后线程A才能输出 “thread A is done” 这个语句。
参考资料:
JavaGuide
细说 Java 的深拷贝和浅拷贝
慕课网----剑指Offer直通车