Java經典面試題(其一)——Java異常和克隆
談一談Java中的Error和Exception
1.Error和Exception的聯繫
繼承關係:Error和Exception都是繼承於Throwable,RuntimeException繼承自Exception。
Error和RuntimeExceptime及其子類被稱爲未檢查異常(Unchecked exception),其它異常稱爲受檢查異常(Checked Exception)。
2.Error和Exception的區別
Error類一般是指與虛擬機相關的問題,如系統崩潰,虛擬機錯誤,內存不足,方法調用棧溢出等。如Java.lang.StackOverFlowError和Java.lang.OutOfMemoryError。對於這類錯誤,Java編譯器不去檢查他們。對於這類錯誤的導致的應用程序中斷,僅靠程序本身無法恢復和預防,遇到這樣的錯誤,建議讓程序終止。
Exception類表示程序可以處理的異常,可以捕獲且可能恢復。遇到這類異常,應該儘可能的處理異常,使程序恢復運行,而不應該隨意終止異常。
3.運行時異常和受檢查異常
Exception又分爲運行時異常(Runtime Exception)和受檢查的異常(Checked Exception)。
RuntimeException:其特點是Java編譯器不去檢查它,也就是說,當程序中可能出現這類異常時,即時沒有用try…catch捕獲,也沒有用throws拋出,還是會編譯通過,如除數爲零的ArithmeticException、錯誤的類型轉換、數組越界訪問和試圖訪問空指針等。處理RuntimeException的原則是:如果出現RuntimeException,那麼一定是程序員的錯誤。
受檢查異常(IOException等):這類異常如果沒有try…catch也沒有throws拋出,編譯器是通不過的。這是在應用環境中出現的外部錯誤。
4.throw和throws兩個關鍵字有什麼不同
throw是用來拋出任意異常的,你可以拋出任意Throwable,包括自定義的異常類對象;throws總是出現在一個函數頭中,用來標明該成員函數可能拋出的各種異常。如果方法拋出了異常,那麼調用這個方法的時候就需要處理這個異常。
5.try-catch-finally-return執行順序
1> 不管是否有異常產生,finally塊中代碼都會執行;
2> 當try和catch中有return語句時,finally塊仍然會執行;
3> finally是在return後面的表達式運算執行的,所以函數返回值在finally執行前確定的,無論finally中的代碼怎麼樣,返回的值都不會改變,仍然是之前return語句中保存的值;
4> finally中最好不要包含return,否則程序會提前退出,返回值不是try或catch中保存的返回值。
6.常見的幾種RunException
一般面試中Java Exception(RuntimeExceptime)是必會問道的問題,常見的異常列出四五種,是基本要求。
常見的幾種如下:
NullPointerException - 空指針引用異常
ClassCastException - 類型強制轉換異常
IllegalArgumentException - 傳遞非法參數異常
ArithmeticException - 算術運算異常
ArrayStoreException - 向數組中存放與聲明類型不兼容對象異常
IndexOutOfBoundsException - 下標越界異常
NegativeArraySizeException - 創建一個大小爲負數的數組錯誤異常
NumberFormatException - 數字格式異常
SecurityException - 安全異常
UnsupportedOperationException - 不支持的操作異常
NegativeArrayException - 數組負下標異常
EOFException - 文件已結束異常
FileNotFoundException - 文件未找到異常
SQLException - 操作數據庫異常
IOException - 輸入輸出異常
NoSuchMethodException - 方法未找到異常
java.lang.AbstractMethodError - 抽象方法錯誤。當應用試圖調用抽象方法時拋出。
java.lang.AssertionError - 斷言錯。用來指示一個斷言失敗的錯誤。
java.lang.ClassCircularityError - 類循環依賴錯誤。在初始化一個類時,若檢測到類之間循環依賴則拋出該異常。
java.lang.ClassFormatError - 類格式錯誤。當Java虛擬機試圖從一個文件中讀取Java類,而檢測到該文件的內容不符合類的有效格式時輸出。
java.lang.Error - 錯誤。是所有錯誤的基類,用於標識嚴重的程序運行問題。這些問題通常描述一些不應被應用程序捕獲的反常情況。
詳解Java中的對象克隆
1.在Java語言中,我們說兩個對象是否相等通常有兩層含義:
對象的內容是否相等,通常使用到對象equals(Object o)函數;
引用的地址是否相同,使用運算符==比較即可。
當兩個對象通過賦值符號 = 賦值時,表明這兩個對象指向了內存中同一個地址,所以改變其中一個對象的內容,也就是間接改變了另一個對象的內容。有時候,我們需要從一個已經存在的對象重新拷貝一份出來,並且不僅這兩個內容相等,在內存中存在兩個獨立的存儲地址,互不影響,這時,就需要用到Java的克隆機制。
2.Cloneable
通過Cloneable接口可以很輕鬆地實現Java對象的克隆,只需要implements Cloneable並實現Object的clone()方法即可,如:
public class User implements Cloneable{
private String username;
private String password;
public User(String username, String password) {
super();
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public boolean equals(Object obj) {
User user = (User) obj;
if (username.equals(user.username) && password.equals(user.password)) {
return true;
}
return false;
}
}
// 注意這裏對象實現的是Object的clone方法,因爲Cloneable是一個空接口
package java.lang;
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
* By convention, classes that implement this interface should override
* <tt>Object.clone</tt> (which is protected) with a public method.
* See {@link java.lang.Object#clone()} for details on overriding this
* method.
* <p>
* Note that this interface does <i>not</i> contain the <tt>clone</tt> method.
* Therefore, it is not possible to clone an object merely by virtue of the
* fact that it implements this interface. Even if the clone method is invoked
* reflectively, there is no guarantee that it will succeed.
*
* @author unascribed
* @see java.lang.CloneNotSupportedException
* @see java.lang.Object#clone()
* @since JDK1.0
*/
public interface Cloneable {
}
// 從源碼中可以看出,需要實現Object類中的clone()方法(注意:clone()方法是一個native方法,同時拋出了一個異常)
/**
* Creates and returns a copy of this object. The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
* <p>
* By convention, the returned object should be obtained by calling
* {@code super.clone}. If a class and all of its superclasses (except
* {@code Object}) obey this convention, it will be the case that
* {@code x.clone().getClass() == x.getClass()}.
* <p>
* By convention, the object returned by this method should be independent
* of this object (which is being cloned). To achieve this independence,
* it may be necessary to modify one or more fields of the object returned
* by {@code super.clone} before returning it. Typically, this means
* copying any mutable objects that comprise the internal "deep structure"
* of the object being cloned and replacing the references to these
* objects with references to the copies. If a class contains only
* primitive fields or references to immutable objects, then it is usually
* the case that no fields in the object returned by {@code super.clone}
* need to be modified.
* <p>
* The method {@code clone} for class {@code Object} performs a
* specific cloning operation. First, if the class of this object does
* not implement the interface {@code Cloneable}, then a
* {@code CloneNotSupportedException} is thrown. Note that all arrays
* are considered to implement the interface {@code Cloneable} and that
* the return type of the {@code clone} method of an array type {@code T[]}
* is {@code T[]} where T is any reference or primitive type.
* Otherwise, this method creates a new instance of the class of this
* object and initializes all its fields with exactly the contents of
* the corresponding fields of this object, as if by assignment; the
* contents of the fields are not themselves cloned. Thus, this method
* performs a "shallow copy" of this object, not a "deep copy" operation.
* <p>
* The class {@code Object} does not itself implement the interface
* {@code Cloneable}, so calling the {@code clone} method on an object
* whose class is {@code Object} will result in throwing an
* exception at run time.
*
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not
* support the {@code Cloneable} interface. Subclasses
* that override the {@code clone} method can also
* throw this exception to indicate that an instance cannot
* be cloned.
* @see java.lang.Cloneable
*/
protected native Object clone() throws CloneNotSupportedException;
// 從clone()函數的註釋中能夠看出對象與克隆對象之間的關係,測試代碼如下(注意:我們在User對象中重寫了equals()函數)
public static void main(String[] args) throws CloneNotSupportedException{
User userOne, userTwo, userThree;
userOne = new User("username", "password");
userTwo = userOne;
userThree = (User) userOne.clone();
System.out.println(userTwo==userOne); //true
System.out.println(userTwo.equals(userOne)); //true
System.out.println(userThree==userOne); //false
System.out.println(userThree.equals(userOne)); //true
}
// 測試結果顯示,通過clone()函數,我們成功地從userOne對象中克隆出來一份獨立的userThree對象
- 淺克隆與深克隆
// 談此之前,我們先看一個例子,定義一個名爲Company的類,並添加一個類型爲User的成員變量
public class Company implements Cloneable{
private User user;
private String address;
public Company(User user, String address) {
super();
this.user = user;
this.address = address;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public boolean equals(Object obj) {
Company company = (Company) obj;
if (user.equals(company.getUser()) && address.equals(company.address)) {
return true;
}
return false;
}
}
// 測試代碼及測試結果如下
public static void main(String[] args) throws CloneNotSupportedException{
Company companyOne, companyTwo, companyThree;
companyOne = new Company(new User("username", "password"), "上海市");
companyTwo = companyOne;
companyThree = (Company) companyOne.clone();
System.out.println(companyTwo==companyOne); //true
System.out.println(companyTwo.equals(companyOne)); //true
System.out.println(companyThree==companyOne); //false
System.out.println(companyThree.equals(companyOne)); //true
System.out.println(companyThree.getUser()==companyOne.getUser()); //true ? 這裏爲什麼不是false呢
System.out.println(companyThree.getUser().equals(companyOne.getUser())); //true
}
問題來了,companyThree與companyOne的User是同一對象!也就是說companyThree只是克隆了companyOne的基本數據類型的數據,而對於引用的數據沒有進行深度的克隆。也就是俗稱的淺克隆。
淺克隆:顧名思義,就是很表層的克隆,只克隆對象自身的引用地址;
深克隆:也稱“N層克隆”,克隆對象自身以及對象所包含的引用類型對象的引用地址。
這裏需要注意的是,對於基本數據類型(primitive)和使用常量池方式創建的String類型,都會針對原值克隆,所以不存在引用地址一說。當然不包括他們對應的包裝類。
所以使用深克隆可以解決上述Company類的Clone()函數:
@Override
protected Object clone() throws CloneNotSupportedException {
Company company = (Company) super.clone();
company.user = (User) company.getUser().clone();
return company;
}
// 再運行測試代碼,就能得到companyThree.getUser()==companyOne.getUser() 爲false的結果
4.Serializable實現
通過上述介紹,我們知道,實現一個對象的克隆,需要如下幾步:
對象所在的類實現Cloneable接口;
2.重寫clone()函數,如果包涵引用類型的成員變量,需要使用深克隆。
如果對象不包含引用類型成員或者數量少的話,使用Cloneable接口還能接受,但當對象包含多個引用類型的成員,同時這些成員又包含了引用類型的成員,那層層克隆豈不是相當繁瑣,並且維護不便?所以,這裏接受一種更加方便的實現方式,使用ObjectOutputStream和ObjectInputStrean來實現對象的序列化和反序列化
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public abstract class BeanUtils {
@SuppressWarnings("unchecked")
public static <T> T cloneTo(T src) throws RuntimeException {
ByteArrayOutputStream memoryBuffer = new ByteArrayOutputStream();
ObjectOutputStream out = null;
ObjectInputStream in = null;
T dist = null;
try {
out = new ObjectOutputStream(memoryBuffer);
out.writeObject(src);
out.flush();
in = new ObjectInputStream(new ByteArrayInputStream(memoryBuffer.toByteArray()));
dist = (T) in.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (out != null)
try {
out.close();
out = null;
} catch (IOException e) {
throw new RuntimeException(e);
}
if (in != null)
try {
in.close();
in = null;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
return dist;
}
}
// 只要要克隆的對象以及對象所包含的引用類型的成員對象所在的類實現了java.io.Serializable 接口即可做到完美克隆。