1 源码
Object类是Java中其他所有类的父类,所有类包括自定义类默认继承了Object类;
没有Object类Java面向对象无从谈起。作为其他所有类的基类,Object具有哪些属性和行为,是Java语言设计背后的思维体现。
Object类位于java.lang包中,java.lang包包含着Java最核心类,在编译时会自动导入。Object类没有定义属性,一共有13个方法,13个方法之中并不是所有方法都是子类可访问的,一共有11个方法是所有子类都继承了的。
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 {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }
}
2 native方法简介
先看源码,发现Object类中有好几个native方法只有方法头却没有实现,但Object类又不是抽象类,所以native方法不是抽象方法,只是形式上相似而已;既然native方法不是抽象方法,那么谁提供这类方法的实现呢?先说答案,后简单聊聊native方法。
native方法:又称本地方法;native方法的实现由操作系统提供实现,编译时操作系统自动生成fun.dll(假设native方法名为fun),fun.dll中是对应native方法的本地语言实现,本地语言与操作系统有关,一般是C/C++;
native方法既然是由操作系统提供实现,不需要用户编写java代码实现,那么native方法源码不用了解太深,我们只需要简单了解其功能即可。
- native方法的格式和抽象方法类似;
private static native void registerNatives();
static {
registerNatives();
}
registerNatives
作用是为当前类的所有natvie方法申请相应的.dll文件;该方法在静态代码块中,类加载时最先执行;
3 equals和hashcode
3.1 equals和"=="的区别
这是一个比较常见的面试题,大多数人的答案是:
- “==” :比较的是栈中的内容,如果是基本类型,比较的是值;如果是引用类型,比较的是地址;
- equals :比较的是对象的内容或者说所有属性值;
equals“比较所有属性值”这种说法是不准确的;这种说法只是equals的一种通过覆写自定义实现而已;
3.2 equals
(1) equals方法默认实现
public boolean equals(Object obj) {
return (this == obj);
}
可以看到Object类的默认equals方法就是用的"",因此""是equals的默认实现;
当然一个类可以去复写父类的equals方法,只要合理即可,都是equals的自定义实现;
(2) equals等价关系
- 自反性
x.equals(x); // true
- 对称性
x.equals(y) == y.equals(x); // true
- 传递性
if (x.equals(y) && y.equals(z))
x.equals(z); // true;
- 一致性
多次调用 equals() 方法结果不变
x.equals(y) == x.equals(y); // true
- 与null比较
x.equals(null); //false;x是不为空的对象
(3) equals方法自定义实现
任何类都可以复写equals方法,equals自定义实现;
equals()方法的正确理解应该是:判断两个对象是否等价。那么判断对象等价的标尺又是什么?
在Object类中,此标尺即为"=="。当然,这个标尺不是固定的,其他类中可以按照实际的需要对此标尺含义复写自定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。当然了,如果自定义的类没有重写equals()方法来重新定义此标尺,那么默认的将是其父类的equals(),直到object基类。
对于User bean,由实际的业务需求可知当属性uid相同时,表示的是同一个User,即两个User对象等价。则可以重写equals以重定义User对象相等的标尺。
下面给出了一种常见的equals方法自定义实现:
- “this==object”;
- 判断是否是同一个类;
- 将object对象向下转型;
- 判断每个属性是否相等;
public class A {
private int x;
private int y;
private int z;
public A(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
A that = (A) object;
if (x != that.x) return false;
if (y != that.y) return false;
return z == that.z;
}
}
(4) equals和"=="的区别
再回到这个问题,下面是准确的说法:
- “==” :是否相等;比较的是栈中的内容;如果是基本类型,比较的是值;如果是引用类型,比较的是地址;
- equals :是否等价;比较的是对象的内容;Object默认实现是"==";可以复写这个方法,进行自定义实现;常见的一种自定义实现是比较所有属性值;
3.3 hashcode方法
(1) hashcode定义
- hashCode() 返回对象的int类型哈希值;
- hashCode不一定是对象的物理地址,也不一定是逻辑地址;
- 两个方法同时复写(或同时不复写)的前提下:等价的两个对象哈希值一定相同,但是哈希值相同的两个对象不一定等价。即:
两个对象等价 <=> equals为true =>两个对象相同
- equals返回false,hashcode可能相同,就是哈希冲突;
- 其实同时不复写,就是默认情况:
==为true => hashcode相同
(2) 两个方法要求同时复写
- equals和 hashCode() 两个方法要求同时复写;以保证等价的两个对象哈希值一定相同
前面的A类中,我们只复写了equals方法,却没有复写 hashCode() 方法,看看有什么问题;
A a1 = new A(1, 1, 1);
A a2 = new A(1, 1, 1);
System.out.println(a1==a2); // false
System.out.println(a1.equals(a2)); // true
HashSet<A> set = new HashSet<>();//Set不含重复元素的集合(所有元素哈希值均不同)
set.add(a1);
set.add(a2);
System.out.println(set.size()); // 2,Object默认hashcode实现,两个对象哈希值不同,相当于是添加了两个元素
在我们印象中,set.size()应该是1才对,a1 ,a2应该是俩个重复元素;为什么结果和我们想的不同呢?
Set不添加重复哈希值元素;如果两个对象"=="返回false,Object的默认哈希值不同,相当于是添加了两个元素;
所以equals和 hashCode() 两个方法要求同时复写;下面我们给出一种 hashCode() 常见实现;
(3) hashcode的常见实现
理想的哈希函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上;这就要求哈希函数要把所有属性值计算进来。下面是hashcode的一种常见实现:
/**
*31*x == (x<<5)-x
**/
@Override
public int hashCode() {
int result = 17;
result = ( result << 5 ) - result + x;
result = ( result << 5 ) - result+ y;
result = ( result << 5 ) - result + z;
return result;
}
这样在A类中就同时复写了equals和hashcode方法:
public class A {
private int x;
private int y;
private int z;
public A(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
A that = (A) object;
if (x != that.x) return false;
if (y != that.y) return false;
return z == that.z;
}
@Override
public int hashCode() {
int result = 17;
result = ( result << 5 ) - result + x;
result = ( result << 5 ) - result+ y;
result = ( result << 5 ) - result + z;
return result;
}
}
现在a1和a2两个对象equals返回true,俩对象的哈希值也相同,会被Set当成是一个元素,set.size()也就返回1了;
(3) hashcode的作用
- hashcode的作用:主要用于提高哈希表查找效率;
以集合类中,以Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现)
4 getClass()方法
public final native Class<?> getClass();
- final方法,不允许被复写;
- getClass()方法用于获取Class对象;
String str = new String("Hello");
Class clazz = str.getClass();//调用的是Object的getClass()方法
5 toString() 方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
- toString() 方法返回值的表现形式:
类名@哈希值的无符号十六进制表示
;
System.out.println(a1.toString());//输出: A@4554617c
- toString()是由对象的类型和其哈希码唯一确定,同一类型但不相等的两个对象分别调用toString()方法返回的结果可能相同。
6 clone()方法
protected native Object clone() throws CloneNotSupportedException;
6.1 cloneable
clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去复写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
public class A {
private int a;
private int b;
}
//其他类中
A a1 = new A();
// A a2 = a1.clone(); // 'clone()' has protected access in 'java.lang.Object'
复写 clone() 得到以下实现:
public class A implements Cloneable{
private int a;
private int b;
@Override
public A clone() throws CloneNotSupportedException {
return (A)super.clone();
}
}
A a1 = new A();
try {
A a2 = a1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
- 规定复写了clone()方法的类必须实现Cloneable接口,以支持clone;否则编译时会抛出CloneNotSupportedException异常;
应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
6.2 浅拷贝
- 浅拷贝:拷贝对象的引用;只拷贝栈中的内容;
- Object的clone()默认实现是浅拷贝;
- 通过clone()实现浅拷贝;
public class A implements Cloneable {
private int[] array;
public ShallowCloneExample() {
array = new int[10];
for (int i = 0; i < array.length; i++) {
array[i] = i;
}
}
public void set(int index, int value) {
array[index] = value;
}
public int get(int index) {
return array[index];
}
@Override
public A clone() throws CloneNotSupportedException {
return (A) super.clone();
}
}
A a1 = new A();
A a2 = null;
try {
a2 = a1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
a1.set(2, 222);
System.out.println(a2.get(2)); // 222
6.3 深拷贝
-
深拷贝:拷贝对象本身;同时拷贝堆和栈中的内容;
-
深拷贝的一种作用:数据不共享方式解决并发;
深拷贝的实现方式有多种:
-
通过clone实现;
-
通过拷贝函数实现;
-
通过序列化对象实现;
-
通过json实现:对象转json,json再转对象;Gson和FastJson都可以实现;
-
通过反射实现;
前面三种方式下面给出示例代码,后两种仅作了解
6.3.1 通过clone实现深拷贝
修改clone方法:
public class A implements Cloneable {
//...
//除了clone方法不同,其他代码和通过clone实现浅拷贝代码完全一样
@Override
protected DeepCloneExample clone() throws CloneNotSupportedException{
//拷贝栈中内容
A result = (A) super.clone();
//拷贝堆中内容
result.array = new int[array.length];
for (int i = 0; i < array.length; i++) {
result.array[i] = arr[i];
}
return result;
}
}
A a1 = new A();
A a2 = null;
try {
a2 = a1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
a1.set(2, 222);
System.out.println(a2.get(2)); // 2
通过clone实现浅拷贝和深拷贝的上面两段代码的内存分配模型图如下:
- 注意:通过clone方式不管是实现浅拷贝还是深拷贝,都必须实现Cloneable接口并且clone方法
throws CloneNotSupportedException
;
6.3.2 通过拷贝函数实现深拷贝
通过clone来实现深拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数来拷贝一个对象。
public class A {
private int[] array;
//无参构造函数,用于对象的初始化
public A() {
array = new int[10];
for (int i = 0; i < array.length; i++) {
array[i] = i;
}
}
//参数是本类引用的构造函数,用于深拷贝
public A(A original) {
array = new int[original.array.length];
for (int i = 0; i < original.array.length; i++) {
array[i] = original.array[i];
}
}
public void set(int index, int value) {
array[index] = value;
}
public int get(int index) {
return array[index];
}
}
A a1 = new A();//实例化一个对象
A a2 = new A(a1);//深拷贝对象
a1.set(2, 222);
System.out.println(a2.get(2)); // 2
6.3.3 通过序列化对象实现深拷贝
//克隆对象类:通过Serializable实现序列化,要求所有属性要求是可序列化的
public class A implements Serializable {
private static final long serialVersionUID = 1L;
private int []array=new int[10];
public int getArray(int index) {
return array[index];
}
public void setArray(int index,int value) {
array[index] = value;
}
}
//克隆器,可以深度拷贝一个可序列化的对象
public class Cloner {
@SuppressWarnings("unchecked")
public static <T>T cloneObj(T obj){
T result = null;
try{
// 将对象写入流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
// 从流中读出对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
result = (T)ois.readObject();
}catch(Exception e){
e.printStackTrace();
}
return result;
}
}
//测试类
public class CloneTest {
public static void main(String[] args) {
A a1 = new A();
for (int i=0;i<10;i++){
a1.setArray(i,i);
}
A a2= Cloner.cloneObj(a1);
a1.setArray(2,222);
System.out.println(a2.getArray(2));//2
}
}
7 wait方法和notify方法
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 {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
wait(…) / notify() | notifyAll()几个方法,主要用于多线程协作。这些方法都是final方法,无法被重写。
- object.wait方法:表示挂起当前线程,当前线程进入于(无)限期等待状态;等待其他线程调用同一object对象的object.notify()唤醒当前线程;释放锁;
- object.notify方法:唤醒一个等待队列中的线程;至于是哪个等待线程不确定,这由jvm决定,但必须是通过调用同一object对象object.wait方法进入等待状态的;并且不是按优先级唤醒;
- object.notifyAll方法:唤醒等待队列中所有线程;
注意事项:
- wait方法和notify方法配套使用,这三个方法都==必须在同步代码块或者同步方法中调用,否则会抛出异常;
- 调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
- wait() 和 sleep() 的区别
- wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
- wait() 会释放锁,sleep() 不会。
-
- 调用wait()为什么线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。
- wait(long timeout, int nanos)方法定义内部实质上也是通过调用wait(long timeout)完成。而wait(long timeout)是一个native方法。因此,wait(…)方法本质上都是native方式实现。
下面是俩个例子:
public class WaitNotifyTest {
volatile static int flag = 1;//volatile保证数据对所有线程可见性
volatile static Object object = new Object(); //object作为锁对象,用于线程使用wait和notify方法
public static void main(String[] args) {
new Thread(() -> {//wait和notify必须在同步代码块(或同步方法)内使用
synchronized (object) {
while (true) {
if (flag == 0) {
try {
Thread.sleep(2000);//模拟线程1运行
System.out.println("thread1 wait");
object.wait();//使当前线程等待,释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("thread1 run");
flag = 0;
object.notify();//唤醒一个等待线程,此处是t2从阻塞变就绪
System.out.println("notify t2");
}
}
}).start();
new Thread(() -> {
while (true) {
synchronized (object) {
if (flag == 1) {
try {
Thread.sleep(2000);
System.out.println("thread2 wait");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("thread2 run");
flag = 1;
object.notify();
System.out.println("notify t1");
}
}
}).start();
}
}
//循环
thread1 run
notify t2
thread1 wait
thread2 run
notify t1
thread2 wait
thread1 run
notify t2
public class WaitNotifyExample {
public synchronized void before() {
System.out.println("before");//满足条件
notifyAll();//唤醒线程,有点像V原语
}
public synchronized void after() {
try {
wait();//等待条件满足,有点像P原语
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
WaitNotifyExample example = new WaitNotifyExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}
}
before
after
8 finalize()方法
protected void finalize() throws Throwable { }
finalize()方法与Java垃圾回收机制有关;
finalize()方为什么定义为空方法?Object中定义finalize方法表明Java中每一个对象都将具有finalize这种行为;当jvm gc前,这个方法被调用。
9 参考博客
- http://cyc2018.gitee.io/cs-notes/#/notes/Java%20%E5%9F%BA%E7%A1%80
- https://www.jianshu.com/p/a509edc4fc08
- http://www.cnblogs.com/Jtianlin/p/4605477.html
- http://www.cnblogs.com/szlbm/p/5504603.html