2021精選 Java面試題附答案(一)

初一丶發表於2021-04-07

1.什麼是Java

Java是一門物件導向的高階程式語言,不僅吸收了C++語言的各種優點,比如繼承了C++語言面嚮物件的技術核心。還摒棄了C++裡難以理解的多繼承、指標等概念,,同時也增加了垃圾回收機制,釋放掉不被使用的記憶體空間,解決了管理記憶體空間的煩惱。

因此Java語言具有功能強大和簡單易用兩個特徵。Java語言作為靜態物件導向程式語言的代表,極好地實現了物件導向理論,允許程式設計師以優雅的思維方式進行復雜的程式設計 。

面試手冊PDF下載連結https://shimo.im/docs/Wyj8QRpq39jkC3jD
面試手冊PDF下載連結https://shimo.im/docs/Wyj8QRpq39jkC3jD
面試手冊PDF下載連結https://shimo.im/docs/Wyj8QRpq39jkC3jD

2. Java的特點有哪些

Java 語言是一種分散式的面嚮物件語言,具有物件導向、平臺無關性、簡單性、解釋執行、多執行緒、安全性等很多特點,下面針對這些特點進行逐一介紹。

1. 物件導向

Java 是一種物件導向的語言,它對物件中的類、物件、繼承、封裝、多型、介面、包等均有很好的支援。為了簡單起見,Java 只支援類之間的單繼承,但是可以使用介面來實現多繼承。使用 Java 語言開發程式,需要採用物件導向的思想設計程式和編寫程式碼。

2. 平臺無關性

平臺無關性的具體表現在於,Java 是“一次編寫,到處執行(Write Once,Run any Where)”的語言,因此採用 Java 語言編寫的程式具有很好的可移植性,而保證這一點的正是 Java 的虛擬機器機制。在引入虛擬機器之後,Java 語言在不同的平臺上執行不需要重新編譯。

Java 語言使用 Java 虛擬機器機制遮蔽了具體平臺的相關資訊,使得 Java 語言編譯的程式只需生成虛擬機器上的目的碼,就可以在多種平臺上不加修改地執行。

3. 簡單性

Java 語言的語法與 C 語言和 C++ 語言很相近,使得很多程式設計師學起來很容易。對 Java 來說,它捨棄了很多 C++ 中難以理解的特性,如操作符的過載和多繼承等,而且 Java 語言不使用指標,加入了垃圾回收機制,解決了程式設計師需要管理記憶體的問題,使程式設計變得更加簡單。

4. 解釋執行

Java 程式在 Java 平臺執行時會被編譯成位元組碼檔案,然後可以在有 Java 環境的作業系統上執行。在執行檔案時,Java 的直譯器對這些位元組碼進行解釋執行,執行過程中需要加入的類在連線階段被載入到執行環境中。

5. 多執行緒

Java 語言是多執行緒的,這也是 Java 語言的一大特性,它必須由 Thread 類和它的子類來建立。Java 支援多個執行緒同時執行,並提供多執行緒之間的同步機制。任何一個執行緒都有自己的 run() 方法,要執行的方法就寫在 run() 方法體內。

6. 分散式

Java 語言支援 Internet 應用的開發,在 Java 的基本應用程式設計介面中就有一個網路應用程式設計介面,它提供了網路應用程式設計的類庫,包括 URL、URLConnection、Socket 等。Java 的 RIM 機制也是開發分散式應用的重要手段。

7. 健壯性

Java 的強型別機制、異常處理、垃圾回收機制等都是 Java 健壯性的重要保證。對指標的丟棄是 Java 的一大進步。另外,Java 的異常機制也是健壯性的一大體現。

8. 高效能

Java 的高效能主要是相對其他高階指令碼語言來說的,隨著 JIT(Just in Time)的發展,Java 的執行速度也越來越高。

9. 安全性

Java 通常被用在網路環境中,為此,Java 提供了一個安全機制以防止惡意程式碼的攻擊。除了 Java 語言具有許多的安全特性以外,Java 還對通過網路下載的類增加一個安全防範機制,分配不同的名字空間以防替代本地的同名類,幷包含安全管理機制。

Java 語言的眾多特性使其在眾多的程式語言中佔有較大的市場份額,Java 語言對物件的支援和強大的 API 使得程式設計工作變得更加容易和快捷,大大降低了程式的開發成本。Java 的“一次編寫,到處執行”正是它吸引眾多商家和程式設計人員的一大優勢。

3. JDK和JRE和JVM的區別

1. JDK

JDK(Java SE Development Kit),Java標準的開發包,提供了編譯、執行Java程式所需要的各種工具和資源,包括了Java編譯器、Java執行時環境、以及常用的Java類庫等。

2. JRE

JRE(Java Runtime Environment),Java執行時環境,用於解釋執行Java的位元組碼檔案。普通使用者只需要安裝JRE來執行Java程式即可,而作為一名程式設計師必須安裝JDK,來編譯、除錯程式。

3. JVM

JVM(Java Virtual Mechinal),Java虛擬機器,是JRE的一部分。它是整個Java實現跨平臺的核心,負責解釋執行位元組碼檔案,是可執行Java位元組碼檔案的虛擬計算機。所有平臺上的JVM向編譯器提供相同的介面,而編譯器只需要面向虛擬機器,生成虛擬機器能識別的程式碼,然後由虛擬機器來解釋執行。

當使用Java編譯器編譯Java程式時,生成的是與平臺無關的位元組碼,這些位元組碼只面向JVM。也就是說JVM是執行Java位元組碼的虛擬機器。

不同平臺的JVM是不同的,但是他們都提供了相同的介面。JVM是Java程式跨平臺的關鍵部分,只要為不同平臺實現了相同的虛擬機器,編譯後的Java位元組碼就可以在該平臺上執行。

為什麼要採用位元組碼:

