Java8源码 java.lang 包 01、Object 类

概述

Object 类是类继承结构的根。它是每个类的顶级父类。所有对象(包括数组)都实现这个类的方法。

Object 类中的方法如下:

Object类中的方法

equals()

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

可以看出默认情况下 equals 进行对象比较时只判断了对象是否是其自身,当有特殊的“相等”逻辑时,则需要覆盖 equals 方法。

equals 方法的通用约定:

  • 自反性: 对于任何非 null 的引用值 x,x.equals(x) 必须返回 true。
  • 对称性: 对于任何非 null 的引用值 x 和 y,当且仅当 y.equals(x) 返回 true 时,x.equals(y) 必须返回 true。
  • 传递性: 对于任何非 null 的引用值 x、y、z,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么x.equals(z) 也必须返回 ture。
  • 一致性: 对于任何非 null 的引用值 x 和 y,只要 equals 的比较操作在对象中所用的信息没有被修改,多次调用 x.equals(y) 就会一致的返回 ture,或者一致的返回 false。
  • 非空性: 对于任何非 null 的引用值 x,x.equals(null) 必须返回 false。

在类继承尤其要注意实现的 equals 方法是否满足约定。

一个不满足约定的例子如下:

有个 People 父类:

public class People {

    private float weight;
    private String name;
	
    public People(float weight, String name) {
        this.weight = weight;
        this.name = name;
    }
    
    @Override
    public boolean equals(Object o) {
        if (o != null && o instanceof People) {
            People people = (People) o;
            return (this.weight == people.weight) && Objects.equals(this.name, people.name);
        }

        return false;
    }
}

有个 Student 子类:

public class Student extends People {

    private String address;

    public Student(float weight, String name, String address) {
        super(weight, name);
        this.address = address;
    }

    @Override
    public boolean equals(Object o) {
        if (o != null && o instanceof Student) {
            Student student = (Student) o;
            return super.equals(student) && this.address.equals(student.address);
        }
        return false;
    }
}

执行一下比较:

 	public static void main(String[] args) {
        People people = new People(51.0F, "张三");
        Student student = new Student(51.0F, "张三", "北京");
        System.out.println(people.equals(student));	// true
        System.out.println(student.equals(people));	// false
    }

很显然,这并不符合对称性。

事实上:在扩展可实例化的类的同时,是无法既增加新的值组件,同时又保留 equals 的约定,除非愿意放弃面向对象的抽象所带来的优势。

那这个问题该如何解决呢:

方法一:

不要拿 People 和 Student 去比,这依赖于人的主观自觉,虽然可以既利用继承带来的优势,但总归还要取决于人,有一定风险。

方法二:

将继承转为组合。

public class Student {

    private String address;
    private People people;

    public Student(String address, People people) {
        this.address = address;
        this.people = people;
    }

    @Override
    public boolean equals(Object o) {
        if (o != null && o instanceof Student) {
            Student student = (Student) o;
            return people.equals(student.people) && this.address.equals(student.address);
        }
        return false;
    }
}

这时,再进行比较,结果是满足 equals 约定的。

    public static void main(String[] args) {
        People people = new People(51.0F, "张三");
        Student student = new Student("北京", people);
        System.out.println(people.equals(student));	// false
        System.out.println(student.equals(people));	// false
    }

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

hashcode()

public native int hashCode();

hashCode方法的约定:

(1)在应用程序执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode 方法都必须始终如一的返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。

(2)如果两个对象根据 equals(Object) 方法比较是相等的,那么调用这两个对象中任意一个对象的 hashCode 方法都必须产生同样的整数结果。

(3)如果两个对象根据 equals(Object) 方法比较是不相等的,那么调用这两个对象中任意一个对象的 hashCode 方法,则不一定要产生不同的整数结果。(尽管如此,理想的 hashCode 方法对不相等的对象,应当提供不同的 hashCode 值,这样的话再将对象作为 Map 的 key 时就可以提高散列表的性能)

如果覆盖了 equals 方法,但是没有覆盖 hashCode 方法,就会违反上述第 2 条约定。

用上面的 People 类来验证一下:

    public static void main(String[] args) {
        People people1 = new People(51.0F, "张三");
        People people2 = new People(51.0F, "张三");
        System.out.println(people1.equals(people2));	// true
        System.out.println(people1.hashCode() == people2.hashCode());	// false
    }

这会带来什么后果呢?如果把 people1 放到 HashMap 中,随后试图用一个 “相等” 的people2 来找回时,就会出问题。如下:

    public static void main(String[] args) {
        People people1 = new People(51.0F, "张三");
        People people2 = new People(51.0F, "张三");

        Map<People, Integer> peopleMap = new HashMap<>();
        peopleMap.put(people1, 1);
        System.out.println("the number of People:" + peopleMap.get(people2));
    }

