1. 程式人生 > >BAT面試題集錦——Java基礎(一)

BAT面試題集錦——Java基礎(一)

一、java面試題

熟練掌握java是很關鍵的,大公司不僅僅要求你會使用幾個api,更多的是要你熟悉原始碼實現原理,甚至要你知道有哪些不足,怎麼改進,還有一些java有關的一些演算法,設計模式等等。

(一) java基礎面試知識點

java中==和equals和hashCode的區別

  • \==是運算子,用於比較兩個變數是否相等。一般用於基本型別的比較
  • equals,是Objec類的方法,用於比較兩個物件是否相等,預設Object類的equals方法是比較兩個物件的地址,跟\==的結果一樣。
public boolean equals(Object obj) {
    return
(this == obj); }
  • hashCode也是Object類的一個方法返回一個離散的int型整數。在集合類操作中使用,為了提高查詢速度。(HashMap,HashSet等)
將物件放入到集合中時,首先判斷要放入物件的hashcode值與集合中的任意一個元素的hashcode值是否相等
,如果不相等直接將該物件放入集合中。如果hashcode值相等,然後再通過equals方法判斷要放入物件與
集合中的任意一個物件是否相等,如果equals判斷不相等,直接將該元素放入到集合中,否則不放入。

回過來說get的時候,HashMap也先調key.hashCode()算出陣列下標,然後看equals
如果是true就是找到了, 所以就涉及了equals。 一個很常見的錯誤根源在於沒有覆蓋hashCode方法。在每個覆蓋了equals方法的類中,也必須覆蓋hashCode方法。 如果不這樣做的話,就會違反Object.hashCode的通用約定,從而導致該類無法結合所有基於雜湊的集合一起正常運作, 這樣的集合包括HashMap、HashSet和Hashtable。

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

  • 整型 int 4位元組
  • 長整型 long 4位元組
  • 字元型 char 1位元組
  • 單精度 float 4位元組
  • 雙精度 double 8位元組
  • 長雙精度 long double 8位元組

int與integer的區別

  1. Integer是int的包裝類,int則是java的一種基本資料型別
  2. Integer變數必須例項化後才能使用,而int變數不需要
  3. Integer實際是物件的引用,當new一個Integer時,實際上是生成一個指標指向此物件;而int則是直接儲存資料值
  4. Integer的預設值是null,int的預設值是0
Integer i = new Integer(100);
Integer j = new Integer(100);
System.out.print(i == j); //false

上面:因為是物件地址比較

Integer i = new Integer(100);
int j = 100;
System.out.print(i == j); //true

為包裝類Integer和基本資料型別int比較時,java會自動拆包裝為int,然後進行比較,實際上就變為兩個int變數的比較

Integer i = new Integer(100);
Integer j = 100;
System.out.print(i == j); //false

非new生成的Integer變數指向的是java常量池中的物件,而new Integer()生成的變數指向堆中新建的物件,兩者在記憶體中的地址不同

Integer i = 100;
Integer j = 100;
System.out.print(i == j); //true
Integer i = 128;
Integer j = 128;
System.out.print(i == j); //false

java在編譯Integer i = 100 ;時,會翻譯成為Integer i = Integer.valueOf(100);

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

java對於-128到127之間的數,會進行快取,Integer i = 127時,會將127進行快取,下次再寫Integer j = 127時,就會直接從快取中取,就不會new了

