天天看點

Java 面試(一) | Java基礎部分Java面試基礎部分Java面試面向對象部分Java面試異常處理部分Java面試常用API部分Java面試IO部分Java面試集合部分

文章目錄

  • Java面試基礎部分
    • 1.JDK和JRE的差別
    • 2.==和equals的差別
    • 3.兩個對象equals比較為true,但卻可以有不同的hashcode,對嗎?
    • 4.String類相關
    • 5.&和&&的差別
    • 6.java中如何跳出多重嵌套循環
    • 7.重載和重寫的差別?
    • 8.為什麼不能根據傳回類型來區分方法重載
    • 9.當一個對象被當作參數傳遞到一個方法後,此方法可改變這個對象的屬性,并可傳回變化後的結果,那麼這裡是值傳遞還是引用傳遞?
    • 10.char型變量中能否存儲一個中文漢字,為什麼?
    • 11.抽象(abstract)方法是否可同時是靜态的?是否可同時是本地方法?是否可同時是synchronized?
    • 12.抽象類和接口有什麼異同?
    • 13.靜态變量和執行個體變量的差別?
    • 14.break和continue的差別?
    • 15.String s = "Hello";s = s + " world!";這兩行代碼執行後,原始的 String 對象中的内容變了沒有?
    • 16.switch能否作用在byte、String、long上?
    • 17.數組是否有length()方法?String呢?如何擷取集合類型資料的長度?
    • 18.Java的基本資料類型有哪些?各占幾個位元組?
    • 19.Stirng是基本資料類型嗎?
    • 20.short s1=1;s1=s1+1;有錯嗎?short s1=1;s1+=1;有錯嗎?
    • 21.int和Integer有什麼差別?
    • 22.下面Integer類型的數值比較輸出的結果是?(自動裝箱相關)
    • 23.String類的常用方法?
    • 24.String、StringBuffer、StringBuilder的差別?
    • 24.基本資料類型和字元串之間的轉換
  • Java面試面向對象部分
    • 1.面向對象包括哪些特性?怎麼了解的?
    • 2.通路權限修飾符public、protected、private、不寫(預設)時的差別?
    • 3.java中為什麼要用到clone?
    • 4.new一個對象和clone一個對象過程的差別?
    • 5.Java中實作多态的機制是什麼?
    • 6.談談你對面向對象的了解?
  • Java面試異常處理部分
    • 1.final、finally、finalize的差別?
    • 2.Java中的異常分為哪些種類?
    • 3.Error和Exception的差別?
    • 4.調用下面的方法,得到的傳回值是什麼?(catch和finally的執行過程問題)
    • 5.說出5個最常見的RuntimeException?
    • 6.throw和throws的差別?
  • Java面試常用API部分
    • 1.Math.round(11.5)的值是多少?Math.round(-11.5)呢?
    • 2.String、StringBuilder、StringBuffer的差別?
    • 3.請說出下面程式的輸出?(字元串對象的比較)
    • 4.如何取得年月日、小時分鐘秒?
    • 5.如何取得從 1970 年 1 月 1 日 0 時 0 分 0 秒到現在的毫秒數?
    • 6.如何格式化日期
  • Java面試IO部分
    • 1.Java中流的分類?
    • 2.位元組流如何轉化為字元流?
    • 3.如何将一個 java 對象序列化到檔案裡?又如何反序列化到程式中讀出?
    • 4.位元組流和字元流的差別?
    • 5.什麼是Java序列化?如何實作?
  • Java面試集合部分
    • 1.請問ArrayList、HashSet、HashMap是線程安全的嗎?如果不是怎麼擷取線程安全的集合?
    • 2.集合的初始容量、加載因子、擴容增量彙總
    • 3.集合中哪些可以存儲null值?

Java面試基礎部分

1.JDK和JRE的差別

  • JDK:Java Development Kit 的簡稱,Java 開發工具包,提供了 Java 的開發環境和運作環境。
  • JRE:Java Runtime Environment 的簡稱,Java 運作環境,為 Java 的運作提供了所需環境。
具體來說 JDK 其實包含了 JRE,同時還包含了編譯 Java 源碼的編譯器 Javac,還包含了很多 Java 程式調試和分析的工具。簡單來說:如果你需要運作 Java 程式,隻需安裝 JRE 就可以了,如果你需要編寫 Java 程式,需要安裝 JDK。

2.==和equals的差別

最大不同:equals是方法,==是運算符

==

  • 基本類型:比較值是否相同
  • 引用類型:比較引用是否相同

equals

  • 本質上就是==,但不能用于基資料本類型變量的比較,如果沒有對equals方法重寫,則比較的是引用類型變量所指向的對象的位址
  • 大多數類如String、Integer等都重寫了equals方法,先根據==判斷,如果不等,再根據值判斷

3.兩個對象equals比較為true,但卻可以有不同的hashcode,對嗎?

不對,如果兩個對象的equals比較為true,那麼他們的hashcode應該相同

Java規定

  • 如果兩個對象相同(equals方法傳回true),那麼他們的hashcode一定要相同
  • 如果兩個對象的hashcode相同,那麼他們不一定相同
  • 雖然可以不按照上述要求做,但是會引起一系列問題,比如對于使用hash存儲的系統,如果hashcode頻繁沖突會造成存取性能急劇下降

equals方法要滿足的特性

  • 自反性(x.equals(x)必須傳回true)
  • 對稱性(x.equals(y)傳回true 時,y.equals(x)也必須傳回true)
  • 傳遞性(x.equals(y)和y.equals(z)都傳回true時,x.equals(z)也必須傳回true)
  • 一緻性(當x和y引用的對象資訊沒有被修改時,多次調用x.equals(y)應該得到同樣的傳回值)
  • 對于任何非null值的引用x,x.equals(null)必須傳回false

