java常用类之Objec类

Java常用类之Object类

  • 我们都知道Object类是所有类的父类,任何类都默认继承Object类
  • 在使用是都默认或者间接导入import java.lang.Object包;由于所有类都继承Object类,所以可以省略了extends Object这一步骤。
//Object类源码内容
package java.lang;
import jdk.internal.HotSpotIntrinsicCandidate;
public class Object {
    private static native void registerNatives();
    
    @HotSpotIntrinsicCandidate
    public Object() {
    }
    
    @HotSpotIntrinsicCandidate
    public final native Class<?> getClass();

    @HotSpotIntrinsicCandidate
    public native int hashCode();

    public boolean equals(Object obj) {
        return this == obj;
    }

    @HotSpotIntrinsicCandidate
    protected native Object clone() throws CloneNotSupportedException;

    public String toString() {
        return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
    }

    @HotSpotIntrinsicCandidate
    public final native void notify();

    @HotSpotIntrinsicCandidate
    public final native void notifyAll();

    public final void wait() throws InterruptedException {
        this.wait(0L);
    }

    public final native void wait(long var1) throws InterruptedException;

    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0L) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        } else if (nanos >= 0 && nanos <= 999999) {
            if (nanos > 0) {
                ++timeoutMillis;
            }

            this.wait(timeoutMillis);
        } else {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    }

    /** @deprecated */
    @Deprecated(
        since = "9"
    )
    protected void finalize() throws Throwable {
    }

    static {
        registerNatives();
    }
}
  • 该类中主要有以下方法:hashcode(), toString(),getClass(),equals(),clone(),finalize(), 其中toString(),getClass(),equals是其中最为重要的方法。

  • 注意:因为 Object类中的getClass(),notify(),notifyAll(),wait()等方法被定义为final类型,因此不能重写。

hashCode()方法

对hashCode迷茫的朋友可以看一下,针不戳!

public native int hashCode();
  • 返回该对象的hashcode值,使用此方法的目的是为了提高哈希表的性能;

  • 因为该方法是一个public方法,所以子类可以重写它。

  • 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。

  • 如果两个对象的equals相等,那么这两个对象的hashCode一定也相同。

  • 如果两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。但是,我们应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能

  • 重写了equals方法一般都要重写hashCode方法。

/*
比如:下面Test类重写了equals方法,但是没有重写hashCode方法,看输出结果,对象a和对象b使用equals方法相等,按照上面的hashcode的用法,那么他们两个的hashcode肯定相等,但是这里由于没重写hashcode方法,他们两个hashcode并不一样,所以,我们在重写了equals方法后,尽量也重写了hashcode方法,通过一定的算法,使他们在equals相等时,也会有相同的hashcode值。
*/
class Test{
    int a=1;
    int b=2;


    @Override
    public boolean equals(Object obj) {//重写equals方法
        if(this==obj);
        Test test = (Test) obj;
        if (this.a==test.a && this.b==test.b){
            return true;
        }
        return false;
    }
}

public class hashCodetest {
    public static void main(String[] args) {
        Test a = new Test();
        Test b = new Test();
        System.out.println(a.hashCode());//结果为381259350
        System.out.println(b.hashCode());//结果为2129789493
        System.out.println(a.equals(b));//结果为true
    }
}

equals()方法

public boolean equals(Object obj) {
        return this == obj;
    }

用于比较当前对象与目标对象是否相等,默认是比较引用是否指向同一对象。为public方法,子类可重写。

equals 方法在非空对象引用上实现相等关系:

  • 自反性:对于任何非空引用值 xx.equals(x) 都应返回 true
  • 对称性:对于任何非空引用值 xy,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 才应返回 true
  • 传递性:对于任何非空引用值 xyz,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 应返回 true
  • 一致性:对于任何非空引用值 xy,多次调用 x.equals(y) 始终返回 true 或始终返回 false,前提是对象上 equals 比较中所用的信息没有被修改。
  • 对于任何非空引用值 xx.equals(null) 都应返回 false

