Object类的常用方法

        Object类是所有类的父类,任何类都默认继承Object,位于java.lang包中,Object类主要实现了哪些方法呢?

toString方法

        toString方法可以将任何一个对象转换成字符串返回,返回值的生成算法为:getClass().getName() + '@' + Integer.toHexString(hashCode())。不过,一般子类都会重写该方法。

equals方法

        Object类中的equals方法,用来比较两个引用的虚地址。当且仅当两个引用在物理上是同一个对象时,返回值为true,否则将返回false。不过,一般子类都会重写该方法。

hashCode方法

        获取对象的哈希码值,为16进制。

clone方法

        保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。

getClass方法

        final方法,获取类的class对象。

wait方法

        导致线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。wait(long timeout)可以设定一个超时间隔,调用该方法后当前线程进入睡眠状态,直到以下事件发生:

        (1)其他线程调用了该对象的notify方法。

        (2)其他线程调用了该对象的notifyAll方法。

        (3)其他线程调用了interrupt中断该线程。

        (4)时间间隔到了。

        此时该线程就可以被调度了,如果是被中断的话就抛出一个InterruptedException异常。

notify方法

        该方法唤醒在该对象上等待的某个线程。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

notifyAll方法

        解除所有那些在该对象上调用wait方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

为什么重写equals方法必须重写hashcode方法

        因为如果只重写了equals方法而没有重写hashCode方法,则两个不同的实例a和b虽然equals结果相等(业务逻辑上,比如:两个对象的值全部相等),但却会有不同的hashcode,这样hashmap里面会同时存在a和b,而实际上我们需要hashmap里面只能保存其中一个,因为从业务逻辑方向看它们是相等的。

        equals方法和hashCode方法如果不同时按你自己逻辑重写的话,HashMap就会出问题。比如你只重写了equals方法而没有重写hashCode方法,那么HashMap在第一步寻找链表的时候会出错,有同样值的两个对象a和b并不会指向同一个链表或桶,因为你没有提供自己的hashCode方法,那么就会使用Object的hashCode方法,该方法是根据内存地址来比较两个对象是否一致,由于a和b有不同的内存地址,所以会指向不同的链表,这样HashMap会认为b不存在,虽然我们期望a和b是同一个对象;反之如果只重写了hashCode方法而没有覆盖equals方法,那么虽然第一步操作会使a和b找到同一个链表,但是由于equals没有覆盖,那么在遍历链表的元素时,a.equals(b)也不为true(事实上Object的equals方法也是比较内存地址),从而HashMap认为不存在b对象,这同样也是不正确的。

代码测试:

        没有覆盖equals方法和hashCode方法。

package com.redistext.equals;

/**
 * @author: maorui
 * @description: TODO
 * @date: 2021/10/27 16:53
 * @description: V1.0
 */
public class Student {

    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;
    }


    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
package com.redistext.equals;

import java.util.HashSet;
import java.util.Set;

/**
 * @author: maorui
 * @description: TODO
 * @date: 2021/10/27 16:54
 * @description: V1.0
 */
public class EqualsTest {

    public static void main(String[] args) {
        Set<Student> set = new HashSet<Student>();
        Student stu1  = new Student("张三",3);
        Student stu2  = new Student("张三",3);
        set.add(stu1);
        set.add(stu2);
        System.out.println("stu1 == stu2 : "+(stu1 == stu2));
        System.out.println("stu1.equals(stu2) : "+stu1.equals(stu2));
        System.out.println("set size:"+ set.size());
    }



}

        测试结果:

         Student类没有覆盖equals方法,stu1调用equals方法实际上调用的是Object的equals方法。所以采用对象内存地址是否相等来判断对象是否相等。因为是两个新对象所以对象的内存地址不相等,所以stu1.equals(stu2) 是false。

        但是从业务逻辑出发,我们认为stu1和stu2的属性值相等,那这两个对象就是一样的,那么我们就需要重写equals方法。

package com.redistext.equals;

/**
 * @author: maorui
 * @description: TODO
 * @date: 2021/10/27 16:53
 * @description: V1.0
 */
public class Student {

    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;
    }


    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

}

        测试结果:

         因为Student两个对象的age和name属性相等,而且又是通过重写的equals方法来判断的,所示stu1.equals(stu2) 为true。但是还有个问题,我们知道Set集合是不保存重复元素的,但是set集合的size还是2,说明在set集合中认为stu1和stu2还是不同的。

        这就和我们没有重写hashCode方法有关系了,我们重写一下hashCode方法。

package com.redistext.equals;

/**
 * @author: maorui
 * @description: TODO
 * @date: 2021/10/27 16:53
 * @description: V1.0
 */