如何實作高品質的equals方法

  • 使用==操作符檢查"參數是否為這個對象的引用"
  • 使用 instanceof操作符檢查"參數是否為正确的類型"
  • 對于類中的關鍵屬性,檢查參數傳入對象的屬性是否與之相比對
  • 編寫完equals方法後,問自己它是否滿足對稱性、傳遞性、一緻性
  • 重寫equals時總是要重寫hashCode
  • 不要将equals方法參數中的Object對象替換為其他的類型,在重寫時不要忘掉@Override注解

4.String類相關

String類可以被繼承嗎

String類用final修飾,無法被繼承,以下為String類的源代碼片段
Java 面試(一) | Java基礎部分Java面試基礎部分Java面試面向對象部分Java面試異常處理部分Java面試常用API部分Java面試IO部分Java面試集合部分

String的不可變是如何設計的

  • 什麼是不可變
如下圖,給一個已有字元串"abcd"第二次指派成"abcedl",不是在原記憶體位址上修改資料,而是重新指向一個新對象,新位址。
Java 面試(一) | Java基礎部分Java面試基礎部分Java面試面向對象部分Java面試異常處理部分Java面試常用API部分Java面試IO部分Java面試集合部分
  • String為什麼不可變
首先,String中的存儲成員是一個final修飾的char[] value數組,是以這個value數組不可變,但這裡的不可變是指引用位址不可變,而數組中的成員是可以變的
是以要維護String的不可變,關鍵是java設計人員在封裝String類的時候沒有去改變這個value數組中的值,沒有暴露value數組中的值給外面,比如将value數組用private修飾,将通路權限降到最低

最後還加了個保險,将String類聲明為final,防止被繼承,進而被他人破環了所封裝的結構

是以String不可變的關鍵在于底層是如何實作的,而不僅僅是依靠一個final

為什麼要把String類定義為不可變類呢?

  • 線程安全:多個線程讀同一資源,是不會引發競态條件的,隻有對資源做寫操作才有危險,而不可變對象不能寫,是以線程安全
  • 支援字元串常量池:用相同的字元串字面量給字元串指派,它們都指向的是同一塊記憶體位址,這樣在大量使用字元串的場景下,可以節省記憶體空間,提高效率,而之是以可以實作這個特性,String的不可變性是一個基本條件,如果記憶體裡多個對象指向的字元串内容可以任意變化,那麼這麼做就完全沒有意義,因為一個對象改變了這個字元串的值,那麼所有指向這個值的對象(的值)都将改變,是以當要改變指向這個字元串的對象時,會重新開辟空間存放值,而不是在原來的記憶體位址中修改
  • 安全:如果字元串可變,會引發一系列安全問題,大多數字段都是用的字元串來設值,如果字元串可變,那麼改變所指向字元串的值,就會引發安全漏洞
  • 因為字元串是不可變的,是以在它建立的時候HashCode就被緩存了,不需要重新計算。這就使得字元串很适合作為Map中的鍵,字元串的處理速度要快過其它的鍵對象。這就是HashMap中的鍵往往都使用字元串。

final除了修飾類,還有哪些用法

  • final修飾的變量,一旦指派,不可重新指派
  • final修飾的方法無法被覆寫
  • final修飾的執行個體變量,必須一建立就手動指派,或者構造器指派,不能采用系統預設值
  • final修飾的執行個體變量一般和static聯用,用來聲明靜态常量,這樣的變量隻能一開始就指派,不能借助構造器
  • final不能和abstract聯用,因為abstract是希望得到子類的繼承或方法的重寫
  • 總之,final代表最終的,不可變的

5.&和&&的差別

&和&&

  • &為邏輯與,&&為短路與
  • &和&&的執行結果是完全一樣的,但是&&存在短路現象,當&&兩邊的左邊表達式為false時,那麼就不用再執行右邊的表達式了,因為執不執行最終的結果都是false;而&是不管左邊表達式true還是false,都會再執行右邊的表達式
  • &運算符還可代表按位與,用于二進制運算,區分邏輯于與按位與是靠兩邊的操作數類型是boolean型還是整型

|和||

  • |為邏輯或,||為短路或
  • 和上邊一樣,||存在短路現象,當||左邊表達式為true時,右邊的表達式不會再執行,因為最終的結果必然會是true
  • |也可代表按位或

6.java中如何跳出多重嵌套循環

在最外層循環前加一個标記如outfor,然後用break outfor,可以跳出多重循環,如下圖:
Java 面試(一) | Java基礎部分Java面試基礎部分Java面試面向對象部分Java面試異常處理部分Java面試常用API部分Java面試IO部分Java面試集合部分

7.重載和重寫的差別?

方法的重載和重寫都是實作多态的方式,差別在于前者是實作編譯時的多态,而後者實作的是運作時的多态
重載發生在一個類當中,同名的方法如果有不同的參數類型或者不同的參數個數或者不同的參數順序,那麼可以視為重載,不能依靠傳回類型判斷是否為重載
重寫發生在子類和父類當中,要求子類重寫的方法和原方法名稱、參數、傳回類型完全一緻,并且通路權限不能低于父類方法,不能比父類方法聲明更多的異常

方法重載規則

  • 方法名相同,參數個數、參數類型、參數類型任意一個或多個不同
  • 與傳回類型無關,存在于父類和子類、同類中
  • 可以抛出不同的異常,可以有不同的修飾符

