重拾JavaSE基础——Object类

Object类是所有类的父类,处于Java的最顶端。Object类有几个方法值得我们关注

registerNatives方法

大家会发现Object类的最上面有这样的代码,不只是Object类,只要需要用到本地方法的类都有这样的代码

private static native void registerNatives();
static {
    registerNatives();
}

代码的第一行定义了registerNatives方法,在类加载是执行registerNatives方法,看名字大家都能猜出来他是负责注册本地方法的,包含registerNatives()方法的类被加载的时候,除了registerNatives()方法以外的所有本地方法会被注册,经过注册的方法才能被使用

在Java调用本地方法的时候需要首先通过System.loadLibrary()将包含本地方法实现的动态文件加载进内存,当Java程序需要调用本地方法时,虚拟机在加载的动态文件中定位并链接该本地方法,从而得以执行本地方法。registerNatives让程序主动将本地方法链接到调用方,当Java程序需要调用本地方法时就可以直接调用,而不需要虚拟机再去定位并链接。

关于本地方法以后再总结吧

equlas方法

比较调用者和入参的内存地址是否相同

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

对于String字符串类型其实更加在意内容是否相同,所以对他进行了重写,在我们自己写的JavaBean中,我们应该对内部的成员变量也判断一下,我们可以使用intelilJ IDEA生成这个方法

@Override
public boolean equals(Object o) {
    // 判断地址是否相同
    if (this == o) return true;
    // 判断参数是否为空,判断是否为相同的类
    if (o == null || getClass() != o.getClass()) return false;
    // 转换类型
    UserDTO userDTO = (UserDTO) o;
    // 判断内部
    return Objects.equals(username, userDTO.username) &&
            Objects.equals(password, userDTO.password) &&
            Objects.equals(age, userDTO.age) &&
            Objects.equals(sex, userDTO.sex);
}

这里有个小问题,如果让我自己写这个equals方法,我可能回写成这个样子

 @Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o instanceof BasicUserDTO){

        BasicUserDTO userDTO = (BasicUserDTO) o;
        return Objects.equals(username, userDTO.username) &&
            Objects.equals(password, userDTO.password) &&
            Objects.equals(age, userDTO.age) &&
            Objects.equals(sex, userDTO.sex);
    } else {
        return false;
    }
}

如果存在一个BasicUserDTO的子类UserDTO,以下用上面两种方法会有不同结果

BasicUserDTO basicUserDTO = new BasicUserDTO();
basicUserDTO.setAge(10);
basicUserDTO.setUsername("Rhythm");
basicUserDTO.setPassword("123456");
basicUserDTO.setSex('M');

UserDTO userDTO = new UserDTO();
userDTO.setAge(10);
userDTO.setUsername("Rhythm");
userDTO.setPassword("123456");
userDTO.setSex('M');

System.out.println(basicUserDTO.equals(userDTO));

IDEA生成的equals方法返回的是false,我自己写的则返回false,这里顺便复习一下getClass返回的是类名,而用intanceof是用于判断前面的引用变量是否是后面的类、子类或实现类,大部分情况还是用getClass比较合适,不需要考虑继承

IDEA生成的equals方法中使用到`Objects.equals,内部对入参和当前对象进行了非空判断,避免空指针

toString方法

这个方法返回的是类名和该类的哈希值,当我们执行System.out.println(变量)时默认调用这个方法

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

对于我们自己写的JavaBean来说,我们更希望实现返回该实体类的所有字段的值,所以我们都会选择重写这个方法

@Override
public String toString() {
    return "UserDTO{" +
            "username='" + username + '\'' +
            ", password='" + password + '\'' +
            ", age=" + age +
            ", sex=" + sex +
            '}';
}

hashCode方法

生成哈希值,是个本地方法,具体实现由操作系统提供

public native int hashCode();

在集合中使用散列码查询效率会比较高

在Java应用程序的执行过程中,无论什么时候在同一个对象上调用它,hashCode方法都必须返回相同的值,不过前提条件是不对对象上的equals比较中使用的信息进行修改。此整数不需要在应用程序的一次执行与同一应用程序的另一次执行之间保持一致。如果根据equals(Object)方法,两个对象是相等的,那么在每个对象上调用hashCode方法必须产生相同的整数结果。

意思就是只要equlas方法中判断的那些变量值不变,散列码就不会改变,如果调用equals方法返回true,他们的散列码必须相同

如果两个对象根据equals(Object)方法是不相等的,那么在每个对象上调用hashCode方法必须产生不同的整数结果,这是不需要的。

equals方法返回false,就不用保证散列码一致了

但是,为了不相等的对象生成不同的整数结果可能会提高哈希表的性能
在相当实际的情况下,类对象定义的hashCode方法确实会为不同的对象返回不同的整数。

意思就是我们还是要重写这个方法,hashCode方法是个本地方法,不需要我们实现

getClass方法

public final native Class<?> getClass();

返回运行时类型,在堆中this指针指着的那个类型

finalize方法

protected void finalize() throws Throwable { }

用于释放资源,当垃圾收集器准备收集这个对象时被调用,调用后下一次GC的时候对象才会被释放,这个方法基本不用,有以下几个原因

  • 程序只有在内存濒临不够用的时候才会去释放对象
  • 垃圾回收并非“析构”
  • 垃圾回收只跟内存打交道,释放的内存时经过再三考虑的,程序不会再去使用
这个方法资源的释放比较佛,大家还是自己释放资源吧

clone方法

protected native Object clone() throws CloneNotSupportedException;

克隆方法,这里需要注意一下,该方法只是复制了一份引用,新的引用还是指向原来的方法,这种方式称为浅克隆

浅克隆.png

完美的复制一份才叫深克隆,想实现深克隆

深克隆.png

在Java中要做到完美的克隆时比较难的,因为难免会用到别人的jar包,如果别人的jar包内部没有重写clone方法,那就不能做到完美地克隆了

深克隆很难.png

wait方法

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

使得当前线程进入睡眠状态,直到有线程调用此对象的notify方法或执行notifyAll方法的时候会被唤醒。

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

可以传入睡眠的时间

public final void wait(long timeout, int nanos) throws InterruptedException

也可以传入额外时间,不过这个方法是用微毫秒来计算的,公式为100 000 * timeout + nanos

进入睡眠的线程如何被唤醒呢:

  • 其他线程执行该对象的notify方法
  • 其他线程执行该对象的notifyAll方法
  • 时间到了
  • 其他线程通过interrupt中断该线程

notify方法

public final native void notify();

唤醒在该对象上睡眠的某个线程

notifyAll方法

public final native void notifyAll();

唤醒在该对象上睡眠的所有线程

阅读 225

推荐阅读