Java面試參考指南

Java面向對象相關概念

Java是一種基於面向對象概念的編程語言,使用高度抽象化來解決現實世界的問題。    面向對象的方法將現實世界中的對象進行概念化,以便於在應用之間進行重用。例如:椅子、風扇、狗和電腦等。html

Java裏的類(Class)是一個藍圖、模板,或者稱之爲原型,它定義了同一類事物的相同屬性和行爲。實例(Instance)是某個類的一個具體實現,同一個類全部的實例擁有相同的屬性。舉例來講,你能夠定義一個類叫作「房子(House)」,這個類擁有一個屬性叫作「房間數(number of room)」,這樣你就能夠建立一個「房間數」爲2的「房子」實例,你還能夠建立一個「房間數」爲3的「房子」實例,等等等等。前端

優勢:java

面向對象軟件開發的若干優勢在於:面試

  • 模塊化,維護成本低;
  • 更好的代碼重用,具有繼承性,開發更爲敏捷;
  • 更好的代碼可靠性和靈活性;
  • 對現實世界進行建模,易於理解;
  • 對象水平的抽象;
  • 從一個開發階段向另外一個開發階段過渡更爲簡便。

面向對象軟件系統(OOPS)的四大主要特徵爲:編程

  • 封裝(Encapsulation)
  • 繼承(Inheritance)
  • 多態(Polymorphism)
  • 抽象(Abstraction)

 封裝(Encapsulation)

封裝機制在對象之間提供了一種隱藏域可見性的協議。Java中使用可見性修飾符private將方法和變量限制在類內部。Java提供的可見性修飾符包括public/ default/ protected/ private,用來在不一樣層面上隱藏變量、方法和類,但最終目的在於封裝那些不須要進行修改的東西。實踐代表,每一個類應該只存在一種被修改的緣由,而封裝(Encapsulate)讓這種「惟一緣由」原則成爲現實。後端

同時,最佳實踐代表,封裝意味着將會常常改變的東西隱藏起來,以防止對其餘類形成破壞。安全

封裝的優勢

  • 咱們能夠經過隱藏屬性來保護對象的內部狀態;
  • 可以防止對象之間不恰當的相互做用,進而促進代碼模塊化;
  • 加強可用性;
  • 在特定對象之間維護互訪協議;
  • 封裝以促進代碼維護;
  • 能夠獨立地進行代碼修改

多態(Polymorphism)

多態是指使用相同的接口來應對不一樣的底層形式(例如數據類型)的能力。這就意味着同一個類可使用一個共同的接口來實現多種不一樣的功能,並能經過傳遞特定的類引用來動態觸發這些功能。多線程

一個經典的多態的實例爲「形狀」類(Shape),以及全部繼承Shape的類,如方形(square)、圓形(circle)、多面體(dodecahedron)、不規則多邊形(irregular polygon)和長條(splat)等。在這個例子中,每一個類中都擁有一個本身的Draw()函數,客戶端程序代碼能夠簡簡單單地以下所示:併發

Shape shape=new Square ();

執行Shape.area() 能夠獲得任何形狀的正確面積。app

多態的美妙之處在於,不一樣類裏的代碼不須要知道本身所在的是哪一個類,它們的使用方式都是同樣的。

面向對象的編程語言在運行時所實現的多態過程叫作動態綁定。

注:多態是指根據調用函數的對象來選擇更具針對性的方法的特性。當沒有抽象類的時候就可使用多態。

多態的優勢

  • 可用於建立可重用代碼:一旦類被建立,實施和測試,就能夠直接進行使用而不考慮具體的代碼細節;
  • 提供更爲泛化和鬆耦合的代碼;
  • 編譯時間更短,開發更爲敏捷;
  • 動態綁定;
  • 可使用同一個接口的不一樣方法來實現不一樣的功能;
  • 可使用相同的方法簽名來代替徹底實施。

方法覆蓋實現多態:覆蓋涉及到兩個不一樣的方法,一個父類的方法,另外一個則是子類中的方法,兩個方法具備相同的函數名和方法簽名。