方法重寫規則

  • 參數清單、傳回類型、方法名必須完全一緻
  • 方法通路權限不能低于父類
  • 方法抛出的異常不能比父類更寬泛
  • 構造方法不能重寫
  • 聲明為final的方法不能重寫
  • 聲明為static的方法不存在重寫
  • 方法重寫是實作多态的必要條件

8.為什麼不能根據傳回類型來區分方法重載

看一下以下代碼

public void  testMethod(){
    doSome();
}
public void doSome()){}
public int doSome(){return 1;}

           
在java語言中,調用一個方法,即使這個方法有傳回值,我們也可以不接收這個傳回值,例如上面這兩個方法doSome(),在testMethod()中調用的時候,java編譯器無法區分調用的具體是哪一個方法,是以對于編譯器來說,doSome()方法不是重載而是重複,編譯器報錯。是以區分這兩個方法不能依靠方法的傳回值類型

9.當一個對象被當作參數傳遞到一個方法後,此方法可改變這個對象的屬性,并可傳回變化後的結果,那麼這裡是值傳遞還是引用傳遞?

是值傳遞,java中的參數傳遞都為值傳遞
當參數為基本類型的時候,傳遞的參數是變量值的拷貝
當參數為引用類型的時候,傳遞的參數是引用變量所指向位址的拷貝,當這個拷貝傳入方法後未改變所指向的位址時,改變這個位址所指向的記憶體空間中的内容,那麼原來的内容也會被改變;當傳入方法後改變了所指向的位址後,對内容的修改不會影響到原來的值

10.char型變量中能否存儲一個中文漢字,為什麼?

可以,java中采用unicode編碼,而java中的unicode占兩個位元組,而char也是占兩個位元組,unicode字元包含了中文,是以可以存儲一個中文漢字
char的範圍為0~216-1(即0 ~ 65535)

補充:java虛拟機采用UCS2(通用字元集)标準即UTF-16儲存字元,所有的字元在記憶體中都是2個位元組,這樣虛拟機處理字元串的截取、長度和判斷都非常容易。其他語言如PHP、Python也是,在運作時采用固定長度存儲字元。

相對應編譯後的class,java規定采用UTF-8儲存,因為大部分是英文字元,隻有一個位元組,可以大量節省存儲空間。

11.抽象(abstract)方法是否可同時是靜态的?是否可同時是本地方法?是否可同時是synchronized?

都不能

  • 抽象方法需要子類重寫,而靜态方法無法被重寫,隻能被子類重新定義
  • 本地方法是由本地代碼(如c/c++)實作的方法,而抽象方法是沒有實作的
  • synchronized和方法的實作細節有關,抽象方法不涉及實作細節,隻有一個方法頭

12.抽象類和接口有什麼異同?

相同點

  • 接口和抽象類都不能被執行個體化,它們都位于繼承樹的頂端,用于被其他類實作和繼承
  • 可以将抽象類和接口類型作為引用類型
  • 接口和抽象類都包含抽象方法
  • 一個類如果繼承了某個抽象類或者實作了某個接口都需要對其中的抽象方法全部進行實作,對于抽象類來說如果沒有全部實作,那麼該類仍然需要被聲明為抽象類

不同點

  • 一個類可以實作多個接口,一個類隻能繼承一個抽象類;
  • 一個類可以實作多個接,一個類隻能繼承一個抽象類;
  • 接口中不能定義構造器,抽象類中可以定義構造器;抽象類的構造器不是用來建立對象,而是讓其子類調用完成抽象類的初始化操作;
  • 接口中的方法全部都是抽象方法(jdk1.8前),抽象類中可以有抽象方法和具體方法;
  • 接口中的成員全都是public 的,抽象類中的成員可以是 private、預設、protected、public;
  • 接口中定義的成員變量實際上都是常量,抽象類中可以定義成員變量;
  • 接口中不能有靜态方法,抽象類中可以包含靜态方法;(注意:JDK1.8之後接口中可以有靜态方法)

13.靜态變量和執行個體變量的差別?

  • 靜态變量是屬于類的,在類被加載時就會建立,而且記憶體中有且隻有一個,能被多個對象共享
  • 執行個體變量是屬于對象的,隻有建立了對象才能通路到它

14.break和continue的差別?

  • 都是用來控制循環的語句
  • break用于完全跳出所在循環
  • continue用于跳出本次循環,繼續下次循環

15.String s = “Hello”;s = s + " world!";這兩行代碼執行後,原始的 String 對象中的内容變了沒有?

沒有,因為String類建立的對象是不可變對象,如果改變對象的内容,隻會重新開辟記憶體空間新生成一個對象,将String變量指向這個新對象的位址,而原來的對象依然存在,隻是String變量不再指向它而已

對于字元串常量,如果内容相同,Java 認為它們代表同一個 String 對象。而用關鍵字 new 調用構造器,總是會建立一個新的對象,無論内容是否相同。
至于為什麼要把 String 類設計成不可變類,是它的用途決定的。其實不隻String,很多 Java 标準類庫中的類都是不可變的。在開發一個系統的時候,我們有時候也需要設計不可變類,來傳遞一組相關的值,這也是面向對象思想的展現。不可變類有一些優點,比如因為它的對象是隻讀的,是以多線程并發通路也不會有任何問題。當然也有一些缺點,比如每個不同的狀态都要一個對象來代表,可能會造成性能上的問題。是以 Java 标準類庫還提供了一個可變版本,即 StringBuffer和StringBuilder。

16.switch能否作用在byte、String、long上?

可以作用在byte和String上,long不行

