Object类大概是每个JAVA程序员认识的第一个类,因为它是所有其他类的祖先类。在JAVA单根继承的体系下,这个类中的每个方法都显得尤为重要,因为每个类都能够调用或者重写这些方法。当你JAVA学到一定阶段,尤其是学到了反射机制、多线程和JVM之后,再回过头看一眼这些方法,可能会有新的体会。

Object根类方法

public final native Class<?> getClass()

public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public String toString()

public final native void notify()

public final native void notifyAll()

public final native void wait(long timeout) throws InterruptedException

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

public final void wait() throws InterruptedException

protected void finalize() throws Throwable {}

equals()

equals()的实现:

  • 检查是否为同一个对象的引用,如果是直接返回 true;
  • 检查是否是同一个类型,如果不是,直接返回 false;
  • 将 Object 对象进行转型;
  • 判断每个关键域是否相等。

源码如下:

public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}

equals() 与 ==的区别:

  • 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
  • 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。

hashCode()

hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。这句话一定要想清楚,如果知道散列冲突的话,这句话也不难理解。在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。

比如下面这个例子,由于没有覆盖hashCode()方法,set会认为是两个不同的对象,去重失败。

EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size()); // 2

简单来说,hashCode() 方法通过哈希算法为每个对象生成一个整数值,称为散列值。

hashCode()方法的算法约定为:

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

下面以一个例子演示hashCode方法的覆写:

public class User {

    private long id;
private String name;
private String email;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (this.getClass() != o.getClass()) return false;
User user = (User) o;
return id != user.id
&& (!name.equals(user.name)
&& !email.equals(user.email));
}
}

上面的代码中重写了equals方法,hashCode方法的不同重现版本如下:

//实现1
@Override
public int hashCode() {
return 1;
} //实现2
@Override
public int hashCode() {
return (int) id * name.hashCode() * email.hashCode();
} //实现3(标准实现)
@Override
public int hashCode() {
int hash = 7;
hash = 31 * hash + (int) id;
hash = 31 * hash + (name == null ? 0 : name.hashCode());
hash = 31 * hash + (email == null ? 0 : email.hashCode());
return hash;
}
  • 实现1:

    哈希表中所有对象保存在同一个位置,哈希表退化成了链表。

  • 实现2:

    这个比较好,这样不同对象的哈希码发生的碰撞的概率就比较小了

  • 实现3(标准实现):

    用到了素数31,至于为什么要用31,Effective Java中做了比较清楚的解答,这里直接粘过来了:

    之所以选择31,是因为它是个素数。如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为与2相乘等价于移位运算。使用素数的好处并不明显。但是习惯上都使用素数来计算散列结果。31有很好的特性,即用移位和减法来代替乘法,可以得到更好的性能:31 * i == (i << 5) - i。现在 虚拟机可以自动完成这种优化。

    这段话的最后讲到了重点,即使用31还是主要出于效率上的考虑。

toString()

toString()方法用于返回对象的字符串表示,默认实现如下:

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

该字符串由类名 + ”@“ + 此对象散列值的无符号十六进制表示组成。输出对象时会自动调用toString方法把对象转化为字符串,比如System.out.println(obj);

Effective JAVA第12条指出:始终要覆盖toString方法。因为这种默认的输出形式不太可能是我们想要的,每个类应该实现自己的toString方法,比如下面这个例子:

public class User implements Serializable {
private long id;
private long phone;
private String name;
private String password;
@Override
public String toString() {
return "User{" + "id=" + id + ", phone=" + phone + ", name=" + name + ", password=" + password + "}";
}
}

clone()

clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。

一个类要重写clone()方法必须要实现Cloneable接口,即:

public class CloneDemo implements Cloneable {
private int a;
private int b; @Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

但是clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。实际上这个接口没有包含任何的方法,Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。这种规定看上去的确非常奇怪,难怪Effective JAVA作者会说「Cloneable是接口的一种极端非典型的用法,也不值得效仿。通常情况下,实现接口是为了表明类可以为它的客户做些什么。」

clone又分为浅拷贝和深拷贝,这两者的区别也在面试中经常被问到。

简单说,两者的区别就是,在浅拷贝中拷贝对象和原始对象的引用类型引用同一个对象,在深拷贝中拷贝对象和原始对象的引用类型引用不同对象。

下面以一个例子说明,这个例子来自Effective JAVA。假如我们要为HashTable类实现Clone方法,它的内部维护了一个节点数组,部分代码是这样的:

class HashTable implements Cloneable {
private Entry[] buckets; private static class Entry {
final Object key;
Object value;
Entry next; public Entry(Object key, Object value, Entry next) {
super();
this.key = key;
this.value = value;
this.next = next;
}
}
// clone()...
}

浅拷贝如下,虽然拷贝对象也有自己的散列桶数组,但这个数组引用的链表与原始数组是一样的,这样就会引发诸多不确定行为。

