女皇武則天:我不肯被 extends

0一、

利用繼承,咱們能夠基於已存在的類構造一個新類。繼承的好處在於,子類能夠複用父類的非 private 的方法和非 private 成員變量。java

is-a 是繼承的一個明顯特徵,就是說子類的對象引用類型能夠是一個父類。咱們能夠將通用的方法和成員變量放在父類中,達到代碼複用的目的;而後將特殊的方法和成員變量放在子類中,除此以外,子類還能夠覆蓋父類的方法。這樣,子類也就煥發出了新的生命力。面試

一個對象變量能夠引用多種類型的現象被稱爲多態。多態發生的前提條件就是繼承。也就是說,先有繼承,後有多態。apache

class Wanger {

    public void write() {
        System.out.println("我爲本身活着");
    }

}

class Wangxiaoer extends Wanger {
    public void write() {
        System.out.println("我也爲本身活着");
    }
}

class Test {
    public static void main(String [] args) {
        Wanger wanger;
        wanger = new Wanger();
        wanger = new Wangxiaoer();

        Wangxiaoer wangxiaoer;
        //wangxiaoer = new Wanger(); // 不能夠
        wangxiaoer = new Wangxiaoer(); // 只能這樣
    }
}

wanger 這個對象變量既能夠引用 Wanger 對象,也能夠引用 Wangxiaoer對象。但 wangxiaoer 就只能引用 Wangxiaoer 對象,不能引用 Wanger 對象。根本的緣由在於 WangxiaoerWanger 的繼承者。編程

當使用 wanger 調用 write() 方法時,程序會在運行時自動識別其引用的對象類型,而後選擇調用哪一個方法——這種現象稱爲動態綁定。編程語言

動態綁定有一個很是重要的特性:無需對現有的代碼進行修改,就能對程序進行擴展。假如 Wangdaer 也繼承了 Wanger,而且 wanger 引用了Wangdaer 的對象,那麼 wanger.write() 仍然能夠正常運行。ide

固然了,有些類不肯意被繼承,也無法被繼承。誰不肯意被繼承呢?好比武則天,親手弄死本身的親兒子。誰無法被繼承呢,每朝每代最後的那位倒黴皇帝。性能

類怎麼作到不被繼承呢?可使用 final 關鍵字。final 關鍵字修飾的類不能被繼承,final 修飾的方法不能被覆蓋。spa

final class Wanger {

    public final void write() {
        System.out.println("大家誰都別想繼承我");
    }

}

繼承是面向對象編程當中舉足輕重的一個概念,與多態、封裝共爲面向對象的三個基本特徵。 繼承可使得子類具備父類的成員變量和方法,還能夠從新定義、追加成員變量和方法等。設計

在設計繼承的時候,能夠將通用的方法和成員變量放在父類中。但不建議爲所欲爲地將成員變量以 protected 的形式放在父類當中;儘管容許這樣作,而且子類能夠在須要的時候直接訪問,但這樣作會破壞類的封裝性(封裝要求成員變量以 private 的形式出現,而且提供對應 getter / setter 用來訪問)。指針

Java 是不容許多繼承的,爲何呢?

若是有兩個類共同繼承一個有特定方法的父類,那麼該方法會被兩個子類重寫。而後,若是你決定同時繼承這兩個子類,那麼在你調用該重寫方法時,編譯器不能識別你要調用哪一個子類的方法。

這也正是著名的菱形問題,見下圖。ClassC 同時繼承了 ClassA 和 ClassB,ClassC 的對象在調用 ClassA 和 ClassB 中重載的方法時,就不知道該調用 ClassA 的方法,仍是 ClassB 的方法。

0二、

在 Java 中,全部類都由 Object 類繼承而來。Object 這個單詞的英文意思是對象,是否是忽然感受頓悟了——萬物皆對象?沒錯,Java 的設計者真是良苦用心了啊!如今,你必定明白了爲何 Java 是面向對象編程語言的緣由。

你可能會疑惑地反問道:「個人類明明沒有繼承 Object 類啊?」若是一個類沒用顯式地繼承某一個類,那麼它就會隱式地繼承 Object 類。換句話說,不論是雞生了蛋,仍是蛋孵出了雞,總有一隻 Object 雞或者一個 Object 蛋。

在面試的時候,你可能會被問到這麼一個問題:「Object 類包含了哪些方法呢?」

1)protected Object clone() throws CloneNotSupportedException 建立並返回此對象的副本。

不過,《阿里巴巴 Java 開發手冊》上建議:慎用 Object 的 clone 方法來拷貝對象。由於 Object 的 clone 方法默認是淺拷貝,若是想實現深拷貝須要重寫 clone 方法實現屬性對象的拷貝。

什麼是淺拷貝,什麼是深拷貝呢?

淺拷貝是指在拷貝對象時,會對基本數據類型的變量從新複製一份,而對於引用類型的變量只拷貝了引用,並無對引用指向的對象進行拷貝。

深拷貝是指在拷貝對象時,同時對引用指向的對象進行拷貝。

淺拷貝和深拷貝的區別就在因而否拷貝了對象中的引用變量所指向的對象。

2)public boolean equals(Object obj) 判斷另外一對象與此對象是否「相等」。

該方法使用的區分度最高的「==」操做符進行判斷,因此只要兩個對象不是同一個對象,那麼 equals() 方法必定返回 false

《阿里巴巴 Java 開發手冊》上強調:因爲 Object 的 equals 方法容易拋出空指針異常,因此應該使用常量或者肯定不爲 null 的對象來調用 equals。

正例:"test".equals(object);
反例:object.equals("test");

在正式的開發項目當中,最常用該方法進行判斷的就是字符串。不過,建議使用org.apache.commons.lang3.StringUtils,不用擔憂出現空指針異常。具體使用狀況以下所示:

StringUtils.equals(null, null)   = true
StringUtils.equals(null, "abc")  = false
StringUtils.equals("abc", null)  = false
StringUtils.equals("abc", "abc") = true
StringUtils.equals("abc", "ABC") = false

3)public native int hashCode() 返回此對象的哈希碼。hashCode() 是一個 native 方法,並且返回值類型是整形;實際上,該方法將對象在內存中的地址做爲哈希碼返回,能夠保證不一樣對象的返回值不一樣。

A native method is a Java method whose implementation is provided by non-java code.

native 方法是一個 Java 調用非 Java 代碼的接口。該方法的實現由非 Java 語言實現,好比 C。這個特徵並不是 Java 所特有,其它的編程語言也有這個機制,好比 C++

hashCode() 一般在哈希表中起做用,好比 HashMap

向哈希表中添加 Object 時,首先調用 hashCode() 方法計算 Object 的哈希碼,經過哈希碼能夠直接定位 Object 在哈希表中的位置。若是該位置沒有對象,能夠直接將 Object 插入該位置;若是該位置有對象,則調用 equals() 方法比較這個對象與 Object 是否相等,若是相等,則不須要保存 Object;若是不相等,則將該 Object 加入到哈希表中。

4)protected void finalize() throws Throwable 當垃圾回收機制肯定該對象再也不被調用時,垃圾回收器會調用此方法。不過,fnalize 機制如今已經不被推薦使用,而且在 JDK 9 開始被標記爲 deprecated(過期的)。

5)public final Class getClass() 返回此對象的運行時類。

當咱們想知道一個類自己的一些信息(好比說類名),該怎麼辦呢?這時候就須要用到 Class 類,該類包含了與類有關的信息。請看如下代碼:

Wanger wanger = new Wanger();
Class c1 = wanger.getClass();
System.out.println(c1.getName());
// 輸出 Wanger

6)public String toString() 返回此對象的字符串表示形式。

《阿里巴巴 Java 開發手冊》強制規定:POJO 類必須重寫 toString 方法;可使用 Eclipse 直接生成,點擊 「Source」→「Generate toString」。示例以下:

class Wanger {
    private Integer age;

    @Override
    public String toString() {
        return "Wanger [age=" + age + "]";
    }

}

重寫 toString() 有什麼好處呢?當方法在執行過程當中拋出異常時,能夠直接調用 POJO 的 toString() 方法打印其屬性值,便於排查問題。

POJO(Plain Ordinary Java Object)指簡單的 Java 對象,也就是普通的 JavaBeans,包含一些成員變量及其 getter / setter ,沒有業務邏輯。有時叫作 VO (value - object),有時叫作 DAO (Data Transform Object)。

0三、

Java 語言雖然號稱「萬物皆對象」,但基本類型是例外的——有 8 個基本類型不是 Object,包括 booleanbyteshortcharintfoatdoublelong

咱們以 int 爲例,來談一談 Integerint 之間的差異。Integerint 的包裝器,intInteger 的基本類型。Java 5 的時候,引入了自動裝箱和自動拆箱功能(boxing / unboxing),Integerint 能夠根據上下文自動進行轉換。

好比說:

Integer a = 100;

實際的字節碼是(裝箱和拆箱發生在 javac 階段而不是運行時):

Integer a = Integer.valueOf(100);

Java 爲何要這麼作呢?由於在實際的應用當中,Java 的設計者發現大部分數據操做都集中在有限的、較小的數值範圍內。爲了提高性能,Java 5 新增了靜態工廠方法 valueOf()

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

那麼 IntegerCache.lowIntegerCache.high 的值是多少呢?來看源碼:

private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

這說明最小值是 -128,最大值是 127。如今,咱們來看一個有意思的代碼,看看它們會輸出什麼。

Integer a = 127, b = 127;
System.out.println(a == b); // 輸出  true

Integer a1 = 128, b1 = 128;
System.out.println(a1 == b1); // 輸出 false

Integer a2 = 127;
int b2 = 127;
System.out.println(a2 == b2); // 輸出 true

Integer a3 = 128;
int b3 = 128;
System.out.println(a3 == b3); // 輸出 true

我是怎麼判斷出來結果的呢?這須要藉助 class 字節碼,以下:

Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b);

Integer a1 = Integer.valueOf(128);
Integer b1 = Integer.valueOf(128);
System.out.println(a1 == b1);

Integer a2 = Integer.valueOf(127);
int b2 = 127;
System.out.println(a2.intValue() == b2);

Integer a3 = Integer.valueOf(128);
int b3 = 128;
System.out.println(a3.intValue() == b3);

1)a == b 爲 true,是由於 127 恰好在 -128 到 127 的邊界,會從 IntegerCache 中產生。

2)a1 == b1 爲 false,是由於 128 恰好不在 -128 到 127 之間,a1 和 b1 都是從新 new 出來的 Integer 對象,兩個對象之間是不會 == 的。

3)a2 == b2a3 == b3 爲 true是由於兩個 int 值在比較。儘管源碼是 Integer == int,但編譯後的字節碼實際是 Integer.intValue() == intintValue() 返回的類型爲 int),也就是兩個基本類型在比較,它們的值是相等的,因此返回 true。

Integer a = int 是自動裝箱;int a = Integer.intValue() 是自動拆箱。

《阿里巴巴 Java 開發手冊》強制規定:全部相同類型的包裝器對象之間值的比較,應該使用 equals() 方法比較。

0四、

本篇,咱們先談了面向對象的重要特徵繼承;而後談到了繼承的終極父類 Object;最後談到了「萬物皆對象」的漏網之魚基本類型。這些知識點都至關的重要,請務必深刻理解!

相關文章
相關標籤/搜索