什么是Object类?
Object类存储在java.lang包中,是所有java类(Object类除外)的终极父类。当然,数组也继承了Object类。然而,接口是不继承Object类的。
Object类中声明了以下函数
protected Object clone() //创建并返回此对象的一个副本。
Class extends Object> getClass() //返回一个对象的运行时类。
String toString() //返回该对象的字符串表示。
int hashCode() //返回该对象的哈希码值。
boolean equals(Object obj) //指示某个其他对象是否与此对象“相等”。
void wait() //导致当前的线程等待,直到其他线程调用此对象的
notify() 方法或 notifyAll() 方法。
void wait(long timeout) //导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或notifyAll() 方法,或者超过指定的时间量。
void wait(long timeout, int nanos) //导致当前的线程等待,直到其他线程调用此对象的notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。
void notify() //唤醒在此对象监视器上等待的单个线程。
void notifyAll() //唤醒在此对象监视器上等待的所有线程。
protected void finalize() //当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
java的任何类都继承了这些函数,并且可以覆盖不被final修饰的函数。例如,没有final修饰的toString()函数可以被覆盖,但是final wait()函数就不行。
Java中可以直接继承Object类,但是我不推荐去写,因为java是单继承原则,谢了之后就不能再继承其他类了。并且默认是继承Object类的。
clone()函数是用来做什么的?
答:clone()可以产生一个相同的类并且返回给调用者。
clone()是如何工作的?
答:Object将clone()作为一个本地方法来实现,这意味着它的代码存放在本地的库中。当代码执行的时候,将会检查调用对象的类(或者父类)是否实现了java.lang.Cloneable接口(Object类不实现Cloneable)。如果没有实现这个接口,clone()将会抛出一个检查异常()——java.lang.CloneNotSupportedException,如果实现了这个接口,clone()会创建一个新的对象,并将原来对象的内容复制到新对象,最后返回这个新对象的引用。因此,当我们使用clone方法克隆一个自定义对象时,该类必须实现java.lang.Cloneable接口。
问:怎样调用clone()来克隆一个对象?
答:用想要克隆的对象来调用clone(),将返回的对象从Object类转换到克隆的对象所属的类,赋给对象的引用即可,这里的克隆一般是浅克隆。
getClass()方法是用来做什么的?
答: 通过gerClass()方法可以得到一个和这个类有关的java.lang.Class对象(字节码对象)。返回的Class对象是一个被static synchronized方法封装的代表这个类的对象;例如,static sychronized void foo(){}。这也是指向反射API。因为调用getClass()的对象的类是在内存中的,保证了类型安全。
还有其他方法得到Class对象吗?
答: 获取Class对象的方法有两种。可以使用类字面常量,它的名字和类型相同,后缀位.class;例如,Account.class。另外一种就是调用Class的forName()方法。类字面常量更加简洁,并且编译器强制类型安全;如果找不到指定的类编译就不会通过。
通过forName()可以动态地通过指定包名载入任意类型地引用。但是,不能保证类型安全,可能会导致Runtime异常。
toString() 方法实现了什么功能?
toString() 方法将根据调用它的对象返回其对象的字符串形式,通常用于debug。
当 toString() 方法没有被覆盖的时候,返回的字符串通常是什么样子的?
当 toString() 没有被覆盖的时候,返回的字符串格式是 类名@哈希值,哈希值是十六进制的。例如,假设有一个 Employee 类,toString() 方法返回的结果可能是 Empoyee@1c7b0f4d。
因此,一般在自定义类中,如果需要打印对象的所有成员变量信息,则需要在类里重写toString()方法。在集合中,默认实现了toString方法打印集合元素。
System.out.println(o.toString()); 和 System.out.println(o) 的区别是什么?
System.out.println(o.toString()); 和 System.out.println(o) 两者的输出结果中都包含了对象的字符串形式。区别是,System.out.println(o.toString()); 直接调用toString() 方法,而System.out.println(o) 则是隐式调用了 toString()。
euqals()函数是用来做什么的?
答:equals()函数可以用来检查一个对象与调用这个equals()的这个对象是否相等。一般常用来比较字符串是否相等。如果是基本数据类型一般用“==”来比较。
为什么不用“==”运算符来判断两个对象是否相等呢?
答:虽然“==”运算符可以比较两个数据是否相等,但是要来比较对象的话,恐怕达不到预期的结果。就是说,“==”通过是否引用了同一个对象来判断两个对象是否相等,这被称为“引用相等”。一般是比较对象的内存地址是否相等。这个运算符不能通过比较两个对象的内容来判断它们是不是逻辑上的相等。
覆盖equals()函数的时候要遵守那些规则?
答:覆盖equals()函数的时候需要遵守的规则:
自反性:对于任意非空的引用值x,x.equals(x)返回值为真。
对称性:对于任意非空的引用值x和y,x.equals(y)必须和y.equals(x)返回相同的结果。
传递性:对于任意的非空引用值x,y和z,如果x.equals(y)返回真,y.equals(z)返回真,那么x.equals(z)也必须返回真。
一致性:对于任意非空的引用值x和y,无论调用x.equals(y)多少次,都要返回相同的结果。在比较的过程中,对象中的数据不能被修改。
对于任意的非空引用值x,x.equals(null)必须返回假。
hashCode()方法是用来做什么的?
答:hashCode()方法返回给调用者此对象的哈希码(其值由一个hash函数计算得来)。这个方法通常用在基于hash的集合类中,像java.util.HashMap,java.until.HashSet和java.util.Hashtable.
在类中覆盖equals()的时候,为什么要同时覆盖hashCode()?
答: 在覆盖equals()的时候同时覆盖hashCode()可以保证对象的功能兼容于hash集合。这是一个好习惯,即使这些对象不会被存储在hash集合中,如果要用集合进行存储对象,必须要重写equals()和hashCode()方法。
hashCode()的一般规则如下:
在同一个Java程序中,对一个相同的对象,无论调用多少次hashCode(),hashCode()返回的整数必须相同,因此必须保证equals()方法比较的内容不会更改。但不必在另一个相同的Java程序中也保证返回值相同。
如果两个对象用equals()方法比较的结果是相同的,那么这两个对象调用hashCode()应该返回相同的整数值。
当两个对象使用equals()方法比较的结果是不同的,hashCode()返回的整数值可以不同。然而,hashCode()的返回值不同可以提高哈希表的性能。
如果覆盖了equals()却不覆盖hashCode()会有什么后果?
答: 当覆盖equals()却不覆盖hashCode()的时候,在hash集合中存储对象时就会出现问题。
更多equals和hashCode详细介绍
wait(),notify() 和 notifyAll() 是用来干什么的?
wait(),notify() 和 notifyAll() 可以让线程协调完成一项任务。例如,一个线程生产,另一个线程消费。生产线程不能在前一产品被消费之前运行,而应该等待前一个被生产出来的产品被消费之后才被唤醒,进行生产。同理,消费线程也不能在生产线程之前运行,即不能消费不存在的产品。所以,应该等待生产线程执行一个之后才执行。利用这些方法,就可以实现这些线程之间的协调。从本质上说,一个线程等待某种状态(例如一个产品被生产),另一个线程正在执行,知道产生了某种状态(例如生产了一个产品)。
不同的 wait() 方法之间有什么区别?
没有参数的 wait() 方法被调用之后,线程就会一直处于睡眠状态,直到本对象(就是 wait() 被调用的那个对象)调用 notify() 或 notifyAll() 方法。相应的wait(long timeout)和wait(long timeout, int nanos)方法中,当等待时间结束或者被唤醒时(无论哪一个先发生)将会结束等待。
notify() 和 notifyAll() 方法有什么区别?
notify() 方法随机唤醒一个等待的线程,而 notifyAll() 方法将唤醒所有在等待的线程。
线程被唤醒之后会发生什么?
当一个线程被唤醒之后,除非本对象(调用 notify() 或 notifyAll() 的对象)的同步锁被释放,否则不会立即执行。唤醒的线程会按照规则和其他线程竞争同步锁,得到锁的线程将执行。所以notifyAll()方法执行之后,可能会有一个线程立即运行,也可能所有的线程都没运行。
finalize()方法是用来做什么的?
finalize()方法可以被子类对象所覆盖,然后作为一个终结者,当GC被调用的时候完成最后的清理工作(例如释放系统资源之类)。这就是终止。默认的finalize()方法什么也不做,当被调用时直接返回。
对于任何一个对象,它的finalize()方法都不会被JVM执行两次。如果你想让一个对象能够被再次调用的话(例如,分配它的引用给一个静态变量),注意当这个对象已经被GC回收的时候,finalize()方法不会被调用第二次。
有人说要避免使用finalize()方法,这是真的吗?
答: 通常来讲,你应该尽量避免使用finalize()。相对于其他JVM实现,终结器被调用的情况较少——可能是因为终结器线程的优先级别较低的原因。如果你依靠终结器来关闭文件或者其他系统资源,可能会将资源耗尽,当程序试图打开一个新的文件或者新的系统资源的时候可能会崩溃,就因为这个缓慢的终结器。
应该使用什么来替代终结器?
答: 提供一个明确的用来销毁这个对象的方法(例如,java.io.FileInputStream的void close()方法),并且在代码中使用try - finally结构来调用这个方法,以确保无论有没有异常从try中抛出,都会销毁这个对象。参考下面释放锁的代码:
什么情况下适合使用终结器?
答: 终结器可以作为一个安全保障,以防止声明的终结方法(像是java.io.FileOutputStream对象的close()方法或者java.util.concurrent.Lock对象的Lock()方法)没有被调用。万一这种情况出现,终结器可以在最后被调用,释放临街资源。