Java Class Object

来源:互联网 发布:路由器怎么设置网络快 编辑:程序博客网 时间:2024/06/08 03:35

刚开始学的时候,老师对我们说的第一句话就是在Java的世界里一切都是对象。那么这个对象是什么呢?就是在java.lang包下面的Object对象。Object是一个具体的类,它的设计主要是为了扩展。在JDK中对于Object的描述如下:

/**
* Class {@code Object} is the root of the class hierarchy.
* Every class has {@code Object} as a superclass. All objects,
* including arrays, implement the methods of this class.
*
* @author unascribed
* @see java.lang.Class
* @since JDK1.0
*/

它的意思是Object在类层级中是root,是每个类的父类。所有对象,包括数组以及类里面的方法。

1、Object与接口的关系

但是这里面没有提及到接口。而且我们在IDE里面查询对象以及接口的层级结构也不一样:
我们可以看到在java.util.Timer的类表示.

Timer.class
这里写图片描述

但是我们可以使用Ctrl + h查看它的类结构体系如下:

这里写图片描述

然后我们再来查看JDK中的接口java.util.concurrent.Future的类继承体系。

这里写图片描述

可以看到Future接口使用idea查看类结构体系并没有Object。但是当我们使用接口实例调用Object中的方法时并不会报错。

public class InterfaceHierarchyTest {    public void invokeObjectMethodByInterface(Runnable runnable){        System.out.println(runnable.toString());    }}

在上面的类中,我们可以看到,在runnable这个接口实例中可以使用Object中的toString()方法。但是如果我们重写了Object的方法,不按照重写规则就会报编译错误。

这里写图片描述

但是我们可以在查看Java虚拟机规范 – 9.2. Interface Members。

If an interface has no direct superinterfaces, then the interface implicitly declares a public abstract member method m with signature s, return type r, and throws clause t corresponding to each public instance method m with signature s, return type r, and throws clause t declared in Object, unless an abstract method with the same signature, same return type, and a compatible throws clause is explicitly declared by the interface.It is a compile-time error if the interface explicitly declares such a method m in the case where m is declared to be final in Object.It is a compile-time error if the interface explicitly declares a method with a signature that is override-equivalent (§8.4.2) to a public method of Object, but which has a different return type, or an incompatible throws clause, or is not abstract.

如果一个没有直接父类接口,接口声明了一个public abstract方法m,JVM将会为接口标隐含定义了Object类中的方法签名完全相同的方法。除非在接口明确的定义了在接口中定义了同样方法签名,同样返回值以及throws同样的异常。

但是要注意以下两点:

  1. 如果在接口中定义了与Object定义相同的方法,并且声明为final。那么就会出现编译异常。
  2. 如果在接口中定义了与Object定义签名相同的方法。但是它不符合Object的重写规范那。就是返回值类型不同,或者抛出不兼容的异常,或者不是abstract的方法。也会出现编译异常。

2、Object中的方法

下面我们就来看一看Object里面的方法。Object作为通用的类,作为通用的类不能够满足于各种各样的业务场景,所以对于它所有的非final方法(equals、hashcode、toString、clone和finalize)都有明确的通用约定,因为它们的设计成是要被覆盖的。任何一个类,它在覆盖这些方法的时候,都有责任遵守这些通用约定,其他依赖于这些约定的类(例如HashMap和HashSet)就无法结合该类一起正常工作了。

2.1 getClass – 获取当前对象的类类型

获取当前对象的类类型,使用这个对象可以执行反射操作。如果对反射有疑惑可以参看我之前的Blog – Java JDK Reflect.

2.2 hashCode – 获取当前对象的hashCode

返回当前对象的hash code值。当这个对象需要使用到hash表的时候需要重写这个方法。同时也需要重写equals方法。

2.3 equals – 判断两个对象是否相等

表明其它对象是否写当前对象相等。默认是判断两个对象的引用地址是否相等。而在Effective Java中推荐覆盖equals方法的时候总要覆盖hashCode方法。而需要覆盖equals方法的时候需要遵守它的通用规定。下面就约定的内容:

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

