java基礎面試之語法之一

在這裏插入圖片描述

對於Java面試來說的本文只是對Java基礎知識的一個小結,並不是什麼武功祕籍或者什麼速成法,更多的要求面試者自己腳踏實地的去學,多看書,多敲代碼,多去思考,纔是面試的王者之道。在這裏作者提供了Java官方的API以供大家參考和學習點我去官方API

一個".java"源文件中是否可以包括多個類(不是內部類)?有什麼限制?
一個“.java”源文件裏面可以包含多個類,但是隻允許有一個public類,並且類名必須和文件名一致。
每個編譯單元只能有一個public 類。這麼做的意思是,每個編譯單元只能有一個公開的接口,而這個接口就由其public 類來表示。
你可以根據需要,往這個文件裏面添加任意多個提供輔助功能的package 權限的類。但是如果這個編譯單元裏面有兩個或兩個以上的public 類的話,程序就不知道從哪裏導入了,編譯器就會報錯。  
在這裏插入圖片描述

&和&&的區別
&和&&都可以用作邏輯與的運算符,表示邏輯與(and),當運算符兩邊的表達式的結果都爲true時,整個運算結果才爲true,否則,只要有一方爲false,則結果爲false。

&&還具有短路的功能,即如果第一個表達式爲false,則不再計算第二個表達式,例如,對於if(str != null&& !str.equals(“”))表達式,當str爲null時,後面的表達式不會執行,所以不會出現NullPointerException如果將&&改爲&,則會拋出NullPointerException異常。If(x33 &++y>0) y會增長,If(x33 && ++y>0)不會增長

&還可以用作位運算符,當&操作符兩邊的表達式不是boolean類型時,&表示按位與操作,我們通常使用0x0f來與一個整數進行&運算,來獲取該整數的最低4個bit位,例如,0x31 & 0x0f的結果爲0x01。

備註:這道題先說兩者的共同點,再說出&&和&的特殊之處,並列舉一些經典的例子來表明自己理解透徹深入、實際經驗豐富。

在這裏插入圖片描述

下面的例子更清楚
y增長
在這裏插入圖片描述

y沒有增長
在這裏插入圖片描述

在JAVA中如何跳出當前的多重嵌套循環?
在Java中,要想跳出多重循環,可以在外面的循環語句前定義一個標號,然後在裏層循環體的代碼中使用帶有標號的break語句,即可跳出外層循環。例如,
在這裏插入圖片描述

並不推薦這種方式,讓外層的循環條件表達式的結果可以受到裏層循環體代碼的控制,跳出本層循環

在這裏插入圖片描述

switch語句能否作用在byte上,能否作用在long上,能否作用在String上?
在switch(expr1)中,expr1只能是一個整數表達式或者枚舉常量(更大字體),整數表達式可以是int基本類型或Integer包裝類型,由於,byte,short,char都可以隱含轉換爲int,所以,這些類型以及這些類型的包裝類型也是可以的。顯然,long符合switch的語法規定,並且不能被隱式轉換成int類型,所以,它不能作用於swtich語句中。

在這裏插入圖片描述

short s1 = 1; s1 = s1 + 1;有什麼錯? short s1 = 1; s1 += 1;有什麼錯?
對於short s1 = 1; s1 += 1;由於 +=是java語言規定的運算符,java編譯器會對它進行特殊處理,因此可以正確編譯。
在這裏插入圖片描述

char型變量中能不能存貯一箇中文漢字?爲什麼?
當然是可以了,char型變量是用來存儲Unicode編碼的字符的,unicode編碼字符集中包含了漢字。補充說明:unicode編碼佔用兩個字節,所以,char類型的變量也是佔用兩個字節。
在這裏插入圖片描述

8、用最有效率的方法算出2乘以8等於幾?
2<<3,位移三位就可以了,而位運算cpu直接支持的,效率最高。
在這裏插入圖片描述

int和Integer有什麼區別?
Java是一個近乎純潔的面向對象編程語言,但是爲了編程的方便還是引入了基本數據類型,但是爲了能夠將這些基本數據類型當成對象操作,Java爲每一個基本數據類型都引入了對應的包裝類型(wrapper class),int的包裝類就是Integer,從Java 5開始引入了自動裝箱/拆箱機制,使得二者可以相互轉換。
Java 爲每個原始類型提供了包裝類型:

  • 原始類型: boolean,char,byte,short,int,long,float,double
  • 包裝類型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
    在這裏插入圖片描述