 @Override
protected Object clone() throws CloneNotSupportedException {
HashTable result = (HashTable) super.clone();
result.buckets = buckets.clone();
return result;
}

深拷贝如下,可以看到深拷贝用递归的方式重新创建了一个新的散列桶数组,和原对象的不同。

@Override
protected Object clone() throws CloneNotSupportedException {
HashTable result = (HashTable) super.clone();
result.buckets = new Entry[buckets.length];
for (int i = 0; i < buckets.length; i++)
if (buckets[i] != null)
result.buckets[i] = buckets[i].deepCopy();
return result;
}
private Entry deepCopy() {
return new Entry(key, value, next == null ? null : next.deepCopy());
}

Effective JAVA提到对象拷贝的更好的办法是使用拷贝工厂而不是重写clone()方法,除非拷贝的是数组。

getClass()

getClass方法利用反射机制获取当前对象的Class对象。getClass方法是一个final方法,不允许子类重写,并且也是一个native方法。

wait() / notify() / notifyAll()

这几个方法用于java多线程之间的协作。

  • wait():调用此方法所在的当前线程等待,直到在其他线程上调用notify() / notifyAll()方法唤醒该线程。
  • wait(long timeout):调用此方法所在的当前线程等待,直到在其他线程上调用notify() / notifyAll()方法或者等待时间超过传入参数表示的时间,该线程被唤醒。
  • notify() / notifyAll():唤醒在此对象监视器上等待的单个线程/所有线程。

下面举个例子说明:

public class ThreadTest {