在 Java 中,JVM 可以理解的程式碼就叫做位元組碼(即Java原始碼經過虛擬機器編譯器編譯後副檔名為 .class 的檔案),它不面向任何特定的處理器,只面向虛擬機器。Java 語言通過位元組碼的方式,在一定程度上解決了傳統解釋型語言執行效率低的問題,同時又保留了解釋型語言可移植的特點。所以 Java 程式執行時比較高效,而且,由於位元組碼並不針對一種特定的機器,因此,Java 程式無須重新編譯便可在多種不同作業系統的計算機上執行。

什麼是跨平臺:

所謂跨平臺性,是指java語言編寫的程式,一次編譯後,可以在多個系統平臺上執行。

實現原理:Java程式是通過java虛擬機器在系統平臺上執行的,只要該系統可以安裝相應的java虛擬機器,該系統就可以執行java程式。

Java 程式從原始碼到執行需要三步:

4. 總結

  1. JDK 用於開發,JRE 用於執行java程式 ;如果只是執行Java程式,可以只安裝JRE,無序安裝JDK。
  2. JDk包含JRE,JDK 和 JRE 中都包含 JVM。
  3. JVM 是 Java 程式語言的核心並且具有平臺獨立性。

4. Oracle JDK 和 OpenJDK 的對比

  • Oracle JDK版本將每三年釋出一次,而OpenJDK版本每三個月釋出一次;

  • OpenJDK 是一個參考模型並且是完全開源的,而Oracle JDK是OpenJDK的一個實現,並不是完全開源的;

  • Oracle JDK 比 OpenJDK 更穩定。OpenJDK和Oracle JDK的程式碼幾乎相同,但Oracle JDK有更多的類和一些錯誤修復。因此,如果您想開發企業/商業軟體,我建議您選擇Oracle JDK,因為它經過了徹底的測試和穩定。某些情況下,有些人提到在使用OpenJDK 可能會遇到了許多應用程式崩潰的問題,但是,只需切換到Oracle JDK就可以解決問題;

  • 在響應性和JVM效能方面,Oracle JDK與OpenJDK相比提供了更好的效能;

  • Oracle JDK不會為即將釋出的版本提供長期支援,使用者每次都必須通過更新到最新版本獲得支援來獲取最新版本;

  • Oracle JDK根據二進位制程式碼許可協議獲得許可,而OpenJDK根據GPL v2許可獲得許可。

5. Java有哪些資料型別

Java有 8 種基本資料型別,分別為:

  • 6 種數字型別 (四個整數形,兩個浮點型):byte、short、int、long、float、double

  • 1 種字元型別:char

  • 1 種布林型:boolean。

byte:

  • byte 資料型別是8位、有符號的,以二進位制補碼錶示的整數;
  • 最小值是 -128(-2^7)
  • 最大值是 127(2^7-1)
  • 預設值是 0
  • byte 型別用在大型陣列中節約空間,主要代替整數,因為 byte 變數佔用的空間只有 int 型別的四分之一;
  • 例子:byte a = 100,byte b = -50。

short:

  • short 資料型別是 16 位、有符號的以二進位制補碼錶示的整數
  • 最小值是 -32768(-2^15)
  • 最大值是 32767(2^15 - 1)
  • Short 資料型別也可以像 byte 那樣節省空間。一個short變數是int型變數所佔空間的二分之一;
  • 預設值是 0
  • 例子:short s = 1000,short r = -20000。

int:

  • int 資料型別是32位、有符號的以二進位制補碼錶示的整數;
  • 最小值是 -2,147,483,648(-2^31)
  • 最大值是 2,147,483,647(2^31 - 1)
  • 一般地整型變數預設為 int 型別;
  • 預設值是 0
  • 例子:int a = 100000, int b = -200000。

long:

  • 注意:Java 裡使用 long 型別的資料一定要在數值後面加上 L,否則將作為整型解析

  • long 資料型別是 64 位、有符號的以二進位制補碼錶示的整數;

  • 最小值是 -9,223,372,036,854,775,808(-2^63)

  • 最大值是 9,223,372,036,854,775,807(2^63 -1)

  • 這種型別主要使用在需要比較大整數的系統上;

  • 預設值是 0L

  • 例子: long a = 100000L,Long b = -200000L。
    "L"理論上不分大小寫,但是若寫成"l"容易與數字"1"混淆,不容易分辯。所以最好大寫。

float:

  • float 資料型別是單精度、32位、符合IEEE 754標準的浮點數;
  • float 在儲存大型浮點陣列的時候可節省記憶體空間;
  • 預設值是 0.0f
  • 浮點數不能用來表示精確的值,如貨幣;
  • 例子:float f1 = 234.5f。

double:

  • double 資料型別是雙精度、64 位、符合IEEE 754標準的浮點數;
  • 浮點數的預設型別為double型別;
  • double型別同樣不能表示精確的值,如貨幣;
  • 預設值是 0.0d
  • 例子:double d1 = 123.4。