Object 类的 equals 方法实现对象上差别可能性最大的相等关系;即,对于任何非空引用值 xy,当且仅当 xy 引用同一个对象时,此方法才返回 truex == y 具有值 true)。

  • 注意:当此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。

    //只针对equals例子
    
    class Test1{
        int a=1;
        int b=2;
        @Override
        public boolean equals(Object obj) {
            if(this==obj) return true;
            Test1 o = (Test1) obj;
            if (o.a==o.b){
                return true;
            }
            return false;
        }
    }
    public class Equalstest{
        public static void main(String[] args) {
            Test1 m=new Test1();
            Test1 n=new Test1();
            System.out.println(m.equals(n));//false
        }
    }
    

toString()方法

 public String toString() {
        return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
    }
  • Object 类的 toString 方法返回一个字符串,该字符串由类名(对象是该类的一个实例)、at 标记符“@”和此对象哈希码的无符号十六进制表示组成。如:java.lang.Object@2d98a335

  • 返回该对象的字符串表示。通常, toString 方法会返回一个“以文本方式表示”此对象的字符串。结果应是一个简明但易于读懂的信息表达式。建议所有子类都重写此方法。

  • 为什么需要toString方法?返回当前对象的字符串表示,可以将其打印方便查看对象的信息,方便记录日志信息提供调试。

public class toStringtest {
    public static void main(String[] args) {
        Object t = new Object();
        System.out.println(t.toString());//结果为java.lang.Object@2d98a335
    }
}

clone()方法

protected native Object clone() throws CloneNotSupportedException;
  • 创建并返回此对象的一个副本。“副本”的准确含义可能依赖于对象的类。

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

  • 如果对象的类不支持 Cloneable 接口,则重写 clone 方法的子类也会抛出此异常,以指示无法复制某个实例。

  • 在java里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法(实现深拷贝)。

对于clone()和copy的区别

假设现在有一个Animal对象,Animal cat =new Animal(“huahua”,6);

通常我们会有这样的赋值Animal dog=cat,这个时候只是简单了copy了一下相关内容,而cat和dog都指向内存中同一个object,这样就导致了cat或者dog中的任何一个操作都可能影响到对方。

比如,如果我们通过cat.raiseSalary()方法改变了salary域的值,那么dog通过getSalary()方法得到的就是修改之后的salary域的值,这显然这不是我们愿意看到的。

我们希望得到dog的一个精确拷贝,同时两者互不影响,这时候我们就可以使用clone()方法来满足我们的需求。Animal dog=cat.clone(),这时会生成一个新的Animal对象,并且和cat具有相同的属性值和方法。

/*通过例子区别一下clone()方法和copy之间的差异,copy可以理解改一下,则其他也会变;而一旦使用clon方法,则会创建一个新的对象,其内容改变不会影响其它的*/

public class CloneTest implements Cloneable{
    private String name;
    private int age;
    protected CloneTest clone() throws CloneNotSupportedException {
        return (CloneTest)super.clone();
    }
    public static void main(String args[]) throws CloneNotSupportedException{
        CloneTest cat = new CloneTest("huahua",6);
        CloneTest pig = cat;
        System.out.println(cat==pig);//true
        System.out.println(cat.name==pig.name);//true
        System.out.println(cat.age==pig.age);//true
        System.out.println("========================");
        CloneTest dog = cat.clone();
        System.out.println(cat==dog);//false
        System.out.println(cat.name==dog.name);//true
        System.out.println(cat.age==dog.age);//true
    }
}

浅拷贝,深拷贝

  • 浅拷贝:拷贝的是引用;从上面用clone方法的输出结果来看,clone的对象是虽然是一个新的对象;但原对象与clone对象的 String类型 的name却是同一个引用,这说明,当super.clone方法对成员变量进行操作时,如果是引用类型,则进行是浅拷贝。
  • 深拷贝:新开辟内存空间,进行值拷贝;如果想要深拷贝一个对象, 这个对象必须要实现Cloneable接口,实现clone方法,并且在clone方法内部,把该对象引用的其他对象也要clone一份 , 这就要求这个被引用的对象必须也要实现Cloneable接口并且实现clone方法。