第二種情況
在這裏插入圖片描述

解釋內存中的棧(stack)、堆(heap)和靜態區(static area)的用法。
通常我們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用內存中的棧空間;而通過new關鍵字和構造器創建的對象放在堆空間;程序中的字面量(literal)如直接書寫的100、”hello”和常量都是放在靜態區中。棧空間操作起來最快但是棧很小,通常大量的對象都是放在堆空間,理論上整個內存沒有被其他進程使用的空間甚至硬盤上的虛擬內存都可以被當成堆空間來使用。
String str = new String(“hello”);
上面的語句中變量str放在棧上,用new創建出來的字符串對象放在堆上,而”hello”這個字面量放在靜態區。

數組有沒有length()方法?String有沒有length()方法?
這個問題代碼敲多了都應該知道,數組本身就有長度,String字符串則需要length方法來獲取長度

構造器(constructor)是否可被重寫(override)?
構造器不能被繼承,因此不能被重寫,但可以被重載。

兩個對象值相同(x.equals(y) == true),但卻可有不同的hash code,這句話對不對?
不對,如果兩個對象x和y滿足x.equals(y) == true,它們的哈希碼(hash code)應當相同。Java對於eqauls方法和hashCode方法是這樣規定的:(1)如果兩個對象相同(equals方法返回true),那麼它們的hashCode值一定要相同;(2)如果兩個對象的hashCode相同,它們並不一定相同。當然,你未必要按照要求去做,但是如果你違背了上述原則就會發現在使用容器時,相同的對象可以出現在Set集合中,同時增加新元素的效率會大大下降(對於使用哈希存儲的系統,如果哈希碼頻繁的衝突將會造成存取性能急劇下降)。
補充:關於equals和hashCode方法,很多Java程序都知道,但很多人也就是僅僅知道而已,在Joshua Bloch的大作《Effective Java》(很多軟件公司,《Effective Java》、《Java編程思想》以及《重構:改善既有代碼質量》是Java程序員必看書籍,如果你還沒看過,那就趕緊去亞馬遜買一本吧)中是這樣介紹equals方法的:首先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方法的訣竅包括:1. 使用==操作符檢查”參數是否爲這個對象的引用”;2. 使用instanceof操作符檢查”參數是否爲正確的類型”;3. 對於類中的關鍵屬性,檢查參數傳入對象的屬性是否與之相匹配;4. 編寫完equals方法後,問自己它是否滿足對稱性、傳遞性、一致性;5. 重寫equals時總是要重寫hashCode;6. 不要將equals方法參數中的Object對象替換爲其他的類型,在重寫時不要忘掉@Override註解。

是否可以繼承String類?
String 類是final類,不可以被繼承。

補充:繼承String本身就是一個錯誤的行爲,對String類型最好的重用方式是關聯關係(Has-A)和依賴關係(Use-A)而不是繼承關係(Is-A)。

當一個對象被當作參數傳遞到一個方法後,此方法可改變這個對象的屬性,並可返回變化後的結果,那麼這裏到底是值傳遞還是引用傳遞?
是值傳遞。Java語言的方法調用只支持參數的值傳遞。當一個對象實例作爲一個參數被傳遞到方法中時,參數的值就是對該對象的引用。對象的屬性可以在被調用過程中被改變,但對對象引用的改變是不會影響到調用者的。

String和StringBuilder、StringBuffer的區別?
Java平臺提供了兩種類型的字符串:String和StringBuffer/StringBuilder,它們可以儲存和操作字符串。其中String是隻讀字符串,也就意味着String引用的字符串內容是不能被改變的。而StringBuffer/StringBuilder類表示的字符串對象可以直接進行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,區別在於它是在單線程環境下使用的,因爲它的所有方面都沒有被synchronized修飾,因此它的效率也比StringBuffer要高。
什麼情況下用+運算符進行字符串連接比調用StringBuffer/StringBuilder對象的append方法連接字符串性能更好?
string旦賦值或實例化後就不可更改,如果賦予新值將會重新開闢內存地址進行存儲。
而StringBuffer類使用append和insert等方法改變字符串值時只是在原有對象存儲的內存地址上進行連續操作,減少了資源的開銷