覆蓋能夠以不一樣的方式對不一樣的對象類型定義相同的操做,例如:

while(it.hasNext()) {
    Shape s = (Shape) it.next();
    totalArea += s.area(dim); //多態方法調用,將根據對象類型自動調用正確的方法
}

方法重載、Ad-hoc多態性和靜態多態

重載涉及的是同一個類內具備相同名稱,但方法簽名不一樣的多個方法。能夠用不一樣的方法爲不一樣的數據定義相同的操做。咱們常常所說的靜態多態實際上並非真正意義上的多態。

方法重載實際上就是指兩個方法使用相同的名稱,但參數不一樣。這與繼承和多態徹底沒有關係。重載方法不是覆蓋方法。[Head First Java深刻淺出]

Java中基於泛型的參數多態性

當進行類聲明時,一個屬性域名稱能夠與多種不一樣的數據類型相關聯,一個方法也能夠與不一樣的參數類型和返回類型相關聯,Java支持使用泛型的參數多態性。例如,一個list對象能夠經過泛型來接收它所包含的數據類型:

List<String> list = new ArrayList<String>();

爲何在Java裏咱們不能覆蓋靜態(static)方法?

覆蓋依賴於具體的類實例。多態的關鍵之處在於你能夠繼承一個類,而該類的子類所定義的實例對象又能夠對父類中定義的方法進行了覆蓋。而靜態方法是不依賴與具體實例的,所以覆蓋這一律念不適用於靜態方法。

在Java設計早起有兩點考慮直接致使了這一現象。第一是對性能方面的考慮:以前人們對Smalltalk語言(一種面向對象編程語言)運行太慢(垃圾回收和多態調用所致)的批評不絕於耳,Java的設計者決定迴避這一弊端。第二是考慮到Java的預期受衆主要是C++開發人員,而使靜態方法能直接被調用恰好能迎合C++編程人員的開發習慣,同時因爲不用上溯類層級結構來查找要調用的方法,而是直接調用指定類中的特定方法,這一設計使得代碼運行很是快速。

繼承(Inheritance)

繼承是指派生類中包含了基類中的全部的行爲(即方法)和狀態(即變量),並能經過該派生類進行訪問。繼承的關鍵好處在於它提供了代碼重用和避免重複的一遍機制。

繼承類經過重用父類的方法並添加一些新的功能來擴展應用程序的功能。這回致使緊耦合,若是你想對父類進行修改,你必須清楚其全部子類的具體細節以防止阻斷。

這是一種軟件複用性,新類(子類)繼承已有的父類,重用父類的特徵並能添加一些新的功能。

所以,舉例來講,若是你有一個Parent類和一個擴展(使用關鍵字extends)Parent類的Child類,那麼Child類繼承了Parent類全部特徵。

優勢

  • 促進重用性;
  • 創建邏輯「is a」關係,如:Dog is an animal.
  • 使代碼模塊化;
  • 避免衝突。

缺點

  • 緊耦合:子類的實現依賴於父類,致使緊耦合。

抽象(Abstraction)

抽象意味着只須要開發類的接口和功能聲明,而不須要實現具體的實施細節。抽象類呈現的是接口,而不須要具體實現,將對象的實施與行爲或實現分離開來,進而經過隱藏無關的細節來減小複雜度。

優勢

  • 經過使用抽象,咱們能夠將不一樣類別的東西分離開來;
  • 常常須要修改的屬性和方法能夠被分離出來造成一個單獨的類別,而那些主要留下的部分就不須要進行修改了,進而加強面向對象的分析與設計(OOAD)原則,即「代碼應該易於擴展,而不該該常常修改」;
  • 簡化領域模型的表徵。

抽象和封裝的區別:

封裝做爲一種策略,被用做廣義抽象的一部分。封裝是與對象狀態相關的——對象將本身的狀態封裝起來並對外界不可見,類外部的用戶只能經過該類的方法來與其進行交互,但不能直接改變其狀態。所以,類能夠將與狀態相關的實施細節經過抽象隔離開來。

