Java源码解析之object类

在源码的阅读过程中,可以了解别人实现某个功能的涉及思路,看看他们是怎么想,怎么做的。接下来,我们看看这篇Java源码解析之object的详细内容。

Java基类Object

  java.lang.Object,Java所有类的父类,在你编写一个类的时候,若无指定父类(没有显式extends一个父类)编译器(一般编译器完成该步骤)会默认的添加Object为该类的父类(可以将该类反编译看其字节码,不过貌似Java7自带的反编译javap现在看不到了)。

  再说的详细点:假如类A,没有显式继承其他类,编译器会默认添加Object为其父类;若有,那么那个显式父类呢?要么是没有显式继承,那么Object是这个父类的父类,那肯定也是类A的父类,如果有,以此类推,所以,Object是Java所有类的祖先类(父类)。

声明

  1.本系列是JDK1.7(oracle)的源码分析,若和你查看到的源码有差异,请对比JDK版本。

  2.本系列是本人对Java源码的解析,但由于本人水平有限,势必不能做到完全解读,甚至只能说通过搜索阅读学习,做一些表面的解析,有不足之处,望指教,望谅解。

Object源码

public class Object {
 //本地方法,C/C++在DLL中实现,通过JNI调用
 private static native void registerNatives();
 //类初始化调用此方法
 static {
  registerNatives();
 }
 //返回此Object的运行时类(每个类的Class类对象)
 public final native Class<?> getClass();
 //获得该对象的hash值
 public native int hashCode();
 //对比两对象的内存地址,如果不重写,equals方法比较的是对象地址
 public boolean equals(Object obj) {
  return (this == obj);
 }
 //本地clone方法,用于对象的赋值
 protected native Object clone() throws CloneNotSupportedException;
 //返回对象的的字符串表示,默认是:类名+@+hash值
 public String toString() {
  return getClass().getName() + "@" + Integer.toHexString(hashCode());
 //notify()/notifyAll()/wait()以及wait两个重载方法都是线程同步相关方法
 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 {
  if (timeout < 0) {
   throw new IllegalArgumentException("timeout value is negative");
  }
  if (nanos < 0 || nanos > 999999) {
   throw new IllegalArgumentException(
        "nanosecond timeout value out of range");
  }
  if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
   timeout++;
  }
  wait(timeout);
 }
 public final void wait() throws InterruptedException {
  wait(0);
 }
 //对象被回收时调用,不管如何,一个对象只调用一次
 protected void finalize() throws Throwable { }

概述

  因为Object是Java所有类的祖先类,所以Java所有类都有Object中的方法,在看这些方法的时候要联系这些方法不是针对Objec一个类,而是所有类。

  既然是所有类共有,设计的时候肯定想的是所有类的共性,比如:equals方法就是用来比较任意两个相同类型对象是否相等的,toString是用来将任意对象转换成String,方便打印查看。当然,以上方法的实现都是默认的,想要实现自己的逻辑需要在自己类中覆盖重写。

  以上的native方法,在Oracle的jdk是看不到的,但在OpenJDK或其他开源JDK是可以找到对应的C/C++代码的。
源码详解

1.构造方法

  源码中并没有Object的构造方法,但是,同样的,编译器在编译期间会给Object(事实上,所有的Java类,只要类中没有构造方法,编译器都会默认的给一个空构造方法,若已有构造方法,则不会添加)一个默认的空的构造方法:

public Object(){}

2.registerNatives

  带有native修饰的都是本地方法,所谓的本地方法是不通过Java语言实现的方法,但可以通过JNI,像调用Java方法一样调用这些方法。详细的可以搜索查看JNI。

  这个方法的作用是对Object以下几个本地方法(hashCode/clone/notify等)进行注册(可以理解为,这个方法是告诉JVM这几个本地方法的实现映射),每一个有本地方法的都会有这个方法,但其内容不一样(因为注册的方法不一样嘛)。

3.getClass