char:

  • char型別是一個單一的 16 位 Unicode 字元;
  • 最小值是 \u0000(即為 0);
  • 最大值是 \uffff(即為 65535);
  • char 資料型別可以儲存任何字元;
  • 例子:char letter = 'A';(單引號

boolean:

  • boolean資料型別表示一位的資訊;
  • 只有兩個取值:true 和 false;
  • 這種型別只作為一種標誌來記錄 true/false 情況;
  • 預設值是 false
  • 例子:boolean one = true。

這八種基本型別都有對應的包裝類分別為:Byte、Short、Integer、Long、Float、Double、Character、Boolean

型別名稱 位元組、位數 最小值 最大值 預設值 例子
byte位元組 1位元組,8位 -128(-2^7) 127(2^7-1) 0 byte a = 100,byte b = -50
short短整型 2位元組,16位 -32768(-2^15) 32767(2^15 - 1) 0 short s = 1000,short r = -20000
int整形 4位元組,32位 -2,147,483,648(-2^31) 2,147,483,647(2^31 - 1) 0 int a = 100000, int b = -200000
lang長整型 8位元組,64位 -9,223,372,036,854,775,808(-2^63) 9,223,372,036,854,775,807(2^63 -1) 0L long a = 100000L,Long b = -200000L
double雙精度 8位元組,64位 double型別同樣不能表示精確的值,如貨幣 0.0d double d1 = 123.4
float單精度 4位元組,32位 在儲存大型浮點陣列的時候可節省記憶體空間 不同統計精準的貨幣值 0.0f float f1 = 234.5f
char字元 2位元組,16位 \u0000(即為0) \uffff(即為65,535) 可以儲存任何字元 char letter = 'A';
boolean布林 返回true和false兩個值 這種型別只作為一種標誌來記錄 true/false 情況; 只有兩個取值:true 和 false; false boolean one = true

6. Java中引用資料型別有哪些,它們與基本資料型別有什麼區別?

引用資料型別分3種:類,介面,陣列;

簡單來說,只要不是基本資料型別.都是引用資料型別。 那他們有什麼不同呢?

1、從概念方面來說

1,基本資料型別:變數名指向具體的數值

2,引用資料型別:變數名不是指向具體的數值,而是指向存資料的記憶體地址,.也及時hash值

2、從記憶體的構建方面來說(記憶體中,有堆記憶體和棧記憶體兩者)

1,基本資料型別:被建立時,在棧記憶體中會被劃分出一定的記憶體,並將數值儲存在該記憶體中.

2,引用資料型別:被建立時,首先會在棧記憶體中分配一塊空間,然後在堆記憶體中也會分配一塊具體的空間用來儲存資料的具體資訊,即hash值,然後由棧中引用指向堆中的物件地址.

舉個例子

//基本資料型別作為方法引數被呼叫
public class Main{
	public static void main(String[] args){
		//基本資料型別
		int i = 1;
		int j = 1;
		double d = 1.2;
 
		//引用資料型別
		String str = "Hello";
		String str1= "Hello";
	}
}

由上圖可知,基本資料型別中會存在兩個相同的1,而引用型型別就不會存在相同的資料。
假如"hello"的引用地址是xxxxx1,宣告str變數並其賦值"hello"實際上就是讓str變數引用了"hello"的記憶體地址,這個記憶體地址就儲存在堆記憶體中,是不會改變的,當再次宣告變數str1也是賦值為"hello"時,此時就會在堆記憶體中查詢是否有"hello"這個地址,如果堆記憶體中已經存在這個地址了,就不會再次建立了,而是讓str1變數也指向xxxxx1這個地址,如果沒有的話,就會重新建立一個地址給str1變數。

7. 從使用方面來說

1,基本資料型別:判斷資料是否相等,用和!=判斷。
2,引用資料型別:判斷資料是否相等,用equals()方法,
和!=是比較數值的。而equals()方法是比較記憶體地址的。

補充:資料型別選擇的原則

  • 如果要表示整數就使用int,表示小數就使用double;

  • 如果要描述日期時間數字或者表示檔案(或記憶體)大小用long;

  • 如果要實現內容傳遞或者編碼轉換使用byte;

  • 如果要實現邏輯的控制,可以使用booleam;

  • 如果要使用中文,使用char避免中文亂碼;

  • 如果按照儲存範圍:byte < int < long < double;

8. Java中的自動裝箱與拆箱

什麼是自動裝箱拆箱?

從下面的程式碼中就可以看到裝箱和拆箱的過程

//自動裝箱
Integer total = 99;

//自定拆箱
int totalprim = total;

裝箱就是自動將基本資料型別轉換為包裝器型別;拆箱就是自動將包裝器型別轉換為基本資料型別。

在Java SE5之前,自動裝箱要這樣寫:Integer i = new` `Integer(10``);

對於Java的自動裝箱和拆箱,我們看看原始碼編譯後的class檔案,其實裝箱呼叫包裝類的valueOf方法,拆箱呼叫的是Integer.Value方法,下面就是變編譯後的程式碼:

常見面試一:

這段程式碼輸出什麼?

public class Main {
    public static void main(String[] args) {
         
        Integer i1 = 100;
        Integer i2 = 100;
        Integer i3 = 200;
        Integer i4 = 200;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

答案是:

true
false

為什麼會出現這樣的結果?輸出結果表明i1和i2指向的是同一個物件,而i3和i4指向的是不同的物件。此時只需一看原始碼便知究竟,下面這段程式碼是Integer的valueOf方法的具體實現:

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

        static {
            final int low = -128;

            // high value may be configured by property
            int h = 127;
            if (integerCacheHighPropValue != null) {
                // Use Long.decode here to avoid invoking methods that
                // require Integer's autoboxing cache to be initialized
                int i = Long.decode(integerCacheHighPropValue).intValue();
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - -low);
            }
            high = h;

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

        private IntegerCache() {}
    }

從這2段程式碼可以看出,在通過valueOf方法建立Integer物件的時候,如果數值在[-128,127]之間,便返回指向IntegerCache.cache中已經存在的物件的引用;否則建立一個新的Integer物件。

上面的程式碼中i1和i2的數值為100,因此會直接從cache中取已經存在的物件,所以i1和i2指向的是同一個物件,而i3和i4則是分別指向不同的物件。

常見面試二:

public class Main {
    public static void main(String[] args) {
         
        Double i1 = 100.0;
        Double i2 = 100.0;
        Double i3 = 200.0;
        Double i4 = 200.0;
         
        System.out.println(i1==i2);
        System.out.println(i3==i4);
    }
}

輸出結果為:

false
false

原因很簡單,在某個範圍內的整型數值的個數是有限的,而浮點數卻不是。

9. 為什麼要有包裝型別?

讓基本資料型別也具有物件的特徵

基本型別 包裝器型別
boolean Boolean
char Character
int Integer
byte Byte
short Short
long Long
float Float
double Double

為了讓基本型別也具有物件的特徵,就出現了包裝型別(如我們在使用集合型別Collection時就一定要使用包裝型別而非基本型別)因為容器都是裝object的,這是就需要這些基本型別的包裝器類了。

自動裝箱:new Integer(6);,底層呼叫:Integer.valueOf(6)

自動拆箱: int i = new Integer(6);,底層呼叫i.intValue();方法實現。

Integer i  = 6;
Integer j = 6;
System.out.println(i==j);

答案在下面這段程式碼中找:

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

二者的區別:

  1. 宣告方式不同:基本型別不使用new關鍵字,而包裝型別需要使用new關鍵字來在堆中分配儲存空間
  2. 儲存方式及位置不同:基本型別是直接將變數值儲存在棧中,而包裝型別是將物件放在堆中,然後通過引用來使用;
  3. 初始值不同:基本型別的初始值如int為0,boolean為false,而包裝型別的初始值為null;
  4. 使用方式不同:基本型別直接賦值直接使用就好,而包裝型別在集合如Collection、Map時會使用到。

10. a=a+b與a+=b有什麼區別嗎?

+=操作符會進行隱式自動型別轉換,此處a+=b隱式的將加操作的結果型別強制轉換為持有結果的型別,而a=a+b則不會自動進行型別轉換.如:

byte a = 127;
byte b = 127;
b = a + b; // 報編譯錯誤:cannot convert from int to byte
b += a; 

以下程式碼是否有錯,有的話怎麼改?

short s1= 1;
s1 = s1 + 1;

有錯誤.short型別在進行運算時會自動提升為int型別,也就是說s1+1的運算結果是int型別,而s1是short型別,此時編譯器會報錯.

正確寫法:

short s1= 1; 
s1 += 1; 

+=操作符會對右邊的表示式結果強轉匹配左邊的資料型別,所以沒錯.

11. 能將 int 強制轉換為 byte 型別的變數嗎?如果該值大於 byte 型別的範圍,將會出現什麼現象?

我們可以做強制轉換,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果強制轉化,int 型別的高 24 位將會被丟棄,因為byte 型別的範圍是從 -128 到 127

12. Java程式是如何執行的

我們日常的工作中都使用開發工具(IntelliJ IDEA 或 Eclipse 等)可以很方便的除錯程式,或者是通過打包工具把專案打包成 jar 包或者 war 包,放入 Tomcat 等 Web 容器中就可以正常執行了,但你有沒有想過 Java 程式內部是如何執行的?其實不論是在開發工具中執行還是在 Tomcat 中執行,Java 程式的執行流程基本都是相同的,它的執行流程如下:

  • 先把 Java 程式碼編譯成位元組碼,也就是把 .java 型別的檔案編譯成 .class 型別的檔案。這個過程的大致執行流程:Java 原始碼 -> 詞法分析器 -> 語法分析器 -> 語義分析器 -> 字元碼生成器 -> 最終生成位元組碼,其中任何一個節點執行失敗就會造成編譯失敗;
  • 把 class 檔案放置到 Java 虛擬機器,這個虛擬機器通常指的是 Oracle 官方自帶的 Hotspot JVM;
  • Java 虛擬機器使用類載入器(Class Loader)裝載 class 檔案;
  • 類載入完成之後,會進行位元組碼效驗,位元組碼效驗通過之後 JVM 直譯器會把位元組碼翻譯成機器碼交由作業系統執行。但不是所有程式碼都是解釋執行的,JVM 對此做了優化,比如,以 Hotspot 虛擬機器來說,它本身提供了 JIT(Just In Time)也就是我們通常所說的動態編譯器,它能夠在執行時將熱點程式碼編譯為機器碼,這個時候位元組碼就變成了編譯執行。Java 程式執行流程圖如下:

13. final 在 Java 中有什麼作用?

final作為Java中的關鍵字可以用於三個地方。用於修飾類、類屬性和類方法。

特徵:凡是引用final關鍵字的地方皆不可修改!

(1)修飾類:表示該類不能被繼承;

(2)修飾方法:表示方法不能被重寫;

(3)修飾變數:表示變數只能一次賦值以後值不能被修改(常量)。

14. final有哪些用法?

final也是很多面試喜歡問的地方,但我覺得這個問題很無聊,通常能回答下以下5點就不錯了:

  • 被final修飾的類不可以被繼承
  • 被final修飾的方法不可以被重寫
  • 被final修飾的變數不可以被改變.如果修飾引用,那麼表示引用不可變,引用指向的內容可變.
  • 被final修飾的方法,JVM會嘗試將其內聯,以提高執行效率
  • 被final修飾的常量,在編譯階段會存入常量池中.

除此之外,編譯器對final域要遵守的兩個重排序規則更好:

在建構函式內對一個final域的寫入,與隨後把這個被構造物件的引用賦值給一個引用變數,這兩個操作之間不能重排序 初次讀一個包含final域的物件的引用,與隨後初次讀這個final域,這兩個操作之間不能重排序.

15. static都有哪些用法?

所有的人都知道static關鍵字這兩個基本的用法:靜態變數和靜態方法.也就是被static所修飾的變數/方法都屬於類的靜態資源,類例項所共享.

除了靜態變數和靜態方法之外,static也用於靜態塊,多用於初始化操作:

public calss PreCache{
    static{
        //執行相關操作
    }
}

此外static也多用於修飾內部類,此時稱之為靜態內部類.

最後一種用法就是靜態導包,即import static.import static是在JDK 1.5之後引入的新特性,可以用來指定匯入某個類中的靜態資源,並且不需要使用類名,可以直接使用資源名,比如:

import static java.lang.Math.*;
 
public class Test{
 
    public static void main(String[] args){
        //System.out.println(Math.sin(20));傳統做法
        System.out.println(sin(20));
    }
}

16. static和final區別

關鍵詞 修飾物 影響
final 變數 分配到常量池中,程式不可改變其值
final 方法 子類中將不能被重寫
final 不能被繼承
static 變數 分配在記憶體堆上,引用都會指向這一個地址而不會重新分配記憶體
static 方法塊 虛擬機器優先載入
static 可以直接通過類來呼叫而不需要new

17. 為什麼有些java類要實現Serializable介面

為了網路進行傳輸或者持久化

什麼是序列化

將物件的狀態資訊轉換為可以儲存或傳輸的形式的過程

除了實現Serializable介面還有什麼序列化方式

  • Json序列化
  • FastJson序列化
  • ProtoBuff序列化

18. 什麼是java序列化,如何實現java序列化?或者請解釋Serializable介面的作用。

我們有時候將一個java物件變成位元組流的形式傳出去或者從一個位元組流中恢復成一個java物件,例如,要將java物件儲存到硬碟或者傳送給網路上的其他計算機,這個過程我們可以自己寫程式碼去把一個java物件變成某個格式的位元組流再傳輸。

但是,jre本身就提供了這種支援,我們可以呼叫OutputStreamwriteObject方法來做,如果要讓java幫我們做,要被傳輸的物件必須實現serializable介面,這樣,javac編譯時就會進行特殊處理,編譯的類才可以被writeObject方法操作,這就是所謂的序列化。需要被序列化的類必須實現Serializable介面,該介面是一個mini介面,其中沒有需要實現方法,implements Serializable只是為了標註該物件是可被序列化的。

例如,在web開發中,如果物件被儲存在了Session中,tomcat在重啟時要把Session物件序列化到硬碟,這個物件就必須實現Serializable介面。如果物件要經過分散式系統進行網路傳輸,被傳輸的物件就必須實現Serializable介面。

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

內部類的定義

將一個類定義在另一個類裡面或者一個方法裡面,這樣的類稱為內部類。

內部類的作用:

1、成員內部類 成員內部類可以無條件訪問外部類的所有成員屬性和成員方法(包括private成員和靜態成員)。 當成員內部類擁有和外部類同名的成員變數或者方法時,會發生隱藏現象,即預設情況下訪問的是成員內部類的成員。

2、區域性內部類 區域性內部類是定義在一個方法或者一個作用域裡面的類,它和成員內部類的區別在於區域性內部類的訪問僅限於方法內或者該作用域內。

3、匿名內部類 匿名內部類就是沒有名字的內部類

4、靜態內部類 指被宣告為static的內部類,他可以不依賴內部類而例項,而通常的內部類需要例項化外部類,從而例項化。靜態內部類不可以有與外部類有相同的類名。不能訪問外部類的普通成員變數,但是可以訪問靜態成員變數和靜態方法(包括私有型別) 一個 靜態內部類去掉static 就是成員內部類,他可以自由的引用外部類的屬性和方法,無論是靜態還是非靜態。但是不可以有靜態屬性和方法

20. Excption與Error包結構

Java可丟擲(Throwable)的結構分為三種型別:被檢查的異常(CheckedException)執行時異常(RuntimeException)錯誤(Error)

1、執行時異常

定義:RuntimeException及其子類都被稱為執行時異常。

特點:Java編譯器不會檢查它。也就是說,當程式中可能出現這類異常時,倘若既"沒有通過throws宣告丟擲它",也"沒有用try-catch語句捕獲它",還是會編譯通過。例如,除數為零時產生的ArithmeticException異常,陣列越界時產生的IndexOutOfBoundsException異常,fail-fast機制產生的ConcurrentModificationException異常(java.util包下面的所有的集合類都是快速失敗的,“快速失敗”也就是fail-fast,它是Java集合的一種錯誤檢測機制。當多個執行緒對集合進行結構上的改變的操作時,有可能會產生fail-fast機制。記住是有可能,而不是一定。例如:假設存在兩個執行緒(執行緒1、執行緒2),執行緒1通過Iterator在遍歷集合A中的元素,在某個時候執行緒2修改了集合A的結構(是結構上面的修改,而不是簡單的修改集合元素的內容),那麼這個時候程式就會丟擲 ConcurrentModificationException 異常,從而產生fail-fast機制,這個錯叫併發修改異常。Fail-safe,java.util.concurrent包下面的所有的類都是安全失敗的,在遍歷過程中,如果已經遍歷的陣列上的內容變化了,迭代器不會丟擲ConcurrentModificationException異常。如果未遍歷的陣列上的內容發生了變化,則有可能反映到迭代過程中。這就是ConcurrentHashMap迭代器弱一致的表現。ConcurrentHashMap的弱一致性主要是為了提升效率,是一致性與效率之間的一種權衡。要成為強一致性,就得到處使用鎖,甚至是全域性鎖,這就與Hashtable和同步的HashMap一樣了。)等,都屬於執行時異常。

常見的五種執行時異常:

  • ClassCastException(類轉換異常)

  • IndexOutOfBoundsException(陣列越界)

  • NullPointerException(空指標異常)

  • ArrayStoreException(資料儲存異常,運算元組是型別不一致)

  • BufferOverflowException

2、被檢查異常

定義:Exception類本身,以及Exception的子類中除了"執行時異常"之外的其它子類都屬於被檢查異常。

特點 : Java編譯器會檢查它。 此類異常,要麼通過throws進行宣告丟擲,要麼通過try-catch進行捕獲處理,否則不能通過編譯。例如,CloneNotSupportedException就屬於被檢查異常。

當通過clone()介面去克隆一個物件,而該物件對應的類沒有實現Cloneable介面,就會丟擲CloneNotSupportedException異常。被檢查異常通常都是可以恢復的。 如:

IOException

FileNotFoundException

SQLException

被檢查的異常適用於那些不是因程式引起的錯誤情況,比如:讀取檔案時檔案不存在引發的FileNotFoundException。然而,不被檢查的異常通常都是由於糟糕的程式設計引起的,比如:在物件引用時沒有確保物件非空而引起的NullPointerException

3、錯誤

定義 : Error類及其子類。

特點 : 和執行時異常一樣,編譯器也不會對錯誤進行檢查。

當資源不足、約束失敗、或是其它程式無法繼續執行的條件發生時,就產生錯誤。程式本身無法修復這些錯誤的。例如,VirtualMachineError就屬於錯誤。出現這種錯誤會導致程式終止執行。OutOfMemoryError、ThreadDeath。

Java虛擬機器規範規定JVM的記憶體分為了好幾塊,比如堆,棧,程式計數器,方法區等

21. try {}裡有一個return語句,那麼緊跟在這個try後的finally{}裡的code會不會被執行,什麼時候被執行,在return前還是後?

我們知道finally{}中的語句是一定會執行的,那麼這個可能正常脫口而出就是return之前,return之後可能就出了這個方法了,鬼知道跑哪裡去了,但更準確的應該是在return中間執行,請看下面程式程式碼的執行結果:

public classTest {
    public static void main(String[]args) {
       System.out.println(newTest().test());;
    }
    static int test()
    {
       intx = 1;
       try
       {
          return x;
       }
       finally
       {
          ++x;
       }
    }
}

執行結果如下:

1

執行結果是1,為什麼呢?主函式呼叫子函式並得到結果的過程,好比主函式準備一個空罐子,當子函式要返回結果時,先把結果放在罐子裡,然後再將程式邏輯返回到主函式。所謂返回,就是子函式說,我不執行了,你主函式繼續執行吧,這沒什麼結果可言,結果是在說這話之前放進罐子裡的。

22. 執行時異常與一般異常有何異同?

異常表示程式執行過程中可能出現的非正常狀態,執行時異常表示虛擬機器的通常操作中可能遇到的異常,是一種常見執行錯誤。java編譯器要求方法必須宣告丟擲可能發生的非執行時異常,但是並不要求必須宣告丟擲未被捕獲的執行時異常。

23. error和exception有什麼區別?

error 表示恢復不是不可能但很困難的情況下的一種嚴重問題。比如說記憶體溢位。不可能指望程式能處理這樣的情況。exception表示一種設計或實現問題。也就是說,它表示如果程式執行正常,從不會發生的情況。

24. 簡單說說Java中的異常處理機制的簡單原理和應用。

異常是指java程式執行時(非編譯)所發生的非正常情況或錯誤,與現實生活中的事件很相似,現實生活中的事件可以包含事件發生的時間、地點、人物、情節等資訊,可以用一個物件來表示,Java使用物件導向的方式來處理異常,它把程式中發生的每個異常也都分別封裝到一個物件來表示的,該物件中包含有異常的資訊。

Java對異常進行了分類,不同型別的異常分別用不同的Java類表示,所有異常的根類為java.lang.Throwable。

Throwable下面又派生了兩個子類:

  • Error和Exception,Error表示應用程式本身無法克服和恢復的一種嚴重問題,程式只有奔潰了,例如,說記憶體溢位和執行緒死鎖等系統問題。

  • Exception表示程式還能夠克服和恢復的問題,其中又分為系統異常和普通異常:

系統異常是軟體本身缺陷所導致的問題,也就是軟體開發人員考慮不周所導致的問題,軟體使用者無法克服和恢復這種問題,但在這種問題下還可以讓軟體系統繼續執行或者讓軟體掛掉,例如,陣列指令碼越界(ArrayIndexOutOfBoundsException),空指標異常(NullPointerException)、類轉換異常(ClassCastException);

普通異常是執行環境的變化或異常所導致的問題,是使用者能夠克服的問題,例如,網路斷線,硬碟空間不夠,發生這樣的異常後,程式不應該死掉。

java為系統異常和普通異常提供了不同的解決方案,編譯器強制普通異常必須try..catch處理或用throws宣告繼續拋給上層呼叫方法處理,所以普通異常也稱為checked異常,而系統異常可以處理也可以不處理,所以,編譯器不強制用try..catch處理或用throws宣告,所以系統異常也稱為unchecked異常。

25. == 和 equals 的區別是什麼?

"=="

對於基本型別和引用型別 == 的作用效果是不同的,如下所示:

  • 基本型別:比較的是值是否相同;
  • 引用型別:比較的是引用是否相同;
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true

因為 x 和 y 指向的是同一個引用,所以 == 也是 true,而 new String()方法則重寫開闢了記憶體空間,所以 == 結果為 false,而 equals 比較的一直是值,所以結果都為 true。

equals

equals 本質上就是 ==,只不過 String 和 Integer 等重寫了 equals 方法,把它變成了值比較。看下面的程式碼就明白了。

首先來看預設情況下 equals 比較一個有相同值的物件,程式碼如下:

class Cat {
    public Cat(String name) {
        this.name = name;
    }

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Cat c1 = new Cat("葉痕秋");
Cat c2 = new Cat("葉痕秋");
System.out.println(c1.equals(c2)); // false

輸出結果出乎我們的意料,竟然是 false?這是怎麼回事,看了 equals 原始碼就知道了,原始碼如下:

public boolean equals(Object obj) {
        return (this == obj);
}

原來 equals 本質上就是 ==。

那問題來了,兩個相同值的 String 物件,為什麼返回的是 true?程式碼如下:

String s1 = new String("葉子");
String s2 = new String("葉子");
System.out.println(s1.equals(s2)); // true

同樣的,當我們進入 String 的 equals 方法,找到了答案,程式碼如下:

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;
}

原來是 String 重寫了 Object 的 equals 方法,把引用比較改成了值比較。

總結

== 對於基本型別來說是值比較,對於引用型別來說是比較的是引用;而 equals 預設情況下是引用比較,只是很多類重新了 equals 方法,比如 String、Integer 等把它變成了值比較,所以一般情況下 equals 比較的是值是否相等。

26. Hashcode的作用

java的集合有兩類,一類是List,還有一類是Set。前者有序可重複,後者無序不重複。當我們在set中插入的時候怎麼判斷是否已經存在該元素呢,可以通過equals方法。但是如果元素太多,用這樣的方法就會比較滿。

於是有人發明了雜湊演算法來提高集合中查詢元素的效率。 這種方式將集合分成若干個儲存區域,每個物件可以計算出一個雜湊碼,可以將雜湊碼分組,每組分別對應某個儲存區域,根據一個物件的雜湊碼就可以確定該物件應該儲存的那個區域。

hashCode方法可以這樣理解:它返回的就是根據物件的記憶體地址換算出的一個值。這樣一來,當集合要新增新的元素時,先呼叫這個元素的hashCode方法,就一下子能定位到它應該放置的物理位置上。如果這個位置上沒有元素,它就可以直接儲存在這個位置上,不用再進行任何比較了;如果這個位置上已經有元素了,就呼叫它的equals方法與新元素進行比較,相同的話就不存了,不相同就雜湊其它的地址。這樣一來實際呼叫equals方法的次數就大大降低了,幾乎只需要一兩次。

27. 兩個物件的 hashCode() 相同, 那麼 equals() 也一定為 true嗎?

不對,兩個物件的 hashCode() 相同,equals() 不一定 true。

程式碼示例:

String str1 = "keep";
String str2 = "brother";
System. out. println(String. format("str1:%d | str2:%d",  str1. hashCode(),str2. hashCode()));
System. out. println(str1. equals(str2));

執行結果:

str1:1179395 | str2:1179395

false

程式碼解讀:很顯然“keep”和“brother”的 hashCode() 相同,然而 equals() 則為 false,因為在雜湊表中,hashCode() 相等即兩個鍵值對的雜湊值相等,然而雜湊值相等,並不一定能得出鍵值對相等。

28. 泛型常用特點

泛型是Java SE 1.5之後的特性, 《Java 核心技術》中對泛型的定義是:

“泛型” 意味著編寫的程式碼可以被不同型別的物件所重用。

“泛型”,顧名思義,“泛指的型別”。我們提供了泛指的概念,但具體執行的時候卻可以有具體的規則來約束,比如我們用的非常多的ArrayList就是個泛型類,ArrayList作為集合可以存放各種元素,如Integer, String,自定義的各種型別等,但在我們使用的時候通過具體的規則來約束,如我們可以約束集合中只存放Integer型別的元素,如

List<Integer> iniData = new ArrayList<>()

使用泛型的好處?

以集合來舉例,使用泛型的好處是我們不必因為新增元素型別的不同而定義不同型別的集合,如整型集合類,浮點型集合類,字串集合類,我們可以定義一個集合來存放整型、浮點型,字串型資料,而這並不是最重要的,因為我們只要把底層儲存設定了Object即可,新增的資料全部都可向上轉型為Object。 更重要的是我們可以通過規則按照自己的想法控制儲存的資料型別。

29. 物件導向的特徵

物件導向的程式語言有封裝、繼承 、抽象、多型等4個主要的特徵。

  1. 封裝: 把描述一個物件的屬性和行為的程式碼封裝在一個模組中,也就是一個類中,屬性用變數定義,行為用方法進行定義,方法可以直接訪問同一個物件中的屬性。
  2. 抽象: 把現實生活中的物件抽象為類。分為過程抽象和資料抽象
  • 資料抽象 -->鳥有翅膀,羽毛等(類的屬性)
  • 過程抽象 -->鳥會飛,會叫(類的方法)
  1. 繼承:子類繼承父類的特徵和行為。子類可以有父類的方法,屬性(非private)。子類也可以對父類進行擴充套件,也可以重寫父類的方法。缺點就是提高程式碼之間的耦合性。
  2. 多型: 多型是指程式中定義的引用變數所指向的具體型別和通過該引用變數發出的方法呼叫在程式設計時並不確定,而是在程式執行期間才確定(比如:向上轉型,只有執行才能確定其物件屬性)。方法覆蓋和過載體現了多型性。

30. Java多型的理解

  1. 多型是繼封裝、繼承之後,物件導向的第三大特性。
  2. 多型現實意義理解:
  • 現實事物經常會體現出多種形態,如學生,學生是人的一種,則一個具體的同學張三既是學生也是人,即出現兩種形態。
  • Java作為物件導向的語言,同樣可以描述一個事物的多種形態。如Student類繼承了Person類,一個Student的物件便既是Student,又是Person。
  1. 多型體現為父類引用變數可以指向子類物件。
  2. 前提條件:必須有子父類關係。

注意:在使用多型後的父類引用變數呼叫方法時,會呼叫子類重寫後的方法。

  1. 多型的定義與使用格式

定義格式:父類型別 變數名=new 子類型別();

31. 過載和重寫的區別

重寫(Override)

從字面上看,重寫就是 重新寫一遍的意思。其實就是在子類中把父類本身有的方法重新寫一遍。子類繼承了父類原有的方法,但有時子類並不想原封不動的繼承父類中的某個方法,所以在方法名,引數列表,返回型別(除過子類中方法的返回值是父類中方法返回值的子類時)都相同的情況下, 對方法體進行修改或重寫,這就是重寫。但要注意子類函式的訪問修飾許可權不能少於父類的。

public class Father {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Son s = new Son();
        s.sayHello();
    }

    public void sayHello() {
        System.out.println("Hello");
    }
}

class Son extends Father{

    @Override
    public void sayHello() {
        // TODO Auto-generated method stub
        System.out.println("hello by ");
    }
}

重寫 總結:

1.發生在父類與子類之間

2.方法名,引數列表,返回型別(除過子類中方法的返回型別是父類中返回型別的子類)必須相同

3.訪問修飾符的限制一定要大於被重寫方法的訪問修飾符(public>protected>default>private)

4.重寫方法一定不能丟擲新的檢查異常或者比被重寫方法申明更加寬泛的檢查型異常

過載(Overload)

在一個類中,同名的方法如果有不同的引數列表(引數型別不同、引數個數不同甚至是引數順序不同)則視為過載。同時,過載對返回型別沒有要求,可以相同也可以不同,但不能通過返回型別是否相同來判斷過載

public class Father {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Father s = new Father();
        s.sayHello();
        s.sayHello("wintershii");

    }

    public void sayHello() {
        System.out.println("Hello");
    }

    public void sayHello(String name) {
        System.out.println("Hello" + " " + name);
    }
}