抽象是一個更泛化的概念,能夠經過子類來實現具體的功能。例如:在Java標準庫中,List是「一串事物」的抽象,ArrayList和LinkedList是List的兩個具體的類型,做用於抽象List的代碼一樣抽象地不指明具體所使用的List類型。

若是沒有經過封裝隱藏底層狀態,也就不可能進行抽象處理。也就是說,若是一個類的內部狀態所有都是公開的,內部功能沒法被更改,該類也就沒法進行抽象。

什麼是抽象類和抽象方法?

在程序設計過程當中,你但願基類只是其派生類的一個接口,也就是說,你不但願任何人能實例化該基類。你只是想隱式(能夠實現多態性)地提出它,以即可以使用它的接口。那麼你可使用abstract關鍵字來定義一個抽象類。

爲該抽象類設定一些限制,全部使用該抽象類的子類都必須實現其中的抽象方法,並提供多態性。

抽象類中能夠既包括抽象方法和具體方法,若是一個方法是抽象方法,其所在的類必須被聲明成抽象類。反之否則,若是一個類是抽象類,其中不必定包括抽象方法。

若是一個方法只提供了方法簽名,但沒有被具體實現,則這個方法是一個抽象方法,該方法的具體實現是在擴展該抽象類的子類中進行的。

抽象方法不能被實例化,其餘類只能擴展它。

何時使用抽象類?

抽象類定義了一些默認的行爲,而將具體的功能留給子類來實現。例如:List是一個接口,而抽象類AbstractList提供了List的默認方法,這些默認方法能夠被子類ArrayList繼承或從新定義。

什麼是接口?

Interface關鍵字使得接口相比於抽象類更進了一步,接口中不能定義實現的方法。實現(使用關鍵字implements)接口的非抽象類必須實現該接口的全部方法。接口是面向對象(OO)中的一個很是有用和經常使用的概念,它將接口和具體實現分離開來,並保證數據安全性:

接口是抽象類的延伸,java了保證數據安全是不能多重繼承的,也就是說繼承只能存在一個父類,可是接口不一樣,一個類能夠同時實現多個接口,無論這些接口之間有沒有關係,因此接口彌補了抽象類不能多重繼承的缺陷,可是推薦繼承和接口共同使用,由於這樣既能夠保證數據安全性又能夠實現多重繼承。

使用接口的優勢

  1. 多重繼承;
  2. 能對操做進行鬆耦合的抽象,能夠分離實現任何功能, JDBC, JPA, JTA等等;
  3. 開發接口而不須要具體實現;
  4. 使用動態綁定的多態——揭示一個對象的編程接口,而無需展示其具體的實現細節;
  5. 抽象層:分離問題。

接口和抽象類之間的區別

  • 接口是一種協議,要實現接口的類須要根據接口中定義地來實現接口,它只是一個提供了方法聲明的空殼;
  • 抽象類定義了一些通用方法,其子類能夠定義新的具體或特殊的方法;
  • 抽象類中的方法和參數能夠被定義成任何可見性的,而接口中的全部方法必須由public可見性修飾符定義;
  • 繼承一個抽象類,子類須要實現其中的抽象方法,然而接口能夠擴展另外一個接口而無需實現其中的方法;
  • 子類只能繼承單個抽象類,而一個接口或類能夠實現多個接口;
  • 繼承抽象類的子類能夠以相同或更低的可見性實現父類中的抽象方法,而實現接口的類只能以與原抽象方法相同的可見性實現接口中的方法;
  • 接口沒有構造函數,抽象類有;
  • 接口中的變量都是final型的,而抽象類中能夠包含非final型變量;
    • 接口中的成員默認是public類型的,但抽象類中的成員的訪問類型能夠是public,protected和默認類型。

合成

代碼的重用性能夠經過集成和合成來實現,可是用合成實現代碼重用比繼承居右更好的封裝性,由於對後端代碼的修改無需任何對僅依賴於前端類的代碼的破壞。

合成石實現類之間「has-a」關係的設計技術,咱們可使用Java的繼承或對象合成來實現代碼重用。