因此我們得出結論:當需要進行頻繁修改字符串的操作時先建立StringBuffer類對象進行操作,
將最後結果轉化成String類對象返回,這樣效率會高很多。

重載(Overload)和重寫(Override)的區別。重載的方法能否根據返回類型進行區分?
方法的重載和重寫都是實現多態的方式,區別在於前者實現的是編譯時的多態性,而後者實現的是運行時的多態性。重載發生在一個類中,同名的方法如果有不同的參數列表(參數類型不同、參數個數不同或者二者都不同)則視爲重載;重寫發生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問,不能比父類被重寫方法聲明更多的異常(里氏代換原則)。重載對返回類型沒有特殊的要求。

面試題:華爲的面試題中曾經問過這樣一個問題 – “爲什麼不能根據返回類型來區分重載”,快說出你的答案吧!
因爲調用時不能指定類型信息,編譯器不知道你要調用哪個函數。
float max(int a, int b); int max(int a, int b);

描述一下JVM加載class文件的原理機制?
JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。

由於Java的跨平臺性,經過編譯的Java源程序並不是一個可執行程序,而是一個或多個類文件。當Java程序需要使用某個類時,JVM會確保這個類已經被加載、連接(驗證、準備和解析)和初始化。

類的加載是指把類的.class文件中的數據讀入到內存中,通常是創建一個字節數組讀入.class文件,然後產生與所加載類對應的Class對象。加載完成後,Class對象還不完整,所以此時的類還不可用。

當類被加載後就進入連接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。
最後JVM對類進行初始化,包括:1)如果類存在直接的父類並且這個類還沒有被初始化,那麼就先初始化父類;2)如果類中存在初始化語句,就依次執行這些初始化語句。
類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。從Java 2(JDK 1.2)開始,類加載過程採取了父親委託機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其他的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。
下面是關於幾個類加載器的說明:

Bootstrap:一般用本地代碼實現,負責加載JVM基礎核心類庫(rt.jar);
Extension:從java.ext.dirs系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;
System:又叫應用類加載器,其父類是Extension。它是應用最廣泛的類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。

抽象類(abstract class)和接口(interface)有什麼異同?
抽象類和接口都不能夠實例化,但可以定義抽象類和接口類型的引用。一個類如果繼承了某個抽象類或者實現了某個接口都需要對其中的抽象方法全部進行實現,否則該類仍然需要被聲明爲抽象類。接口比抽象類更加抽象,因爲抽象類中可以定義構造器,可以有抽象方法和具體方法,而接口中不能定義構造器而且其中的方法全部都是抽象方法。抽象類中的成員可以是private、默認、protected、public的,而接口中的成員全都是public的。抽象類中可以定義成員變量,而接口中定義的成員變量實際上都是常量。有抽象方法的類必須被聲明爲抽象類,而抽象類未必要有抽象方法。

靜態嵌套類(Static Nested Class)和內部類(Inner Class)的不同?
Static Nested Class是被聲明爲靜態(static)的內部類,它可以不依賴於外部類實例被實例化。而通常的內部類需要在外部類實例化後才能實例化,其語法看起來挺詭異的。

Java 中會存在內存泄漏嗎,請簡單描述。
理論上Java因爲有垃圾回收機制(GC)不會存在內存泄露問題(這也是Java被廣泛使用於服務器端編程的一個重要原因);然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被GC回收,因此也會導致內存泄露的發生。例如hibernate的Session(一級緩存)中的對象屬於持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,如果不及時關閉(close)或清空(flush)一級緩存就可能導致內存泄露。下面例子中的代碼也會導致內存泄露。

抽象的(abstract)方法是否可同時是靜態的(static),是否可同時是本地方法(native),是否可同時被synchronized修飾?
都不能。抽象方法需要子類重寫,而靜態的方法是無法被重寫的,因此二者是矛盾的。本地方法是由本地代碼(如C代碼)實現的方法,而抽象方法是沒有實現的,也是矛盾的。synchronized和方法的實現細節有關,抽象方法不涉及實現細節,因此也是相互矛盾的。
在這裏插入圖片描述

