夯實Java基礎系列9:深刻理解Class類和Object類

目錄

- Object類

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到個人倉庫裏查看程序員

https://github.com/h2pl/Java-...

喜歡的話麻煩點下Star哈github

文章首發於個人我的博客:面試

www.how2playlife.com

本文是微信公衆號【Java技術江湖】的《夯實Java基礎系列博文》其中一篇,本文部份內容來源於網絡,爲了把本文主題講得清晰透徹,也整合了不少我認爲不錯的技術博客內容,引用其中了一些比較好的博客文章,若有侵權,請聯繫做者。
該系列博文會告訴你如何從入門到進階,一步步地學習Java基礎知識,並上手進行實戰,接着瞭解每一個Java知識點背後的實現原理,更完整地瞭解整個Java技術體系,造成本身的知識框架。爲了更好地總結和檢驗你的學習成果,本系列文章也會提供每一個知識點對應的面試題以及參考答案。算法

若是對本系列文章有什麼建議,或者是有什麼疑問的話,也能夠關注公衆號【Java技術江湖】聯繫做者,歡迎你參與本系列博文的創做和修訂。bootstrap

<!-- more -->segmentfault

Java中Class類及用法

Java程序在運行時,Java運行時系統一直對全部的對象進行所謂的運行時類型標識,即所謂的RTTI。後端

這項信息紀錄了每一個對象所屬的類。虛擬機一般使用運行時類型信息選準正確方法去執行,用來保存這些類型信息的類是Class類。Class類封裝一個對象和接口運行時的狀態,當裝載類時,Class類型的對象自動建立。

說白了就是:

Class類也是類的一種,只是名字和class關鍵字高度類似。Java是大小寫敏感的語言。

Class類的對象內容是你建立的類的類型信息,好比你建立一個shapes類,那麼,Java會生成一個內容是shapes的Class類的對象

Class類的對象不能像普通類同樣,以 new shapes() 的方式建立,它的對象只能由JVM建立,由於這個類沒有public構造函數

/*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
     //私有構造方法,只能由jvm進行實例化
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }
Class類的做用是運行時提供或得到某個對象的類型信息,和C++中的typeid()函數相似。這些信息也可用於反射。

Class類原理

看一下Class類的部分源碼

//Class類中封裝了類型的各類信息。在jvm中就是經過Class類的實例來獲取每一個Java類的全部信息的。

public class Class類 {
    Class aClass = null;

//    private EnclosingMethodInfo getEnclosingMethodInfo() {
//        Object[] enclosingInfo = getEnclosingMethod0();
//        if (enclosingInfo == null)
//            return null;
//        else {
//            return new EnclosingMethodInfo(enclosingInfo);
//        }
//    }

    /**提供原子類操做
     * Atomic operations support.
     */
//    private static class Atomic {
//        // initialize Unsafe machinery here, since we need to call Class.class instance method
//        // and have to avoid calling it in the static initializer of the Class class...
//        private static final Unsafe unsafe = Unsafe.getUnsafe();
//        // offset of Class.reflectionData instance field
//        private static final long reflectionDataOffset;
//        // offset of Class.annotationType instance field
//        private static final long annotationTypeOffset;
//        // offset of Class.annotationData instance field
//        private static final long annotationDataOffset;
//
//        static {
//            Field[] fields = Class.class.getDeclaredFields0(false); // bypass caches
//            reflectionDataOffset = objectFieldOffset(fields, "reflectionData");
//            annotationTypeOffset = objectFieldOffset(fields, "annotationType");
//            annotationDataOffset = objectFieldOffset(fields, "annotationData");
//        }

        //提供反射信息
    // reflection data that might get invalidated when JVM TI RedefineClasses() is called