過載 總結:

1.過載Overload是一個類中多型性的一種表現

2.過載要求同名方法的引數列表不同(引數型別,引數個數甚至是引數順序)

3.過載的時候,返回值型別可以相同也可以不相同。無法以返回型別作為過載函式的區分標準

33. Java建立物件有幾種方式?

java中提供了以下四種建立物件的方式:

  • new建立新物件
  • 通過反射機制
  • 採用clone機制
  • 通過序列化機制

34. ConcurrentModificationException異常出現的原因

public class Test {
    public static void main(String[] args)  {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                list.remove(integer);
        }
    }
}

執行上段程式碼是有問題的,會丟擲ConcurrentModificationException異常。

原因:呼叫list.remove()方法導致modCountexpectedModCount的值不一致。

final void checkForComodification() {
    if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
}

解決辦法:在迭代器中如果要刪除元素的話,需要呼叫Iterator類的remove方法。

public class Test {
    public static void main(String[] args)  {
        ArrayList<Integer> list = new ArrayList<Integer>();
        list.add(2);
        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            Integer integer = iterator.next();
            if(integer==2)
                iterator.remove();   //注意這個地方
        }
    }
}

35. HashMap和HashTable、ConcurrentHashMap區別?

相同點:

  1. HashMap和Hashtable都實現了Map介面
  2. 都可以儲存key-value資料

