Java知识点系列:java.lang.Object

Object类位于java.lang包中,java.lang包包含着Java最基础和核心的类,在编译时会自动导入。Object类没有定义属性,一共有13个方法,具体的类定义结构以下图:java

1.类构造器public Object();算法

大部分状况下,Java中经过形如 new A(args..)形式建立一个属于该类型的对象。其中A便是类名,A(args..)即此类定义中相对应的构造函数。经过此种形式建立的对象都是经过类中的构造函数完成。为体现此特性,Java中规定:在类定义过程当中,对于未定义构造函数的类,默认会有一个无参数的构造函数,做为全部类的基类,Object类天然要反映出此特性,在源码中,未给出Object类构造函数定义,但实际上,此构造函数是存在的。多线程

固然,并非全部的类都是经过此种方式去构建,也天然的,并非全部的类构造函数都是public。ide

2.private static native void registerNatives();函数

registerNatives函数前面有native关键字修饰,Java中,用native关键字修饰的函数代表该方法的实现并非在Java中去完成,而是由C/C++去完成,并被编译成了.dll,由Java去调用。方法的具体实现体在dll文件中,对于不一样平台,其具体实现应该有所不一样。用native修饰,即表示操做系统,须要提供此方法,Java自己须要使用。具体到registerNatives()方法自己,其主要做用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。性能

既然如此,可能有人会问,registerNatives()修饰符为private,且并无执行,做用何以达到?其实,在Java源码中,此方法的声明后有紧接着一段静态代码块:ui

1 private static native void registerNatives();
2 static {
3     registerNatives();
4 }
 

3.protected native Object clone() throws CloneNotSupportedException;this

看,clone()方法又是一个被声明为native的方法,所以,咱们知道了clone()方法并非Java的原生方法,具体的实现是有C/C++完成的。clone英文翻译为"克隆",其目的是建立并返回此对象的一个副本。形象点理解,这有一辆科鲁兹,你看着不错,想要个如出一辙的。你调用此方法便可像变魔术同样变出一辆如出一辙的科鲁兹出来。配置同样,长相同样。但今后刻起,原来的那辆科鲁兹若是进行了新的装饰,与你克隆出来的这辆科鲁兹没有任何关系了。你克隆出来的对象变不变彻底在于你对克隆出来的科鲁兹有没有进行过什么操做了。Java术语表述为:clone函数返回的是一个引用,指向的是新的clone出来的对象,此对象与原对象分别占用不一样的堆空间。编码

明白了clone的含义后,接下来看看若是调用clone()函数对象进行此克隆操做。操作系统

首先看一下下面的这个例子:

复制代码

1 package com.corn.objectsummary;
 2 
 3 import com.corn.Person;
 4 
 5 public class ObjectTest {
 6 
 7     public static void main(String[] args) {
 8 
 9         Object o1 = new Object();
10         // The method clone() from the type Object is not visible
11         Object clone = o1.clone();
12     }
13 
14 }

复制代码

例子很简单,在main()方法中,new一个Oject对象后,想直接调用此对象的clone方法克隆一个对象,可是出现错误提示:"The method clone() from the type Object is not visible"

why? 根据提示,第一反应是ObjectTest类中定义的Oject对象没法访问其clone()方法。回到Object类中clone()方法的定义,能够看到其被声明为protected,估计问题就在这上面了,protected修饰的属性或方法表示:在同一个包内或者不一样包的子类能够访问。显然,Object类与ObjectTest类在不一样的包中,可是ObjectTest继承自Object,是Object类的子类,因而,如今却出现子类中经过Object引用不能访问protected方法,缘由在于对"不一样包中的子类能够访问"没有正确理解。

"不一样包中的子类能够访问",是指当两个类不在同一个包中的时候,继承自父类的子类内部且主调(调用者)为子类的引用时才能访问父类用protected修饰的成员(属性/方法)。 在子类内部,主调为父类的引用时并不能访问此protected修饰的成员。!(super关键字除外)

因而,上例改为以下形式,咱们发现,能够正常编译:

复制代码