//    private static class ReflectionData<T> {
//        volatile Field[] declaredFields;
//        volatile Field[] publicFields;
//        volatile Method[] declaredMethods;
//        volatile Method[] publicMethods;
//        volatile Constructor<T>[] declaredConstructors;
//        volatile Constructor<T>[] publicConstructors;
//        // Intermediate results for getFields and getMethods
//        volatile Field[] declaredPublicFields;
//        volatile Method[] declaredPublicMethods;
//        volatile Class<?>[] interfaces;
//
//        // Value of classRedefinedCount when we created this ReflectionData instance
//        final int redefinedCount;
//
//        ReflectionData(int redefinedCount) {
//            this.redefinedCount = redefinedCount;
//        }
//    }
        //方法數組
//    static class MethodArray {
//        // Don't add or remove methods except by add() or remove() calls.
//        private Method[] methods;
//        private int length;
//        private int defaults;
//
//        MethodArray() {
//            this(20);
//        }
//
//        MethodArray(int initialSize) {
//            if (initialSize < 2)
//                throw new IllegalArgumentException("Size should be 2 or more");
//
//            methods = new Method[initialSize];
//            length = 0;
//            defaults = 0;
//        }

    //註解信息
    // annotation data that might get invalidated when JVM TI RedefineClasses() is called
//    private static class AnnotationData {
//        final Map<Class<? extends Annotation>, Annotation> annotations;
//        final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
//
//        // Value of classRedefinedCount when we created this AnnotationData instance
//        final int redefinedCount;
//
//        AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations,
//                       Map<Class<? extends Annotation>, Annotation> declaredAnnotations,
//                       int redefinedCount) {
//            this.annotations = annotations;
//            this.declaredAnnotations = declaredAnnotations;
//            this.redefinedCount = redefinedCount;
//        }
//    }
}
咱們都知道全部的java類都是繼承了object這個類,在object這個類中有一個方法:getclass().這個方法是用來取得該類已經被實例化了的對象的該類的引用,這個引用指向的是Class類的對象。

咱們本身沒法生成一個Class對象(構造函數爲private),而 這個Class類的對象是在當各種被調入時,由 Java 虛擬機自動建立 Class 對象,或經過類裝載器中的 defineClass 方法生成。

//經過該方法能夠動態地將字節碼轉爲一個Class類對象
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    throws ClassFormatError
{
    return defineClass(name, b, off, len, null);
}
咱們生成的對象都會有個字段記錄該對象所屬類在CLass類的對象的所在位置。以下圖所示:

[外鏈圖片轉存失敗(img-ZfMJTzO4-1569074134147)(http://dl.iteye.com/upload/pi...]

如何得到一個Class類對象

請注意,如下這些方法都是值、指某個類對應的Class對象已經在堆中生成之後,咱們經過不一樣方式獲取對這個Class對象的引用。而上面說的DefineClass纔是真正將字節碼加載到虛擬機的方法,會在堆中生成新的一個Class對象。

第一種辦法,Class類的forName函數

public class shapes{}
Class obj= Class.forName("shapes");
第二種辦法,使用對象的getClass()函數

public class shapes{}
shapes s1=new shapes();
Class obj=s1.getClass();
Class obj1=s1.getSuperclass();//這個函數做用是獲取shapes類的父類的類型

第三種辦法,使用類字面常量

Class obj=String.class;
Class obj1=int.class;
注意,使用這種辦法生成Class類對象時,不會使JVM自動加載該類(如String類)。==而其餘辦法會使得JVM初始化該類。==

使用Class類的對象來生成目標類的實例

生成不精確的object實例

==獲取一個Class類的對象後,能夠用 newInstance() 函數來生成目標類的一個實例。然而,該函數並不能直接生成目標類的實例,只能生成object類的實例==

Class obj=Class.forName("shapes");
Object ShapesInstance=obj.newInstance();
使用泛化Class引用生成帶類型的目標實例

Class<shapes> obj=shapes.class;
shapes newShape=obj.newInstance();
由於有了類型限制,因此使用泛化Class語法的對象引用不能指向別的類。

Class obj1=int.class;
Class<Integer> obj2=int.class;
obj1=double.class;
//obj2=double.class; 這一行代碼是非法的,obj2不能改指向別的類

然而,有個靈活的用法,使得你能夠用Class的對象指向基類的任何子類。
Class<? extends Number> obj=int.class;
obj=Number.class;
obj=double.class;

所以,如下語法生成的Class對象能夠指向任何類。
Class<?> obj=int.class;
obj=double.class;
obj=shapes.class;
最後一個奇怪的用法是,當你使用這種泛型語法來構建你手頭有的一個Class類的對象的基類對象時,必須採用如下的特殊語法

public class shapes{}
class round extends shapes{}
Class<round> rclass=round.class;
Class<? super round> sclass= rclass.getSuperClass();
//Class<shapes> sclass=rclass.getSuperClass();
咱們明知道,round的基類就是shapes,可是卻不能直接聲明 Class < shapes >,必須使用特殊語法

Class < ? super round >

這個記住就能夠啦。

Object類

這部分主要參考http://ihenu.iteye.com/blog/2...

Object類是Java中其餘全部類的祖先,沒有Object類Java面向對象無從談起。做爲其餘全部類的基類,Object具備哪些屬性和行爲,是Java語言設計背後的思惟體現。

Object類位於java.lang包中,java.lang包包含着Java最基礎和核心的類,在編譯時會自動導入。Object類沒有定義屬性,一共有13個方法,13個方法之中並非全部方法都是子類可訪問的,一共有9個方法是全部子類都繼承了的。

先大概介紹一下這些方法

1.clone方法
保護方法,實現對象的淺複製,只有實現了Cloneable接口才能夠調用該方法,不然拋出CloneNotSupportedException異常。
2.getClass方法
final方法,得到運行時類型。
3.toString方法
該方法用得比較多,通常子類都有覆蓋。
4.finalize方法
該方法用於釋放資源。由於沒法肯定該方法何時被調用,不多使用。
5.equals方法
該方法是很是重要的一個方法。通常equals和==是不同的,可是在Object中二者是同樣的。子類通常都要重寫這個方法。
6.hashCode方法
該方法用於哈希查找,重寫了equals方法通常都要重寫hashCode方法。這個方法在一些具備哈希功能的Collection中用到。
通常必須知足obj1.equals(obj2)==true。能夠推出obj1.hash- Code()==obj2.hashCode(),可是hashCode相等不必定就知足equals。不過爲了提升效率,應該儘可能使上面兩個條件接近等價。
7.wait方法
wait方法就是使當前線程等待該對象的鎖,當前線程必須是該對象的擁有者,也就是具備該對象的鎖。wait()方法一直等待,直到得到鎖或者被中斷。wait(long timeout)設定一個超時間隔,若是在規定時間內沒有得到鎖就返回。
調用該方法後當前線程進入睡眠狀態,直到如下事件發生。
(1)其餘線程調用了該對象的notify方法。
(2)其餘線程調用了該對象的notifyAll方法。
(3)其餘線程調用了interrupt中斷該線程。
(4)時間間隔到了。
此時該線程就能夠被調度了,若是是被中斷的話就拋出一個InterruptedException異常。
8.notify方法
該方法喚醒在該對象上等待的某個線程。
9.notifyAll方法
該方法喚醒在該對象上等待的全部線程。

類構造器public Object();

大部分狀況下,Java中經過形如 new A(args..)形式建立一個屬於該類型的對象。其中A便是類名,A(args..)即此類定義中相對應的構造函數。經過此種形式建立的對象都是經過類中的構造函數完成。

爲體現此特性,Java中規定:在類定義過程當中,對於未定義構造函數的類,默認會有一個無參數的構造函數,做爲全部類的基類,Object類天然要反映出此特性,在源碼中,未給出Object類構造函數定義,但實際上,此構造函數是存在的。

固然,並非全部的類都是經過此種方式去構建,也天然的,並非全部的類構造函數都是public。

registerNatives()方法;

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源碼中,此方法的聲明後有緊接着一段靜態代碼塊:

private static native void registerNatives();  
static {  
     registerNatives();  
}

Clone()方法實現淺拷貝

protected native Object clone() throwsCloneNotSupportedException;
看,clode()方法又是一個被聲明爲native的方法,所以,咱們知道了clone()方法並非Java的原生方法,具體的實現是有C/C++完成的。clone英文翻譯爲"克隆",其目的是建立並返回此對象的一個副本。

形象點理解,這有一輛科魯茲,你看着不錯,想要個如出一轍的。你調用此方法便可像變魔術同樣變出一輛如出一轍的科魯茲出來。配置同樣,長相同樣。但今後刻起,原來的那輛科魯茲若是進行了新的裝飾,與你克隆出來的這輛科魯茲沒有任何關係了。

你克隆出來的對象變不變徹底在於你對克隆出來的科魯茲有沒有進行過什麼操做了。Java術語表述爲:clone函數返回的是一個引用,指向的是新的clone出來的對象,此對象與原對象分別佔用不一樣的堆空間。

明白了clone的含義後,接下來看看若是調用clone()函數對象進行此克隆操做。

首先看一下下面的這個例子:

package com.corn.objectsummary;  
  
import com.corn.Person;  
  
public class ObjectTest {  
  
    public static void main(String[] args) {  
  
        Object o1 = new Object();  
        // The method clone() from the type Object is not visible  
        Object clone = o1.clone();  
    }  
  
}
例子很簡單,在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關鍵字除外)

因而,上例改爲以下形式,咱們發現,能夠正常編譯:

public class clone方法 {
    public static void main(String[] args) {

    }
    public void test1() {

        User user = new User();
//        User copy = user.clone();
    }
    public void test2() {
        User user = new User();
//        User copy = (User)user.clone();
    }
}

是的,由於此時的主調已是子類的引用了。

上述代碼在運行過程當中會拋出"java.lang.CloneNotSupportedException",代表clone()方法並未正確執行完畢,問題的緣由在與Java中的語法規定:

clone()的正確調用是須要實現Cloneable接口,若是沒有實現Cloneable接口,而且子類直接調用Object類的clone()方法,則會拋出CloneNotSupportedException異常。

Cloneable接口僅是一個表示接口,接口自己不包含任何方法,用來指示Object.clone()能夠合法的被子類引用所調用。

因而,上述代碼改爲以下形式,便可正確指定clone()方法以實現克隆。

public class User implements Cloneable{
public int id;
public String name;
public UserInfo userInfo;

public static void main(String[] args) {
    User user = new User();
    UserInfo userInfo = new UserInfo();
    user.userInfo = userInfo;
    System.out.println(user);
    System.out.println(user.userInfo);
    try {
        User copy = (User) user.clone();
        System.out.println(copy);
        System.out.println(copy.userInfo);
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
}
//拷貝的User實例與原來不同,是兩個對象。
//    com.javase.Class和Object.Object方法.用到的類.User@4dc63996
//    com.javase.Class和Object.Object方法.用到的類.UserInfo@d716361
        //而拷貝後對象的userinfo引用對象是同一個。
    //因此這是淺拷貝
//    com.javase.Class和Object.Object方法.用到的類.User@6ff3c5b5
//    com.javase.Class和Object.Object方法.用到的類.UserInfo@d716361
}

總結:
clone方法實現的是淺拷貝,只拷貝當前對象,而且在堆中分配新的空間,放這個複製的對象。可是對象若是裏面有其餘類的子對象,那麼就不會拷貝到新的對象中。

==深拷貝和淺拷貝的區別==

淺拷貝
淺拷貝是按位拷貝對象,它會建立一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。若是屬性是基本類型,拷貝的就是基本類型的值;若是屬性是內存地址(引用類型),拷貝的就是內存地址 ,所以若是其中一個對象改變了這個地址,就會影響到另外一個對象。

深拷貝
深拷貝會拷貝全部的屬性,並拷貝屬性指向的動態分配的內存。當對象和它所引用的對象一塊兒拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢而且花銷較大。
如今爲了要在clone對象時進行深拷貝, 那麼就要Clonable接口,覆蓋並實現clone方法,除了調用父類中的clone方法獲得新的對象, 還要將該類中的引用變量也clone出來。若是隻是用Object中默認的clone方法,是淺拷貝的。

那麼這兩種方式有什麼相同和不一樣呢?

new操做符的本意是分配內存。程序執行到new操做符時, 首先去看new操做符後面的類型,由於知道了類型,才能知道要分配多大的內存空間。

分配完內存以後,再調用構造函數,填充對象的各個域,這一步叫作對象的初始化,構造方法返回後,一個對象建立完畢,能夠把他的引用(地址)發佈到外部,在外部就可使用這個引用操縱這個對象。

而clone在第一步是和new類似的, 都是分配內存,調用clone方法時,分配的內存和源對象(即調用clone方法的對象)相同,而後再使用原對象中對應的各個域,填充新對象的域,

填充完成以後,clone方法返回,一個新的相同的對象被建立,一樣能夠把這個新對象的引用發佈到外部。

==也就是說,一個對象在淺拷貝之後,只是把對象複製了一份放在堆空間的另外一個地方,可是成員變量若是有引用指向其餘對象,這個引用指向的對象和被拷貝的對象中引用指向的對象是同樣的。固然,基本數據類型仍是會從新拷貝一份的。==

getClass()方法

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

getClass()也是一個native方法,返回的是此Object對象的類對象/運行時類對象Class<?>。效果與Object.class相同。

首先解釋下"類對象"的概念:在Java中,類是是對具備一組相同特徵或行爲的實例的抽象並進行描述,對象則是此類所描述的特徵或行爲的具體實例。

做爲概念層次的類,其自己也具備某些共同的特性,如都具備類名稱、由類加載器去加載,都具備包,具備父類,屬性和方法等。

因而,Java中有專門定義了一個類,Class,去描述其餘類所具備的這些特性,所以,今後角度去看,類自己也都是屬於Class類的對象。爲與常常意義上的對象相區分,在此稱之爲"類對象"。

public class getClass方法 {
    public static void main(String[] args) {
        User user = new User();
        //getclass方法是native方法,能夠取到堆區惟一的Class<User>對象
        Class<?> aClass = user.getClass();
        Class bClass = User.class;
        try {
            Class cClass = Class.forName("com.javase.Class和Object.Object方法.用到的類.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(aClass);
        System.out.println(bClass);
//        class com.javase.Class和Object.Object方法.用到的類.User
//        class com.javase.Class和Object.Object方法.用到的類.User
        try {
            User a = (User) aClass.newInstance();

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

此處主要大量涉及到Java中的反射知識

equals()方法

5.public boolean equals(Object obj);

與equals在Java中常常被使用,你們也都知道與equals的區別:

==表示的是變量值完成相同(對於基礎類型,地址中存儲的是值,引用類型則存儲指向實際對象的地址);

equals表示的是對象的內容徹底相同,此處的內容多指對象的特徵/屬性。

實際上,上面說法是不嚴謹的,更多的只是常見於String類中。首先看一下Object類中關於equals()方法的定義:

public boolean equals(Object obj) {  
     return (this == obj);  
}
因而可知,Object原生的equals()方法內部調用的正是==,與==具備相同的含義。既然如此,爲何還要定義此equals()方法?

equals()方法的正確理解應該是:判斷兩個對象是否相等。那麼判斷對象相等的標尺又是什麼?

如上,在object類中,此標尺即爲==。固然,這個標尺不是固定的,其餘類中能夠按照實際的須要對此標尺含義進行重定義。如String類中則是依據字符串內容是否相等來重定義了此標尺含義。如此能夠增長類的功能型和實際編碼的靈活性。固然了,若是自定義的類沒有重寫equals()方法來從新定義此標尺,那麼默認的將是其父類的equals(),直到object基類。

以下場景的實際業務需求,對於User bean,由實際的業務需求可知當屬性uid相同時,表示的是同一個User,即兩個User對象相等。則能夠重寫equals以重定義User對象相等的標尺。

ObjectTest中打印出true,由於User類定義中重寫了equals()方法,這很好理解,極可能張三是一我的小名,張三丰纔是其大名,判斷這兩我的是否是同一我的,這時只用判斷uid是否相同便可。

如上重寫equals方法表面上看上去是能夠了,實則否則。由於它破壞了Java中的約定:重寫equals()方法必須重寫hasCode()方法。

hashCode()方法;

  1. 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()方法。

public class equals和hashcode方法 {
    @Override
    //修改equals時必須同時修改hashcode方法,不然在做爲key時會出問題
    public boolean equals(Object obj) {
        return (this == obj);
    }
    
    @Override
    //相同的對象必須有相同hashcode,不一樣對象可能有相同hashcode
    public int hashCode() {
        return hashCode() >> 2;
    }
}

toString()方法

7.public String toString();

toString()方法返回該對象的字符串表示。先看一下Object中的具體方法體:

 public String toString() {  
    return getClass().getName() + "@" + Integer.toHexString(hashCode());  
}
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()方法返回的結果可能相同。

wait() notify() notifAll()

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()通常狀況下都是配套使用。下面來看一個簡單的例子:

這是一個生產者消費者的模型,只不過這裏只用flag來標識哪一個線程須要工做

public class wait和notify {
    //volatile保證線程可見性
    volatile static int flag = 1;
    //object做爲鎖對象,用於線程使用wait和notify方法
    volatile static Object o = new Object();
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //wait和notify只能在同步代碼塊內使用
                synchronized (o) {
                    while (true) {
                        if (flag == 0) {
                            try {
                                Thread.sleep(2000);
                                System.out.println("thread1 wait");
                                //釋放鎖,線程掛起進入object的等待隊列,後續代碼運行
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("thread1 run");
                        System.out.println("notify t2");
                        flag = 0;
                        //通知等待隊列的一個線程獲取鎖
                        o.notify();
                    }
                }
            }
        }).start();
        //解釋同上
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (o) {
                        if (flag == 1) {
                            try {
                                Thread.sleep(2000);
                                System.out.println("thread2 wait");
                                o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("thread2 run");
                        System.out.println("notify t1");
                        flag = 1;
                        o.notify();
                    }
                }
            }
        }).start();
    }

    //輸出結果是
//    thread1 run
//    notify t2
//    thread1 wait
//    thread2 run
//    notify t1
//    thread2 wait
//    thread1 run
//    notify t2
//不斷循環
}
從上述例子的輸出結果中能夠得出以下結論:

一、wait(...)方法調用後當前線程將當即阻塞,且適當其所持有的同步代碼塊中的鎖,直到被喚醒或超時或打斷後且從新獲取到鎖後才能繼續執行;

二、notify()/notifyAll()方法調用後,其所在線程不會當即釋放所持有的鎖,直到其所在同步代碼塊中的代碼執行完畢,此時釋放鎖,所以,若是其同步代碼塊後還有代碼,其執行則依賴於JVM的線程調度。

在Java源碼中,能夠看到wait()具體定義以下:

public final void wait() throws InterruptedException {  
     wait(0);  
}
且wait(long timeout, int nanos)方法定義內部實質上也是經過調用wait(long timeout)完成。而wait(long timeout)是一個native方法。所以,wait(...)方法本質上都是native方式實現。

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

Java中線程具備較多的知識點,是一塊比較大且重要的知識點。後期會有博文專門針對Java多線程做出詳細總結。此處再也不細述。

finalize()方法

  1. protected void finalize();

finalize方法主要與Java垃圾回收機制有關。首先咱們看一下finalized方法在Object中的具體定義:

protected void finalize() throws Throwable { }
咱們發現Object類中finalize方法被定義成一個空方法,爲何要如此定義呢?finalize方法的調用時機是怎麼樣的呢?

首先,Object中定義finalize方法代表Java中每個對象都將具備finalize這種行爲,其具體調用時機在:JVM準備對此對形象所佔用的內存空間進行垃圾回收前,將被調用。由此能夠看出,此方法並非由咱們主動去調用的(雖然能夠主動去調用,此時與其餘自定義方法無異)。

CLass類和Object類的關係

Object類和Class類沒有直接的關係。

Object類是一切java類的父類,對於普通的java類,即使不聲明,也是默認繼承了Object類。典型的,可使用Object類中的toString()方法。

Class類是用於java反射機制的,一切java類,都有一個對應的Class對象,他是一個final類。Class 類的實例表示,正在運行的 Java 應用程序中的類和接口。

轉一個知乎頗有趣的問題
https://www.zhihu.com/questio...

Java的對象模型中:
1 全部的類都是Class類的實例,Object是類,那麼Object也是Class類的一個實例。

2 全部的類都最終繼承自Object類,Class是類,那麼Class也繼承自Object。

3 這就像是先有雞仍是先有蛋的問題,請問實際中JVM是怎麼處理的?
這個問題中,第1個假設是錯的:java.lang.Object是一個Java類,但並非java.lang.Class的一個實例。後者只是一個用於描述Java類與接口的、用於支持反射操做的類型。這點上Java跟其它一些更純粹的面嚮對象語言(例如Python和Ruby)不一樣。

而第2個假設是對的:java.lang.Class是java.lang.Object的派生類,前者繼承自後者。雖然第1個假設不對,但「雞蛋問題」仍然存在:在一個已經啓動完畢、可使用的Java對象系統裏,必需要有一個java.lang.Class實例對應java.lang.Object這個類;而java.lang.Class是java.lang.Object的派生類,按「通常思惟」前者應該要在後者完成初始化以後才能夠初始化…

事實是:這些相互依賴的核心類型徹底能夠在「混沌」中一口氣都初始化好,而後對象系統的狀態才叫作完成了「bootstrap」,後面就能夠按照Java對象系統的通常規則去運行。JVM、JavaScript、Python、Ruby等的運行時都有這樣的bootstrap過程。

在「混沌」(boostrap過程)裏,JVM能夠爲對象系統中最重要的一些核心類型先分配好內存空間,讓它們進入[已分配空間]但[還沒有徹底初始化]狀態。此時這些對象雖然已經分配了空間,但由於狀態還不完整因此尚不可以使用。

而後,經過這些分配好的空間把這些核心類型之間的引用關係串好。到此爲止全部動做都由JVM完成,還沒有執行任何Java字節碼。而後這些核心類型就進入了[徹底初始化]狀態,對象系統就能夠開始自我運行下去,也就是能夠開始執行Java字節碼來進一步完成Java系統的初始化了。

參考文章

https://www.cnblogs.com/congs...
https://www.jb51.net/article/...
https://blog.csdn.net/dufufd/...
https://blog.csdn.net/farsigh...
https://blog.csdn.net/xiaomin...

微信公衆號

Java技術江湖

若是你們想要實時關注我更新的文章以及分享的乾貨的話,能夠關注個人公衆號【Java技術江湖】一位阿里 Java 工程師的技術小站,做者黃小斜,專一 Java 相關技術:SSM、SpringBoot、MySQL、分佈式、中間件、集羣、Linux、網絡、多線程,偶爾講點Docker、ELK,同時也分享技術乾貨和學習經驗,致力於Java全棧開發!

Java工程師必備學習資源: 一些Java工程師經常使用學習資源,關注公衆號後,後臺回覆關鍵字 「Java」 便可免費無套路獲取。

個人公衆號

我的公衆號:黃小斜

做者是 985 碩士,螞蟻金服 JAVA 工程師,專一於 JAVA 後端技術棧:SpringBoot、MySQL、分佈式、中間件、微服務,同時也懂點投資理財,偶爾講點算法和計算機理論基礎,堅持學習和寫做,相信終身學習的力量!

程序員3T技術學習資源: 一些程序員學習技術的資源大禮包,關注公衆號後,後臺回覆關鍵字 「資料」 便可免費無套路獲取。

相關文章
相關標籤/搜索