不同點:

  1. HashMap可以把null作為key或value,HashTable不可以
  2. HashMap執行緒不安全,效率高。HashTable執行緒安全,效率低。
  3. HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。

什麼是fail-fast?
就是最快的時間能把錯誤丟擲而不是讓程式執行。

36. 如何保證執行緒安全又效率高?

Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的擴充套件性更好。

ConcurrentHashMap將整個Map分為N個segment(類似HashTable),可以提供相同的執行緒安全,但是效率提升N倍,預設N為16。

37. 我們能否讓HashMap同步?

HashMap可以通過下面的語句進行同步:

Map m = Collections.synchronizeMap(hashMap);

38. Java 中 IO 流分為幾種?

按功能來分:輸入流(input)、輸出流(output)。

按型別來分:位元組流和字元流。

位元組流和字元流的區別是:位元組流按 8 位傳輸以位元組為單位輸入輸出資料,字元流按 16 位傳輸以字元為單位輸入輸出資料。

39. BIO、NIO、AIO 有什麼區別?

  • BIO:Block IO 同步阻塞式 IO,就是我們平常使用的傳統 IO,它的特點是模式簡單使用方便,併發處理能力低。
  • NIO:Non IO 同步非阻塞 IO,是傳統 IO 的升級,客戶端和伺服器端通過 Channel(通道)通訊,實現了多路複用。
  • AIO:Asynchronous IO 是 NIO 的升級,也叫 NIO2,實現了非同步非堵塞 IO ,非同步 IO 的操作基於事件和回撥機制。