1 package com.corn.objectsummary;
 2 
 3 
 4 public class ObjectTest {
 5 
 6     public static void main(String[] args) {
 7         ObjectTest ot1 = new ObjectTest();
 8 
 9         try {
10             ObjectTest ot2 = (ObjectTest) ot1.clone();
11         } catch (CloneNotSupportedException e) {
12             // TODO Auto-generated catch block
13             e.printStackTrace();
14         }
15     }
16 
17 }

复制代码

是的,由于此时的主调已是子类的引用了。

上述代码在运行过程当中会抛出"java.lang.CloneNotSupportedException",代表clone()方法并未正确执行完毕,问题的缘由在与Java中的语法规定:

clone()的正确调用是须要实现Cloneable接口,若是没有实现Cloneable接口,而且子类直接调用Object类的clone()方法,则会抛出CloneNotSupportedException异常。

Cloneable接口仅是一个表示接口,接口自己不包含任何方法,用来指示Object.clone()能够合法的被子类引用所调用。

因而,上述代码改为以下形式,便可正确指定clone()方法以实现克隆。

复制代码

1 package com.corn.objectsummary;
 2 
 3 public class ObjectTest implements Cloneable {
 4 
 5     public static void main(String[] args) {
 6 
 7         ObjectTest ot1 = new ObjectTest();
 8 
 9         try {
10             ObjectTest ot2 = (ObjectTest) ot1.clone();
11             System.out.println("ot2:" + ot2);
12         } catch (CloneNotSupportedException e) {
13             // TODO Auto-generated catch block
14             e.printStackTrace();
15         }
16     }
17 
18 }

复制代码

4.public final native Class<?> getClass();

getClass()也是一个native方法,返回的是此Object对象的类对象/运行时类对象Class<?>。效果与Object.class相同。

首先解释下"类对象"的概念:在Java中,类是是对具备一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例。做为概念层次的类,其自己也具备某些共同的特性,如都具备类名称、由类加载器去加载,都具备包,具备父类,属性和方法等。因而,Java中有专门定义了一个类,Class,去描述其余类所具备的这些特性,所以,今后角度去看,类自己也都是属于Class类的对象。为与常常意义上的对象相区分,在此称之为"类对象"。

此处主要大量涉及到Java中的反射知识,关于反射相关知识后续也会给出相关博文。

 

5.public boolean equals(Object obj);

==与equals在Java中常常被使用,你们也都知道==与equals的区别:

==表示的是变量值完成相同(对于基础类型,地址中存储的是值,引用类型则存储指向实际对象的地址);

equals表示的是对象的内容彻底相同,此处的内容多指对象的特征/属性。

实际上,上面说法是不严谨的,更多的只是常见于String类中。首先看一下Object类中关于equals()方法的定义:

1 public boolean equals(Object obj) {
2     return (this == obj);
3 }

因而可知,Object原生的equals()方法内部调用的正是==,与==具备相同的含义。既然如此,为何还要定义此equals()方法?

equlas()方法的正确理解应该是:判断两个对象是否相等。那么判断对象相等的标尺又是什么?

如上,在object类中,此标尺即为==。固然,这个标尺不是固定的,其余类中能够按照实际的须要对此标尺含义进行重定义。如String类中则是依据字符串内容是否相等来重定义了此标尺含义。如此能够增长类的功能型和实际编码的灵活性。固然了,若是自定义的类没有重写equals()方法来从新定义此标尺,那么默认的将是其父类的equals(),直到object基类。

以下场景的实际业务需求,对于User bean,由实际的业务需求可知当属性uid相同时,表示的是同一个User,即两个User对象相等。则能够重写equals以重定义User对象相等的标尺。

复制代码

1 package com.corn.objectsummary;
 2 
 3 public class User {
 4 
 5     private int uid;
 6     private String name;
 7     private int age;
 8 
 9     public int getUid() {
10         return uid;
11     }
12 
13     public void setUid(int uid) {
14         this.uid = uid;
15     }
16 
17     protected String getName() {
18         return name;
19     }
20 
21     public void setName(String name) {
22         this.name = name;
23     }
24 
25     public int getAge() {
26         return age;
27     }
28 
29     public void setAge(int age) {
30         this.age = age;
31     }
32 
33     @Override
34     public boolean equals(Object obj) {
35         if (obj == null || !(obj instanceof User)) {
36             return false;
37         }
38         if (((User) obj).getUid() == this.getUid()) {
39             return true;
40         }
41         return false;
42     }
43 }