  每一个类在被加载的时候,都会生成一个Class类实例,而这个方法就可以在运行时期获得对象(这里的对象是堆里的那个对象,也就是获得的是动态类型的那个类)的Class对象,Class对象主要用于反射。

class A{}
class B extends A{}
class C extends B{}
A a = new C();//对象new C()的静态类型是A,动态类型是C
B b = (B)a;//引用b指向的还是new C(),动态类型还是C
C c = (C)b;
System.out.println(a.getClass().getName());
System.out.println(b.getClass().getName());
System.out.println(c.getClass().getName());
//打印结果均是:com.xxx.test.C
//对象的动态类型是不会变的,即new后面那个类型(构造对象的那个类型),但是静态类
//型是由指向它的引用决定的,事实上可以这样理解对象只有动态类型,引用类型才是静态类型
//以上说的对象指的是堆里对象,而不是泛指Object o = new Object()中的o
//不明白静态类型,动态类型的可以自行百度

4.hashCode

  获得该对象的hash值,Java虚拟机规范并没有规定这个方法的具体实现,只是规定了同一个对象两次调用(任何条件情形下)这个方法返回的int值要想等(但并没有规定两个不同对象hash值一定不相同),具体实现由各个JVM厂商自己实现,所以返回的值意义并不一定(这里特指Object的hashCode方法),有可能返回的是对象的内存地址,也有可能是某个特定计算公式计算出来的值。

5.equals

  原则上或则说语义上,设计目的上,equals的作用意义,是用来比较两个对象是否相等,这里是我们通常理解的相等:即两个对象其内容是否相等,而不是程序上来看,两个对象是否是同一个对象,即比较其内存地址;如果想比较两个对象是否是同一个对象(这里是说两个引用是否指向同一个对象),直接用==比较即可(==比较的就是对象的内存地址)。但这里重要的是,对于Object来说,它并不能知道子类是如何判断他们的两个实例是如何equals的,所以,默认的equals实现,比较的是两对象内存地址,即,若子类不重写equals方法,其作用等同于==。

//如何重写equals方法实现判断内容相等?
//关键点取决于你的逻辑,你想让两个对象在什么时候相等,你逻辑上就怎么写
class A {
 public int a;
 public String b;
 public D d;

 @Override
 public boolean equals(Object o) {
  if (this == o) return true;//如果指向同一个对象,当然equals
  //如果o为null或两个对象类型都不相同,当然不equals
  if (o == null || getClass() != o.getClass()) return false;
  //动态类型相同,强制转换
  A a1 = (A) o;
  /*下面是自己的逻辑,判断两个对象是否相同,逻辑1 Begin*/
  if (a != a1.a) return false;
  if (b != null ? !b.equals(a1.b) : a1.b != null) return false;
  return d != null ? d.equals(a1.d) : a1.d == null;
  //全部字段相同,则equals。如果对象越复杂,想要实现全部字段相同,也就越复杂
  /* 逻辑1 End */
  /* 逻辑2 begin*/
  //只要字段a相同,就认为两个对象equals
  if(a == a1.a) return true;
  /* 逻辑2 end*/
 }
 @Override
 public int hashCode() {
  int result = a;
  result = 31 * result + (b != null ? b.hashCode() : 0);
  result = 31 * result + (d != null ? d.hashCode() : 0);
  return result;
 }
}
class D{
 public int a;
}

  网上说的,重写equals方法,必重写hashCode,其实不然,若确定所有地方都没有用到类似Map的地方,就不必重写hashCode,因为Map的诸多方法是有用到hashCode方法判断两对象是否相等,而若你仅仅是自己用来判断两个对象是否equals,也就不必重写hashCode(当然,还要确定其他地方不会用到hashCode的地方,比如,以后用,别人用等,不过一般的,推荐重写hashCode方法,这样保证任何地方都不会因此出错)。