探探對java多型的理解

  1. 面向物件的三大特性:封裝、繼承、多型。從一定角度來看,封裝和繼承幾乎都是為多型而準備的。這是我們最後一個概念,也是最重要的知識點。
  2. 多型的定義:指允許不同類的物件對同一訊息做出響應。即同一訊息可以根據傳送物件的不同而採用多種不同的行為方式。(傳送訊息就是函式呼叫)
  3. 實現多型的技術稱為:動態繫結(dynamic binding),是指在執行期間判斷所引用物件的實際型別,根據其實際的型別呼叫其相應的方法。
  4. 多型的作用:消除型別之間的耦合關係。
  5. 現實中,關於多型的例子不勝列舉。比方說按下 F1 鍵這個動作,如果當前在 Flash 介面下彈出的就是 AS 3 的幫助文件;如果當前在 Word 下彈出的就是 Word 幫助;在 Windows 下彈出的就是 Windows 幫助和支援。同一個事件發生在不同的物件上會產生不同的結果。

    多型的好處:

  6. 可替換性(substitutability)。多型對已存在程式碼具有可替換性。例如,多型對圓Circle類工作,對其他任何圓形幾何體,如圓環,也同樣工作。
  7. 可擴充性(extensibility)。多型對程式碼具有可擴充性。增加新的子類不影響已存在類的多型性、繼承性,以及其他特性的執行和操作。實際上新加子類更容易獲得多型功能。例如,在實現了圓錐、半圓錐以及半球體的多型基礎上,很容易增添球體類的多型性。
  8. 介面性(interface-ability)。多型是超類通過方法簽名,向子類提供了一個共同介面,由子類來完善或者覆蓋它而實現的。如圖8.3 所示。圖中超類Shape規定了兩個實現多型的介面方法,computeArea()以及computeVolume()。子類,如Circle和Sphere為了實現多型,完善或者覆蓋這兩個介面方法。
  9. 靈活性(flexibility)。它在應用中體現了靈活多樣的操作,提高了使用效率。
  10. 簡化性(simplicity)。多型簡化對應用軟體的程式碼編寫和修改過程,尤其在處理大量物件的運算和操作時,這個特點尤為突出和重要。

String、StringBuffer、StringBuilder區別

首先說執行速度,或者說是執行速度,在這方面執行速度快慢為:StringBuilder > StringBuffer > String

String最慢的原因:
String為字串常量,而StringBuilder和StringBuffer均為字串變數,即String物件一旦建立之後該物件是不可更改的,但後兩者的物件是變數,是可以更改的。
1 String str="abc";
2 System.out.println(str);
3 str=str+"de";
4 System.out.println(str);

首先建立一個String物件str,並把“abc”賦值給str,然後在第三行中,其實JVM又建立了一個新的物件也名為str,然後再把原來的str的值和“de”加起來再賦值給新的str,而原來的str就會被JVM的垃圾回收機制(GC)給回收掉了,所以,str實際上並沒有被更改,也就是前面說的String物件一旦建立之後就不可更改了。所以,Java中對String物件進行的操作實際上是一個不斷建立新的物件並且將舊的物件回收的一個過程,所以執行速度很慢。

1 String str="abc"+"de";
2 StringBuilder stringBuilder=new StringBuilder().append("abc").append("de");
3 System.out.println(str);
4 System.out.println(stringBuilder.toString());

這樣輸出結果也是“abcde”和“abcde”,但是String的速度卻比StringBuilder的反應速度要快很多,這是因為第1行中的操作和String str=”abcde”;是完全一樣的,所以會很快,而如果寫成下面這種形式

1 String str1="abc";
2 String str2="de";
3 String str=str1+str2;

那麼JVM就會像上面說的那樣,不斷的建立、回收物件來進行這個操作了。速度就會很慢。

線上程安全上,StringBuilder是執行緒不安全的,而StringBuffer是執行緒安全的

總結一下
  • String:適用於少量的字串操作的情況
  • StringBuilder:適用於單執行緒下在字元緩衝區進行大量操作的情況
  • StringBuffer:適用多執行緒下在字元緩衝區進行大量操作的情況

什麼是內部類?內部類的作用

可以將一個類的定義放在另一個類的定義的內部,這就是內部類。

內部類的作用:
1. 內部類可以很好的實現隱藏
2. 內部類擁有外圍類的所有元素的訪問許可權
3. 可以實現多重繼承(不要誤解,這個是在一個外部類裡面多個內部類繼承不同基類達到一個外部類擁有多個基類的方法)
4. 可以避免介面中的方法和同一個類中的方法同名的問題

給第三點舉例:

public class Example1 {
   public String name()
   {
       return "liutao";
   }
}
public class Example2 {