40. Files的常用方法都有哪些?

  • Files. exists():檢測檔案路徑是否存在。
  • Files. createFile():建立檔案。
  • Files. createDirectory():建立資料夾。
  • Files. delete():刪除一個檔案或目錄。
  • Files. copy():複製檔案。
  • Files. move():移動檔案。
  • Files. size():檢視檔案個數。
  • Files. read():讀取檔案。
  • Files. write():寫入檔案。

41. Java反射的作用於原理

1、定義:

反射機制是在執行時,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意個物件,都能夠呼叫它的任意一個方法。在java中,只要給定類的名字,就可以通過反射機制來獲得類的所有資訊。

這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為Java語言的反射機制。

2、哪裡會用到反射機制?

jdbc就是典型的反射

Class.forName('com.mysql.jdbc.Driver.class');//載入MySQL的驅動類

這就是反射。如hibernate,struts等框架使用反射實現的。

42. 反射的實現方式

第一步:獲取Class物件,有4種方法: 1)Class.forName(“類的路徑”); 2)類名.class 3)物件名.getClass() 4)基本型別的包裝類,可以呼叫包裝類的Type屬性來獲得該包裝類的Class物件

43. 實現Java反射的類:

1)Class:表示正在執行的Java應用程式中的類和介面 注意: 所有獲取物件的資訊都需要Class類來實現。 2)Field:提供有關類和介面的屬性資訊,以及對它的動態訪問許可權。 3)Constructor:提供關於類的單個構造方法的資訊以及它的訪問許可權 4)Method:提供類或介面中某個方法的資訊

