JAVA 基礎重構 七 (Class 和 Object 類)

Java中Class類及用法

    Java程序在運行時,Java運行時系統一直對所有的對象進行所謂的運行時類型標識,即所謂的RTTI。這項信息紀錄了每個對象所屬的類。虛擬機通常使用運行時類型信息選準正確方法去執行,用來保存這些類型信息的類是Class類。Class類封裝一個對象和接口運行時的狀態,當裝載類時,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類的作用是運行時提供或獲得某個對象的類型信息,這些信息也可用於反射。

看一下Class類的部分源碼

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類對象

第一種辦法,Class類的forName函數  :public class persons{} Class obj= Class.forName(“persons”); 

第二種辦法,使用對象的getClass()函數

public class persons{} persons s1=new persons(); 

Class obj=s1.getClass();

 Class obj1=s1.getSuperclass();//這個函數作用是獲取persons 類的父類的類型

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

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

Object類

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。

clone()方法

clone方法實現的是淺拷貝,只拷貝當前對象,並且在堆中分配新的空間,放入這個複製的對象。但是對象如果裏面有其他類的子對象,那麼就不會拷貝到新的對象中。

深拷貝和淺拷貝的區別

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

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

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

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

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

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

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

==也就是說,一個對象在淺拷貝以後,只是把對象複製了一份放在堆空間的另一個地方,但是成員變量如果有引用指向其他對象,這個引用指向的對象和被拷貝的對象中引用指向的對象是一樣的。當然,基本數據類型還是會重新拷貝一份的。==

equals()方法
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()方法;

public native int 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()返回的哈希碼可能相同。

toString()方法

7.public String toString();

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

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

wait() notify() notifAll()

一說到wait(…) / notify() | notifyAll()幾個方法,首先想到的是線程。確實,這幾個方法主要用於java多線程之間的協作。先具體看下這幾個方法的主要含義:
wait():調用此方法所在的當前線程等待,直到在其他線程上調用此方法的主調(某一對象)的notify()/notifyAll()方法。
wait(long timeout)/wait(long timeout, int nanos):調用此方法所在的當前線程等待,直到在其他線程上調用此方法的主調(某一對象)的notisfy()/notisfyAll()方法,或超過指定的超時時間量。
notify()/notifyAll():喚醒在此對象監視器上等待的單個線程/所有線程。

Class類和Object類的關係

Object類和Class類沒有直接的關係。
Object類是一切java類的父類,對於普通的java類,即便不聲明,也是默認繼承了Object類。典型的,可以使用Object類中的toString()方法。
Class類是用於java反射機制的,一切java類,都有一個對應的Class對象,他是一個final類。Class 類的實例表示,正在運行的 Java 應用程序中的類和接口

微信公衆號:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章