//深拷贝案例
public class deepClonetest {
    static class Body implements Cloneable {
        public Head head;
        public Body() {
        }
        public Body(Head head) {
            this.head = head;
        }
        @Override
        protected Object clone() throws CloneNotSupportedException {
            Body newBody =  (Body) super.clone();
            newBody.head = (Head) head.clone();
            return newBody;
        }
    }

    static class Head implements Cloneable {
        public Face face;
        public Head() {
        }
        public Head(Face face) {
            this.face = face;
        }
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

    }
    static class Face{}

    public static void main(String[] args) throws CloneNotSupportedException {
        Body body = new Body(new Head());
        Body body1 = (Body) body.clone();
        System.out.println(body == body1);//false
        System.out.println(body.head == body1.head);//false
        System.out.println(body.head.face == body1.head.face);//true

    }
}

getClass()方法

public final native Class<?> getClass();

返回次Object的运行时类类型。不可重写,要调用的话,一般和getName()联合使用,如getClass().getName();

  • 具体理解:类加载的第一阶段类的加载就是将.class文件加载到内存,并生成一个java.lang.Class对象的过程。getClass()方法就是获取这个对象,这是当前类的对象在运行时类的所有信息的集合。

notify()方法与notifyAll()方法

  • notify
 public final native void notify();
  • 唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。

  • 此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:

    • 通过执行此对象的同步实例方法。
    • 通过执行在此对象上进行同步的 synchronized 语句的正文。
    • 对于 Class 类型的对象,可以通过执行该类的同步静态方法。

注:一次只能有一个线程拥有对象的监视器。

  • notifyAll
public final native void notifyAll();
  • 唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
  • 此方法只应由作为此对象监视器的所有者的线程来调用。

两者共同点:1、IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者;

​ 2、此方法只应由作为此对象监视器的所有者的线程来调用

wait() | wait(long timeout) | wait(long timeout,int naos)方法

  • wait()
public final void wait() throws InterruptedException {
        this.wait(0L);
    }
  • wait(long timeout)
public final native void wait(long var1) throws InterruptedException;
  • wait(long timeout,int naos)
//long timeout:要等待的最长时间(以毫秒为单位);    int naos:额外时间(以微毫秒为单位)
public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0L) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        } else if (nanos >= 0 && nanos <= 999999) {
            if (nanos > 0) {
                ++timeoutMillis;
            }

            this.wait(timeoutMillis);
        } else {
            throw new IllegalArgumentException("nanosecond timeout value out of range");
        }
    }
  • 抛出的异常:

    • IllegalArgumentException - 如果超时值是负数,或者毫微秒值不在 0-999999 范围内。
    • IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。
    • InterruptedException - 如果在当前线程等待通知之前或者正在等待通知时,任何线程中断了当前线程。在抛出此异常时,当前线程的中断状态被清除。
  • 这三个方法都是是用来线程间通信用的,其作用是阻塞当前线程 ,等待其他线程调用notify()/notifyAll()方法将其唤醒。这些方法都是public final的,不可被重写。

注意:

  1. 此方法只能在当前线程获取到对象的锁监视器之后才能调用,否则会抛出IllegalMonitorStateException异常。
  2. 调用wait方法,线程会将锁监视器进行释放;而Thread.sleep,Thread.yield()并不会释放锁 。
  3. wait方法会一直阻塞,直到其他线程调用当前对象的notify()/notifyAll()方法将其唤醒;而wait(long)是等待给定超时时间内(单位毫秒),如果还没有调用notify()/nofiyAll()会自动唤醒;waite(long,int)如果第二个参数大于0并且小于999999,则第一个参数+1作为超时时间;

调用该方法后当前线程进入睡眠状态,直到以下事件发生。

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

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

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

(4)时间间隔到了。

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

其他

推荐两篇优质文章:

第一篇

第二篇

posted @ 2022-01-16 19:49  Devin-Y  阅读(247)  评论(0编辑  收藏  举报