闡述靜態變量和實例變量的區別。
靜態變量是被static修飾符修飾的變量,也稱爲類變量,它屬於類,不屬於類的任何一個對象,一個類不管創建多少個對象,靜態變量在內存中有且僅有一個拷貝;實例變量必須依存於某一實例,需要先創建對象然後通過對象才能訪問到它。靜態變量可以實現讓多個對象共享內存。
靜態變量是所有對象共有的,某一個對象將它的值改變了,其他對象再去獲取它的值,得到的是改變後的值;
實例變量則是每一個對象私有的,某一個對象將它的值改變了,不影響其他對象取值的結果,其他對象仍會得到實例變量一開始就被賦予的值。
例:

public class A{
static int a = 0;   // 這是靜態變量
int b = 0;   // 這是實例變量
} 

public class B{
public static void main (String[] args){
A a1 = new A();
A a2 = new A();
a1.a = 3;    //  對象a1將3賦值給a
a1.b = 4 ;   //  對象a1將4賦值給“它自己的b”
System.out.println(a2.a); //結果爲3
                //靜態變量是針對所有對象的,所以a1改變a的值,a2獲取到的a的值也改變了的
System.out.println(a2.b); //結果爲0
 //實例變量只改變自身所獲取的值,所以a1獲取的b的值改變了,不影響a2獲取的b的值
 }
}

GC是什麼?爲什麼要有GC?
GC是垃圾收集的意思,內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會導致程序或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測對象是否超過作用域從而達到自動回收內存的目的,Java語言沒有提供釋放已分配內存的顯示操作方法。Java程序員不用擔心內存管理,因爲垃圾收集器會自動進行管理。要請求垃圾收集,可以調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉顯示的垃圾回收調用。
垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作爲一個單獨的低優先級的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。在Java誕生初期,垃圾回收是Java最大的亮點之一,因爲服務器端的編程需要有效的防止內存泄露問題,然而時過境遷,如今Java的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶通常覺得iOS的系統比Android系統有更好的用戶體驗,其中一個深層次的原因就在於Android系統中垃圾回收的不可預知性。
GC是垃圾收集的意思,內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會導致程序或系統的不穩定甚至崩潰,Java提供的GC功能可以自動監測對象是否超過作用域從而達到自動回收內存的目的,Java語言沒有提供釋放已分配內存的顯示操作方法。Java程序員不用擔心內存管理,因爲垃圾收集器會自動進行管理。要請求垃圾收集,可以調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉顯示的垃圾回收調用。
垃圾回收可以有效的防止內存泄露,有效的使用可以使用的內存。垃圾回收器通常是作爲一個單獨的低優先級的線程運行,不可預知的情況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或所有對象進行垃圾回收。在Java誕生初期,垃圾回收是Java最大的亮點之一,因爲服務器端的編程需要有效的防止內存泄露問題,然而時過境遷,如今Java的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶通常覺得iOS的系統比Android系統有更好的用戶體驗,其中一個深層次的原因就在於Android系統中垃圾回收的不可預知性。
與垃圾回收相關的JVM參數:

-Xms / -Xmx — 堆的初始大小 / 堆的最大大小
-Xmn — 堆中年輕代的大小
-XX:-DisableExplicitGC — 讓System.gc()不產生任何作用
-XX:+PrintGCDetails — 打印GC的細節
-XX:+PrintGCDateStamps — 打印GC操作的時間戳
-XX:NewSize / XX:MaxNewSize — 設置新生代大小/新生代最大大小
-XX:NewRatio — 可以設置老生代和新生代的比例
-XX:PrintTenuringDistribution — 設置每次新生代GC後輸出倖存者樂園中對象年齡的分佈
-XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設置老年代閥值的初始值和最大值
-XX:TargetSurvivorRatio:設置倖存區的目標使用率

String s = new String(“xyz”);創建了幾個字符串對象?
兩個對象,一個是靜態區的”xyz”,一個是用new創建在堆上的對象。

接口是否可繼承(extends)接口?抽象類是否可實現(implements)接口?抽象類是否可繼承具體類(concrete class)?
接口可以繼承接口,而且支持多重繼承。抽象類可以實現(implements)接口,抽象類可繼承具體類也可以繼承抽象類。

Anonymous Inner Class(匿名內部類)是否可以繼承其它類?是否可以實現接口?
匿名的內部類不能extends(繼承)其它類,但一個內部類可以作爲一個接口,由另一個內部類實現。

