Java面試題沖刺第五天--基礎(chǔ)篇2
面試題1:說(shuō)一下抽象類(lèi)和接口有哪些區(qū)別?
正經(jīng)回答:
抽象類(lèi)和接口的主要區(qū)別:
從設(shè)計(jì)層面來(lái)說(shuō),抽象類(lèi)是對(duì)類(lèi)的抽象,是一種模板設(shè)計(jì);接口是行為的抽象,是一種行為的規(guī)范。
- 一個(gè)類(lèi)可以有多個(gè)接口 只能有繼承一個(gè)父類(lèi)
- 抽象類(lèi)可以有構(gòu)造方法,接口中不能有構(gòu)造方法。
- 抽象類(lèi)中可以有普通成員變量,接口中沒(méi)有普通成員
- 變量接口里邊全部方法都必須是abstract的;抽象類(lèi)的可以有實(shí)現(xiàn)了的方法
- 抽象類(lèi)中的抽象方法的訪問(wèn)類(lèi)型可以是public,protected;但接口中的抽象方法只能是public類(lèi)型的,并且默認(rèn)即為public abstract類(lèi)型
- 抽象類(lèi)中可以包含靜態(tài)方法,接口中不能包含靜態(tài)方法
- 抽象類(lèi)和接口中都可以包含靜態(tài)成員變量,抽象類(lèi)中的靜態(tài)成員變量的訪問(wèn)類(lèi)型可以任意;但接口中定義的變量只能是public static final類(lèi)型,并且默認(rèn)即為public static final類(lèi)型。
Java8中接口中引入默認(rèn)方法和靜態(tài)方法,以此來(lái)減少抽象類(lèi)和接口之間的差異
。
接口和抽象類(lèi)各有優(yōu)缺點(diǎn),在接口和抽象類(lèi)的選擇上,必須遵守這樣一個(gè)原則:
行為模型應(yīng)該總是通過(guò)接口而不是抽象類(lèi)定義,所以通常是優(yōu)先選用接口,盡量少用抽象類(lèi)。
選擇抽象類(lèi)的時(shí)候通常是如下情況:需要定義子類(lèi)的行為,又要為子類(lèi)提供通用的功能。
深入追問(wèn):
追問(wèn)1:說(shuō)一說(shuō)你對(duì)抽象類(lèi)的理解吧,他到底是干啥用的
我們常說(shuō)面向?qū)ο蟮暮诵乃枷胧牵合瘸橄?,后具體。
抽象類(lèi)是含有抽象方法的類(lèi),不能被實(shí)例化,抽象類(lèi)常用作當(dāng)做模板類(lèi)使用。
接口更多的是在系統(tǒng)架構(gòu)設(shè)計(jì)方法發(fā)揮作用,主要用于定義模塊之間的通信契約。
而抽象類(lèi)在代碼實(shí)現(xiàn)方面發(fā)揮作用,可以實(shí)現(xiàn)代碼的重用,例如,模板方法設(shè)計(jì)模式是抽象類(lèi)的一個(gè)典型應(yīng)用,假設(shè)某個(gè)項(xiàng)目的所有Servlet類(lèi)都要用相同的方式進(jìn)行權(quán)限判斷、記錄訪問(wèn)日志和處理異常,那么就可以定義一個(gè)抽象的基類(lèi),讓所有的Servlet都繼承這個(gè)抽象基類(lèi),在抽象基類(lèi)的service方法中完成權(quán)限判斷、記錄訪問(wèn)日志和處理異常的代碼,在各個(gè)子類(lèi)中只是完成各自的業(yè)務(wù)邏輯代碼。父類(lèi)方法中間的某段代碼不確定,再留給子類(lèi)干,就用模板方法設(shè)計(jì)模式。
追問(wèn)2:用抽象類(lèi)實(shí)現(xiàn)一個(gè)接口,和普通類(lèi)實(shí)現(xiàn)接口會(huì)有什么不同么?
一般來(lái)說(shuō)我們使用普通類(lèi)來(lái)實(shí)現(xiàn)接口,這個(gè)普通類(lèi)就必須實(shí)現(xiàn)接口中所有的方法,這樣的結(jié)果就是普通類(lèi)中就需要實(shí)現(xiàn)多余的方法,造成代碼冗余。但是如果我們使用的是抽象類(lèi)來(lái)實(shí)現(xiàn)接口,那么就可以只實(shí)現(xiàn)接口中的部分方法,并且當(dāng)其他類(lèi)繼承這個(gè)抽象類(lèi)時(shí),仍然可以實(shí)現(xiàn)接口中有但抽象類(lèi)并未實(shí)現(xiàn)的方法。
如以下代碼,抽象類(lèi)只是實(shí)現(xiàn)了接口A中的方法a,方法b,但是當(dāng)類(lèi)C繼承抽象類(lèi)B時(shí),可以直接實(shí)現(xiàn)接口A中的c方法,有一點(diǎn)需要注意的是,類(lèi)C中的方法a,方法b都是調(diào)用的父類(lèi)B的方法a,方法b,不是直接實(shí)現(xiàn)接口的方法a和b。
/** *接口 */ interface A{ public void aaa(); public void bbb(); public void ccc(); } /** *抽象類(lèi) */ abstract class B implements A{ public void aaa(){} public void bbb(){} } /** * 實(shí)現(xiàn)類(lèi) */ public class C extends B{ public void aaa(){} public void bbb(){} public void ccc(){} }
追問(wèn)3:抽象類(lèi)能使用 final 修飾嗎?
不能,定義抽象類(lèi)就是讓其他類(lèi)繼承的,如果定義為 final 該類(lèi)就不能被繼承,這樣彼此就會(huì)產(chǎn)生矛盾,所以 final 不能修飾抽象類(lèi)。
面試題2:final 在 Java 中有什么作用?
正經(jīng)回答:
用于修飾類(lèi)、方法和屬性;
1、修飾類(lèi)
當(dāng)用final修飾類(lèi)的時(shí),表明該類(lèi)不能被其他類(lèi)所繼承。需要注意的是:final類(lèi)中所有的成員方法都會(huì)隱式的定義為final方法。
2、修飾方法
使用final方法的原因主要是把方法鎖定,以防止繼承類(lèi)對(duì)其進(jìn)行更改或重寫(xiě)。
若父類(lèi)中final方法的訪問(wèn)權(quán)限為private,將導(dǎo)致子類(lèi)中不能直接繼承該方法,因此,此時(shí)可以在子類(lèi)中定義相同方法名的函數(shù),此時(shí)不會(huì)與重寫(xiě)final的矛盾,而是在子類(lèi)中重新地定義了新方法。
class A{ private final void getName(){ System.out.println("getName - A"); } } public class B extends A{ public void getName(){ System.out.println("getName - B"); } public void main(String[]args){ this.getName(); // 日志輸出:getName - B } }
3、修飾變量
當(dāng)final修飾一個(gè)基本數(shù)據(jù)類(lèi)型時(shí),表示該基本數(shù)據(jù)類(lèi)型的值一旦在初始化后便不能發(fā)生變化;如果final修飾一個(gè)引用類(lèi)型時(shí),則在對(duì)其初始化之后便不能再讓其指向其他對(duì)象了,但該引用所指向的對(duì)象的內(nèi)容是可以發(fā)生變化的。本質(zhì)上是一回事,因?yàn)橐玫闹凳且粋€(gè)地址,final要求值,即地址的值不發(fā)生變化。
final修飾一個(gè)成員變量(屬性),必須要顯示初始化。這里有兩種初始化方式,一種是在變量聲明的時(shí)候初始化;第二種方法是在聲明變量的時(shí)候不賦初值,但是要在這個(gè)變量所在的類(lèi)的所有的構(gòu)造函數(shù)中對(duì)這個(gè)變量賦初值。
當(dāng)函數(shù)的參數(shù)類(lèi)型聲明為final時(shí),說(shuō)明該參數(shù)是只讀型的。即你可以讀取使用該參數(shù),但是無(wú)法改變?cè)搮?shù)的值。
深入追問(wèn):
追問(wèn)1:能分別說(shuō)一下final、finally、finalize的區(qū)別么?
- final可以修飾類(lèi)、變量、方法,修飾類(lèi)表示該類(lèi)不能被繼承、修飾方法表示該方法不能被重寫(xiě)、修飾變量表示該變量是一個(gè)常量不能被重新賦值。
- finally一般作用在try-catch代碼塊中,在處理異常的時(shí)候,通常我們將一定要執(zhí)行的代碼方法finally代碼塊中,表示不管是否出現(xiàn)異常,該代碼塊都會(huì)執(zhí)行,一般用來(lái)存放一些關(guān)閉資源的代碼。當(dāng)然,
還有多種情況走不了finally~
- finalize是一個(gè)方法,屬于Object類(lèi)的一個(gè)方法,而Object類(lèi)是所有類(lèi)的父類(lèi),該方法一般由垃圾回收器來(lái)調(diào)用,當(dāng)我們調(diào)用System.gc() 方法的時(shí)候,由垃圾回收器調(diào)用finalize(),回收垃圾,一個(gè)對(duì)象是否可回收的最后判斷。
面試題3:你對(duì)Java序列化了解么?
正經(jīng)回答:
序列化過(guò)程:
是指把一個(gè)Java對(duì)象變成二進(jìn)制內(nèi)容,實(shí)質(zhì)上就是一個(gè)byte[]數(shù)組。
因?yàn)樾蛄谢罂梢园裝yte[]保存到文件中,或者把byte[]通過(guò)網(wǎng)絡(luò)傳輸?shù)竭h(yuǎn)程(IO),這樣,就相當(dāng)于把Java對(duì)象存儲(chǔ)到文件或者通過(guò)網(wǎng)絡(luò)傳輸出去了。
反序列化過(guò)程:
把一個(gè)二進(jìn)制內(nèi)容(也就是byte[]數(shù)組)變回Java對(duì)象。有了反序列化,保存到文件中的byte[]數(shù)組又可以“變回”Java對(duì)象,或者從網(wǎng)絡(luò)上讀取byte[]并把它“變回”Java對(duì)象。
以下是一些使用序列化的示例:
以面向?qū)ο蟮姆绞綄?shù)據(jù)存儲(chǔ)到磁盤(pán)上的文件,例如,Redis存儲(chǔ)Student對(duì)象的列表。
將程序的狀態(tài)保存在磁盤(pán)上,例如,保存游戲狀態(tài)。
通過(guò)網(wǎng)絡(luò)以表單對(duì)象形式發(fā)送數(shù)據(jù),例如,在聊天應(yīng)用程序中以對(duì)象形式發(fā)送消息。
一個(gè)Java對(duì)象要能序列化,必須實(shí)現(xiàn)一個(gè)特殊的java.io.Serializable接口
,它的定義如下:
public interface Serializable { }
Serializable接口沒(méi)有定義任何方法,它是一個(gè)空接口。我們把這樣的空接口稱(chēng)為“標(biāo)記接口”(Marker Interface),實(shí)現(xiàn)了標(biāo)記接口的類(lèi)僅僅是給自身貼了個(gè)“標(biāo)記”,并沒(méi)有增加任何方法。
深入追問(wèn):
追問(wèn)1:Java序列化是如何工作的?
當(dāng)且僅當(dāng)對(duì)象的類(lèi)實(shí)現(xiàn)java.io.Serializable
接口時(shí),該對(duì)象才有資格進(jìn)行序列化??尚蛄谢?是一個(gè)標(biāo)記接口(不包含任何方法),該接口告訴Java虛擬機(jī)(JVM)該類(lèi)的對(duì)象已準(zhǔn)備好寫(xiě)入持久性存儲(chǔ)或通過(guò)網(wǎng)絡(luò)進(jìn)行讀取。
默認(rèn)情況下,JVM負(fù)責(zé)編寫(xiě)和讀取可序列化對(duì)象的過(guò)程。序列化/反序列化功能通過(guò)對(duì)象流類(lèi)的以下兩種方法公開(kāi):
ObjectOutputStream。writeObject(Object):
將可序列化的對(duì)象寫(xiě)入輸出流。如果要序列化的某些對(duì)象未實(shí)現(xiàn)Serializable接口,則此方法將引發(fā)NotSerializableException
。
ObjectInputStream。readObject():
從輸入流讀取,構(gòu)造并返回一個(gè)對(duì)象。如果找不到序列化對(duì)象的類(lèi),則此方法將引發(fā)ClassNotFoundException。
如果序列化使用的類(lèi)有問(wèn)題,則這兩種方法都將引發(fā)InvalidClassException
,如果發(fā)生I / O錯(cuò)誤,則將引發(fā)IOException
。無(wú)論NotSerializableException
和InvalidClassException
是子類(lèi)IOException異常。
讓我們來(lái)看一個(gè)簡(jiǎn)單的例子。以下代碼將String對(duì)象序列化為名為“ data.ser”的文件。字符串對(duì)象是可序列化的,因?yàn)镾tring類(lèi)實(shí)現(xiàn)了Serializable 接口:
String filePath = "data.ser"; String message = "Java Serialization is Cool"; try ( FileOutputStream fos = new FileOutputStream(filePath); ObjectOutputStream outputStream = new ObjectOutputStream(fos); ) { outputStream.writeObject(message); } catch (IOException ex) { System.err.println(ex); }
以下代碼反序列化文件“ data.ser”中的String對(duì)象:
String filePath = "data.ser"; try ( FileInputStream fis = new FileInputStream(filePath); ObjectInputStream inputStream = new ObjectInputStream(fis); ) { String message = (String) inputStream.readObject(); System.out.println("Message: " + message); } catch (ClassNotFoundException ex) { System.err.println("Class not found: " + ex); } catch (IOException ex) { System.err.println("IO error: " + ex); }
請(qǐng)注意,readObject()返回一個(gè)Object類(lèi)型的對(duì)象,因此您需要將其強(qiáng)制轉(zhuǎn)換為可序列化的類(lèi),在這種情況下為String類(lèi)。
讓我們看一個(gè)涉及使用自定義類(lèi)的更復(fù)雜的示例。
給定以下學(xué)生班:
import java.io.*; import java.util.*; /** * Student.java * @author chenhh */ public class Student extends Person implements Serializable { public static final long serialVersionUID = 1234L; private long studentId; private String name; private transient int age; public Student(long studentId, String name, int age) { super(); this.studentId = studentId; this.name = name; this.age = age; System.out.println("Constructor"); } public String toString() { return String.format("%d - %s - %d", studentId, name, age); } }
如上面代碼,你會(huì)發(fā)現(xiàn)兩點(diǎn):
long serialVersionUID類(lèi)型的常量。
成員變量age被標(biāo)記為transient。 下面兩個(gè)問(wèn)題讓我們搞明白它們。
追問(wèn)2:什么是serialVersionUID常數(shù)
serialVersionUID
是一個(gè)常數(shù),用于唯一標(biāo)識(shí)可序列化類(lèi)的版本。從輸入流構(gòu)造對(duì)象時(shí),JVM在反序列化過(guò)程中檢查此常數(shù)。如果正在讀取的對(duì)象的serialVersionUID
與類(lèi)中指定的序列號(hào)不同,則JVM拋出InvalidClassException。這是為了確保正在構(gòu)造的對(duì)象與具有相同serialVersionUID
的類(lèi)兼容。
請(qǐng)注意,serialVersionUID
是可選的。這意味著如果您不顯式聲明Java編譯器,它將生成一個(gè)。
那么,為什么要顯式聲明serialVersionUID
呢?
原因是:自動(dòng)生成的serialVersionUID
是基于類(lèi)的元素(成員變量,方法,構(gòu)造函數(shù)等)計(jì)算的。如果這些元素之一發(fā)生更改,serialVersionUID也將更改。想象一下這種情況:
- 您編寫(xiě)了一個(gè)程序,將Student類(lèi)的某些對(duì)象存儲(chǔ)到文件中。Student類(lèi)沒(méi)有顯式聲明的serialVersionUID。
- 有時(shí),您更新了Student類(lèi)(例如,添加了一個(gè)新的私有方法),現(xiàn)在自動(dòng)生成的serialVersionUID也被更改了。
- 您的程序無(wú)法反序列化先前編寫(xiě)的Student對(duì)象,因?yàn)槟抢锏膕erialVersionUID不同。JVM拋出InvalidClassException。
這就是為什么建議為可序列化類(lèi)顯式添加serialVersionUID的原因。
追問(wèn)3、那你知道什么是瞬時(shí)變量么?
在上面的Student類(lèi)中,您看到成員變量age被標(biāo)記為transient,對(duì)嗎?JVM 在序列化過(guò)程中跳過(guò)瞬態(tài)變量。這意味著在序列化對(duì)象時(shí)不會(huì)存儲(chǔ)age變量的值。
因此,如果成員變量不需要序列化,則可以將其標(biāo)記為瞬態(tài)。
以下代碼將Student對(duì)象序列化為名為“ students.ser”的文件:
String filePath = "students.ser"; Student student = new Student(123, "John", 22); try ( FileOutputStream fos = new FileOutputStream(filePath); ObjectOutputStream outputStream = new ObjectOutputStream(fos); ) { outputStream.writeObject(student); } catch (IOException ex) { System.err.println(ex); }
請(qǐng)注意,在序列化對(duì)象之前,變量age的值為22。
下面的代碼從文件中反序列化Student對(duì)象:
String filePath = "students.ser"; try ( FileInputStream fis = new FileInputStream(filePath); ObjectInputStream inputStream = new ObjectInputStream(fis); ) { Student student = (Student) inputStream.readObject(); System.out.println(student); } catch (ClassNotFoundException ex) { System.err.println("Class not found: " + ex); } catch (IOException ex) { System.err.println("IO error: " + ex); }
此代碼將輸出以下輸出:
1個(gè) 123 - John - 0
總結(jié)
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
如何在Java中使用支付寶SDK來(lái)獲取用戶的OpenID
在支付寶開(kāi)放平臺(tái)中,獲取用戶的OpenID通常是在用戶授權(quán)后通過(guò)支付寶的OAuth 2.0授權(quán)流程實(shí)現(xiàn)的,以下是一個(gè)基本的步驟說(shuō)明,以及如何在Java中使用支付寶SDK來(lái)獲取用戶的OpenID2024-08-08Java 17 更新后的 strictfp 關(guān)鍵字
strictfp 可能是最沒(méi)有存在感的關(guān)鍵字了,很多人寫(xiě)了多年 Java 甚至都不知道它的存在,strictfp,字面意思就是嚴(yán)格的浮點(diǎn)型。這玩意兒居然還有個(gè)關(guān)鍵字,可見(jiàn)其地位還是很高的。下面文章小編就帶大家詳細(xì)介紹其關(guān)鍵字,需要的朋友可以參考一下2021-09-09Spring動(dòng)態(tài)配置計(jì)時(shí)器觸發(fā)時(shí)間的實(shí)例代碼
這篇文章主要介紹了Spring動(dòng)態(tài)配置計(jì)時(shí)器觸發(fā)時(shí)間的實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-06-06Eclipse手動(dòng)導(dǎo)入DTD文件實(shí)現(xiàn)方法解析
這篇文章主要介紹了Eclipse手動(dòng)導(dǎo)入DTD文件實(shí)現(xiàn)方法解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-10-10淺談Java之Map 按值排序 (Map sort by value)
下面小編就為大家?guī)?lái)一篇淺談Java之Map 按值排序 (Map sort by value)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08