<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                Java 是非常典型的面向對象語言,曾經有一段時間,程序員整天把面向對象、設計模式掛在嘴邊。雖然如今大家對這方面已經不再那么狂熱,但是不可否認,掌握面向對象設計原則和技巧,是保證高質量代碼的基礎之一。 面向對象提供的基本機制,對于提高開發、溝通等各方面效率至關重要。考察面向對象也是面試中的常見一環,下面我來聊聊**面向對象設計基礎**。 今天我要問你的問題是,談談接口和抽象類有什么區別? ## 典型回答 接口和抽象類是 Java 面向對象設計的兩個基礎機制。 接口是對行為的抽象,它是抽象方法的集合,利用接口可以達到 API 定義和實現分離的目的。接口,不能實例化;不能包含任何非常量成員,任何 field 都是隱含著 public static final 的意義;同時,沒有非靜態方法實現,也就是說要么是抽象方法,要么是靜態方法。Java 標準類庫中,定義了非常多的接口,比如 java.util.List。 抽象類是不能實例化的類,用 abstract 關鍵字修飾 class,其目的主要是代碼重用。除了不能實例化,形式上和一般的 Java 類并沒有太大區別,可以有一個或者多個抽象方法,也可以沒有抽象方法。抽象類大多用于抽取相關 Java 類的共用方法實現或者是共同成員變量,然后通過繼承的方式達到代碼復用的目的。Java 標準庫中,比如 collection 框架,很多通用部分就被抽取成為抽象類,例如 java.util.AbstractList。 Java 類實現 interface 使用 implements 關鍵詞,繼承 abstract class 則是使用 extends 關鍵詞,我們可以參考 Java 標準庫中的 ArrayList。 ~~~ public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { //... } ~~~ ## 考點分析 這是個非常高頻的 Java 面向對象基礎問題,看起來非常簡單的問題,如果面試官稍微深入一些,你會發現很多有意思的地方,可以從不同角度全面地考察你對基本機制的理解和掌握。比如: * 對于 Java 的基本元素的語法是否理解準確。能否定義出語法基本正確的接口、抽象類或者相關繼承實現,涉及重載(Overload)、重寫(Override)更是有各種不同的題目。 * 在軟件設計開發中妥善地使用接口和抽象類。你至少知道典型應用場景,掌握基礎類庫重要接口的使用;掌握設計方法,能夠在 review 代碼的時候看出明顯的不利于未來維護的設計。 * 掌握 Java 語言特性演進。現在非常多的框架已經是基于 Java 8,并逐漸支持更新版本,掌握相關語法,理解設計目的是很有必要的。 ## 知識擴展 我會從接口、抽象類的一些實踐,以及語言變化方面去闡述一些擴展知識點。 Java 相比于其他面向對象語言,如 C++,設計上有一些基本區別,比如**Java 不支持多繼承**。這種限制,在規范了代碼實現的同時,也產生了一些局限性,影響著程序設計結構。Java 類可以實現多個接口,因為接口是抽象方法的集合,所以這是聲明性的,但不能通過擴展多個抽象類來重用邏輯。 在一些情況下存在特定場景,需要抽象出與具體實現、實例化無關的通用邏輯,或者純調用關系的邏輯,但是使用傳統的抽象類會陷入到單繼承的窘境。以往常見的做法是,實現由靜態方法組成的工具類(Utils),比如 java.util.Collections。 設想,為接口添加任何抽象方法,相應的所有實現了這個接口的類,也必須實現新增方法,否則會出現編譯錯誤。對于抽象類,如果我們添加非抽象方法,其子類只會享受到能力擴展,而不用擔心編譯出問題。 接口的職責也不僅僅限于抽象方法的集合,其實有各種不同的實踐。有一類沒有任何方法的接口,通常叫作 Marker Interface,顧名思義,它的目的就是為了聲明某些東西,比如我們熟知的 Cloneable、Serializable 等。這種用法,也存在于業界其他的 Java 產品代碼中。 從表面看,這似乎和 Annotation 異曲同工,也確實如此,它的好處是簡單直接。對于 Annotation,因為可以指定參數和值,在表達能力上要更強大一些,所以更多人選擇使用 Annotation。 Java 8 增加了函數式編程的支持,所以又增加了一類定義,即所謂 functional interface,簡單說就是只有一個抽象方法的接口,通常建議使用 @FunctionalInterface Annotation 來標記。Lambda 表達式本身可以看作是一類 functional interface,某種程度上這和面向對象可以算是兩碼事。我們熟知的 Runnable、Callable 之類,都是 functional interface,這里不再多介紹了,有興趣你可以參考:[https://www.oreilly.com/learning/java-8-functional-interfaces](https://www.oreilly.com/learning/java-8-functional-interfaces)。 還有一點可能讓人感到意外,嚴格說,**Java 8 以后,接口也是可以有方法實現的!** 從 Java 8 開始,interface 增加了對 default method 的支持。Java 9 以后,甚至可以定義 private default method。Default method 提供了一種二進制兼容的擴展已有接口的辦法。比如,我們熟知的 java.util.Collection,它是 collection 體系的 root interface,在 Java 8 中添加了一系列 default method,主要是增加 Lambda、Stream 相關的功能。我在專欄前面提到的類似 Collections 之類的工具類,很多方法都適合作為 default method 實現在基礎接口里面。 你可以參考下面代碼片段: ~~~ public interface Collection<E> extends Iterable<E> { /** * Returns a sequential Stream with this collection as its source * ... **/ default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } } ~~~ **面向對象設計** 談到面向對象,很多人就會想起設計模式,那些是非常經典的問題和設計方法的總結。我今天來夯實一下基礎,先來聊聊面向對象設計的基本方面。 我們一定要清楚面向對象的基本要素:封裝、繼承、多態。 **封裝**的目的是隱藏事務內部的實現細節,以便提高安全性和簡化編程。封裝提供了合理的邊界,避免外部調用者接觸到內部的細節。我們在日常開發中,因為無意間暴露了細節導致的難纏 bug 太多了,比如在多線程環境暴露內部狀態,導致的并發修改問題。從另外一個角度看,封裝這種隱藏,也提供了簡化的界面,避免太多無意義的細節浪費調用者的精力。 **繼承**是代碼復用的基礎機制,類似于我們對于馬、白馬、黑馬的歸納總結。但要注意,繼承可以看作是非常緊耦合的一種關系,父類代碼修改,子類行為也會變動。在實踐中,過度濫用繼承,可能會起到反效果。 **多態**,你可能立即會想到重寫(override)和重載(overload)、向上轉型。簡單說,重寫是父子類中相同名字和參數的方法,不同的實現;重載則是相同名字的方法,但是不同的參數,本質上這些方法簽名是不一樣的,為了更好說明,請參考下面的樣例代碼: ~~~ public int doSomething() { return 0; } // 輸入參數不同,意味著方法簽名不同,重載的體現 public int doSomething(List<String> strs) { return 0; } // return 類型不一樣,編譯不能通過 public short doSomething() { return 0; } ~~~ 這里你可以思考一個小問題,方法名稱和參數一致,但是返回值不同,這種情況在 Java 代碼中算是有效的重載嗎? 答案是不是的,編譯都會出錯的。 進行面向對象編程,掌握基本的設計原則是必須的,我今天介紹最通用的部分,也就是所謂的 S.O.L.I.D 原則。 * 單一職責(Single Responsibility),類或者對象最好是只有單一職責,在程序設計中如果發現某個類承擔著多種義務,可以考慮進行拆分。 * 開關原則(Open-Close, Open for extension, close for modification),設計要對擴展開放,對修改關閉。換句話說,程序設計應保證平滑的擴展性,盡量避免因為新增同類功能而修改已有實現,這樣可以少產出些回歸(regression)問題。 * 里氏替換(Liskov Substitution),這是面向對象的基本要素之一,進行繼承關系抽象時,凡是可以用父類或者基類的地方,都可以用子類替換。 * 接口分離(Interface Segregation),我們在進行類和接口設計時,如果在一個接口里定義了太多方法,其子類很可能面臨兩難,就是只有部分方法對它是有意義的,這就破壞了程序的內聚性。 對于這種情況,可以通過拆分成功能單一的多個接口,將行為進行解耦。在未來維護中,如果某個接口設計有變,不會對使用其他接口的子類構成影響。 * 依賴反轉(Dependency Inversion),實體應該依賴于抽象而不是實現。也就是說高層次模塊,不應該依賴于低層次模塊,而是應該基于抽象。實踐這一原則是保證產品代碼之間適當耦合度的法寶。 **OOP 原則實踐中的取舍** 值得注意的是,現代語言的發展,很多時候并不是完全遵守前面的原則的,比如,Java 10 中引入了本地方法類型推斷和 var 類型。按照,里氏替換原則,我們通常這樣定義變量: ~~~ List<String> list = new ArrayList<>(); ~~~ 如果使用 var 類型,可以簡化為 ~~~ var list = new ArrayList<String>(); ~~~ 但是,list 實際會被推斷為“ArrayList ” ~~~ ArrayList<String> list = new ArrayList<String>(); ~~~ 理論上,這種語法上的便利,其實是增強了程序對實現的依賴,但是微小的類型泄漏卻帶來了書寫的便利和代碼可讀性的提高,所以,實踐中我們還是要按照得失利弊進行選擇,而不是一味得遵循原則。 **OOP 原則在面試題目中的分析** 我在以往面試中發現,即使是有多年編程經驗的工程師,也還沒有真正掌握面向對象設計的基本的原則,如開關原則(Open-Close)。看看下面這段代碼,改編自朋友圈盛傳的某偉大公司產品代碼,你覺得可以利用面向對象設計原則如何改進? ~~~ public class VIPCenter { void serviceVIP(T extend User user>) { if (user instanceof SlumDogVIP) { // 窮 X VIP,活動搶的那種 // do somthing } else if(user instanceof RealVIP) { // do somthing } // ... } ~~~ 這段代碼的一個問題是,業務邏輯集中在一起,當出現新的用戶類型時,比如,大數據發現了我們是肥羊,需要去收獲一下, 這就需要直接去修改服務方法代碼實現,這可能會意外影響不相關的某個用戶類型邏輯。 利用開關原則,我們可以嘗試改造為下面的代碼: ~~~ public class VIPCenter { private Map<User.TYPE, ServiceProvider> providers; void serviceVIP(T extend User user) { providers.get(user.getType()).service(user); } } interface ServiceProvider{ void service(T extend User user) ; } class SlumDogVIPServiceProvider implements ServiceProvider{ void service(T extend User user){ // do somthing } } class RealVIPServiceProvider implements ServiceProvider{ void service(T extend User user) { // do something } } ~~~ 上面的示例,將不同對象分類的服務方法進行抽象,把業務邏輯的緊耦合關系拆開,實現代碼的隔離保證了方便的擴展。 今天我對 Java 面向對象技術進行了梳理,對比了抽象類和接口,分析了 Java 語言在接口層面的演進和相應程序設計實現,最后回顧并實踐了面向對象設計的基本原則,希望對你有所幫助。 ## 一課一練 關于接口和抽象類的區別,你做到心中有數了嗎?給你布置一個思考題,思考一下自己的產品代碼,有沒有什么地方違反了基本設計原則?那些一改就崩的代碼,是否遵循了開關原則?
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看