class Car{
    void move(){}
}
interface Person{
    void learn();
}
abstract class Animal{
    abstract void eat();
}
public class AnonymousInnerClassDemo {
 
    public static void main(String[] args) {
        Car car = new Car(){
            @Override
            void move() {
                System.out.println("匿名內部類的move方法");
            }
        };
        car.move();
 
        Person person = new Person() {
 
            public void learn() {
                System.out.println("匿名內部類的learn方法");
            }
        };
        person.learn();
 
        Animal animal = new Animal() {
            @Override
            void eat() {
                System.out.println("匿名內部類的eat方法");
            }
        };
        animal.eat();
    }
}

指出下面程序的運行結果。
在這裏插入圖片描述
執行結果:1a2b2b。創建對象時構造器的調用順序是:先初始化靜態成員,然後調用父類構造器,再初始化非靜態成員,最後調用自身構造器。
如果不能給出此題的正確答案,說明之前Java類加載機制還沒有完全理解,趕緊再看看吧。

數據類型之間的轉換:

  • 如何將字符串轉換爲基本數據類型?
  • 如何將基本數據類型轉換爲字符串?
    調用基本數據類型對應的包裝類中的方法parseXXX(String)或valueOf(String)即可返回相應基本類型;
  • 一種方法是將基本數據類型與空字符串(””)連接(+)即可獲得其所對應的字符串;另一種方法是調用String 類中的valueOf()方法返回相應字符串
    Character ch2 = ‘8’; (char是基本數據類型,Character是其包裝類型。)
      int num2 = Integer.parseInt(ch2.toString());

如何實現字符串的反轉及替換?
遞歸實現字符串反轉
在這裏插入圖片描述

日期和時間:

  • 如何取得年月日、小時分鐘秒?
  • 如何取得從1970年1月1日0時0分0秒到現在的毫秒數?
  • 如何取得某月的最後一天?
  • 如何格式化日期?
    創建java.util.Calendar 實例,調用其get()方法傳入不同的參數即可獲得參數所對應的值。Java 8中可以使用java.time.LocalDateTimel來獲取,代碼如下所示。
    在這裏插入圖片描述

以下方法均可獲得該毫秒數。

Calendar.getInstance().getTimeInMillis();
System.currentTimeMillis();
Clock.systemDefaultZone().millis(); // Java 8

取得某月的最後一天
在這裏插入圖片描述

利用java.text.DataFormat 的子類(如SimpleDateFormat類)中的format(Date)方法可將日期格式化。Java 8中可以用java.time.format.DateTimeFormatter來格式化時間日期,代碼如下所示。

在這裏插入圖片描述

打印昨天的當前時刻。

在這裏插入圖片描述

比較一下Java和JavaSciprt。
基於對象和麪向對象:Java是一種真正的面向對象的語言,即使是開發簡單的程序,必須設計對象;JavaScript是種腳本語言,它可以用來製作與網絡無關的,與用戶交互作用的複雜軟件。它是一種基於對象(Object-Based)和事件驅動(Event-Driven)的編程語言,因而它本身提供了非常豐富的內部對象供設計人員使用。

  • 解釋和編譯:Java的源代碼在執行之前,必須經過編譯。JavaScript是一種解釋性編程語言,其源代碼不需經過編譯,由瀏覽器解釋執行。(目前的瀏覽器幾乎都使用了JIT(即時編譯)技術來提升JavaScript的運行效率)
  • 強類型變量和類型弱變量:Java採用強類型變量檢查,即所有變量在編譯之前必須作聲明;JavaScript中變量是弱類型的,甚至在使用變量前可以不作聲明,JavaScript的解釋器在運行時檢查推斷其數據類型。
  • 代碼格式不一樣。
    補充:上面列出的四點是網上流傳的所謂的標準答案。其實Java和JavaScript最重要的區別是一個是靜態語言,一個是動態語言。目前的編程語言的發展趨勢是函數式語言和動態語言。在Java中類(class)是一等公民,而JavaScript中函數(function)是一等公民,因此JavaScript支持函數式編程,可以使用Lambda函數和閉包(closure),當然Java 8也開始支持函數式編程,提供了對Lambda表達式以及函數式接口的支持。對於這類問題,在面試的時候最好還是用自己的語言回答會更加靠譜,不要背網上所謂的標準答案。