    /**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
synchronized (r) {
try {
System.out.println("main thread 等待t线程执行完");
r.wait();
System.out.println("被notity唤醒,得以继续执行");
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("main thread 本想等待,但被意外打断了");
}
System.out.println("线程t执行相加结果" + r.getTotal());
}
}
} class MyRunnable implements Runnable {
private int total; @Override
public void run() {
synchronized (this) {
System.out.println("Thread name is:" + Thread.currentThread().getName());
for (int i = 0; i < 10; i++) {
total += i;
}
notify();
System.out.println("执行notify后同步代码块中依然可以继续执行直至完毕");
}
System.out.println("执行notify后且同步代码块外的代码执行时机取决于线程调度");
} public int getTotal() {
return total;
}
}

输出结果:

main thread 等待t线程执行完
Thread name is:Thread-0
执行notif后同步代码块中依然可以继续执行直至完毕
执行notif后且同步代码块外的代码执行时机取决于线程调度
被notity唤醒,得以继续执行
线程t执行相加结果45

既然是作用于多线程中,为什么却是Object这个基类所具有的方法?原因在于理论上任何对象都可以视为线程同步中的监听器,且wait() / notify() / notifyAll()方法只能在同步代码块中才能使用。

从上述例子的输出结果中可以得出如下结论:

1、wait()方法调用后当前线程将立即阻塞,且适当其所持有的同步代码块中的锁,直到被唤醒或超时或打断后且重新获取到锁后才能继续执行;

2、notify() / notifyAll()方法调用后,其所在线程不会立即释放所持有的锁,直到其所在同步代码块中的代码执行完毕,此时释放锁,因此,如果其同步代码块后还有代码,其执行则依赖于JVM的线程调度。

finalize()

finalize方法主要与Java垃圾回收机制有关,JVM准备对此对对象所占用的内存空间进行垃圾回收前,将会调用该对象的finalize方法。

finalize()与C++中的析构函数不是对应的。C++中的析构函数调用的时机是确定的(对象离开作用域或delete掉),但Java中的finalize的调用具有不确定性。

关于finalize方法的Best Practice就是在大多数时候不需要手动去调用该方法,让GC为你工作吧!

Object类:又回到最初的起点的更多相关文章

  1. Java的API及Object类、String类、字符串缓冲区

    Java 的API 1.1定义 API: Application(应用) Programming(程序) Interface(接口) Java API就是JDK中提供给开发者使用的类,这些类将底层的代 ...

  2. Object类和常用方法

    Object类是java语言的根类,要么是一个类的直接父类,要么就是一个类的间接父类.所有对象(包括数组)都实现这个类的方法. 引用数据类型:类/接口/数组,引用数据类型又称之位对象类,所谓的数组变量 ...

  3. JS面向对象(3) -- Object类,静态属性,闭包,私有属性, call和apply的使用,继承的三种实现方法

    相关链接: JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式 JS面向对象(2) -- this的使用,对 ...

  4. Object类clone方法的自我理解

    网上搜帖: clone()是java.lang.Object类的protected方法,实现clone方法: 1)类自身需要实现Cloneable接口 2)需重写clone()方法,最好设置修饰符mo ...

  5. Java中的Object类介绍

    Object类是所有类的父类,如果一个类没有使用extends关键字明确标识继承另外一个类,那么这个类默认继承Object类. Object类中的所有方法适用于所有子类 Object中比较常见的方法: ...

  6. Object类的toString方法

          Object类是所有Java类的祖先.每个类都使用 Object 作为超类.所有对象(包括数组)都实现这个类的方法.在不明确给出超类的情况下,Java会自动把Object作为要定义类的超类 ...

  7. Yii2的深入学习--yii\base\Object 类

    之前我们说过 Yii2 中大多数类都继承自 yii\base\Object,今天就让我们来看一下这个类. Object 是一个基础类,实现了属性的功能,其基本内容如下: <?php namesp ...

  8. 重写Object类中的equals方法

    Object是所有类的父亲,这个类有很多方法,我们都可以直接调用,但有些方法并不适合,例如下面的student类 public class Student { //姓名.学号.年纪 private S ...

  9. Java中Object类

    Object类是所有类的父类,如果一个类没有使用extends关键字明确标识继承另一个类,那么这个类默认继承Object类. Object类中的方法,适合所有子类. Object中的几个重要方法: 1 ...

随机推荐

  1. Scala中的Map集合

    1. Map集合 1.1 Scala中的Map介绍 Scala中的Map 和Java类似,也是一个散列表,它存储的内容也是键值对(key-value)映射,Scala中不可变的Map是有序的,可变的M ...

  2. 使用folderLeft函数统计字母出现的次数

    实例:统计字符串中字母出现的次数 import scala.collection.mutable object Demo_018{ def main(args: Array[String]): Uni ...

  3. 基于Java的二叉树的三种遍历方式的递归与非递归实现

    二叉树的遍历方式包括前序遍历.中序遍历和后序遍历,其实现方式包括递归实现和非递归实现. 前序遍历:根节点 | 左子树 | 右子树 中序遍历:左子树 | 根节点 | 右子树 后序遍历:左子树 | 右子树 ...

  4. 团队作业4:第一篇Scrum冲刺博客(歪瑞古德小队)

    目录 一.Alpha阶段任务认领 二.明日任务安排 三.项目预期任务量 四.敏捷开发前的感想 五.团队期望 Author:歪瑞古德小队 Project:海岛漂流 集合贴:团队作业4:项目冲刺集合贴(歪 ...

  5. 兄弟,你爬虫基础这么好,需要研究js逆向了,一起吧(有完整JS代码)

    这几天的确有空了,看更新多快,专门研究了一下几个网站登录中密码加密方法,比起滑块验证码来说都相对简单,适合新手js逆向入门,大家可以自己试一下,试不出来了再参考我的js代码.篇幅有限,完整的js代码在 ...

  6. MacOS开发环境搭建

    1 Java 安装jdk 下载安装即可,没什么可说的,着重说一下配置mac下的环境变量 $ /usr/libexec/java_home -V #查看安装的jdk版本和路径 $ vim ~/.bash ...

  7. ARDUBOY游戏开发之路(一) 初识ARDUBOY

    一.什么是ARDUBOY Arduboy是一个仅有信用卡大小的创造.分享游戏的开放平台.爱好者可以免费从Arduboy中选择一款经典的游戏,然后将游戏在目前最流行的arduino平台上编程.Ardub ...

  8. codeblocks显示:不支持的16位应用程序 解决办法

    我是win10 64位系统,写c++运行就会显示不兼容16位应用程序.以前编出来的exe还能用,今天编出的就炸了. 试了用vs编译.vs能用. 试了网上找的各种解决方案, 360修复, 注册表, 重构 ...

  9. 解决 SQL 注入和 XSS 攻击(Node.js 项目中)

    1.SQL 注入 SQL 注入,一般是通过把 SQL 命令插入到 Web 表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的 SQL 命令. SQL 注入示例 在登录界面,后端会根 ...

  10. 【HttpRunner v3.x】笔记 ——1. 环境安装

    一.环境说明 HttpRunner 是一个基于 Python 开发的测试框架,可以运行在 macOS.Linux.Windows 系统平台上.笔者使用的是windows系统,所以后续都是基于windo ...