在java5前嚴格意義上講switch(expression)中的expression隻支援int類型,但由于可以自動類型轉化,是以可以支援byte、short、char、以及它們的包裝類:Byte、Short、Character、Integer,從java5開始引入了枚舉enum類型,從java7開始,expression還可以是String,但是long是一直不行的。
Java 面試(一) | Java基礎部分Java面試基礎部分Java面試面向對象部分Java面試異常處理部分Java面試常用API部分Java面試IO部分Java面試集合部分

17.數組是否有length()方法?String呢?如何擷取集合類型資料的長度?

數組沒有length()方法,而是有length屬性,String有length方法,擷取集合資料類型的長度可以用size()方法

JavaScript中擷取字元串的長度用的是length屬性,容易和Java混淆

18.Java的基本資料類型有哪些?各占幾個位元組?

  • 基本資料類型:byte、short、int、long、float、double、char、boolean
  • 所占位元組數:1、2、4、8、4、8、2、-
boolean所占位元組數一直有多種說法:1bit(實際占的空間)、1byte(計算機中實際存儲的空間)、4byte(JVM規範:boolean類型當作int處理,而boolean數組當作byte處理)

19.Stirng是基本資料類型嗎?

不是,String由class定義,是以是類,即引用資料類型,底層用char型數組實作

20.short s1=1;s1=s1+1;有錯嗎?short s1=1;s1+=1;有錯嗎?

第一種有錯

Java中指派為1中的1類型預設為int,s執行hort s1=1語句時,當數值沒有超過short範圍時,Java底層會進行強制類型轉化:short s1=(short)1,超過了範圍需要強轉;而執行s1=s1+1時,編譯時并不知道s1的值,java底層不會進行類型轉化,是以相當于:short s1=(int)(s1)+(int)(1),因為java運算時會将數值自動轉化成容量最大的類型,是以最終會報錯

第二種正确

s1+=1相當于s1=(short)(s1+1),有隐含的強制類型轉換

21.int和Integer有什麼差別?

java是一個完全面向對象的語言,但是為了程式設計的友善還是引入了基本資料類型,為了能将這些基本資料類型當作對象來操作,Java為每一種基本資料類型引入了相應的包裝類(wrapper class),int的包裝類就是Integer

從Java5開始引入了自動裝箱和拆箱機制,使得二者可以互相轉換
  • 基本資料類型:byte、short、int、long、float、double、char、boolean
  • 對應的包裝類:Byte、Short、Integer、Long、Float、Double、Character、Boolean

22.下面Integer類型的數值比較輸出的結果是?(自動裝箱相關)

public class Test{
    public static void main(String[] args) {
        Integer f1 = 100, f2 = 100, f3 = 150, f4 = 150;
        System.out.println(f1 == f2);
        System.out.println(f3 == f4);
    }
}
           

如果不明白原理很容易認為兩個輸出要麼都是 true 要麼都是 false。首先需要注意的是 f1、f2、f3、f4 四個變量都是 Integer 對象引用,是以下面的==運算比較的不是值而是引用。裝箱的本質是什麼呢?當我們給一個Integer 對象賦一個 int 值的時候,會調用 Integer 類的靜态方法 valueOf,如果看看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 是 Integer 的内部類,其代碼如下所示:

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 之間,那麼不會 new 新的 Integer 對象,而是直接引用常量池中的Integer對象

  • 不僅是Integer類,Character、Byte、Short、Long這幾個包裝類的valueOf方法實作原理類似,都會有緩存器
  • 對于Double、Float,它們在-128~127之間的數是無限的,是以沒有緩存器
  • 對于Boolean,它在一開始就定義了并建立好了兩個Boolean類型對象,是以後面也不會再建立Boolean對象

是以上面的面試題中f1 == f2的結果是 true,而f3 == f4 的結果是false。

23.String類的常用方法?

Java 面試(一) | Java基礎部分Java面試基礎部分Java面試面向對象部分Java面試異常處理部分Java面試常用API部分Java面試IO部分Java面試集合部分

24.String、StringBuffer、StringBuilder的差別?

  • 可變不可變

String:字元串常量,在修改時不會改變自身;若修改,等于重新生成新的字元串對象。

StringBuffer:在修改時會改變對象自身,每次操作都是對 StringBuffer 對象本身進行修改,不是生成新的對象;使用場景:對字元串經常改變情況下,主要方法:append(),insert()等。

  • 線程是否安全

String:對象定義後不可變,線程安全。

StringBuffer:是線程安全的(對調用方法加入同步鎖),執行效率較慢,适用于多線程下操作字元串緩沖區大量資料。

StringBuilder:是線程不安全的,适用于單線程下操作字元串緩沖區大量資料。

  • 共同點

StringBuilder與StringBuffer有公共父類 AbstractStringBuilder(抽象類)。

StringBuilder、StringBuffer 的方法都會調用 AbstractStringBuilder 中的公共方法,如 super.append(…)。隻是 StringBuffer 會在方法上加 synchronized 關鍵字,進行同步。最後,如果程式不是多線程的,那麼使用StringBuilder 效率高于 StringBuffer。

24.基本資料類型和字元串之間的轉換

字元串轉基本資料類型

調用基本資料類型對應的包裝類中的方法 parseXXX(String)或valueOf(String)即可傳回相應基本類型。

valueOf(String)方法實際是内部調用parseXXX(String)方法

基本類型轉字元串

  • 一種方法是将基本資料類型與空字元串(“”)連接配接(+)即可獲得其所對應的字元串
  • 另一種方法是調用 String類中的 valueOf()方法傳回相應字元串

Java面試面向對象部分

1.面向對象包括哪些特性?怎麼了解的?

  • 封裝:把類的某些資訊隐藏在類内部,不允許外部程式直接通路,而是通過該類提供的方法來實作對隐藏資訊的操作和通路