合成表示的是對象之間的關係,以椅子chair爲例,一把椅子chair有一個座位seat,一個靠背back,幾條腿legs,詞組「has a」表示一把椅子所包含,或所使用的其餘實體,這種「has a」關係就是合成的基礎。

優勢:

  • 控制可見性
  • 運行時能夠更換實現方法
  • 鬆耦合,接口不依賴與具體實現。

合成與繼承之間的區別?

No. 合成Composition (has a) 繼承Inheritance (is a)
1 主張多態和代碼重用 主張多態和代碼重用
2 運行時已經得到對象 編譯時動態得到對象
3 運行時能夠替換實施 編譯時能夠更換實施
4 鬆耦合,子類的存在並不依賴與父類(特別是接口驅動的情形下) 緊耦合,子類嚴格依賴於父類
5 當House中有一個Bathroom時可使用,不能說House是一種Bathroom 繼承是單向的,例如:House是一個Building,但Building不是一個House

注:不要僅僅只爲了代碼重用而使用繼承,若是類之間不存在「is a」關係,建議採用合成來實現代碼重用。

對象關係中合成與聚合的區別:

聚合:聚合是一個類屬於一個集合的關係。描述的是一種「部分與總體」的關係,「總體」不存在的狀況下,「部分」是能夠存在的,這是一種弱關係類型,沒有循環依賴性。例如,訂單和產品的關係。

合成:合成也是一個類隸屬於一個集合的關係。描述的是一種只有「總體」存在,「部分」才能存在的「部分與總體」的關係。在這種關係下,若是「總體」被刪除,則「部分」也就不復存在,體現的是一種強關係類型。例如,多邊形和組成多邊形的點,訂單和訂單明細。

訪問修飾符

對於基本的OOPS(面向對象)概念,請看Java面試參考指南的第一部分。訪問修飾符規定了一個類如何訪問另外一個類及它的成員(包括方法和變量)。

Java中有下列訪問修飾符:

  • private:私有變量和方法(非外部類)只能被聲明它們的類的實例所使用。
  • default:類中的數據、方法和它自己可以被聲明爲默認default。類中全部default成員均可以被本包中的其它類所訪問。
  • protected:相比default有更高的訪問權限。只有成員變量和方法可以被聲明爲protected。父類的protected屬性能被它的全部子類所共享。即便子類和父類不在同一包中,這種訪問也是支持的。
  • public:公共的類、變量和方法均可以毫無限制的被其它Java程序所使用。

注意:

  • 對於非內部類就只存在public訪問修飾符,不存在被protected或private修飾的頂層類。
  • 一個特性至少擁有一個訪問修飾符。若是一個特性沒有訪問修飾符,那麼它的修飾符就是default模式。
  • 方法重載的隱私:重載後的方法修飾符不能低於被重載的方法,好比父類是protected,子類中重載方法不能置爲private。

下表描述了對各個成員修飾符所運行的訪問權限:

修飾符 被修飾者 描述
public 外部(Outer)類,接口,構造器,內部(Inner)類,方法和變量 可在包外部訪問
protected 構造器,內部類,方法和變量 包內訪問或包外的任何子類訪問
private 構造器,內部類,方法和變量 只可以在它們被聲明的內部訪問
無描述符(default) 外部類,內部類,接口,構造器,方法和變量 只可以在它們所在包中被訪問

除了上述基本的訪問符以外,下表描述了Java中的其它描述符。它們可以改變類和它的成員(如:方法、變量和內部類)的行爲。

 Java修飾符

修飾符 內部類/類 方法 變量
static 一個靜態內部類是另外一個普通類的成員,不是該類的對象成員。 一個靜態方法能夠直接被類訪問,而且被該類的對象所共享。 靜態變量是類級變量且在jvm中只可以出現一次。
abstract 抽象類不可以被實例化,它只能被其它類所繼承。 抽象方法只能被聲明在抽象類中,它的實現代碼只能被包含在子類中。 不適用
synchronized 不適用 它在類上加了一個監視器,使得訪問該方法只能經過得到該方法所屬對象的鎖來進行。  
transient     變量不可以被序列化。
final 類不能被繼承 方法不能被重載。 變量值變成常量。