  若hash值不相等,则两个对象肯定不等(不equals);

  若hash值相等,两个对象不一定相等(不一定equals)。

  equals相等,hash值肯定想等,也就是说,hash值相等时equals相等的必要条件。

  hashCode方法一般用来判断两个对象equals前置条件,用来排除,这样做的原因是,hashCode方法速度快,不相等的可快速否决掉,若hash相同,则再调用equals判断。

6.clone

  克隆对象,克隆一个与原先对象所有字段值相等的对象,从而获得一个新的对象,需要注意的是:

想要使用这个方法,对象类型必须实现Cloneable接口,否则会报错,原因是Object的clone方法有对对象类型验证,如没实现则报错抛异常;

clone方法返回的是一个新的对象,这个对象的创建不是通过new(除非你像下面那样不通过Object的clone方法重写)指令,而是JVM通过其他指令创建的;

clone有深度clone和浅clone,这主要是针对类中间具有引用类型而言划分的,详情可参看:Java clone深度解析。

class A{}
A a = new A();
a.clone();//报错,即抛CloneNotSupportedException异常
class A implements Cloneable{}//这样才不会
//但,若你重写clone方法,并且在这个方法中没有调用父clone(也就是Object)方法
class A{
 @Override
 public Object clone() throws CloneNotSupportedException{
  return new A();
 }
}
a.clone();//这个时候调用clone方法即使没有实现Cloneable方法也不会报错
//说白了,你要理解为什么调用clone方法要实现Cloneable的原因,而不是仅仅是记住
//当你理解了,你就能熟练掌握这些规则,而不是记住他们

7.toString

  toString这个方法算是Object比较常用的方法了,它的意义是提供将类的字段以String形式格式化输出这一功能,当然,同样的,Object不可能知道子类的字段信息,所以,默认toString输出的是:全路径类名+@+hash值。

  若你想要输出类的字段信息,需要重写toString方法,将该类字段信息以你自己的格式输出。

8.notify/notifyAll/wait

  这三个方法适用于线程同步,这里只简单介绍其作用,详细请参考:notify/notifyAll/wait。

  notify:随机唤醒等待(wait)队列中一个对象,使其需要该对象的线程继续执行;

  notifyAll:唤醒队列中所有对象

  wait:该对象陷入等待状态,需要该对象的线程将不能再继续执行,直到该对象由其他线程调用notify/notifyAll方法唤醒。

9.finalize

  在对象被GC(垃圾回收,详情可参考:Java GC概述)之前被调用(JVM主动调用),你可以重写这个方法,然后在这个对象回收之前做某些动作,这个方法对于这个对象来说只能调用一次,为什么会这么说呢?对象都回收了,没了,难道不是当然只能调用一次?不是这样的,若你理解了Java GC原理便知道,若当你在finalize方法中,将这个对象重新赋予了强引用,GC这个对象将失败,这个对象将继续存活,而下次这个对象又成为可回收对象了,GC回收这个对象的时候,这个对象的finalize方法将不会再执行。

  另外,需要区分的是:

  finalize不是C/C++中的析构函数,更不是释放内存的方法,它只是提供了在回收一个对象之前做某些操作,如果你熟悉C ,那你知道C 允许你为一个类定义一个撤消函数(destructor ),它在对象正好出作用域之前被调用。Java不支持这个想法也不提供撤消函数。finalize() 方法只和撤消函数的功能接近。当你对Java 有丰富经验时,你将看到因为Java使用垃圾回收子系统,几乎没有必要使用撤消函数。

  而且,在设计之初,这个方法就是为了兼容C/C++程序员习惯(对的,貌似就是这样),后来设计者也说,这是个失败的设计,所以,可以的话,在实践中忘掉这个方法吧。

class D{
 public static D d111;