面向對象的本質就是将現實世界描繪成一系列完全自治、封閉的對象。我們在類中編寫的方法就是對實作細節的一種封裝;我們編寫一個類就是對資料和資料操作的封裝。可以說,封裝就是隐藏一切可隐藏的東西,隻向外界提供最簡單的程式設計接口。
  • 繼承:繼承是從已有類得到繼承資訊建立新類的過程。
提供繼承資訊的類被稱為父類(超類、基類);得到繼承資訊的類被稱為子類(派生類)。繼承讓變化中的軟體系統有了一定的延續性,同時繼承也是封裝程式中可變因素的重要手段。
  • 多态:多态性是指允許不同子類型的對象對同一消息作出不同的響應。簡單的說就是用同樣的對象引用調用同樣的方法但是做了不同的事情。
多态性分為編譯時的多态性和運作時的多态性。如果将對象的方法視為對象向外界提供的服務,那麼運作時的多态性可以解釋為:當 A系統通路B系統提供的服務時,B 系統有多種提供服務的方式,但一切對 A 系統來說都是透明的。方法重載(overload)實作的是編譯時的多态性(也稱為前綁定),而方法重寫(override)實作的是運作時的多态性(也稱為後綁定)。運作時的多态是面向對象最精髓的東西,要實作多态需要做兩件事:
  • 方法重寫(子類繼承父類并重寫父類中已有的或抽象的方法);
  • 對象造型(用父類型引用指向子類型對象,這樣同樣的引用調用同樣的方法就會根據子類對象的不同而表現出不同的行為)。
  • 抽象:抽象是将一類對象的共同特征總結出來構造類的過程,包括資料抽象和行為抽象兩方面。抽象隻關注對象有哪些屬性和行為,并不關注這些行為的細節是什麼。

2.通路權限修飾符public、protected、private、不寫(預設)時的差別?

通路權限由高到低:

  • public:所有類中都可以通路到
  • protected:目前類、同一包下的類以及該類的子類可以通路到
  • 預設:目前類和同一包下的類可以通路到
  • private:隻有目前類可以通路到
Java 面試(一) | Java基礎部分Java面試基礎部分Java面試面向對象部分Java面試異常處理部分Java面試常用API部分Java面試IO部分Java面試集合部分

3.java中為什麼要用到clone?

在實際程式設計過程中,我們常常要遇到這種情況:有一個對象 A,在某一時刻 A 中已經包含了一些有效值,此時可能會需要一個和 A 完全相同新對象 B,并且此後對 B 任何改動都不會影響到 A 中的值,也就是說,A 與 B 是兩個獨立的對象,但 B 的初始值是由 A 對象确定的。在 Java 語言中,用簡單的指派語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但clone()方法是其中最簡單,也是最高效的手段。

說到克隆,談談深克隆和淺克隆?

  • 淺克隆:建立一個新對象,新對象的屬性和原來對象完全相同,對于非基本類型屬性,仍指向原有屬性所指向的對象的記憶體位址。
  • 深克隆:建立一個新對象,屬性中引用的其他對象也會被克隆,不再指向原有對象位址。
繼承至java.lang.Object類的clone()方法是淺克隆,若要實作深克隆需要實作Cloneable接口,并重寫clone()方法,但要實作真正的深克隆是很麻煩的,因為類中可能會循環聲明各種引用類型變量,這樣就會重複的進行對引用類型所在的類重寫clone方法,直到沒有引用類型變量。

4.new一個對象和clone一個對象過程的差別?

  • new 操作符的本意是配置設定記憶體。程式執行到 new 操作符時,首先去看 new 操作符後面的類型,因為知道了類型,才能知道要配置設定多大的記憶體空間。配置設定完記憶體之後,再調用構造函數,填充對象的各個域,這一步叫做對象的初始化,構造方法傳回後,一個對象建立完畢,可以把他的引用(位址)釋出到外部,在外部就可以使用這個引用操縱這個對象。
  • clone 在第一步是和 new 相似的,都是配置設定記憶體,調用 clone 方法時,配置設定的記憶體和原對象(即調用 clone 方法的對象)相同,然後再使用原對象中對應的各個域,填充新對象的域,填充完成之後,clone方法傳回,一個新的相同的對象被建立,同樣可以把這個新對象的引用釋出到外部。

5.Java中實作多态的機制是什麼?

java中的多态靠的是父類或接口所聲明的引用變量可以指向子類或實作類的執行個體對象,而程式調用的方法是在運作期動态綁定的,實際調用的是引用變量所指向的具體執行個體對象的方法,也就是記憶體裡正在運作的那個對象的方法,而不是引用變量的類型中定義的那個方法。

6.談談你對面向對象的了解?

所謂面向對象就是一組資料結構和處理它們的方法組成的,重點“資料”包括對象的特性、狀态等靜态資訊;“方法”也就是行為,包括該對象的對資料的操作、功能等能動資訊。把相同行為的對象歸納為類,類是一個抽象的概念,對象是類的具體。簡單點說:對象就是類的執行個體。比如:汽車是一個類,那麼寶馬就是一個對象。

面向對象的目的:解決軟體系統的可擴充性、可維護性、可重用性