final

final修飾符只可以做用在類、方法和變量上。它的做用是全部被final修飾的內容不能被改變。如:

  • final類不能被繼承。
  • final變量一旦被賦值就不能被改變。
  • final方法不能被重載。

注意:若是一個final變量是對象的引用,這意味着該引用的值是必定不會改變的,可是對象的成員值能夠改變。

static

static修飾符能夠被應用在變量、方法、代碼段和內部類中。若是須要Java實例變量被存儲在單獨的內存中,或須要變量對一個單獨類的全部對象所通用,那麼靜態修飾符須要被應用在該變量的聲明中。

  • 靜態方法:Java的靜態方法對類是通用的而且並不和Java實例相關聯。儘管靜態方法可以自由的訪問類的靜態數據和方法,可是它們並不能使用類的非靜態功能。靜態方法不可以被重載爲非靜態的。對於Java靜態方法不可以使用this關鍵字。
  • 靜態塊:靜態代碼塊在類加載時只執行一次。
  • 靜態變量:Java的實例變量使用單獨的空間進行存儲。若是須要變量對一個類的全部對象都是通用的,那麼這個變量就須要被聲明爲靜態的。類的全部實例對象均可以修改該類的靜態變量。此外,使用對象實例修改靜態成員並非惟一手段,也能夠直接使用java類來修改。靜態變量也能夠被Java對象的方法所訪問。當一個常量的值在編譯器就已經知曉了,它就須要使用static關鍵字來聲明爲final類型。
  • 靜態內部類:只有內部類可以使用static修飾符來聲明,在Java中它被稱爲靜態嵌套類。

外部類和內部類(或者嵌套類)

在Java中,並非全部的類都必須單獨定義。你能夠把一個類定義在另外一個類的內部中。這個在內部定義的類就叫作內部類。包圍類則被稱爲外部類。所以,當你定義了一個內部類,它和包圍類的其它變量、方法和構造器成員同樣成爲該類的成員。

當你訪問外部類的私有數據成員時,JDK會在外部類中建立包級訪問權限的(package-acess)成員函數以便內部類來訪問外部類的私有成員。這種結果會致使一個安全漏洞。一般狀況下,咱們應當儘可能避免使用內部類。

只有當內部類僅僅只和外部類的上下文相關或者內部類須要被設置爲私有的且只能被外部類訪問的這些狀況下,咱們才使用內部類。內部類主要被用來實現相似Iterators、Comparators這些輔助類。它們被使用在外部類的上下文中。

靜態嵌套類和非靜態嵌套類的區別

非靜態嵌套類(即內部類)對它嵌套的外部類的成員擁有所有的訪問權限。而靜態嵌套類並不包含對外部類的實例引用,這就使得靜態嵌套類不可以調用外部類實例的非靜態方法或者訪問外部類的非靜態字段。

在聲明成員字段和方法上,非靜態嵌套類不可以定義靜態字段和方法。可是,靜態內部類則能夠定義靜態字段和方法也能夠定義非靜態字段和方法。

非靜態內部類的實例是經過使用外部類的對象引用來建立的,也就在說在內部類中已經定義了外部類實例。可是靜態嵌套類實例的建立過程當中並不涉及到外部類的引用,這就是說它並不擁有包圍類的實例。

示例:

public class OuterClass {
 
    class InnerClass {
        // static int x; not allowed here
    }
 
    static class StaticInnerClass {
        static int x; // allowed here
    }
}
 
class Test {
    public static void main(String... str) {
        OuterClass oc = new OuterClass();
        OuterClass.InnerClass obj1 = oc.new InnerClass();// need of inclosing
                                                            // instance
        OuterClass.StaticInnerClass obj2 = new OuterClass.SIC();
        // no need of reference of object of outer class
    }
}

native

native修飾符只可以修飾方法。native修飾符意味着方法的實現體是在JVM以外。

注意:抽象方法的實現體是在子類中,而native方法的實現體則徹底不在Java的虛擬內存中,而是在其它程序庫中。

