Java 基礎面試知識點

騎摩托馬斯發表於2018-06-23

Java 基礎知識相關

Java中 == 和 equals 和 hashCode 的區別

對於關係操作符 ==

  • 若運算元的型別是基本資料型別,則該關係操作符判斷的是左右兩邊運算元的是否相等
  • 若運算元的型別是引用資料型別,則該關係操作符判斷的是左右兩邊運算元的記憶體地址是否相同。也就是說,若此時返回true,則該操作符作用的一定是同一個物件。

對於使用 equals 方法,內部實現分為三個步驟:

  1. 先比較引用是否相同(是否為同一物件),
  2. 再判斷型別是否一致(是否為同一型別),
  3. 最後比較內容是否一致

Java 中所有內建的類的 equals 方法的實現步驟均是如此,特別是諸如 Integer,Double 等包裝器類。如以下 String 中的 equals 方法實現

// String.java 
    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
複製程式碼

hashcode是系統用來快速檢索物件而使用

equals方法本意是用來判斷引用的物件是否一致

重寫equals方法和hashcode方法時,equals方法中用到的成員變數也必定會在hashcode方法中用到,只不過前者作為比較項,後者作為生成摘要的資訊項,本質上所用到的資料是一樣的,從而保證二者的一致性

參考

int、char、long各佔多少位元組數

Java 基本型別佔用的位元組數

  • 1位元組: byte , boolean
  • 2位元組: short , char
  • 4位元組: int , float
  • 8位元組: long , double

注:1位元組(byte)=8位(bits)

int與integer的區別

  • Integer是int的包裝類,int則是java的一種基本資料型別
  • Integer變數必須例項化後才能使用,而int變數不需要
  • Integer實際是物件的引用,當new一個Integer時,實際上是生成一個指標指向此物件;而int則是直接儲存資料值
  • Integer的預設值是null,int的預設值是0
參考

對 Java 多型的理解

多型是指父類的某個方法被子類重寫時,可以產生自己的功能行為,同一個操作作用於不同物件,可以有不同的解釋,產生不同的執行結果。

多型的三個必要條件:

  1. 繼承父類。
  2. 重寫父類的方法。
  3. 父類的引用指向子類物件

然後可以使用結合里氏替換法則進一步的談理解

里氏替換法則 ---- 所有引用基類的地方必須能透明地使用其子類的物件

  • 子類必須完全實現父類的方法

注意 在類中呼叫其他類時務必要使用父類或介面,如果不能使用父類或介面,則說明類的設計已經違背了LSP原則

注意 如果子類不能完整地實現父類的方法,或者父類的某些方法在子類中已經發生“畸變”,則建議斷開父子繼承關係,採用依賴、聚集、組合等關係代替繼承

  • 子類可以有自己的個性
  • 覆蓋或實現父類的方法時輸入引數可以被放大
  • 覆寫或實現父類的方法時輸出結果可以被縮小

在專案中,採用里氏替換原則時,儘量避免子類的“個性”,一旦子類有“個性”,這個子類和父類之間的關係就很難調和了, 把子類當做父類使用,子類的“個性”被抹殺——委屈了點;把子類單獨作為一個業務來使用,則會讓程式碼間的耦合關係變得撲朔迷離——缺乏類替換的標準

String、StringBuffer、StringBuilder 區別

StringBuffer 和 String 一樣都是用來儲存字串的,只不過由於他們內部的實現方式不同,導致他們所使用的範圍不同,對於 StringBuffer 而言,他在處理字串時,若是對其進行修改操作,它並不會產生一個新的字串物件,所以說在記憶體使用方面它是優於 String 的

StringBuilder 也是一個可變的字串物件,他與 StringBuffer 不同之處就在於它是執行緒不安全的,基於這點,它的速度一般都比 StringBuffer 快

String 字串的拼接會被 JVM 解析成 StringBuilder 物件拼接,在這種情況下 String 的速度比 StringBuffer 的速度快

str +=”b”等同於 str = new StringBuilder(str).append(“b”).toString();

詳解內部類

內部類使得多重繼承的解決方案變得更加完整

使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(介面的)實現,所以無論外圍類是否已經繼承了某個(介面的)實現,對於內部類都沒有影響

使用內部類才能實現多重繼承

  • 內部類可以用多個例項,每個例項都有自己的狀態資訊,並且與其他外圍物件的資訊相互獨立。
  • 在單個外圍類中,可以讓多個內部類以不同的方式實現同一個介面,或者繼承同一個類。
  • 建立內部類物件的時刻並不依賴於外圍類物件的建立。
  • 內部類並沒有令人迷惑的“is-a”關係,他就是一個獨立的實體。
  • 內部類提供了更好的封裝,除了該外圍類,其他類都不能訪問。

當我們在建立某個外圍類的內部類物件時,此時內部類物件必定會捕獲一個指向那個外圍類物件的引用,只要我們在訪問外圍類的成員時,就會用這個引用來選擇外圍類的成員

成員內部類

在成員內部類中要注意兩點,

  • 成員內部類中不能存在任何 static 的變數和方法
  • 成員內部類是依附於外圍類的,所以只有先建立了外圍類才能夠建立內部類

靜態內部類

靜態內部類與非靜態內部類之間存在一個最大的區別,我們知道非靜態內部類在編譯完成之後會隱含地儲存著一個引用,該引用是指向建立它的外圍內,但是靜態內部類卻沒有。沒有這個引用就意味著:

  • 它的建立是不需要依賴於外圍類的。
  • 它不能使用任何外圍類的非static成員變數和方法。