Error和Exception有什麼區別?
Error表示系統級的錯誤和程序不必處理的異常,是恢復不是不可能但很困難的情況下的一種嚴重問題;比如內存溢出,不可能指望程序能處理這樣的情況;Exception表示需要捕捉或者需要程序進行處理的異常,是一種設計或實現問題;也就是說,它表示如果程序運行正常,從不會發生的情況。
在這裏插入圖片描述

提示:用遞歸編寫程序時一定要牢記兩點:1. 遞歸公式;2. 收斂條件(什麼時候就不再繼續遞歸)。

try{}裏有一個return語句,那麼緊跟在這個try後的finally{}裏的代碼會不會被執行,什麼時候被執行,在return前還是後?
會執行,在方法返回調用者前執行。
注意:在finally中改變返回值的做法是不好的,因爲如果存在finally代碼塊,try中的return語句不會立馬返回調用者,而是記錄下返回值待finally代碼塊執行完畢之後再向調用者返回其值,然後如果在finally中修改了返回值,就會返回修改後的值。顯然,在finally中返回或者修改返回值會對程序造成很大的困擾,C#中直接用編譯錯誤的方式來阻止程序員幹這種齷齪的事情,Java中也可以通過提升編譯器的語法檢查級別來產生警告或錯誤,Eclipse中可以在如圖所示的地方進行設置,強烈建議將此項設置爲編譯錯誤。

Java語言如何進行異常處理,關鍵字:throws、throw、try、catch、finally分別如何使用?
Java通過面向對象的方法進行異常處理,把各種不同的異常進行分類,並提供了良好的接口。在Java中,每個異常都是一個對象,它是Throwable類或其子類的實例。當一個方法出現異常後便拋出一個異常對象,該對象中包含有異常信息,調用這個對象的方法可以捕獲到這個異常並可以對其進行處理。Java的異常處理是通過5個關鍵詞來實現的:try、catch、throw、throws和finally。一般情況下是用try來執行一段程序,如果系統會拋出(throw)一個異常對象,可以通過它的類型來捕獲(catch)它,或通過總是執行代碼塊(finally)來處理;try用來指定一塊預防所有異常的程序;catch子句緊跟在try塊後面,用來指定你想要捕獲的異常的類型;throw語句用來明確地拋出一個異常;throws用來聲明一個方法可能拋出的各種異常(當然聲明異常時允許無病呻吟);finally爲確保一段代碼不管發生什麼異常狀況都要被執行;try語句可以嵌套,每當遇到一個try語句,異常的結構就會被放入異常棧中,直到所有的try語句都完成。如果下一級的try語句沒有對某種異常進行處理,異常棧就會執行出棧操作,直到遇到有處理這種異常的try語句或者最終將異常拋給JVM。

運行時異常與受檢異常有何異同?
異常表示程序運行過程中可能出現的非正常狀態,運行時異常表示虛擬機的通常操作中可能遇到的異常,是一種常見運行錯誤,只要程序設計得沒有問題通常就不會發生。受檢異常跟程序運行的上下文環境有關,即使程序設計無誤,仍然可能因使用的問題而引發。Java編譯器要求方法必須聲明拋出可能發生的受檢異常,但是並不要求必須聲明拋出未被捕獲的運行時異常。異常和繼承一樣,是面向對象程序設計中經常被濫用的東西,在Effective Java中對異常的使用給出了以下指導原則:

  • 不要將異常處理用於正常的控制流(設計良好的API不應該強迫它的調用者爲了正常的控制流而使用異常)
  • 對可以恢復的情況使用受檢異常,對編程錯誤使用運行時異常
  • 避免不必要的使用受檢異常(可以通過一些狀態檢測手段來避免異常的發生)
  • 優先使用標準的異常
  • 每個方法拋出的異常都要有文檔
  • 保持異常的原子性
  • 不要在catch中忽略掉捕獲到的異常

列出一些你常見的運行時異常?

- ArithmeticException(算術異常)
- ClassCastException (類轉換異常)
- IllegalArgumentException (非法參數異常)
- IndexOutOfBoundsException (下標越界異常)
- NullPointerException (空指針異常)
- SecurityException (安全異常)