transient

transient修飾符只可以應用在變量中,transient變量不作爲它所屬對象的持久狀態的一部分進行存儲。它並不須要被序列化,主要用做安全鍵(security key)或者鏈接(connection)等。

synchronized

synchronized修飾符在多線程程序中對臨界區代碼的訪問進行控制。synchronized關鍵字是保持代碼線程安全的工具之一。

對於某一對象的一個同步方法進行交叉訪問是不可能的。當一個線程在執行某一對象的同步方法時,全部調用該同步方法的其它線程都將被阻塞直到第一個線程完成對該方法的調用。

其次,當一個同步方法執行結束時,對相同對象的後續同步方法調用,它會自動創建以前發生(happens-before)的順序關係。這保證了該對象的狀態更改對全部的線程均可見。

當你將一段代碼塊標記爲同步化時,你須要使用對象做爲該同步塊的參數。當一個執行線程到達該代碼塊時,它首先須要等待在該對象的同步塊上已經沒有其它的執行線程。然而,一個線程能夠進入不一樣對象鎖定的同步方法塊。

但對同一對象的非同步方法能夠直接方法而無需鎖檢測。

若是你同步化的是靜態方法,那麼你同步時獲取的是該方法類的鎖而不是實例的。這就意味着當你同步的是靜態方法時,整個類都將被阻塞。這樣其它的靜態同步方法也將被阻塞。

當一個線程進入同步化實例的方法時,其它線程則不可以再進入該實例的其它同步化方法。

當一個線程進入同步化的靜態方法時,其它線程則不可以再進入該類的其它同步化靜態方法。

注意:同步化的靜態方法和同步化的非靜態方法之間是沒有多少聯繫的。例如:若是靜態和非靜態同步方法可以併發的執行,就須要將你的非靜態方法顯式的聲明爲在它本身的類上進行同步(如:同步MyClass.class{…})。

volatile

只有變量可以被定義爲volatile。這些變量可能會被異步修改,所以編譯器須要對他們額外的關注。Volatile修飾符保證任何讀取該字段的線程都可以獲取它的最近修改值。

volatile使用:使用volatile的一種通用狀況是將它用做boolean變量並做爲判斷線程終止的標誌。

volatile和synchronize之間的區別:

所以,volatile關鍵字只用來在線程內存和主內存之間同步單個變量值,synchronized關鍵字用來同步線程內存和主內存之間的全部變量值以及如何鎖定和釋放一個監視器。清楚的是,synchronized比volatile有着更大的開銷。

volatile變量不容許出現和當前主存中的數據值不一樣的的本地副本。

更準確地說,被聲明爲volatile的變量必須保證它的數據值在全部線程的中是同步的。也就是當你在任一線程中訪問或者更新一個變量時,全部其它線程可以當即訪問到相同的值。

abstract

abstract修飾符適用於類和方法。抽象類不可以被實例化,它必須被繼承纔可以訪問。abstract不可以應用到類的成員變量。

示例:Animal類能夠有travel方法同時它的各子類(蛇,狗,鳥)也有它們本身的travel方法。所以就不可以在超類中提供travel方法,而是須要將travel方法在超類中聲明爲抽象的。

注意:

  • 若是類包含一個或多個抽象方法,編譯器將強制該類必須聲明爲抽象的。
  • abstract類和final類是相對的。final類不可以被繼承,而抽象類必須被繼承。

設計上,你但願基類對繼承類僅僅只做爲接口。這意味着你不但願任何人實例化該基類。你只想進行向上類型轉換(隱式轉換,它可以提供多態性),這樣就只能使用該基類的接口了。這是經過使用abstract關鍵字來修飾該類實現的。

提供限制來不實例化抽象類,這樣就須要任何使用該抽象類的人來實現它。並提供多態性。

何時使用抽象類?

抽象類讓你能夠定義一些默認行爲並促使子類提供任意特殊化行爲。

例如:Spring的依賴注入就使得代碼實現了集合框架中的接口原則和抽象實現。

相關文章
相關標籤/搜索