面向對象的三大特性:封裝、多态和繼承:
(1)封裝(對應可擴充性):隐藏對象的屬性和實作細節,僅對外公開接口,控制在程式中屬性的讀和修改的通路級别。封裝是通過通路控制符(public protected private)來實作。一個類就可看成一個封裝。
(2)繼承(重用性和擴充性):子類繼承父類,可以繼承父類的方法和屬性。可以對父類方向進行覆寫(實作了多态)。但是繼承破壞了封裝,因為他是對子類開放的,修改父類會導緻所有子類的改變,是以繼承一定程度上又破壞了系統的可擴充性,隻有明确的IS-A關系才能使用。繼承要慎用,盡量優先使用組合。
(3)多态(可維護性和可擴充性):接口的不同實作方式即為多态。接口是對行為的抽象,剛才在封裝提到,找到變化部分并封裝起來,但是封裝起來後,怎麼适應接下來的變化?這正是接口的作用,接口的主要目的是為不相關的類提供通用的處理服務,我們可以想象一下。比如鳥會飛,但是超人也會飛,通過飛這個接口,我們可以讓鳥和超人,都實作這個接口。

面向對象程式設計(OOP)其實就是一種設計思想,在程式設計過程中把每一部分都盡量當成一個對象來考慮,以實作軟體系統的可擴充性,可維護性和可重用性

Java面試異常處理部分

1.final、finally、finalize的差別?

  • final:用于修飾屬性、方法、類,分别表示屬性不可被更改、方法不可被重寫、類不可被繼承
  • finally:異常處理語句結構的一部分,表示最終一定會執行
  • finalize:Object 類的一個方法,是以Java對象都有這個方法,當某Java對象沒有更多的引用指向的時候,會被垃圾回收器回收,該對象被回收之前,由垃圾回收器來負責調用此方法,通常在該方法中進行回收前的準備工作。該方法更像是一個對象生命周期的臨終方法,當該方法被系統調用則代表該對象即将“死亡”,但是需要注意的是,我們主動行為上去調用該方法并不會導緻該對象“死亡”,這是一個被動的方法(其實就是回調方法),不需要我們調用。

補充:回調函數:

回調函數是指使用者自己定義一個函數,實作這個函數的程式内容,然後把這個函數(入口位址)作為參數傳入别人(或系統)的函數中,由别人(或系統)的函數在運作時來調用的函數。函數是你實作的,但由别人(或系統)的函數在運作時通過參數傳遞的方式調用,這就是所謂的回調函數。簡單來說,就是由别人的函數運作期間來回調你實作的函數。

2.Java中的異常分為哪些種類?

按照異常處理的時機分為:

  • 編譯時異常(受控異常、CheckedException):Java認為checked異常都是可以被處理的異常,是以Java程式必須顯式地處理checked異常。如果程式沒有處理checked異常,該程式在編譯時就會發生錯誤無法編譯。這展現了Java的設計哲學:沒有完善的錯誤處理的代碼根本沒有機會執行。
  • 運作時異常(非受控異常、UnCheckedException):運作時異常隻有在代碼運作時才發生異常,編譯的時候不需要try…catch。RuntimeException如:除數是零和數組下标越界等,其産生頻繁,處理麻煩,若顯式聲明或捕獲會對程式的可讀性和運作效率影響很大。是以系統自動檢測并将它們交給預設的異常處理程式,當然如果你有處理需求,也可以顯式地捕獲它們。

3.Error和Exception的差別?

Error和Exception都是Throwable類的子類,它們的差別如下:

  • Error:就是程式運作時候抛出的最嚴重級别的錯誤了,如系統崩潰,虛拟機錯誤,記憶體空間不足,方法調用棧溢出等,一般是指與虛拟機相關的問題,如:VirtualMachineError,ThreadDeath。抛出了Error的程式從Java設計的角度來講,程式基本不可以通過後續代碼修複,進而理應終止。當然,從文法上來講,所有這些都可以被寫進catch裡面,但是Error因為上述原因,不應該被代碼處理。
  • Exception:表示程式可以處理的異常,可以捕獲而且能夠修複。遇到這類異常,應該盡可能處理異常,使程式恢複運作,而不應該随意終止異常。
  • Exception又分為受檢異常(CheckedException)和未受檢異常(UnCheckedException、運作時異常),運作時異常如ArithmeticException,IllegalArgumentException編譯能通過,但是一運作就終止了,程式不會處理運作時異常,出現這類異常,程式會終止。而受檢查的異常,要麼用 try…catch 捕獲,要麼用throws字句聲明抛出,交給它的父類處理,否則編譯不會通過。

4.調用下面的方法,得到的傳回值是什麼?(catch和finally的執行過程問題)

public int getNum(){
    try{
        int a = 1/0;
    }catch(Exception e){
        return 2;
    }finally{
        return 3;
    }
}
           

結果:

3

執行流程分析:

代碼走到第三行的時候會發生運作時異常(java.lang.ArithmeticException),這是第四行代碼不會執行,直接進入catch塊,而catch塊中是一個return語句,它會造成函數終止,java中的異常機制有個原則:如果在catch中遇到了return或者異常等能使該函數終止的話那麼有finally就必須先執行完finally代碼塊裡面的代碼然後再傳回值。是以代碼進入finally塊,而裡面也是一個return方法,導緻方法終止,是以該方法會直接傳回3,如果finally塊中不是return等方法終止語句,那麼最終結果将會是2。

5.說出5個最常見的RuntimeException?

  • java.lang.NullPointerException:空指針異常(調用了未初始化的對象或不存在的對象)
  • java.lang.ClassNotFoundException:找不到執行類異常(類的名稱或加載路徑錯誤)
  • java.lang.NumberFormatException:字元串轉化為數字異常(字元型資料包含非數字型字元)
  • java.lang.IndexOutOfBoundsException:數組下标越界異常
  • java.lang.IllegalArgumentException:方法傳遞參數不合法異常
  • java.lang.ClassCastException:類型轉換異常
  • java.lang.NoClassDefFoundException:未找到類定義異常
  • SQLException:SQL異常
  • java.lang.InstantiationException:執行個體化異常
  • java.lang.NoSuchMethodException:方法不存在異常