复制代码

复制代码

1 package com.corn.objectsummary;
 2 
 3 public class ObjectTest implements Cloneable {
 4 
 5     public static void main(String[] args) {
 6         User u1 = new User();
 7         u1.setUid(111);
 8         u1.setName("张三");
 9 
10         User u2 = new User();
11         u2.setUid(111);
12         u2.setName("张三丰");
13 
14         System.out.println(u1.equals(u2)); //返回true
15     }
16 
17 }

复制代码

ObjectTest中打印出true,由于User类定义中重写了equals()方法,这很好理解,极可能张三是一我的小名,张三丰才是其大名,判断这两我的是否是同一我的,这时只用判断uid是否相同便可。

如上重写equals方法表面上看上去是能够了,实则否则。由于它破坏了Java中的约定:重写equals()方法必须重写hasCode()方法。

 

6.public native int hashCode();

hashCode()方法返回一个整形数值,表示该对象的哈希码值。

hashCode()具备以下约定:

1).在Java应用程序程序执行期间,对于同一对象屡次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未作修改。在Java应用程序的一次执行到另一次执行,同一对象的hashCode()返回的哈希码无须保持一致;

2).若是两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等;

3).反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不必定相等。

即严格的数学逻辑表示为: 两个对象相等 <=>  equals()相等  => hashCode()相等。所以,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时能够推理出:hasCode()不相等 => equals()不相等 <=> 两个对象不相等。

可能有人在此产生疑问:既然比较两个对象是否相等的惟一条件(也是冲要条件)是equals,那么为何还要弄出一个hashCode(),而且进行如此约定,弄得这么麻烦?

其实,这主要体如今hashCode()方法的做用上,其主要用于加强哈希表的性能。

以集合类中,以Set为例,当新加一个对象时,须要判断现有集合中是否已经存在与此对象相等的对象,若是没有hashCode()方法,须要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。经过借助于hasCode方法,先计算出即将新加入对象的哈希码,而后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象便可。(注:Set的底层用的是Map的原理实现)

在此须要纠正一个理解上的误区:对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不必定是对象的逻辑地址,hashCode()相同的两个对象,不必定相等,换言之,不相等的两个对象,hashCode()返回的哈希码可能相同。

所以,在上述代码中,重写了equals()方法后,须要重写hashCode()方法。

复制代码

1 package com.corn.objectsummary;
 2 
 3 public class User {
 4 
 5     private int uid;
 6     private String name;
 7     private int age;
 8 
 9     public int getUid() {
10         return uid;
11     }
12 
13     public void setUid(int uid) {
14         this.uid = uid;
15     }
16 
17     protected String getName() {
18         return name;
19     }
20 
21     public void setName(String name) {
22         this.name = name;
23     }
24 
25     public int getAge() {
26         return age;
27     }
28 
29     public void setAge(int age) {
30         this.age = age;
31     }
32 
33     @Override
34     public boolean equals(Object obj) {
35         if (obj == null || !(obj instanceof User)) {
36             return false;
37         }
38         if (((User) obj).getUid() == this.getUid()) {
39             return true;
40         }
41         return false;
42     }
43 
44     @Override
45     public int hashCode() {
46         int result = 17;
47         result = 31 * result + this.getUid();
48         return result;
49     }
50 }

复制代码

注:上述hashCode()的重写中出现了result*31,是由于result*31 = (result<<5) - result。之因此选择31,是由于左移运算和减运算计算效率远大于乘法运算。固然,也能够选择其余数字。

 

7.public String toString();

toString()方法返回该对象的字符串表示。先看一下Object中的具体方法体:

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

toString()方法相信你们都常常用到,即便没有显式调用,但当咱们使用System.out.println(obj)时,其内部也是经过toString()来实现的。

getClass()返回对象的类对象,getClassName()以String形式返回类对象的名称(含包名)。Integer.toHexString(hashCode())则是以对象的哈希码为实参,以16进制无符号整数形式返回此哈希码的字符串表示形式。

如上例中的u1的哈希码是638,则对应的16进制为27e,调用toString()方法返回的结果为:com.corn.objectsummary.User@27e。