public class Student {

    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;
    }


    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        if (age != student.age) return false;
        return name != null ? name.equals(student.name) : student.name == null;
    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

        测试结果:

         stu1和stu2通过equals方法比较相等,而且返回的hashCode值一样,所以放入set集合中时只放入了一个对象。

wait、notify方法使用场景

        在某些情况下,线程需要交替执行。比如一个线程向一个存储单元执行存放数据,而另一个操作执行取值操作,线程间同步完成这个存取任务,需要将这些线程同步。要解决线程交替执行但是又要保证共享资源安全,这需要使用到wait()、notify()方法。

package com.redistext.wait;

/**
 * @author: maorui
 * @description: TODO
 * @date: 2021/10/27 17:35
 * @description: V1.0
 */
public class WaitNotifyTest {

    public static void main(String[] args) {
        Object lock = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程A等待获取lock锁");
                synchronized (lock){
                    try {
                        System.out.println("线程A获取到了lock锁");
                        Thread.sleep(1000);
                        System.out.println("线程A将要运行lock.wait()方法进行等待");
                        lock.wait();
                        System.out.println("线程A等待结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程B等待获取lock锁");
                synchronized (lock){
                    try {
                        System.out.println("线程B获取到了lock锁");
                        Thread.sleep(1000);
                        System.out.println("线程B将要运行lock.notify()方法进行通知");
                        lock.notify();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();


    }

}

        测试结果:

         在上文中,我们说了wait()方法导致线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常

        测试代码:

package com.redistext.wait;

/**
 * @author: maorui
 * @description: TODO
 * @date: 2021/10/27 17:35
 * @description: V1.0
 */
public class WaitNotifyTest {

    public static void main(String[] args) {
        Object lock = new Object();
        Object lock1 = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程A等待获取lock锁");
                synchronized (lock){
                    try {
                        System.out.println("线程A获取到了lock锁");
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程A将要运行lock.wait()方法进行等待");
                    try {
                        lock1.wait(1000);
                    } catch (Exception e) {
                        System.out.println("线程A lock1.wait()方法调用异常");
                        e.printStackTrace();
                    }
                    System.out.println("线程A等待结束");


                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程B等待获取lock锁");
                synchronized (lock){
                    try {
                        System.out.println("线程B获取到了lock锁");
                        Thread.sleep(1000);
                        System.out.println("线程B将要运行lock.notify()方法进行通知");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        lock1.notify();
                    } catch (Exception e) {
                        System.out.println("线程B lock1.notify()方法调用异常");
                        e.printStackTrace();
                    }
                }
            }
        }).start();


    }

}

        测试结果:

为什么wait、notify必须强制要求放在synchronized中?

        在日常开发,我们都知道wait/notify有着非常固定的一套模板,就像上面的例子,synchronized同步块包裹着Object.wait()方法,如果不通过同步块包住的话JVM会抛出IllegalMonitorStateException异常。

        假设Object.wait()/notify不需要同步,我们来实现一个BlockingQueue的代码。

class BlockingQueue {
    Queue<String> buffer = new LinkedList<String>();

    public void give(String data) {
        buffer.add(data);
        notify();                   // 往队列里添加的时候notify,因为可能有人在等着take
    }

    public String take() throws InterruptedException {
        while (buffer.isEmpty())    // 用while,防止spurious wakeups(虚假唤醒)
            wait(); // 当buffer是空的时候就等着别人give
        return buffer.remove();
    }
}

        上面的代码在多线程的环境下执行,可能会出现这种情况:

  1. 当前buffer是空的,这时来了一个take的请求,尚未执行到wait语句,执行了buffer.isEmpty()为true
  2. 同时又来了一个give请求,完整执行完了整个give方法并且发送了notify
  3. 此时take方法才走到wait,因为它错过了上一个notify,所以会在明明buffer不空的情况下挂起线程,take方法挂起。假如再没有人调用过give方法了,在业务上的表现就会是这个take线程永远也取不到buffer中的内容。

        因为你只要用notify,那就是为了在多线程环境下同步,notify/wait机制本身就是为了多线程的同步而存在的,那就只能配套synchronized,所以为了防止上面情况的发生,就直接强制抛异常来限制开发的代码模式了。

        

为什么wait、notify定义在Object中,而不是Thread中?

参考文档:

object类中常用的方法 - 摇曳星君 - 博客园

java中Object类中有哪些常用方法以及作用_gradonisis的博客-CSDN博客_object有哪些常用方法

hashCode和equals方法的关系_七界的博客-CSDN博客_hashcode和equals的关系

为什么wait/notify必须要强制要求放在synchronized中 - 灰信网(软件开发博客聚合)

  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值