    public int age()
    {
        return 25;  
    }
}
public class MainExample
{
   private class test1 extends Example1
    {
        public String name()
        {
          return super.name();
        }
    }
    private class test2 extends Example2
    {
       public int age()
       {
         return super.age();
       }
    }
   public String name()
    {
    return new test1().name();
   }
   public int age()
   {
       return new test2().age();
   }
   public static void main(String args[])
   {
       MainExample mi=new MainExample();
       System.out.println("姓名:"+mi.name());
       System.out.println("年齡:"+mi.age());
   }
}

抽象類和介面區別

  1. 抽象類和介面都不能直接例項化,如果要例項化,抽象類變數必須指向實現所有抽象方法的子類物件,介面變數必須指向實現所有介面方法的類物件。
  2. 抽象類要被子類繼承,介面要被類實現。
  3. 介面只能做方法申明,抽象類中可以做方法申明,也可以做方法實現
  4. 接口裡定義的變數只能是公共的靜態的常量,抽象類中的變數是普通變數。
  5. 抽象類裡的抽象方法必須全部被子類所實現,如果子類不能全部實現父類抽象方法,那麼該子類只能是抽象類。同樣,一個實現介面的時候,如不能全部實現介面方法,那麼該類也只能為抽象類。
  6. 抽象方法只能申明,不能實現,介面是設計的結果 ,抽象類是重構的結果
  7. 抽象類裡可以沒有抽象方法
  8. 如果一個類裡有抽象方法,那麼這個類只能是抽象類
  9. 抽象方法要被實現,所以不能是靜態的,也不能是私有的。
  10. 介面可繼承介面,並可多繼承介面,但類只能單根繼承。

抽象類的意義

java中抽象類更利於程式碼的維護和重用。
抽象類往往用來表徵對問題領域進行分析、設計中得出的抽象概念,是對一系列看上去不同,但是本質上相同的具體概念的抽象。具體分析如下:

  1. 因為抽象類不能例項化物件,所以必須要有子類來實現它之後才能使用。這樣就可以把一些具有相同屬性和方法的元件進行抽象,這樣更有利於程式碼和程式的維護。
    比如本科和研究生可以抽象成學生,他們有相同的屬性和方法。這樣當你對其中某個類進行修改時會受到父類的限制,這樣就會提醒開發人員有些東西不能進行隨意修改,這樣可以對比較重要的東西進行統一的限制,也算是一種保護,對維護會有很大的幫助。
  2. 當又有一個具有相似的元件產生時,只需要實現該抽象類就可以獲得該抽象類的那些屬性和方法。
    比如學校又新產生了專科生這類學生,那麼專科生直接繼承學生,然後對自己特有的屬性和方法進行補充即可。這樣對於程式碼的重用也是很好的體現。
    所以,Java中抽象類對於程式碼的維護和重用有很好的幫助,也是Java面向物件的一個重要體現。

java中可以,但是實際意義。

介面的意義

  1. 重要性:在Java語言中, abstract class 和interface 是支援抽象類定義的兩種機制。正是由於這兩種機制的存在,才賦予了Java強大的 面向物件能力。

  2. 簡單、規範性:如果一個專案比較龐大,那麼就需要一個能理清所有業務的架構師來定義一些主要的介面,這些介面不僅告訴開發人員你需要實現那些業務,而且也將命名規範限制住了(防止一些開發人員隨便命名導致別的程式設計師無法看明白)。

  3. 維護、拓展性:比如你要做一個畫板程式,其中裡面有一個面板類,主要負責繪畫功能,然後你就這樣定義了這個類。
    可是在不久將來,你突然發現這個類滿足不了你了,然後你又要重新設計這個類,更糟糕是你可能要放棄這個類,那麼其他地方可能有引用他,這樣修改起來很麻煩。
    如果你一開始定義一個介面,把繪製功能放在接口裡,然後定義類時實現這個介面,然後你只要用這個介面去引用實現它的類就行了,以後要換的話只不過是引用另一個類而已,這樣就達到維護、拓展的方便性。
  4. 安全、嚴密性:介面是實現軟體鬆耦合的重要手段,它描敘了系統對外的所有服務,而不涉及任何具體的實現細節。這樣就比較安全、嚴密一些(一般軟體服務商考慮的比較多)。

