Object类:又回到最初的起点
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类:又回到最初的起点的更多相关文章
- Java的API及Object类、String类、字符串缓冲区
Java 的API 1.1定义 API: Application(应用) Programming(程序) Interface(接口) Java API就是JDK中提供给开发者使用的类,这些类将底层的代 ...
- Object类和常用方法
Object类是java语言的根类,要么是一个类的直接父类,要么就是一个类的间接父类.所有对象(包括数组)都实现这个类的方法. 引用数据类型:类/接口/数组,引用数据类型又称之位对象类,所谓的数组变量 ...
- JS面向对象(3) -- Object类,静态属性,闭包,私有属性, call和apply的使用,继承的三种实现方法
相关链接: JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式 JS面向对象(2) -- this的使用,对 ...
- Object类clone方法的自我理解
网上搜帖: clone()是java.lang.Object类的protected方法,实现clone方法: 1)类自身需要实现Cloneable接口 2)需重写clone()方法,最好设置修饰符mo ...
- Java中的Object类介绍
Object类是所有类的父类,如果一个类没有使用extends关键字明确标识继承另外一个类,那么这个类默认继承Object类. Object类中的所有方法适用于所有子类 Object中比较常见的方法: ...
- Object类的toString方法
Object类是所有Java类的祖先.每个类都使用 Object 作为超类.所有对象(包括数组)都实现这个类的方法.在不明确给出超类的情况下,Java会自动把Object作为要定义类的超类 ...
- Yii2的深入学习--yii\base\Object 类
之前我们说过 Yii2 中大多数类都继承自 yii\base\Object,今天就让我们来看一下这个类. Object 是一个基础类,实现了属性的功能,其基本内容如下: <?php namesp ...
- 重写Object类中的equals方法
Object是所有类的父亲,这个类有很多方法,我们都可以直接调用,但有些方法并不适合,例如下面的student类 public class Student { //姓名.学号.年纪 private S ...
- Java中Object类
Object类是所有类的父类,如果一个类没有使用extends关键字明确标识继承另一个类,那么这个类默认继承Object类. Object类中的方法,适合所有子类. Object中的几个重要方法: 1 ...
随机推荐
- 非对称加密与HTTPS(转)
序:HTTPS更安全,为什么? 因为HTTP协议本身毫无安全性可言. 当你访问一个纯HTTP的网站(以及与这个网站有任何网络交互)时,你发出去一个请求.在这个请求到达网站服务器的路途上,不管是你家的路 ...
- 喵的Unity游戏开发之路 - 轨道摄像机
前言 很多童鞋没有系统的Unity3D游戏开发基础,也不知道从何开始学.为此我们精选了一套国外优秀的Unity3D游戏开发教程,翻译整理后放送给大家,教您从零开始一步一步掌握Unity3 ...
- golang 创建 tun 设备
源码: package main import ( "flag" "fmt" "github.com/pkg/errors" "n ...
- git提交限制后提交出错的暴力解决 (使用小乌龟)
1.右键-> TortoiseGit-> 显示日志 2.右键->重置到哪个版本 3. 重新修改提交信息提交
- 四则运算(C语言实现)
四则运算(c语言实现) 合伙人:魏甫——3118004973 ,温钦益——3118004975 https://github.com/iamdate/work/tree/master 一.项目及其要 ...
- Java数据结构——二叉树的遍历(汇总)
二叉树的遍历分为深度优先遍历(DFS)和广度优先遍历(BFS) DFS遍历主要有: 前序遍历 中序遍历 后序遍历 一.递归实现DFSNode.java: public class Node { pri ...
- 【转】C# 利用反射根据类名创建类的实例对象
原文地址:https://www.cnblogs.com/feiyuhuo/p/5793606.html “反射”其实就是利用程序集的元数据信息. 反射可以有很多方法,编写程序时请先导入 System ...
- Spark SQL dropDuplicates
spark sql 数据去重 在对spark sql 中的dataframe数据表去除重复数据的时候可以使用dropDuplicates()方法 dropDuplicates()有4个重载方法 第一个 ...
- C#-接口(Interface)详解
定义 在 C# 语言中,类之间的继承关系仅支持单重继承,而接口是为了实现多重继承关系设计的.一个类能同时实现多个接口,还能在实现接口的同时再继承其他类,并且接口之间也可以继承.无论是表示类之间的继承还 ...
- python os库的使用方法 + 自动化安装第三方库脚本
一.os库基本介绍 os库提供通用的.基本的操作系统交互功能,包括windows.Mac os.linux os库是python标准库,包含几百个函数 常用路径操作.进程管理.环境参数等几类 路径操作 ...