Java面試題集(51-70)
分享一個大神的人工智慧教程!http://blog.csdn.net/jiangjunshow
Java程式設計師面試題集(51-70)
摘要:這一部分主要講解了異常、多執行緒、容器和I/O的相關面試題。首先,異常機制提供了一種在不打亂原有業務邏輯的前提下,把程式在執行時可能出現的狀況處理掉的優雅的解決方案,同時也是面向物件的解決方案。而Java的執行緒模型是建立在共享的、預設的可見的可變狀態以及搶佔式執行緒排程兩個概念之上的。Java內建了對多執行緒程式設計的支援在20世紀90年代可以說是一個巨大的進步,但是最初的設計在當下看來已經給程式帶來很多困擾了。感謝Doug Lea在Java 5中提供了他里程碑式的傑作java.util.concurrent包,它的出現讓Java的多執行緒程式設計能夠更好的工作。Java 1.4中引入NIO實現了對非阻塞I/O的支援,NIO為I/O操作抽象出緩衝區和通道層,解決了字符集的編碼和解碼問題,提供了將檔案對映為記憶體資料的介面。NIO無疑使Java向前邁出了一大步,但為了方便Java對檔案系統的處理,NIO.2進一步對Java的I/O操作進行了增強,提供了能批量獲取檔案屬性的檔案系統介面,還提供了套接字和檔案都能進行非同步IO操作的API,完成了JSR-51中定義的套接字。對於Java中的容器(集合框架)而言,Java 5中引入泛型無疑是程式設計師的福音,然而那僅僅是糖衣語法,底層實現沒有本質的差別,因此與C#相比,Java的泛型顯得不那麼讓人痛快。
51、類ExampleA 繼承Exception,類ExampleB 繼承ExampleA。
有如下程式碼片斷:
-
try{
-
throw
new ExampleB(
"b")
-
}
catch(ExampleA e){
-
System.out.println(
"ExampleA"
);
-
}
catch(Exception e){
-
System.out.println(
"Exception");
-
}
請問執行此段程式碼的輸出是什麼?
答:輸出:ExampleA。(根據里氏代換原則[能使用父型別的地方一定能使用子型別],抓取ExampleA型別異常的catch塊能夠抓住try塊中丟擲的ExampleB型別的異常)
補充:比此題略複雜的一道面試題如下所示(此題的出處是《Java程式設計思想》),說出你的答案吧!
-
class Annoyance extends Exception {}
-
class Sneeze extends Annoyance {}
-
-
class Human {
-
-
public static void main(String[] args)
-
throws Exception {
-
try {
-
try {
-
throw
new Sneeze();
-
}
-
catch ( Annoyance a ) {
-
System.out.println(
"Caught Annoyance");
-
throw a;
-
}
-
}
-
catch ( Sneeze s ) {
-
System.out.println(
"Caught Sneeze");
-
return ;
-
}
-
finally {
-
System.out.println(
"Hello World!");
-
}
-
}
-
}
52、List、Set、Map 是否繼承自Collection 介面?
答:List、Set 是,Map 不是。Map是鍵值對對映容器,與List和Set有明顯的區別,而Set儲存的零散的元素且不允許有重複元素(數學中的集合也是如此),List是線性結構的容器,適用於按數值索引訪問元素的情形。
53、說出ArrayList、Vector、LinkedList 的儲存效能和特性?
答:ArrayList 和Vector都是使用陣列方式儲存資料,此陣列元素數大於實際儲存的資料以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及陣列元素移動等記憶體操作,所以索引資料快而插入資料慢,Vector由於使用了synchronized 方法(執行緒安全),通常效能上較ArrayList 差,而LinkedList 使用雙向連結串列實現儲存(將記憶體中零散的記憶體單元通過附加的引用關聯起來,形成一個可以按序號索引的線性結構,這種鏈式儲存方式與陣列的連續儲存方式相比,其實對記憶體的利用率更高),按序號索引資料需要進行前向或後向遍歷,但是插入資料時只需要記錄本項的前後項即可,所以插入速度較快。Vector屬於遺留容器(早期的JDK中使用的容器,除此之外Hashtable、Dictionary、BitSet、Stack、Properties都是遺留容器),現在已經不推薦使用,但是由於ArrayList和LinkedListed都是非執行緒安全的,如果需要多個執行緒操作同一個容器,那麼可以通過工具類Collections中的synchronizedList方法將其轉換成執行緒安全的容器後再使用(這其實是裝潢模式最好的例子,將已有物件傳入另一個類的構造器中建立新的物件來增加新功能)。
補充:遺留容器中的Properties類和Stack類在設計上有嚴重的問題,Properties是一個鍵和值都是字串的特殊的鍵值對對映,在設計上應該是關聯一個Hashtable並將其兩個泛型引數設定為String型別,但是Java API中的Properties直接繼承了Hashtable,這很明顯是對繼承的濫用。這裡複用程式碼的方式應該是HAS-A關係而不是IS-A關係,另一方面容器都屬於工具類,繼承工具類本身就是一個錯誤的做法,使用工具類最好的方式是HAS-A關係(關聯)或USE-A關係(依賴)。同理,Stack類繼承Vector也是不正確的。
54、Collection 和Collections 的區別?
答:Collection 是一個介面,它是Set、List等容器的父介面;Collections 是個一個工具類,提供了一系列的靜態方法來輔助容器操作,這些方法包括對容器的搜尋、排序、執行緒安全化等等。
55、List、Map、Set 三個介面,存取元素時,各有什麼特點?
答:List以特定索引來存取元素,可有重複元素。Set不能存放重複元素(用物件的equals()方法來區分元素是否重複)。Map儲存鍵值對(key-value pair)對映,對映關係可以是一對一或多對一。Set和Map容器都有基於雜湊儲存和排序樹的兩種實現版本,基於雜湊儲存的版本理論存取時間複雜度為O(1),而基於排序樹版本的實現在插入或刪除元素時會按照元素或元素的鍵(key)構成排序樹從而達到排序和去重的效果。
56、TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?
答:TreeSet要求存放的物件所屬的類必須實現Comparable介面,該介面提供了比較元素的compareTo()方法,當插入元素時會回撥該方法比較元素的大小。TreeMap要求存放的鍵值對對映的鍵必須實現Comparable介面從而根據鍵對元素進行排序。Collections工具類的sort方法有兩種過載的形式,第一種要求傳入的待排序容器中存放的物件比較實現Comparable介面以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,但是要求傳入第二個引數,引數是Comparator介面的子型別(需要重寫compare方法實現元素的比較),相當於一個臨時定義的排序規則,其實就是是通過介面注入比較元素大小的演算法,也是對回撥模式的應用。
例子1:
Student.java
-
package com.lovo.demo;
-
-
public
class Student implements Comparable<Student> {
-
private String name;
// 姓名
-
private
int age;
// 年齡
-
-
public Student(String name, int age) {
-
this.name = name;
-
this.age = age;
-
}
-
-
@Override
-
public String toString() {
-
return
"Student [name=" + name +
", age=" + age +
"]";
-
}
-
-
@Override
-
public int compareTo(Student o) {
-
return
this.age - o.age;
// 比較年齡(年齡的升序)
-
}
-
-
}
Test01.java
-
package com.lovo.demo;
-
-
import java.util.Set;
-
import java.util.TreeSet;
-
-
class Test01 {
-
-
public static void main(String[] args) {
-
Set<Student> set =
new TreeSet<>();
// Java 7的鑽石語法(構造器後面的尖括號中不需要寫型別)
-
set.add(
new Student(
"Hao LUO",
33));
-
set.add(
new Student(
"XJ WANG",
32));
-
set.add(
new Student(
"Bruce LEE",
60));
-
set.add(
new Student(
"Bob YANG",
22));
-
-
for(Student stu : set) {
-
System.out.println(stu);
-
}
-
// 輸出結果:
-
// Student [name=Bob YANG, age=22]
-
// Student [name=XJ WANG, age=32]
-
// Student [name=Hao LUO, age=33]
-
// Student [name=Bruce LEE, age=60]
-
}
-
}
例子2:
Student.java
-
package com.lovo.demo;
-
-
public
class Student {
-
private String name;
// 姓名
-
private
int age;
// 年齡
-
-
public Student(String name, int age) {
-
this.name = name;
-
this.age = age;
-
}
-
-
/**
-
* 獲取學生姓名
-
*/
-
public String getName() {
-
return name;
-
}
-
-
/**
-
* 獲取學生年齡
-
*/
-
public int getAge() {
-
return age;
-
}
-
-
@Override
-
public String toString() {
-
return
"Student [name=" + name +
", age=" + age +
"]";
-
}
-
-
}
Test02.java
-
package com.lovo.demo;
-
-
import java.util.ArrayList;
-
import java.util.Collections;
-
import java.util.Comparator;
-
import java.util.List;
-
-
class Test02 {
-
-
public static void main(String[] args) {
-
List<Student> list =
new ArrayList<>();
// Java 7的鑽石語法(構造器後面的尖括號中不需要寫型別)
-
list.add(
new Student(
"Hao LUO",
33));
-
list.add(
new Student(
"XJ WANG",
32));
-
list.add(
new Student(
"Bruce LEE",
60));
-
list.add(
new Student(
"Bob YANG",
22));
-
-
// 通過sort方法的第二個引數傳入一個Comparator介面物件
-
// 相當於是傳入一個比較物件大小的演算法到sort方法中
-
// 由於Java中沒有函式指標、仿函式、委託這樣的概念
-
// 因此要將一個演算法傳入一個方法中唯一的選擇就是通過介面回撥
-
Collections.sort(list,
new Comparator<Student> () {
-
-
@Override
-
public int compare(Student o1, Student o2) {
-
return o1.getName().compareTo(o2.getName());
// 比較學生姓名
-
}
-
});
-
-
for(Student stu : list) {
-
System.out.println(stu);
-
}
-
// 輸出結果:
-
// Student [name=Bob YANG, age=22]
-
// Student [name=Bruce LEE, age=60]
-
// Student [name=Hao LUO, age=33]
-
// Student [name=XJ WANG, age=32]
-
}
-
}
57、sleep()和wait()有什麼區別?
答:sleep()方法是執行緒類(Thread)的靜態方法,導致此執行緒暫停執行指定時間,將執行機會給其他執行緒,但是監控狀態依然保持,到時後會自動恢復(執行緒回到就緒(ready)狀態),因為呼叫sleep 不會釋放物件鎖。wait()是Object 類的方法,對此物件呼叫wait()方法導致本執行緒放棄物件鎖(執行緒暫停執行),進入等待此物件的等待鎖定池,只有針對此物件發出notify 方法(或notifyAll)後本執行緒才進入物件鎖定池準備獲得物件鎖進入就緒狀態。
補充:這裡似乎漏掉了一個作為先決條件的問題,就是什麼是程序,什麼是執行緒?為什麼需要多執行緒程式設計?答案如下所示:
程序是具有一定獨立功能的程式關於某個資料集合上的一次執行活動,是作業系統進行資源分配和排程的一個獨立單位;執行緒是程序的一個實體,是CPU排程和分派的基本單位,是比程序更小的能獨立執行的基本單位。執行緒的劃分尺度小於程序,這使得多執行緒程式的併發性高;程序在執行時通常擁有獨立的記憶體單元,而執行緒之間可以共享記憶體。使用多執行緒的程式設計通常能夠帶來更好的效能和使用者體驗,但是多執行緒的程式對於其他程式是不友好的,因為它佔用了更多的CPU資源。
58、sleep()和yield()有什麼區別?
答:
① sleep()方法給其他執行緒執行機會時不考慮執行緒的優先順序,因此會給低優先順序的執行緒以執行的機會;yield()方法只會給相同優先順序或更高優先順序的執行緒以執行的機會;
② 執行緒執行sleep()方法後轉入阻塞(blocked)狀態,而執行yield()方法後轉入就緒(ready)狀態;
③ sleep()方法宣告丟擲InterruptedException,而yield()方法沒有宣告任何異常;
④ sleep()方法比yield()方法(跟作業系統相關)具有更好的可移植性。
59、當一個執行緒進入一個物件的synchronized方法A之後,其它執行緒是否可進入此物件的synchronized方法?
答:不能。其它執行緒只能訪問該物件的非同步方法,同步方法則不能進入。
60、請說出與執行緒同步相關的方法。
答:
- wait():使一個執行緒處於等待(阻塞)狀態,並且釋放所持有的物件的鎖;
- sleep():使一個正在執行的執行緒處於睡眠狀態,是一個靜態方法,呼叫此方法要捕捉InterruptedException 異常;
- notify():喚醒一個處於等待狀態的執行緒,當然在呼叫此方法的時候,並不能確切的喚醒某一個等待狀態的執行緒,而是由JVM確定喚醒哪個執行緒,而且與優先順序無關;
- notityAll():喚醒所有處入等待狀態的執行緒,注意並不是給所有喚醒執行緒一個物件的鎖,而是讓它們競爭;
- JDK 1.5通過Lock介面提供了顯式(explicit)的鎖機制,增強了靈活性以及對執行緒的協調。Lock介面中定義了加鎖(lock())和解鎖(unlock())的方法,同時還提供了newCondition()方法來產生用於執行緒之間通訊的Condition物件;
- JDK 1.5還提供了訊號量(semaphore)機制,訊號量可以用來限制對某個共享資源進行訪問的執行緒的數量。在對資源進行訪問之前,執行緒必須得到訊號量的許可(呼叫Semaphore物件的acquire()方法);在完成對資源的訪問後,執行緒必須向訊號量歸還許可(呼叫Semaphore物件的release()方法)。
-
package com.lovo;
-
-
/**
-
* 銀行賬戶
-
* @author 駱昊
-
*
-
*/
-
public
class Account {
-
private
double balance;
// 賬戶餘額
-
-
/**
-
* 存款
-
* @param money 存入金額
-
*/
-
public void deposit(double money) {
-
double newBalance = balance + money;
-
try {
-
Thread.sleep(
10);
// 模擬此業務需要一段處理時間
-
}
-
catch(InterruptedException ex) {
-
ex.printStackTrace();
-
}
-
balance = newBalance;
-
}
-
-
/**
-
* 獲得賬戶餘額
-
*/
-
public double getBalance() {
-
return balance;
-
}
-
}
存錢執行緒類:
-
package com.lovo;
-
-
/**
-
* 存錢執行緒
-
* @author 駱昊
-
*
-
*/
-
public
class AddMoneyThread implements Runnable {
-
private Account account;
// 存入賬戶
-
private
double money;
// 存入金額
-
-
public AddMoneyThread(Account account, double money) {
-
this.account = account;
-
this.money = money;
-
}
-
-
@Override
-
public void run()