參考

為什麼Java裡的匿名內部類只能訪問final修飾的外部變數?

匿名內部類用法

public class TryUsingAnonymousClass {
    public void useMyInterface() {
        final Integer number = 123;
        System.out.println(number);

        MyInterface myInterface = new MyInterface() {
            @Override
            public void doSomething() {
                System.out.println(number);
            }
        };
        myInterface.doSomething();

        System.out.println(number);
    }
}
複製程式碼

編譯後的結果

class TryUsingAnonymousClass$1
        implements MyInterface {
    private final TryUsingAnonymousClass this$0;
    private final Integer paramInteger;

    TryUsingAnonymousClass$1(TryUsingAnonymousClass this$0, Integer paramInteger) {
        this.this$0 = this$0;
        this.paramInteger = paramInteger;
    }

    public void doSomething() {
        System.out.println(this.paramInteger);
    }
}
複製程式碼

因為匿名內部類最終用會編譯成一個單獨的類,而被該類使用的變數會以建構函式引數的形式傳遞給該類,例如:Integer paramInteger,如果變數 不定義成final的,paramInteger在匿名內部類被可以被修改,進而造成和外部的paramInteger不一致的問題,為了避免這種不一致的情況,因為Java 規定匿名內部類只能訪問final修飾的外部變數

抽象類和介面的區別

  1. 預設的方法實現抽象類可以有預設的方法實現完全是抽象的。介面根本不存在方法的實現
  2. 實現抽象類使用 extends 關鍵字來繼承抽象類。如果子類不是抽象類的話,它需要提供抽象類中所有宣告的方法的實現。子類使用關鍵字 implements 來實現介面。它需要提供介面中所有宣告的方法的實現。
  3. 抽象類可以有構造器,而介面不能有構造器
  4. 抽象方法可以有public、protected和default這些修飾符。介面方法預設修飾符是public。你不可以使用其它修飾符。
  5. 抽象類在java語言中所表示的是一種繼承關係,一個子類只能存在一個父類,但是可以存在多個介面。
  6. 抽象方法比介面速度要快,介面是稍微有點慢的,因為它需要時間去尋找在類中實現的方法。
  7. 如果往抽象類中新增新的方法,你可以給它提供預設的實現。因此你不需要改變你現在的程式碼。 如果你往介面中新增方法,那麼你必須改變實現該介面的類
參考

泛型中 extends 和 super 的區別

父類的靜態方法能不能被子類重寫

靜態方法與靜態成員變數可以被繼承,但是不能被重寫。它對子類隱藏,因此靜態方法也不能實現多型

程式和執行緒的區別

程式和執行緒的主要差別在於它們是不同的作業系統資源管理方式。程式有獨立的地址空間,一個程式崩潰後,在保護模式下不會對其它程式產生影響,而執行緒只是一個程式中的不同執行路徑。執行緒有自己的堆疊和區域性變數,但執行緒之間沒有單獨的地址空間,一個執行緒死掉就等於整個程式死掉,所以多程式的程式要比多執行緒的程式健壯,但在程式切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變數的併發操作,只能用執行緒,不能用程式。

final, finally, finalize的區別

  • final 用於宣告屬性,方法和類, 分別表示屬性不可變, 方法不可覆蓋, 類不可繼承.

  • finally 是異常處理語句結構的一部分,表示總是執行.

  • finalize 是Object類的一個方法,在垃圾收集器執行的時候會呼叫被回收物件的此方法,可以覆蓋此方法提供垃圾收集時的其他資源回收,例如關閉檔案等. JVM不保證此方法總被呼叫.

參考

Parcelable和Serializable的區別

Serializable是Java中的序列化介面,其使用起來簡單但 是開銷很大,序列化和反序列化過程需要大量I/O操作。而Parcelable是Android中的序列化方式,因此更適合用在Android平臺上,它的缺點就是使用起來稍微麻煩 點,但是它的效率很高,這是Android推薦的序列化方式,因此我們要首選Parcelable。Parcelable主要用在記憶體序列化上,通過Parcelable將物件序列化到儲存裝置 中或者將物件序列化後通過網路傳輸也都是可以的,但是這個過程會稍顯複雜,因此在這兩種情況下建議大家使用Serializable。

參考

為什麼非靜態內部類裡不可以有靜態屬性

談談對kotlin的理解

因人而異,請自行整理答案

String 轉換成 integer的方式及原理

Integer a = 2;
private void test() {
    String s1 = a.toString();  //方式一
    String s2 = Integer.toString(a);  //方式二
    String s3 = String.valueOf(a);  //方式三
}
複製程式碼

方式一原始碼:

    public String toString() {
        return toString(value);
    }

    public static String toString(int i) {
        if (i == Integer.MIN_VALUE)
            return "-2147483648";
        int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
        char[] buf = new char[size];
        getChars(i, size, buf);
        return new String(buf, true);
    }
複製程式碼

可以看出方式一最終呼叫的是方式二

通過toString()方法,可以把整數(包括0)轉化為字串,但是 Integer 如果是 null 的話,就會報空指標異常

方式三原始碼:

public static String valueOf(Object obj) {
return (obj == null) ? "null" : obj.toString();
}
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
複製程式碼

可以看出 當 Integer 是null的時候,返回的String是 字串 "null" 而不是 null

相關文章