泛型中extends和super的區別

總結:
- 1.

class Super{
    }
    class Self extends Super{
    }
    class Son extends Self{
    }

    void test() {
        List<? extends Self> a = new ArrayList<>();//引數型別上界是Self
        a.add(new Son());//error 不能放入任何型別,因為編譯器只知道a中應該放入Self的某個子類,但具體放哪種子類它並不知道,因此,除了null以外,不能放入任何型別
        a.add(new Self());//error
        a.add(new Super());//error
        a.add(null);//error
        Self s1 = a.get(0); //返回型別是確定的Self類,因為<? extends T> 只能用於方法返回,告訴編譯器此返參的型別的最小繼承邊界為T,T和T的父類都能接收,但是入參型別無法確定,只能接受null的傳入
        Super s2 = a.get(0); //Self型別可以用Super接收
        Son s3 = a.get(0); //error:子類不能接收父型別引數

        //--------------------------------------

        List<? super Self> b = new ArrayList<>();//引數型別下界是Self
        b.add(new Son());//ok 只能放入T型別,且滿足T型別的超類至少是Self,換句話說,就是隻能放入Self的子型別
        b.add(new Self());//ok 本身型別也可以
        b.add(new Super());//ok 超類不可以
        b.add(null);//ok
        Object o1 = b.get(0);//返回型別是未知的, 因為<? super T>只能用於限定方法入參,告訴編譯器入參只能是T或其子型別,而返參只能用Object類接收
        Son o2 = b.get(0);//error
        Self o3 = b.get(0);//error
        Super o4 = b.get(0);//error

        List<?> c = new ArrayList<>();

    }

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

public class A extends B{
    public static void f() {
        System.out.println("com.sdkd.A.f()");
    }

    public static void main(String[] args) {
        A.f();
    }
}

class B {
    public static void f() {
        System.out.println("com.sdkd.B.f()");
    }
}

呼叫A.f()發生了什麼呢?
在JAVA虛擬機器中提供了5中方法呼叫位元組碼指令,如下:

  1. invokestatic, 呼叫靜態方法
  2. invokespecial, 呼叫例項構造器init方法、私有方法和父類方法
  3. invokevirtual, 呼叫所有的虛方法[public]
  4. invokeinterface , 呼叫介面方法,會在執行時再確定一個實現此介面的物件
  5. invokedynamic, 先在執行時動態解析出呼叫點限定符所引用的方法,然後再執行該方法,在此之前的4條呼叫指令,分派邏輯是固化在Java虛擬機器內部的,而invokedynamic指令是由引導方法決定的.

首先我們知道普通的public方法是能夠被重寫的,它在class方法中的位元組碼指令是invokevirtual,有了指令也要有引數——方法的入口地址,類似於這種invokevirtual address。該入口地址是要動態解析的,也就是將方法引用解析為直接引用,類似於這種invokevirtual 0xffffff ,也就是有一個符號引用轉化成直接引用地址的過程,但是invokestatic它所需要的符號引用在類載入階段符號引用解析成為直接引用了。也就是說它在執行的時候是這樣的invokestatic 0xfffff。最終結論是由於所用指令不同,所以不能重寫。

程序和執行緒的區別

  1. 程序是資源的分配和排程的一個獨立單元,而執行緒是CPU排程的基本單元
  2. 同一個程序中可以包括多個執行緒,並且執行緒共享整個程序的資源(暫存器、堆疊、上下文),一個進行至少包括一個執行緒。
  3. 程序的建立呼叫fork或者vfork,而執行緒的建立呼叫pthread_create,程序結束後它擁有的所有執行緒都將銷燬,而執行緒的結束不會影響同個程序中的其他執行緒的結束
  4. 執行緒是輕兩級的程序,它的建立和銷燬所需要的時間比程序小很多,所有作業系統中的執行功能都是建立執行緒去完成的
  5. 執行緒中執行時一般都要進行同步和互斥,因為他們共享同一程序的所有資源
  6. 執行緒有自己的私有屬性TCB,執行緒id,暫存器、硬體上下文,而程序也有自己的私有屬性程序控制塊PCB,這些私有屬性是不被共享的,用來標示一個程序或一個執行緒的標誌