所以:toString()是由对象的类型和其哈希码惟一肯定,同一类型但不相等的两个对象分别调用toString()方法返回的结果可能相同。

8/9/10/11/12. wait(...) / notify() / notifyAll()

一说到wait(...) / notify() | notifyAll()几个方法,首先想到的是线程。确实,这几个方法主要用于java多线程之间的协做。先具体看下这几个方法的主要含义:

wait():调用此方法所在的当前线程等待,直到在其余线程上调用此方法的主调(某一对象)的notify()/notifyAll()方法。

wait(long timeout)/wait(long timeout, int nanos):调用此方法所在的当前线程等待,直到在其余线程上调用此方法的主调(某一对象)的notisfy()/notisfyAll()方法,或超过指定的超时时间量。

notify()/notifyAll():唤醒在此对象监视器上等待的单个线程/全部线程。

wait(...) / notify() | notifyAll()通常状况下都是配套使用。下面来看一个简单的例子:

复制代码

1 package com.qqyumidi;
 2 
 3 public class ThreadTest {
 4 
 5     /**
 6      * @param args
 7      */
 8     public static void main(String[] args) {
 9         // TODO Auto-generated method stub
10         MyRunnable r = new MyRunnable();
11         Thread t = new Thread(r);
12         t.start();
13         synchronized (r) {
14             try {
15                 System.out.println("main thread 等待t线程执行完");
16                 r.wait();
17                 System.out.println("被notity唤醒,得以继续执行");
18             } catch (InterruptedException e) {
19                 // TODO Auto-generated catch block
20                 e.printStackTrace();
21                 System.out.println("main thread 本想等待,但被意外打断了");
22             }
23             System.out.println("线程t执行相加结果" + r.getTotal());
24         }
25     }
26 }
27 
28 class MyRunnable implements Runnable {
29     private int total;
30 
31     @Override
32     public void run() {
33         // TODO Auto-generated method stub
34         synchronized (this) {
35             System.out.println("Thread name is:" + Thread.currentThread().getName());
36             for (int i = 0; i < 10; i++) {
37                 total += i;
38             }
39             notify();
40             System.out.println("执行notif后同步代码块中依然能够继续执行直至完毕");
41         }
42         System.out.println("执行notif后且同步代码块外的代码执行时机取决于线程调度");
43     }
44 
45     public int getTotal() {
46         return total;
47     }
48 }

复制代码

运行结果为:

复制代码

1 main thread 等待t线程执行完
2 Thread name is:Thread-0
3 执行notif后同步代码块中依然能够继续执行直至完毕
4 执行notif后且同步代码块外的代码执行时机取决于线程调度  //此行输出位置有具体的JVM线程调度决定,有可能最后执行
5 被notity唤醒,得以继续执行
6 线程t执行相加结果45

复制代码

 

既然是做用于多线程中,为何倒是Object这个基类所具备的方法?缘由在于理论上任何对象均可以视为线程同步中的监听器,且wait(...)/notify()|notifyAll()方法只能在同步代码块中才能使用。

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

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

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

在Java源码中,能够看到wait()具体定义以下:

1  public final void wait() throws InterruptedException {
2      wait(0);
3  }

且wait(long timeout, int nanos)方法定义内部实质上也是经过调用wait(long timeout)完成。而wait(long timeout)是一个native方法。所以,wait(...)方法本质上都是native方式实现。

notify()/notifyAll()方法也都是native方法。

Java中线程具备较多的知识点,是一块比较大且重要的知识点。后期会有博文专门针对Java多线程做出详细总结。此处再也不细述。

 

13. protected void finalize();

finalize方法主要与Java垃圾回收机制有关。首先咱们看一下finalized方法在Object中的具体定义:

1 protected void finalize() throws Throwable { }

咱们发现Object类中finalize方法被定义成一个空方法,为何要如此定义呢?finalize方法的调用时机是怎么样的呢?

首先,Object中定义finalize方法代表Java中每个对象都将具备finalize这种行为,其具体调用时机在:JVM准备对此对形象所占用的内存空间进行垃圾回收前,将被调用。由此能够看出,此方法并非由咱们主动去调用的(虽然能够主动去调用,此时与其余自定义方法无异)。

相关文章
相关标签/搜索