闡述final、finally、finalize的區別。

  • final:修飾符(關鍵字)有三種用法:如果一個類被聲明爲final,意味着它不能再派生出新的子類,即不能被繼承,因此它和abstract是反義詞。將變量聲明爲final,可以保證它們在使用中不被改變,被聲明爲final的變量必須在聲明時給定初值,而在以後的引用中只能讀取不可修改。被聲明爲final的方法也同樣只能使用,不能在子類中被重寫。
  • finally:通常放在try…catch…的後面構造總是執行代碼塊,這就意味着程序無論正常執行還是發生異常,這裏的代碼只要JVM不關閉都能執行,可以將釋放外部資源的代碼寫在finally塊中。
  • finalize:Object類中定義的方法,Java中允許使用finalize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。這個方法是由垃圾收集器在銷燬對象時調用的,通過重寫finalize()方法可以整理系統資源或者執行其他清理工作。

List,Set,Map是否繼承自Collection接口?
List、Set 是,Map 不是。Map是鍵值對映射容器,與List和Set有明顯的區別,而Set存儲的零散的元素且不允許有重複元素(數學中的集合也是如此),List是線性結構的容器,適用於按數值索引訪問元素的情形。

闡述ArrayList、Vector、LinkedList的存儲性能和特性。
ArrayList 和Vector都是使用數組方式存儲數據,此數組元素數大於實際存儲的數據以便增加和插入元素,它們都允許直接按序號索引元素,但是插入元素要涉及數組元素移動等內存操作,所以索引數據快而插入數據慢,Vector中的方法由於添加了synchronized修飾,因此Vector是線程安全的容器,但性能上較ArrayList差,因此已經是Java中的遺留容器。LinkedList使用雙向鏈表實現存儲(將內存中零散的內存單元通過附加的引用關聯起來,形成一個可以按序號索引的線性結構,這種鏈式存儲方式與數組的連續存儲方式相比,內存的利用率更高),按序號索引數據需要進行前向或後向遍歷,但是插入數據時只需要記錄本項的前後項即可,所以插入速度較快。Vector屬於遺留容器(Java早期的版本中提供的容器,除此之外,Hashtable、Dictionary、BitSet、Stack、Properties都是遺留容器),已經不推薦使用,但是由於ArrayList和LinkedListed都是非線程安全的,如果遇到多個線程操作同一個容器的場景,則可以通過工具類Collections中的synchronizedList方法將其轉換成線程安全的容器後再使用(這是對裝潢模式的應用,將已有對象傳入另一個類的構造器中創建新的對象來增強實現)。

Collection和Collections的區別?
Collection是一個接口,它是Set、List等容器的父接口;Collections是個一個工具類,提供了一系列的靜態方法來輔助容器操作,這些方法包括對容器的搜索、排序、線程安全化等等。

List、Map、Set三個接口存取元素時,各有什麼特點?
List以特定索引來存取元素,可以有重複元素。Set不能存放重複元素(用對象的equals()方法來區分元素是否重複)。Map保存鍵值對(key-value pair)映射,映射關係可以是一對一或多對一。Set和Map容器都有基於哈希存儲和排序樹的兩種實現版本,基於哈希存儲的版本理論存取時間複雜度爲O(1),而基於排序樹版本的實現在插入或刪除元素時會按照元素或元素的鍵(key)構成排序樹從而達到排序和去重的效果。

TreeMap和TreeSet在排序時如何比較元素?Collections工具類中的sort()方法如何比較元素?
TreeSet要求存放的對象所屬的類必須實現Comparable接口,該接口提供了比較元素的compareTo()方法,當插入元素時會回調該方法比較元素的大小。TreeMap要求存放的鍵值對映射的鍵必須實現Comparable接口從而根據鍵對元素進行排序。Collections工具類的sort方法有兩種重載的形式,第一種要求傳入的待排序容器中存放的對象比較實現Comparable接口以實現元素的比較;第二種不強制性的要求容器中的元素必須可比較,但是要求傳入第二個參數,參數是Comparator接口的子類型(需要重寫compare方法實現元素的比較),相當於一個臨時定義的排序規則,其實就是通過接口注入比較元素大小的算法,也是對回調模式的應用(Java中對函數式編程的支持)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章