final,finally,finalize的區別

final修飾符(關鍵字)
- 被final修飾的類,就意味著不能再派生出新的子類,不能作為父類而被子類繼承。因此一個類不能既被abstract宣告,又被final宣告。
- 將變數或方法宣告為final,可以保證他們在使用的過程中不被修改。被宣告為final的變數必須在宣告時給出變數的初始值,而在以後的引用中只能讀取。
- 被final宣告的方法也同樣只能使用,不能過載。

finally修飾符:
- 是在異常處理時提供finally塊來執行任何清除操作。不管有沒有異常被丟擲、捕獲,finally塊都會被執行。try塊中的內容是在無異常時執行到結束。catch塊中的內容,是在try塊內容發生catch所宣告的異常時,跳轉到catch塊中執行。finally塊則是無論異常是否發生,都會執行finally塊的內容,所以在程式碼邏輯中有需要無論發生什麼都必須執行的程式碼,就可以放在finally塊中。

finalize是方法名:
- java技術允許使用finalize()方法在垃圾收集器將物件從記憶體中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在確定這個物件沒有被引用時對這個物件呼叫的。它是在object類中定義的,因此所有的類都繼承了它。子類覆蓋finalize()方法以整理系統資源或者被執行其他清理工作。finalize()方法是在垃圾收集器刪除物件之前對這個物件呼叫的。

Serializable 和Parcelable 的區別

  1. 作用Serializable的作用是為了儲存物件的屬性到本地檔案、資料庫、網路流、rmi以方便資料傳輸,當然這種傳輸可以是程式內的也可以是兩個程式間的。而Android的Parcelable的設計初衷是因為Serializable效率過慢,為了在程式內不同元件間以及不同Android程式間(AIDL)高效的傳輸資料而設計,這些資料僅在記憶體中存在,Parcelable是通過IBinder通訊的訊息的載體。

  2. 效率及選擇:
    Parcelable的效能比Serializable好,在記憶體開銷方面較小,所以在記憶體間資料傳輸時推薦使用Parcelable,如activity間傳輸資料,而Serializable可將資料持久化方便儲存,所以在需要儲存或網路傳輸資料時選擇Serializable,因為android不同版本Parcelable可能不同,所以不推薦使用Parcelable進行資料持久化

Serializable序列化不儲存靜態變數,可以使用Transient關鍵字對部分欄位不進行序列化,也可以覆蓋writeObject、readObject方法以實現序列化過程自定義

靜態屬性和靜態方法是否可以被繼承?是否可以被重寫?以及原因?

結論:java中靜態屬性和靜態方法可以被繼承,但是沒有被重寫(overwrite)而是被隱藏.

原因:
1. 靜態方法和屬性是屬於類的,呼叫的時候直接通過類名.方法名完成對,不需要繼承機制及可以呼叫。如果子類裡面定義了靜態方法和屬性,那麼這時候父類的靜態方法或屬性稱之為”隱藏”。如果你想要呼叫父類的靜態方法和屬性,直接通過父類名.方法或變數名完成,至於是否繼承一說,子類是有繼承靜態方法和屬性,但是跟例項方法和屬性不太一樣,存在”隱藏”的這種情況。
2. 多型之所以能夠實現依賴於繼承、介面和重寫、過載(繼承和重寫最為關鍵)。有了繼承和重寫就可以實現父類的引用指向不同子類的物件。重寫的功能是:”重寫”後子類的優先順序要高於父類的優先順序,但是“隱藏”是沒有這個優先順序之分的。
3. 靜態屬性、靜態方法和非靜態的屬性都可以被繼承和隱藏而不能被重寫,因此不能實現多型,不能實現父類的引用可以指向不同子類的物件。非靜態方法可以被繼承和重寫,因此可以實現多型。

更多文章請關注微信公眾號:碼老闆
這裡寫圖片描述