44. 反射機制的優缺點:

優點:

1、能夠執行時動態獲取類的例項,提高靈活性;

2、與動態編譯結合

缺點:

1、使用反射效能較低,需要解析位元組碼,將記憶體中的物件進行解析。

解決方案:

​ 1、通過setAccessible(true)關閉JDK的安全檢查來提升反射速度;

​ 2、多次建立一個類的例項時,有快取會快很多

​ 3、ReflectASM工具類,通過位元組碼生成的方式加快反射速度

2、相對不安全,破壞了封裝性(因為通過反射可以獲得私有方法和屬性)

45. Java 中 IO 流分為幾種?

  • 按照流的流向分,可以分為輸入流和輸出流;
  • 按照操作單元劃分,可以劃分為位元組流和字元流;
  • 按照流的角色劃分為節點流和處理流。

Java Io 流共涉及 40 多個類,這些類看上去很雜亂,但實際上很有規則,而且彼此之間存在非常緊密的聯絡, Java I0 流的 40 多個類都是從如下 4 個抽象類基類中派生出來的。

  • InputStream/Reader: 所有的輸入流的基類,前者是位元組輸入流,後者是字元輸入流。
  • OutputStream/Writer: 所有輸出流的基類,前者是位元組輸出流,後者是字元輸出流。

按操作方式分類結構圖:

相關文章