6.throw和throws的差別?

  • throw:抛出異常對象,即需要new,用在方法體中,由方法體内的語句處理
  • throws:抛出異常類,用在方法聲明後面,表示如果抛出異常,由該方法的調用者來進行異常的處理
  • 當我們在方法中throw 了一RuntimeException或其子類對象,那麼在方法頭中可以不用加throws 相關異常類型
  • 當我們在方法中throw 了一個非RuntimeException類對象,且沒有進行try…catch,那麼方法頭中必須throws 相關異常類型
  • throw在一個方法中隻能抛出一次異常對象,多寫了會編譯失敗,因為多寫也沒用,隻會抛出一個異常,執行throw一定是抛出某種異常
  • thows在一個方法中可以抛出多個異常類,因為這隻是告訴調用者這個方法可能會抛出的異常類型,throws後并不一定會發生這種異常

Java面試常用API部分

1.Math.round(11.5)的值是多少?Math.round(-11.5)呢?

分别為12、-11

這個方法是取最接近該參數的數值,且向正無窮方向取舍,可以了解為四舍五入,注意傳double傳回long類型,傳float傳回int類型

2.String、StringBuilder、StringBuffer的差別?

Java提供兩種類型的字元串:String和StringBuilder/StringBuffer,它們都可以存儲字元串

  • String是隻讀字元串,意味着String引用的字元串的内容是不能被修改的:我們可能會存在下面這樣的誤解:
String str = "abc";
str = "def";
           
如上,有人可能會說字元串str明明可以改變的呀!其實不然,str隻是一個引用對象,它指向了“abc”所在記憶體空間的位址,當我們将“def”指派給str的時候,實際上是新開辟了一塊記憶體空間存放“def”,并将str指向其記憶體位址,而“abc”字元串對象沒有任何改變,隻是沒有引用變量指向它
  • StringBulider/StringBuffer表示的字元串對象是可以直接修改的
StringBuilder是Java5引入的,它和StringBuffer中的方法完全相同,差別在于它是單線程環境下使用的,因為它的所有方法都沒被synchronized修飾,是以它的效率要比StringBuffer要高

3.請說出下面程式的輸出?(字元串對象的比較)

class StringEqualTest {
    public static void main(String[] args) {
        String s1 = "Programming";
        String s2 = new String("Programming");
        String s3 = "Program";
        String s4 = "ming";
        String s5 = "Program" + "ming";
        String s6 = s3 + s4;
        System.out.println(s1 == s2);    
        System.out.println(s1 == s5);    
        System.out.println(s1 == s6);    
        System.out.println(s1 == s6.intern());    
        System.out.println(s2 == s2.intern());    
    }
}/**output
false
true
false
true
false
*/
           

結果分析:

  • s1和s2前者是常量池中的對象,後者是堆中的對象
  • s5由兩個字元串字面量拼接,java編譯器會做優化,直接看成“Programming”,是以和s1是常量池的同一對象
  • s6由兩個字元串變量拼接,java編譯器并不知道這兩個變量中是什麼值,是以不會優化,而字元串的拼接本質是建立了StringBuilder對象進行append操作,然後将拼接後的StringBuilder對象用toString()方法處理成String對像,是以兩個對象不同
  • 關于String的intern()方法,它會得到字元串對象在常量池中對應版本的引用(前提是常量池中有這個字元串與這個String對象的equals方法為true),如果常量池中沒有對應的字元串,則會将該字元串添加到常量池中,然後傳回常量池中字元串的引用

4.如何取得年月日、小時分鐘秒?

import java.time.LocalDateTime;
import java.util.Calendar;

class DateTimeTest {
    public static void main(String[] args) {
        Calendar cal = Calendar.getInstance();
        System.out.println(cal.get(Calendar.YEAR));
        System.out.println(cal.get(Calendar.MONTH)); // 0 - 11
        System.out.println(cal.get(Calendar.DATE));
        System.out.println(cal.get(Calendar.HOUR_OF_DAY));
        System.out.println(cal.get(Calendar.MINUTE));
        System.out.println(cal.get(Calendar.SECOND));
        // Java 8
        LocalDateTime dt = LocalDateTime.now();
        System.out.println(dt.getYear());
        System.out.println(dt.getMonthValue()); // 1 - 12
        System.out.println(dt.getDayOfMonth());
        System.out.println(dt.getHour());
        System.out.println(dt.getMinute());
        System.out.println(dt.getSecond());
    }
}
           

5.如何取得從 1970 年 1 月 1 日 0 時 0 分 0 秒到現在的毫秒數?

class GetTime {
    public static void main(String[] args) {
        System.out.println("第一種:" + Calendar.getInstance().getTimeInMillis());
        System.out.println("第二種:" + System.currentTimeMillis());
        System.out.println("第三種:" + Clock.systemDefaultZone().millis());
        System.out.println("第四種:" + new Date().getTime());
    }
}
           

6.如何格式化日期

class DateFormatTest {
    public static void main(String[] args) {
        SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");
        Date date1 = new Date();
        System.out.println(oldFormatter.format(date1));
        // Java 8
        DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
        LocalDate date2 = LocalDate.now();
        System.out.println(date2.format(newFormatter));
    }
}
           

Java面試IO部分

1.Java中流的分類?

按照流的方向

  • 輸入流(InputStram)
  • 輸出流(OutputStream)