 @Override
 protected void finalize() throws Throwable {
  super.finalize();
  d111 = this;//这个时候该对象第一次回收将失败,而以后将不会在执行该方法
  System.out.println("finalize a = " + this.a);
 }
}
D d = new D();
d = null;
//程序结束
//这个时候,虽然程序结束了,new D()对象也是可回收对象了,但是并不会执行
//finzlize,因为对于JVM来说GC的触发条件是内存不足,所以不会执行GC也就不会调用
//finzlize方法

总结

以上就是本文关于Java源码解析之object类的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:java.lang.Void类源码解析、浅谈Java多线程处理中Future的妙用(附源码)等,有什么问题可以随时留言,小编会及时回复大家的。感谢朋友们对我们网站的支持!

(0)

相关推荐

  • 详谈Java中的Object、T(泛型)、?区别

    因为最近重新看了泛型,又看了些反射,导致我对Object.T(以下代指泛型).?产生了疑惑. 我们先来试着理解一下Object类,学习Java的应该都知道Object是所有类的父类,注意:那么这就意味着它的范围非常广!首先记住这点,如果你的参数类型时Object,那么的参数类型将非常广! <Thinking in Java>中说很多原因促成了泛型的出现,最引人注目的一个原因就是为了创造容器类.这个要怎么来理解呢?我的理解是,可以抛开这个为了创造容器类这个,而是回到泛型的目的是限定某种类型上来.

  • java实现Object和Map之间的转换3种方式