2.4 clone – 对象克隆

创建并且返回这个对象的copy。精确的表达”copy”依赖于该对象的类。一般情况下,对于对象x:

  1. 表达式:x.clone() != x,将会为true
  2. 表达式:x.clone().getClass() == x.getClass(),将会为true
  3. 表达式:x.clone().equals(x),将会为true.
    默认情况下满足这种情况,但这个并不是绝对的。
    按照约定:一般通过调用super.clone获取到返回对象。如果一个类以及它的所有超类就会出现:x.clone().getClass() == x.getClass().

而且Object类并没有实现Cloneable接口。因此如果一个类并没有实现Cloneable接口,而且它的实例还调用clone方法就会出现CloneNotSupportedException.

2.5 toString – 返回代表这个对象的String

虽然java.lang.Object提供了toString方法的一个实现,但它返回的是类的名称以及一个”@”符号,接着是散列码的无符号十六进制表示法。例如:Object@163b91,这种表示方法对于用户是非常不友好的。所以toString的约定是:建议所有的子类都覆盖这个方法。

2.6 notify – 唤醒被当前线程监控正在等待的一个线程

唤醒被当前线程监控正在等待的一个线程。如果有这个对象中许多线程正在等待,就会从它们中选择一个来唤醒。这个选择是根据各个不同的系统来任意决定的,也就是说多个线程在waiting是随机选择一个线程来唤醒。

这个方法仅仅能够被是这个对象的monitor调用。一个线程可以通过以下3种方式来成为这个对象的monitor的拥有者。

  1. 执行这个对象的同步实例方法。
  2. 执行这个对象的同步代码块。
  3. 对于Class对象,执行一个同步静态方法。

并且在同一时间只能有一个线程做为一个对象minitor的拥有者。
具体可以参看:Java的wait(), notify()和notifyAll()使用小结

2.7 notifyall – 唤醒被当前线程监控正在等待所有线程

唤醒被当前线程监控正在等待所有线程。如果是一个线程被这个对象monitor的话,可以调用wait方法。

这个方法也仅难能被这个对象的monitor的拥有者调用。具体可以看一下notity方法中介绍一个线程如何才能够成功一个monitor的拥有者。

2.8 wait() || wait(long) || wait(long, int)

这三个方法的功能都是一样的,都是让线程进入等待状态。只是等待的超时时间的设置方式不同。调用这三个方法都是暂停当前线程,直到另外一个线程调用notify方法或者notifyall方法或者其它线程中断了当前线程,或者过了超时时间。

2.9 finalize – 调用这个对象的垃圾回收

当没有引用指向这个对象的时候,调用这个对象的垃圾回收。子类可以重写finalize方法用来处理系统资源或者执行其它清除操作。

一般情况下当JVM确定这个对象没有被其它对象引用的时候,就会调用finalize方法。一般的纯Java编写的Class不需要重新覆盖这个方法,因为Object已经实现了一个默认的,除非我们要实现特殊的功能(这 里面涉及到很多东西,比如对象空间树等内容)。

不过用Java以外的代码编写的Class(比如JNI,C++的new方法分配的内存),垃圾回收器并不能对这些部分进行正确的回收,这时就需要我们覆盖默认的方法来实现对这部分内存的正确释放和回收(比如C++需要delete)。

总之,finalize相当于析构函数,他是垃圾回收器回收一个对象的时候第一个要调用的方法。不过由于Java的垃圾回收机制能自动为我们做这些事情,所以我们在一般情况下是不需要自己来手工释放的。

3、Object 与 Class

在知乎上看到了一个很意思的话题。就是先有Class还是先有Object?
Java的对象模型中:

  1. 所有的类都是Class类的实例,Object是类,那么Object也是Class类的一个实例。
  2. 所有的类都最终继承自Object类,Class是类,那么Class也继承自Object。

这就像是先有鸡还是先有蛋的问题,请问实际中JVM是怎么处理的?

具体地址:先有Class还是先有Object?

大家有兴趣可以看看。

0 0