Java中的Object类

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文件;该方法在静态代码块中,类加载时最先执行;

参考:自己实现一个Native方法的调用

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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值