按照實作功能

  • 節點流:可以從或向一個特定的地方(節點)讀寫資料,如 FileReader
  • 處理流:是對一個已存在的流的連接配接和封裝,通過所封裝的流的功能調用實作資料讀寫,如 BufferedReader。
處理流的構造方法總是要帶一個其他的流對象做參數。一個流對象經過其他流的多次包裝,稱為流的連結

按照處理資料的機關

  • 位元組流:位元組流繼承于 InputStream 和 OutputStream
  • 字元流:字元流繼承于InputStreamReader 和 OutputStreamWriter
Java 面試(一) | Java基礎部分Java面試基礎部分Java面試面向對象部分Java面試異常處理部分Java面試常用API部分Java面試IO部分Java面試集合部分

2.位元組流如何轉化為字元流?

  • 位元組輸入流轉字元輸入流通過 InputStreamReader 實作,該類的構造函數可以傳入 InputStream 對象。
  • 位元組輸出流轉字元輸出流通過 OutputStreamWriter 實作,該類的構造函數可以傳入 OutputStream 對象。

3.如何将一個 java 對象序列化到檔案裡?又如何反序列化到程式中讀出?

  • ObjectOutputStream:序列化(持久化):對象轉化為位元組流存到檔案/資料庫/位元組數組(記憶體),有writeObject()方法
  • ObjectInputStream:反序列化:将上述的位元組流轉化為對象,有readObject()方法
  • 對象流的構造器中是一個節點流
public class Test {
    public static void main(String[] args) throws Exception {
        //對象輸出流
        ObjectOutputStream objectOutputStream =
                new ObjectOutputStream(new FileOutputStream(new File("D://obj")));
        objectOutputStream.writeObject(new User("zhangsan", 100));
        objectOutputStream.close();
        //對象輸入流
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("D://obj")));
        User user = (User) objectInputStream.readObject();
        System.out.println(user);
        objectInputStream.close();
    }
}
           
在 java 中能夠被序列化的類必須先實作 Serializable 接口,該接口沒有任何抽象方法隻是起到一個标記作用。

4.位元組流和字元流的差別?

  • 位元組流讀取的時候,讀到一個位元組就傳回一個位元組;字元流使用了位元組流讀到一個或多個位元組(中文對應的位元組數是兩個,在 UTF-8 碼表中是 3 個位元組)時,先去查指定的編碼表,将查到的字元傳回
  • 位元組流可以處理所有類型資料,如:圖檔,MP3,AVI視訊檔案,而字元流隻能處理字元資料。隻要是處理純文字資料,就要優先考慮使用字元流,除此之外都用位元組流
  • 位元組流主要是操作 byte 類型資料,以 byte 數組為準,主要操作類就是 OutputStream、InputStream;字元流處理的單元為 2 個位元組的 Unicode 字元,分别操作字元、字元數組或字元串,而位元組流處理單元為 1 個位元組,操作位元組和位元組數組
  • 是以字元流是由 Java 虛拟機将位元組轉化為 2 個位元組的 Unicode 字元為機關的字元而成的,是以它對多國語言支援性比較好!如果是音頻檔案、圖檔、歌曲,就用位元組流好點,如果是關系到中文(文本)的,用字元流好點。在程式中一個字元等于兩個位元組,java 提供了 Reader、Writer 兩個專門操作字元流的類

5.什麼是Java序列化?如何實作?

  • 序列化就是一種用來處理對象流的機制,所謂對象流也就是将對象的内容進行流化。可以對流化後的對象進行讀寫操作,也可将流化後的對象傳輸于網絡之間。序列化是為了解決在對對象流進行讀寫操作時所引發的問題
  • 序 列 化 的 實 現 : 将 需 要 被 序 列 化 的 類 實 現 Serializable 接 口 , 該 接 口 沒 有 需 要 實 現 的 方 法 , implements Serializable 隻是為了标注該對象是可被序列化的,然後使用一個輸出流(如:FileOutputStream)來構造一個 ObjectOutputStream(對象流)對象,接着,使用 ObjectOutputStream 對象的 writeObject(Object obj)方法就可以将參數為 obj 的對象寫出(即儲存其狀态),要恢複的話則用輸入流。

Java面試集合部分

1.請問ArrayList、HashSet、HashMap是線程安全的嗎?如果不是怎麼擷取線程安全的集合?

通過以上類的源碼分析,每個方法都沒有加鎖,是以都是非線程安全的。

在集合中Vector、HashTable是線程安全的,打開源碼會發現其實就是将各自的核心方法加上了synchronized關鍵字。

Collections工具類提供了相關的API将上述3個非線程安全類變為線程安全的:

Collections.synchronizedCollection(c);
Collections.synchronizedList(list);
Collections.synchronizedMap(m);
Collections.synchronizedSet(s);
           
上面幾個函數都有對應的傳回值類型,傳入什麼類型傳回什麼類型。打開源碼其實原理非常簡單,就是将集合的核心方法添加上了synchronized關鍵字。

2.集合的初始容量、加載因子、擴容增量彙總

集合 加載因子 初始容量 擴容後的容量
Vector 1 10 原容量的2倍
ArrayList 1 10 原容量的1.5倍
HashSet 0.75 16 原容量的2倍
Hashtablet 0.75 11 原容量的2倍+1
HashMap 0.75 16 原容量的2倍

3.集合中哪些可以存儲null值?

  • Vector、ArrayList、LinkedList可以存儲null
  • HashSet、LinkedHashSet可以存儲null
  • HashMap、LinkedHashMap的key和value均可以為null,TreeMap的key不能為null,value可以為null;Hashtable、ConcurrentHashMap的key和value都不能為null

更詳細的Java面試集合部分參見:Java 集合(五)| 集合面試彙總