执行程序,得到如下结果:

the number of People:null

toString()

Object 类的 toString 方法 等价于 getClass().getName() + "@" + Integer.toHexString(hashCode())

默认的 toString 方法仅仅返回类名+当前实例 hashCode 值的十六进制串,这非常不便于阅读,且没有包含实例中属性的值。

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

通过重写 toString 方法,可以输出需要的实例信息,例如可以对实例属性值进行格式化显示等等:

    @Override
    public String toString() {
        return "People{" +
                "weight=" + weight +
                ", name='" + name + '\'' +
                '}';
    }

除了手工编写格式化方式,我们还可以借助于一些第三方类库来实现实例的格式化,例如使用 Apache Commons Lang 工具包中的 ToStringBuilder 类。

ToStringBuilder

getClass()

public final native Class<?> getClass();

一个不可被重写的本地方法,返回运行时的类。

获得运行时类信息后,就可以站着 Class 类上做很多事。比如生成类的实例、获取类中的方法、获取类的注解等。

clone()

protected native Object clone() throws CloneNotSupportedException;

clone 方法将创建和返回该对象的一个拷贝。这个 “拷贝” 的精确含义取决于该对象的类。一般的含义是,对于任何对象 x,表达式 x.clone() != x 将会是 true,表达式 x.clone().getClass() == x.getClass() 将会是 true。并且通常情况下,表达式 x.clone().equals(x) 将会是 true。

定义有点啰嗦,见下例就明白了。

一个标准的 clone 实现需要做到以下两点:

  1. 调用 super.clone() 方法
  2. 对于对象中的所有引用类型,均需要实现 Cloneable 接口,并重写 clone 方法,然后对每个引用执行clone 方法。

注意,所有的数组都被视为实现接口 Cloneable。

示例代码:

public class Student extends People implements Cloneable {

    private String address;

    public Student(float weight, String name, String address) {
        super(weight, name);
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.address = this.address;
        return student;
    }

    public static void main(String[] args) {
        try {
            Student student1 = new Student(10.2F, "张三", "北京");
            Student student2 = (Student) student1.clone();
            System.out.println(student2 != student1);	//x.clone() != x 为 true
            System.out.println(student2.getClass() == student1.getClass());	// true
            System.out.println(student2.equals(student1));	//true
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

使用 clone 方法创建对象的的优点:

  • 速度快。clone 方法最终会调用 Object.clone() 方法,这是一个 native 方法,本质是内存块复制,所以在速度上比使用 new 创建对象要快。
  • 灵活。可以在运行时动态的获取对象的类型以及状态,从而创建一个对象。

当然,使用 clone 方法创建对象的缺点同样非常明显:

  • 实现深拷贝较为困难,需要整个类继承系列的所有类都很好的实现 clone 方法。
  • 需要处理 CloneNotSupportedException 异常。Object类中的 clone 方法被声明为可能会抛出CloneNotSupportedException,因此在子类中,需要对这一异常进行处理。

因此,如果想使用 clone 方法的话,需要非常谨慎。《Effective Java》中建议不应该实现 Cloneable 接口,而应该使用拷贝构造器或者拷贝工厂。

finalize()

protected void finalize() throws Throwable { }

根据 java api中的说法:

“当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法”。

“finalize 方法可以采取任何操作,其中包括再次使此对象对其他线程可用;不过,finalize 的主要目的是在不可撤消地丢弃对象之前执行清除操作”。

“Java 编程语言不保证哪个线程将调用某个给定对象的 finalize 方法”。

“对于任何给定对象,Java 虚拟机最多只调用一次 finalize 方法”。

尽管 finalize 在某些时候是有用的,但是在大部分情况下,还是不建议使用,基于以下几点:

(1)不保证会被 jvm 执行,且不知道何时才会执行。这就给程序执行带来了很大不确定性。

(2)不同的 jvm 垃圾回收算法不一致,在一个 jvm 上工作良好,可能在另一个 jvm 上未必有效。

(2)性能。根据《Effective Java》的描述,增加了 finalize 后,对象的创建和销毁时间慢了430 倍。

wait、notify、notifyAll

  • notify()

  • notifyAll()

  • wait(long)

  • wait(long,int)

  • wait()

这几个本地方法都是 public final native 的,不可以被子类重新。这三个方法提供了 java 线程间等待、挂起等协同机制,是 java 多线程的基础。等看到 JUC 包后再来补充~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值