    利用commons.BeanUtils实现Obj和Map之间转换,这种是最简单,也是最经常用的 public static Object mapToObject(Map<String, Object> map, Class<?> beanClass) throws Exception { if (map == null) return null; Object obj = beanClass.newInstance(); org.apache.commons.beanutils.B

  • 详谈Java中Object类中的方法以及finalize函数作用

    Object是所有类的父类,任何类都默认继承Object. 一.Object类中的方法 1.clone方法 保护方法,实现对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常. 主要是JAVA里除了8种基本类型传参数是值传递,其他的类对象传参数都是引用传递,我们有时候不希望在方法里讲参数改变,这是就需要在类中复写clone方法. 2.getClass方法 final方法,获得运行时类型. 3.toString方法 该方法

  • java json字符串转JSONObject和JSONArray以及取值的实例

    java json字符串转JSONObject和JSONArray以及取值的实例 实现代码: import net.sf.json.JSONArray; import net.sf.json.JSONObject; public class JsonTest { public static void main(String[] args) { String joStr = "{name:\"张三\",age:\"20\"}"; //将json字符

  • java Map转Object与Object转Map实现代码

    java Map转Object与 Object转Map 1.定义一个实体类: package reflect; public class User { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } publ

  • Java源码解析之object类

    在源码的阅读过程中,可以了解别人实现某个功能的涉及思路,看看他们是怎么想,怎么做的.接下来,我们看看这篇Java源码解析之object的详细内容. Java基类Object java.lang.Object,Java所有类的父类,在你编写一个类的时候,若无指定父类(没有显式extends一个父类)编译器(一般编译器完成该步骤)会默认的添加Object为该类的父类(可以将该类反编译看其字节码,不过貌似Java7自带的反编译javap现在看不到了). 再说的详细点:假如类A,没有显式继承其他类,编译

  • java源码解析之String类的compareTo(String otherString)方法

    一. 前言 最近我发现了一个事情,那就是在面试笔试中,好多公司都喜欢在String字符串上出问题,涉及到方方面面的知识,包括其中的一些常用方法. String 类代表字符串.Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现. 字符串是常量:它们的值在创建之后不能更改.字符串缓冲区支持可变的字符串.因为 String 对象是不可变的,所以可以共享. 近日研究了一下String类的一些方法, 通过查看源码, 对一些常用的方法也有了更透彻的认识, 也让我更加理解了设

  • Java源码解析之TypeVariable详解

    TypeVariable,类型变量,描述类型,表示泛指任意或相关一类类型,也可以说狭义上的泛型(泛指某一类类型),一般用大写字母作为变量,比如K.V.E等. 源码 public interface TypeVariable<D extends GenericDeclaration> extends Type { //获得泛型的上限,若未明确声明上边界则默认为Object Type[] getBounds(); //获取声明该类型变量实体(即获得类.方法或构造器名) D getGenericDe

  • Java源码解析之GenericDeclaration详解

    学习别人实现某个功能的设计思路,来提高自己的编程水平.话不多说,下面进入正题. GenericDeclaration 可以声明类型变量的实体的公共接口,也就是说,只有实现了该接口才能在对应的实体上声明(定义)类型变量,这些实体目前只有三个:Class(类).Construstor(构造器).Method(方法)(详见:Java源码解析之TypeVariable详解 源码 public interface GenericDeclaration { //获得声明列表上的类型变量数组 public T

  • Java源码解析之HashMap的put、resize方法详解

    一.HashMap 简介 HashMap 底层采用哈希表结构 数组加链表加红黑树实现,允许储存null键和null值 数组优点:通过数组下标可以快速实现对数组元素的访问,效率高 链表优点:插入或删除数据不需要移动元素,只需要修改节点引用效率高 二.源码分析 2.1 继承和实现 public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {

  • Java源码解析之接口Collection

    一.图示 二.方法定义 我们先想一想,公司如果要我们自己去封装一些操作数组或者链表的工具类,我么需要封装哪些功能呢?不妨就是统计其 大小,增删改查.清空或者是查看否含有某条数据等等.而collection接口就是把这些通常操作提取出来,使其更全面.更通用,那现在我们就来看看其源码都有哪些方法. //返回集合的长度,如果长度大于Integer.MAX_VALUE,返回Integer.MAX_VALUE int size(); //如果集合元素总数为0,返回true boolean isEmpty(

  • Java源码解析之接口List

    前言 List接口是Collection接口的三大接口之一,其中的数据可以通过位置检索,用户可以在指定位置插入数据.List的数据可以为空,可以重复.我们来看看api文档是怎么说的: 一.List特有的方法 我们这里就只关注和Collection不同的方法,主要有以下这些: //在指定位置,将指定的集合插入到当前的集合中 boolean addAll(int index, Collection<? extends E> c); //这是一个默认实现的方法,会通过Iterator的方式对每个元素

  • Java源码解析之超级接口Map

    前言 我们在前面说到的无论是链表还是数组,都有自己的优缺点,数组查询速度很快而插入很慢,链表在插入时表现优秀但查询无力.哈希表则整合了数组与链表的优点,能在插入和查找等方面都有不错的速度.我们之后要分析的HashMap就是基于哈希表实现的,不过在JDK1.8中还引入了红黑树,其性能进一步提升了. 今天我们来说一说超级接口Map. 一.接口Map Map是基于Key-Value的数据格式,并且key值不能重复,每个key对应的value值唯一.Map的key也可以为null,但不可重复. 在看Ma

  • Java源码解析之详解ImmutableMap

    一.案例场景 遇到过这样的场景,在定义一个static修饰的Map时,使用了大量的put()方法赋值,就类似这样-- public static final Map<String,String> dayMap= new HashMap<>(); static { dayMap.put("Monday","今天上英语课"); dayMap.put("Tuesday","今天上语文课"); dayMap.p

  • Java源码解析之详解ReentrantLock

    ReentrantLock ReentrantLock是一种可重入的互斥锁,它的行为和作用与关键字synchronized有些类似,在并发场景下可以让多个线程按照一定的顺序访问同一资源.相比synchronized,ReentrantLock多了可扩展的能力,比如我们可以创建一个名为MyReentrantLock的类继承ReentrantLock,并重写部分方法使其更加高效. 当一个线程调用ReentrantLock.lock()方法时,如果ReentrantLock没有被其他线程持有,且不存在

随机推荐