1. Java 基礎(chǔ)奪命連環(huán)34問

        共 22644字,需瀏覽 46分鐘

         ·

        2021-09-11 15:10

        從面試題作為切入點(diǎn)提升大家的 Java 內(nèi)功,所謂根基不牢,地動(dòng)山搖。只有扎實(shí)的基礎(chǔ),才是寫出寫好代碼。

        拒絕知識(shí)碎片化

        碼哥在 《Redis 系列》的開篇 Redis 為什么這么快中說過:學(xué)習(xí)一個(gè)技術(shù),通常只接觸了零散的技術(shù)點(diǎn),沒有在腦海里建立一個(gè)完整的知識(shí)框架和架構(gòu)體系,沒有系統(tǒng)觀。這樣會(huì)很吃力,而且會(huì)出現(xiàn)一看好像自己會(huì),過后就忘記,一臉懵逼。

        我們需要一個(gè)系統(tǒng)觀,清晰完整的去學(xué)習(xí)技術(shù),同時(shí)也不能埋頭苦干,過于死磕某個(gè)細(xì)節(jié)。

        跟著「碼哥」一起來提綱挈領(lǐng),梳理一個(gè)相對(duì)完整的 Java 開發(fā)技術(shù)能力圖譜,將基礎(chǔ)夯實(shí)。

        萬字總結(jié),建議收藏。面試不慌,加薪有望。


        • JVM、JRE、JDK 關(guān)系

        • Java 是解釋執(zhí)行么?

        • 采用字節(jié)碼的好處

        • JDK 1.8 之后有哪些新特性

        • 構(gòu)造器是否可以重寫

        • wait() 和 sleep 區(qū)別

        • &和&&的區(qū)別

        • Java 有哪些數(shù)據(jù)類型?

        • this 關(guān)鍵字的用法

        • super 關(guān)鍵字的用法

        • 成員變量與局部變量的區(qū)別有哪些

        • 動(dòng)態(tài)代理是基于什么原理

        • int 與 Integer 區(qū)別

        • 面向?qū)ο笏拇筇匦?/p>

        • 什么是多態(tài)機(jī)制?

        • Java 語言是如何實(shí)現(xiàn)多態(tài)的?

        • 重載與重寫

        • == 和 equals 的區(qū)別是什么

        • 為什么要有 hashcode

        • 面向?qū)ο蟮幕驹瓌t

        • Exception

        • Error

        • JVM 如何處理異常?

        • NoClassDefFoundError 和 ClassNotFoundException

        • Java 常見異常有哪些?

        • String

        • StringBuilder

        • StringBuffer

        • HashMap 使用 String 作為 key 有什么好處

        • 接口

        • 抽象類

        • 基本數(shù)據(jù)類型

        • 對(duì)象引用類型

        • 值傳遞和引用傳遞有什么區(qū)別?

        Java 平臺(tái)的理解

        ?

        碼老濕,你是怎么理解 Java 平臺(tái)呢?

        Java 是一種面向?qū)ο蟮恼Z言,有兩個(gè)明顯特性:

        • 跨平臺(tái)能力:一次編寫,到處運(yùn)行(Write once,run anywhere);
        • 垃圾收集:

        Java 通過字節(jié)碼和 Java 虛擬機(jī)(JVM)這種跨平臺(tái)的抽象,屏蔽了操作系統(tǒng)和硬件的細(xì)節(jié),這也是實(shí)現(xiàn)「一次編譯,到處執(zhí)行」的基礎(chǔ)。

        Java 通過垃圾收集器(Garbage Collector)回收分配內(nèi)存,大部分情況下,程序員不需要自己操心內(nèi)存的分配和回收。

        最常見的垃圾收集器,如 SerialGC、Parallel GC、 CMS、 G1 等,對(duì)于適用于什么樣的工作負(fù)載最好也心里有數(shù)。

        JVM、JRE、JDK 關(guān)系

        ?

        碼老濕,能說下 JVM、JRE 和 JDK 的關(guān)系么?

        JVM Java Virtual Machine 是 Java 虛擬機(jī),Java 程序需要運(yùn)行在虛擬機(jī)上,不同的平臺(tái)有自己的虛擬機(jī),因此 Java 語言可以實(shí)現(xiàn)跨平臺(tái)。

        JRE Java Runtime Environment包括 Java 虛擬機(jī)和 Java 程序所需的核心類庫等。

        核心類庫主要是 java.lang 包:包含了運(yùn)行 Java 程序必不可少的系統(tǒng)類,如基本數(shù)據(jù)類型、基本數(shù)學(xué)函數(shù)、字符串處理、線程、異常處理類等,系統(tǒng)缺省加載這個(gè)包

        如果想要運(yùn)行一個(gè)開發(fā)好的 Java 程序,計(jì)算機(jī)中只需要安裝 JRE 即可。

        JDK Java Development Kit是提供給 Java 開發(fā)人員使用的,其中包含了 Java 的開發(fā)工具,也包括了 JRE。

        所以安裝了 JDK,就無需再單獨(dú)安裝 JRE 了。其中的開發(fā)工具:編譯工具(javac.exe),打包工具(jar.exe) 等。

        Java 是解釋執(zhí)行么?

        ?

        碼老濕,Java 是解釋執(zhí)行的么?

        這個(gè)說法不太準(zhǔn)確。

        我們開發(fā)的 Java 的源代碼,首先通過 Javac 編譯成為字節(jié)碼(bytecode),在運(yùn)行時(shí),通過 Java 虛擬機(jī)(JVM)內(nèi)嵌的解釋器將字節(jié)碼轉(zhuǎn)換成為最終的機(jī)器碼。

        但是常見的 JVM,比如我們大多數(shù)情況使用的 Oracle JDK 提供的 Hotspot JVM,都提供了 JIT(Just-In-Time)編譯器。

        也就是通常說的動(dòng)態(tài)編譯器,JIT 能夠在運(yùn)行時(shí)將熱點(diǎn)代碼編譯成機(jī)器碼,這種情況下部分熱點(diǎn)代碼就屬于編譯執(zhí)行,而不是解釋執(zhí)行了。

        采用字節(jié)碼的好處

        ?

        什么是字節(jié)碼?采用字節(jié)碼的好處是什么?

        字節(jié)碼:Java 源代碼經(jīng)過虛擬機(jī)編譯器編譯后產(chǎn)生的文件(即擴(kuò)展為.class 的文件),它不面向任何特定的處理器,只面向虛擬機(jī)。

        采用字節(jié)碼的好處

        眾所周知,我們通常把 Java 分為編譯期和運(yùn)行時(shí)。這里說的 Java 的編譯和 C/C++ 是有著不同的意義的,Javac 的編譯,編譯 Java 源碼生成“.class”文件里面實(shí)際是字節(jié)碼,而不是可以直接執(zhí)行的機(jī)器碼。Java 通過字節(jié)碼和 Java 虛擬機(jī)(JVM)這種跨平臺(tái)的抽象,屏蔽了操作系統(tǒng)和硬件的細(xì)節(jié),這也是實(shí)現(xiàn)“一次編譯,到處執(zhí)行”的基礎(chǔ)。

        基礎(chǔ)語法

        JDK 1.8 之后有哪些新特性

        接口默認(rèn)方法:Java8 允許我們給接口添加一個(gè)非抽象的方法實(shí)現(xiàn),只需要使用 default 關(guān)鍵字即可。

        Lambda 表達(dá)式和函數(shù)式接口:Lambda 表達(dá)式本質(zhì)上是一段匿名內(nèi)部類,也可以是一段可以傳遞的代碼。

        Lambda 允許把函數(shù)作為一個(gè)方法的參數(shù)(函數(shù)作為參數(shù)傳遞到方法中),使用 Lambda 表達(dá)式使代碼更加簡(jiǎn)潔,但是也不要濫用,否則會(huì)有可讀性等問題,《EffectiveJava》作者 JoshBloch 建議使用 Lambda 表達(dá)式最好不要超過 3 行。

        StreamAPI:用函數(shù)式編程方式在集合類上進(jìn)行復(fù)雜操作的工具,配合 Lambda 表達(dá)式可以方便的對(duì)集合進(jìn)行處理。

        Java8 中處理集合的關(guān)鍵抽象概念,它可以指定你希望對(duì)集合進(jìn)行的操作,可以執(zhí)行非常復(fù)雜的查找、過濾和映射數(shù)據(jù)等操作。

        使用 StreamAPI 對(duì)集合數(shù)據(jù)進(jìn)行操作,就類似于使用 SQL 執(zhí)行的數(shù)據(jù)庫查詢。也可以使用 StreamAPI 來并行執(zhí)行操作。

        簡(jiǎn)而言之,StreamAPI 提供了一種高效且易于使用的處理數(shù)據(jù)的方式。

        方法引用:方法引用提供了非常有用的語法,可以直接引用已有 Java 類或?qū)ο螅▽?shí)例)的方法或構(gòu)造器。

        與 lambda 聯(lián)合使用,方法引用可以使語言的構(gòu)造更緊湊簡(jiǎn)潔,減少冗余代碼。

        日期時(shí)間 API:Java8 引入了新的日期時(shí)間 API 改進(jìn)了日期時(shí)間的管理。

        Optional 類:著名的 NullPointerException 是引起系統(tǒng)失敗最常見的原因。

        很久以前 GoogleGuava 項(xiàng)目引入了 Optional 作為解決空指針異常的一種方式,不贊成代碼被 null 檢查的代碼污染,期望程序員寫整潔的代碼。

        受 GoogleGuava 的鼓勵(lì),Optional 現(xiàn)在是 Java8 庫的一部分。

        新工具:新的編譯工具,如:Nashorn 引擎 jjs、類依賴分析器 jdeps。

        構(gòu)造器是否可以重寫

        Constructor 不能被 override(重寫),但是可以 overload(重載),所以你可以看到?個(gè)類中有多個(gè)構(gòu)造函數(shù)的情況。

        wait() 和 sleep 區(qū)別

        來源不同:sleep()來自 Thread 類,wait()來自 Object 類。

        對(duì)于同步鎖的影響不同:sleep()不會(huì)該表同步鎖的行為,如果當(dāng)前線程持有同步鎖,那么 sleep 是不會(huì)讓線程釋放同步鎖的。

        wait()會(huì)釋放同步鎖,讓其他線程進(jìn)入 synchronized 代碼塊執(zhí)行。

        使用范圍不同:sleep()可以在任何地方使用。wait()只能在同步控制方法或者同步控制塊里面使用,否則會(huì)拋 IllegalMonitorStateException。

        恢復(fù)方式不同:兩者會(huì)暫停當(dāng)前線程,但是在恢復(fù)上不太一樣。sleep()在時(shí)間到了之后會(huì)重新恢復(fù);

        wait()則需要其他線程調(diào)用同一對(duì)象的 notify()/nofityAll()才能重新恢復(fù)。

        &和&&的區(qū)別

        &運(yùn)算符有兩種用法:

        1. 按位與;

        2. 邏輯與。

        &&運(yùn)算符是短路與運(yùn)算。邏輯與跟短路與的差別是非常巨大的,雖然二者都要求運(yùn)算符左右兩端的布爾值都是 true 整個(gè)表達(dá)式的值才是 true。

        &&之所以稱為短路運(yùn)算,是因?yàn)槿绻?amp;&左邊的表達(dá)式的值是 false,右邊的表達(dá)式會(huì)被直接短路掉,不會(huì)進(jìn)行運(yùn)算。

        注意:邏輯或運(yùn)算符(|)和短路或運(yùn)算符(||)的差別也是如此。

        Java 有哪些數(shù)據(jù)類型?

        Java 語言是強(qiáng)類型語言,對(duì)于每一種數(shù)據(jù)都定義了明確的具體的數(shù)據(jù)類型,在內(nèi)存中分配了不同大小的內(nèi)存空間。

        分類

        • 基本數(shù)據(jù)類型
          • 整數(shù)類型(byte,short,int,long)
          • 浮點(diǎn)類型(float,double)
          • 數(shù)值型
          • 字符型(char)
          • 布爾型(boolean)
        • 引用數(shù)據(jù)類型
          • 類(class)
          • 接口(interface)
          • 數(shù)組([])

        this 關(guān)鍵字的用法

        this 是自身的一個(gè)對(duì)象,代表對(duì)象本身,可以理解為:指向?qū)ο蟊旧淼囊粋€(gè)指針。

        this 的用法在 java 中大體可以分為 3 種:

        1. 普通的直接引用,this 相當(dāng)于是指向當(dāng)前對(duì)象本身。

        2. 形參與成員名字重名,用 this 來區(qū)分:

        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
        1. 引用本類的構(gòu)造函數(shù)
        class Person{
            private String name;
            private int age;

            public Person() {
            }

            public Person(String name) {
                this.name = name;
            }
            public Person(String name, int age) {
                this(name);
                this.age = age;
            }
        }

        super 關(guān)鍵字的用法

        super 可以理解為是指向自己超(父)類對(duì)象的一個(gè)指針,而這個(gè)超類指的是離自己最近的一個(gè)父類。

        super 也有三種用法:

        1. 普通的直接引用:與 this 類似,super 相當(dāng)于是指向當(dāng)前對(duì)象的父類的引用,這樣就可以用 super.xxx 來引用父類的成員。

        2. 子類中的成員變量或方法與父類中的成員變量或方法同名時(shí),用 super 進(jìn)行區(qū)分

          class Person{
              protected String name;

              public Person(String name) {
                  this.name = name;
              }

          }

          class Student extends Person{
              private String name;

              public Student(String name, String name1) {
                  super(name);
                  this.name = name1;
              }

              public void getInfo(){
                  System.out.println(this.name);      //Child
                  System.out.println(super.name);     //Father
              }

          }

          public class Test {
              public static void main(String[] args) {
                 Student s1 = new Student("Father","Child");
                 s1.getInfo();

              }
          }
        3. 引用父類構(gòu)造函數(shù);

        成員變量與局部變量的區(qū)別有哪些

        變量:在程序執(zhí)行的過程中,在某個(gè)范圍內(nèi)其值可以發(fā)生改變的量。從本質(zhì)上講,變量其實(shí)是內(nèi)存中的一小塊區(qū)域。

        成員變量:方法外部,類內(nèi)部定義的變量。

        局部變量:類的方法中的變量。

        區(qū)別如下:

        作用域

        成員變量:針對(duì)整個(gè)類有效。局部變量:只在某個(gè)范圍內(nèi)有效。(一般指的就是方法,語句體內(nèi))

        存儲(chǔ)位置

        成員變量:隨著對(duì)象的創(chuàng)建而存在,隨著對(duì)象的消失而消失,存儲(chǔ)在堆內(nèi)存中。

        局部變量:在方法被調(diào)用,或者語句被執(zhí)行的時(shí)候存在,存儲(chǔ)在棧內(nèi)存中。當(dāng)方法調(diào)用完,或者語句結(jié)束后,就自動(dòng)釋放。

        生命周期

        成員變量:隨著對(duì)象的創(chuàng)建而存在,隨著對(duì)象的消失而消失 局部變量:當(dāng)方法調(diào)用完,或者語句結(jié)束后,就自動(dòng)釋放。

        初始值

        成員變量:有默認(rèn)初始值。

        局部變量:沒有默認(rèn)初始值,使用前必須賦值。

        動(dòng)態(tài)代理是基于什么原理

        基于反射實(shí)現(xiàn)

        反射機(jī)制是 Java 語言提供的一種基礎(chǔ)功能,賦予程序在運(yùn)行時(shí)自?。╥ntrospect,官方用語)的能力。通過反射我們可以直接操作類或者對(duì)象,比如獲取某個(gè)對(duì)象的類定義,獲取類聲明的屬性和方法,調(diào)用方法或者構(gòu)造對(duì)象,甚至可以運(yùn)行時(shí)修改類定義。

        ?

        碼老濕,他的使用場(chǎng)景是什么?

        AOP 通過(動(dòng)態(tài))代理機(jī)制可以讓開發(fā)者從這些繁瑣事項(xiàng)中抽身出來,大幅度提高了代碼的抽象程度和復(fù)用度。

        包裝 RPC 調(diào)用:通過代理可以讓調(diào)用者與實(shí)現(xiàn)者之間解耦。比如進(jìn)行 RPC 調(diào)用,框架內(nèi)部的尋址、序列化、反序列化等,對(duì)于調(diào)用者往往是沒有太大意義的,通過代理,可以提供更加友善的界面。

        int 與 Integer 區(qū)別

        Java 是一個(gè)近乎純潔的面向?qū)ο缶幊陶Z言,但是為了編程的方便還是引入了基本數(shù)據(jù)類型,但是為了能夠?qū)⑦@些基本數(shù)據(jù)類型當(dāng)成對(duì)象操作,Java 為每一個(gè)基本數(shù)據(jù)類型都引入了對(duì)應(yīng)的包裝類型(wrapper class),int 的包裝類就是 Integer,從 Java 5 開始引入了自動(dòng)裝箱/拆箱機(jī)制,使得二者可以相互轉(zhuǎn)換。

        Java 為每個(gè)原始類型提供了包裝類型:

        • 原始類型: boolean,char,byte,short,int,long,float,double。

        • 包裝類型:Boolean,Character,Byte,Short,Integer,Long,F(xiàn)loat,Double。

        int 是我們常說的整形數(shù)字,是 Java 的 8 個(gè)原始數(shù)據(jù)類型(Primitive Types,boolean、byte 、short、char、int、float、double、long)之一。Java 語言雖然號(hào)稱一切都是對(duì)象,但原始數(shù)據(jù)類型是例外。

        Integer 是 int 對(duì)應(yīng)的包裝類,它有一個(gè) int 類型的字段存儲(chǔ)數(shù)據(jù),并且提供了基本操作,比如數(shù)學(xué)運(yùn)算、int 和字符串之間轉(zhuǎn)換等。在 Java 5 中,引入了自動(dòng)裝箱和自動(dòng)拆箱功能(boxing/unboxing),Java 可以根據(jù)上下文,自動(dòng)進(jìn)行轉(zhuǎn)換,極大地簡(jiǎn)化了相關(guān)編程。

        ?

        Integer a= 127 與 Integer b = 127 相等嗎

        對(duì)于對(duì)象引用類型:==比較的是對(duì)象的內(nèi)存地址。對(duì)于基本數(shù)據(jù)類型:==比較的是值。

        大部分?jǐn)?shù)據(jù)操作都是集中在有限的、較小的數(shù)值范圍,因而,在 Java 5 中新增了靜態(tài)工廠方法 valueOf,在調(diào)用它的時(shí)候會(huì)利用一個(gè)緩存機(jī)制,帶來了明顯的性能改進(jìn)。按照 Javadoc,這個(gè)值默認(rèn)緩存是 -128 到 127 之間。

        如果整型字面量的值在-128 到 127 之間,那么自動(dòng)裝箱時(shí)不會(huì) new 新的 Integer 對(duì)象,而是直接引用常量池中的 Integer 對(duì)象,超過范圍 a1==b1 的結(jié)果是 false。

        public static void main(String[] args) {
            Integer a = new Integer(3);
            Integer b = 3;  // 將3自動(dòng)裝箱成Integer類型
            int c = 3;
            System.out.println(a == b); // false 兩個(gè)引用沒有引用同一對(duì)象
            System.out.println(a == c); // true a自動(dòng)拆箱成int類型再和c比較
            System.out.println(b == c); // true

            Integer a1 = 128;
            Integer b1 = 128;
            System.out.println(a1 == b1); // false

            Integer a2 = 127;
            Integer b2 = 127;
            System.out.println(a2 == b2); // true
        }

        面向?qū)ο?/span>

        ?

        面向?qū)ο笈c面向過程的區(qū)別是什么?

        面向過程

        優(yōu)點(diǎn):性能比面向?qū)ο蟾?,因?yàn)轭愓{(diào)用時(shí)需要實(shí)例化,開銷比較大,比較消耗資源;比如單片機(jī)、嵌入式開發(fā)、Linux/Unix 等一般采用面向過程開發(fā),性能是最重要的因素。

        缺點(diǎn):沒有面向?qū)ο笠拙S護(hù)、易復(fù)用、易擴(kuò)展

        面向?qū)ο?/strong>

        優(yōu)點(diǎn):易維護(hù)、易復(fù)用、易擴(kuò)展,由于面向?qū)ο笥蟹庋b、繼承、多態(tài)性的特性,可以設(shè)計(jì)出低耦合的系統(tǒng),使系統(tǒng)更加靈活、更加易于維護(hù)

        缺點(diǎn):性能比面向過程低

        面向過程是具體化的,流程化的,解決一個(gè)問題,你需要一步一步的分析,一步一步的實(shí)現(xiàn)。

        面向?qū)ο笫悄P突?,你只需抽象出一個(gè)類,這是一個(gè)封閉的盒子,在這里你擁有數(shù)據(jù)也擁有解決問題的方法。需要什么功能直接使用就可以了,不必去一步一步的實(shí)現(xiàn),至于這個(gè)功能是如何實(shí)現(xiàn)的,管我們什么事?我們會(huì)用就可以了。

        面向?qū)ο蟮牡讓悠鋵?shí)還是面向過程,把面向過程抽象成類,然后封裝,方便我們使用的就是面向?qū)ο罅恕?/p>

        面向?qū)ο缶幊桃驗(yàn)槠渚哂胸S富的特性(封裝、抽象、繼承、多態(tài)),可以實(shí)現(xiàn)很多復(fù)雜的設(shè)計(jì)思路,是很多設(shè)計(jì)原則、設(shè)計(jì)模式等編碼實(shí)現(xiàn)的基礎(chǔ)。

        面向?qū)ο笏拇筇匦?/span>

        ?

        碼老濕,如何理解面向?qū)ο蟮乃拇筇匦裕?/p>

        抽象

        抽象是將一類對(duì)象的共同特征總結(jié)出來構(gòu)造類的過程,包括數(shù)據(jù)抽象和行為抽象兩方面。抽象只關(guān)注對(duì)象有哪些屬性和行為,并不關(guān)注這些行為的細(xì)節(jié)是什么。

        另外,抽象是一個(gè)寬泛的設(shè)計(jì)思想,開發(fā)者能不能設(shè)計(jì)好代碼,抽象能力也至關(guān)重要。

        很多設(shè)計(jì)原則都體現(xiàn)了抽象這種設(shè)計(jì)思想,比如基于接口而非實(shí)現(xiàn)編程、開閉原則(對(duì)擴(kuò)展開放、對(duì)修改關(guān)閉)、代碼解耦(降低代碼的耦合性)等。

        在面對(duì)復(fù)雜系統(tǒng)的時(shí)候,人腦能承受的信息復(fù)雜程度是有限的,所以我們必須忽略掉一些非關(guān)鍵性的實(shí)現(xiàn)細(xì)節(jié)。

        封裝

        把一個(gè)對(duì)象的屬性私有化,同時(shí)提供一些可以被外界訪問的屬性的方法,如果屬性不想被外界訪問,我們大可不必提供方法給外界訪問。

        通過封裝,只需要暴露必要的方法給調(diào)用者,調(diào)用者不必了解背后的業(yè)務(wù)細(xì)節(jié),用錯(cuò)的概率就減少。

        繼承

        使用已存在的類的定義作為基礎(chǔ)建立新類的技術(shù),新類的定義可以增加新的數(shù)據(jù)或新的功能,也可以用父類的功能,但不能選擇性地繼承父類。

        通過使用繼承我們能夠非常方便地復(fù)用以前的代碼,需要注意的是,過度使用繼承,層級(jí)深就會(huì)導(dǎo)致代碼可讀性和可維護(hù)性變差

        關(guān)于繼承如下 3 點(diǎn)請(qǐng)記?。?/p>

        1. 子類擁有父類非 private 的屬性和方法。
        2. 子類可以擁有自己屬性和方法,即子類可以對(duì)父類進(jìn)行擴(kuò)展。
        3. 子類可以用自己的方式實(shí)現(xiàn)父類的方法。(以后介紹)。

        多態(tài)

        所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定,而是在程序運(yùn)行期間才確定。

        即一個(gè)引用變量到底會(huì)指向哪個(gè)類的實(shí)例對(duì)象,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類中實(shí)現(xiàn)的方法,必須在由程序運(yùn)行期間才能決定。

        在 Java 中有兩種形式可以實(shí)現(xiàn)多態(tài):繼承(多個(gè)子類對(duì)同一方法的重寫)和接口(實(shí)現(xiàn)接口并覆蓋接口中同一方法)。

        多態(tài)也是很多設(shè)計(jì)模式、設(shè)計(jì)原則、編程技巧的代碼實(shí)現(xiàn)基礎(chǔ),比如策略模式、基于接口而非實(shí)現(xiàn)編程、依賴倒置原則、里式替換原則、利用多態(tài)去掉冗長(zhǎng)的 if-else 語句等等。

        什么是多態(tài)機(jī)制?

        所謂多態(tài)就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發(fā)出的方法調(diào)用在編程時(shí)并不確定,而是在程序運(yùn)行期間才確定,即一個(gè)引用變量倒底會(huì)指向哪個(gè)類的實(shí)例對(duì)象,該引用變量發(fā)出的方法調(diào)用到底是哪個(gè)類中實(shí)現(xiàn)的方法,必須在由程序運(yùn)行期間才能決定。

        因?yàn)樵诔绦蜻\(yùn)行時(shí)才確定具體的類,這樣,不用修改源程序代碼,就可以讓引用變量綁定到各種不同的類實(shí)現(xiàn)上,從而導(dǎo)致該引用調(diào)用的具體方法隨之改變,即不修改程序代碼就可以改變程序運(yùn)行時(shí)所綁定的具體代碼,讓程序可以選擇多個(gè)運(yùn)行狀態(tài),這就是多態(tài)性。

        多態(tài)分為編譯時(shí)多態(tài)和運(yùn)行時(shí)多態(tài)。

        其中編輯時(shí)多態(tài)是靜態(tài)的,主要是指方法的重載,它是根據(jù)參數(shù)列表的不同來區(qū)分不同的函數(shù),通過編輯之后會(huì)變成兩個(gè)不同的函數(shù),在運(yùn)行時(shí)談不上多態(tài)。

        而運(yùn)行時(shí)多態(tài)是動(dòng)態(tài)的,它是通過動(dòng)態(tài)綁定來實(shí)現(xiàn)的,也就是我們所說的多態(tài)性。

        Java 語言是如何實(shí)現(xiàn)多態(tài)的?

        Java 實(shí)現(xiàn)多態(tài)有三個(gè)必要條件:繼承、重寫、向上轉(zhuǎn)型。

        繼承:在多態(tài)中必須存在有繼承關(guān)系的子類和父類。

        重寫:子類對(duì)父類中某些方法進(jìn)行重新定義,在調(diào)用這些方法時(shí)就會(huì)調(diào)用子類的方法。

        向上轉(zhuǎn)型:在多態(tài)中需要將子類的引用賦給父類對(duì)象,只有這樣該引用才能夠具備技能調(diào)用父類的方法和子類的方法。

        只有滿足了上述三個(gè)條件,我們才能夠在同一個(gè)繼承結(jié)構(gòu)中使用統(tǒng)一的邏輯實(shí)現(xiàn)代碼處理不同的對(duì)象,從而達(dá)到執(zhí)行不同的行為。

        重載與重寫

        方法的重載和重寫都是實(shí)現(xiàn)多態(tài)的方式,區(qū)別在于前者實(shí)現(xiàn)的是編譯時(shí)的多態(tài)性,而后者實(shí)現(xiàn)的是運(yùn)行時(shí)的多態(tài)性。

        重載:發(fā)生在同一個(gè)類中,方法名相同參數(shù)列表不同(參數(shù)類型不同、個(gè)數(shù)不同、順序不同),與方法返回值和訪問修飾符無關(guān),即重載的方法不能根據(jù)返回類型進(jìn)行區(qū)分。

        重寫:發(fā)生在父子類中,方法名、參數(shù)列表必須相同,返回值小于等于父類,拋出的異常小于等于父類,訪問修飾符大于等于父類(里氏代換原則);如果父類方法訪問修飾符為 private 則子類中就不是重寫。

        == 和 equals 的區(qū)別是什么

        == : 它的作用是判斷兩個(gè)對(duì)象的地址是不是相等。即,判斷兩個(gè)對(duì)象是不是同一個(gè)對(duì)象。(基本數(shù)據(jù)類型 == 比較的是值,引用數(shù)據(jù)類型 == 比較的是內(nèi)存地址)。

        equals() : 它的作用也是判斷兩個(gè)對(duì)象是否相等。但它一般有兩種使用情況:

        • 類沒有覆蓋 equals() 方法。則通過 equals() 比較該類的兩個(gè)對(duì)象時(shí),等價(jià)于通過“==”比較這兩個(gè)對(duì)象。

        • 類覆蓋了 equals() 方法。一般,我們都覆蓋 equals() 方法來兩個(gè)對(duì)象的內(nèi)容相等;若它們的內(nèi)容相等,則返回 true (即,認(rèn)為這兩個(gè)對(duì)象相等)。

        ?

        為什么重寫 equals 時(shí)必須重寫 hashCode 方法?

        如果兩個(gè)對(duì)象相等,則 hashcode 一定也是相同的

        兩個(gè)對(duì)象相等,對(duì)兩個(gè)對(duì)象分別調(diào)用 equals 方法都返回 true

        兩個(gè)對(duì)象有相同的 hashcode 值,它們也不一定是相等的.

        因此,equals 方法被覆蓋過,則 hashCode 方法也必須被覆蓋

        為什么要有 hashcode

        我們以“HashSet 如何檢查重復(fù)”為例子來說明為什么要有 hashCode

        當(dāng)你把對(duì)象加入 HashSet 時(shí),HashSet 會(huì)先計(jì)算對(duì)象的 hashcode 值來判斷對(duì)象加入的位置,同時(shí)也會(huì)與其他已經(jīng)加入的對(duì)象的 hashcode 值作比較,如果沒有相符的 hashcode,HashSet 會(huì)假設(shè)對(duì)象沒有重復(fù)出現(xiàn)。

        但是如果發(fā)現(xiàn)有相同 hashcode 值的對(duì)象,這時(shí)會(huì)調(diào)用 equals()方法來檢查 hashcode 相等的對(duì)象是否真的相同。

        如果兩者相同,HashSet 就不會(huì)讓其加入操作成功。

        如果不同的話,就會(huì)重新散列到其他位置。這樣我們就大大減少了 equals 的次數(shù),相應(yīng)就大大提高了執(zhí)行速度。

        面向?qū)ο蟮幕驹瓌t

        ?

        碼老濕,什么是 SOLID?

        這是面向?qū)ο缶幊痰囊环N設(shè)計(jì)原則,對(duì)于每一種設(shè)計(jì)原則,我們需要掌握它的設(shè)計(jì)初衷,能解決哪些編程問題,有哪些應(yīng)用場(chǎng)景。

        • 單一職責(zé)原則 SRP(Single Responsibility Principle) 類的功能要單一,不能包羅萬象,跟雜貨鋪似的。
        • 開放封閉原則 OCP(Open-Close Principle) 一個(gè)模塊對(duì)于拓展是開放的,對(duì)于修改是封閉的,想要增加功能熱烈歡迎,想要修改,哼,一萬個(gè)不樂意。
        • 里式替換原則 LSP(the Liskov Substitution Principle LSP) 子類可以替換父類出現(xiàn)在父類能夠出現(xiàn)的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~(其實(shí)多態(tài)就是一種這個(gè)原則的一種實(shí)現(xiàn))。
        • 接口分離原則 ISP(the Interface Segregation Principle ISP) 設(shè)計(jì)時(shí)采用多個(gè)與特定客戶類有關(guān)的接口比采用一個(gè)通用的接口要好。就比如一個(gè)手機(jī)擁有打電話,看視頻,玩游戲等功能,把這幾個(gè)功能拆分成不同的接口,比在一個(gè)接口里要好的多。
        • 依賴倒置原則 DIP(the Dependency Inversion Principle DIP) :高層模塊(high-level modules)不要依賴低層模塊(low-level)。高層模塊和低層模塊應(yīng)該通過抽象(abstractions)來互相依賴。除此之外,抽象(abstractions)不要依賴具體實(shí)現(xiàn)細(xì)節(jié)(details),具體實(shí)現(xiàn)細(xì)節(jié)(details)依賴抽象(abstractions)。
          • 抽象不應(yīng)該依賴于具體實(shí)現(xiàn),具體實(shí)現(xiàn)應(yīng)該依賴于抽象。就是你出國要說你是中國人,而不能說你是哪個(gè)村子的。
          • 比如說中國人是抽象的,下面有具體的 xx 省,xx 市,xx 縣。你要依賴的抽象是中國人,而不是你是 xx 村的。
          • 所謂高層模塊和低層模塊的劃分,簡(jiǎn)單來說就是,在調(diào)用鏈上,調(diào)用者屬于高層,被調(diào)用者屬于低層。
          • Tomcat 就是高層模塊,我們編寫的 Web 應(yīng)用程序代碼就是低層模塊。Tomcat 和應(yīng)用程序代碼之間并沒有直接的依賴關(guān)系,兩者都依賴同一個(gè)「抽象」,也就是 Servlet 規(guī)范。
          • Servlet 規(guī)范不依賴具體的 Tomcat 容器和應(yīng)用程序的實(shí)現(xiàn)細(xì)節(jié),而 Tomcat 容器和應(yīng)用程序依賴 Servlet 規(guī)范。
        ?

        碼老濕,接口隔離與單一職責(zé)有什么區(qū)別?

        單一職責(zé)側(cè)重點(diǎn)是模塊、類、接口的設(shè)計(jì)思想。

        接口隔離原則側(cè)重于接口設(shè)計(jì),提供了一種判斷接口職責(zé)是否單一的標(biāo)準(zhǔn)。

        Exception 與 Error 區(qū)別?

        ?

        碼老濕,他們的相同點(diǎn)是什么呀?

        Exception 和 Error 都是繼承了 Throwable 類,在 Java 中只有 Throwable 類型的實(shí)例才可以被拋出(throw)或者捕獲(catch),它是異常處理機(jī)制的基本組成類型。

        Exception 和 Error 體現(xiàn)了 Java 平臺(tái)設(shè)計(jì)者對(duì)不同異常情況的分類。

        異常使用規(guī)范:

        • 盡量不要捕獲類似 Exception 這樣的通用異常,而是應(yīng)該捕獲特定異常
        • 不要生吞(swallow)異常。這是異常處理中要特別注意的事情,因?yàn)楹芸赡軙?huì)導(dǎo)致非常難以診斷的詭異情況。

        Exception

        Exception 是程序正常運(yùn)行中,可以預(yù)料的意外情況,可能并且應(yīng)該被捕獲,進(jìn)行相應(yīng)處理。

        就好比開車去洗桑拿,前方道路施工,禁止通行。但是我們換條路就可以解決。

        Exception 又分為可檢查(checked)異常和不檢查(unchecked)異常,可檢查異常在源代碼里必須顯式地進(jìn)行捕獲處理,這是編譯期檢查的一部分。

        不檢查異常就是所謂的運(yùn)行時(shí)異常,類似 NullPointerException、ArrayIndexOutOfBoundsException 之類,通常是可以編碼避免的邏輯錯(cuò)誤,具體根據(jù)需要來判斷是否需要捕獲,并不會(huì)在編譯期強(qiáng)制要求。

        Checked Exception 的假設(shè)是我們捕獲了異常,然后恢復(fù)程序。但是,其實(shí)我們大多數(shù)情況下,根本就不可能恢復(fù)。

        Checked Exception 的使用,已經(jīng)大大偏離了最初的設(shè)計(jì)目的。Checked Exception 不兼容 functional 編程,如果你寫過 Lambda/Stream 代碼,相信深有體會(huì)。

        Error

        此類錯(cuò)誤一般表示代碼運(yùn)行時(shí) JVM 出現(xiàn)問題。通常有 Virtual MachineError(虛擬機(jī)運(yùn)行錯(cuò)誤)、NoClassDefFoundError(類定義錯(cuò)誤)等。

        比如 OutOfMemoryError:內(nèi)存不足錯(cuò)誤;StackOverflowError:棧溢出錯(cuò)誤。此類錯(cuò)誤發(fā)生時(shí),JVM 將終止線程。

        絕大多數(shù)導(dǎo)致程序不可恢復(fù),這些錯(cuò)誤是不受檢異常,非代碼性錯(cuò)誤。因此,當(dāng)此類錯(cuò)誤發(fā)生時(shí),應(yīng)用程序不應(yīng)該去處理此類錯(cuò)誤。按照 Java 慣例,我們是不應(yīng)該實(shí)現(xiàn)任何新的 Error 子類的!

        比如開車去洗桑拿,老王出車禍了。無法洗了,只能去醫(yī)院。

        JVM 如何處理異常?

        在一個(gè)方法中如果發(fā)生異常,這個(gè)方法會(huì)創(chuàng)建一個(gè)異常對(duì)象,并轉(zhuǎn)交給 JVM,該異常對(duì)象包含異常名稱,異常描述以及異常發(fā)生時(shí)應(yīng)用程序的狀態(tài)。

        創(chuàng)建異常對(duì)象并轉(zhuǎn)交給 JVM 的過程稱為拋出異常??赡苡幸幌盗械姆椒ㄕ{(diào)用,最終才進(jìn)入拋出異常的方法,這一系列方法調(diào)用的有序列表叫做調(diào)用棧。

        JVM 會(huì)順著調(diào)用棧去查找看是否有可以處理異常的代碼,如果有,則調(diào)用異常處理代碼。

        當(dāng) JVM 發(fā)現(xiàn)可以處理異常的代碼時(shí),會(huì)把發(fā)生的異常傳遞給它。如果 JVM 沒有找到可以處理該異常的代碼塊,JVM 就會(huì)將該異常轉(zhuǎn)交給默認(rèn)的異常處理器(默認(rèn)處理器為 JVM 的一部分),默認(rèn)異常處理器打印出異常信息并終止應(yīng)用程序。

        NoClassDefFoundError 和 ClassNotFoundException

        NoClassDefFoundError 是一個(gè) Error 類型的異常,是由 JVM 引起的,不應(yīng)該嘗試捕獲這個(gè)異常。

        引起該異常的原因是 JVM 或 ClassLoader 嘗試加載某類時(shí)在內(nèi)存中找不到該類的定義,該動(dòng)作發(fā)生在運(yùn)行期間,即編譯時(shí)該類存在,但是在運(yùn)行時(shí)卻找不到了,可能是變異后被刪除了等原因?qū)е拢?/p>

        ClassNotFoundException 是一個(gè)受查異常,需要顯式地使用 try-catch 對(duì)其進(jìn)行捕獲和處理,或在方法簽名中用 throws 關(guān)鍵字進(jìn)行聲明。

        當(dāng)使用 Class.forName, ClassLoader.loadClass 或 ClassLoader.findSystemClass 動(dòng)態(tài)加載類到內(nèi)存的時(shí)候,通過傳入的類路徑參數(shù)沒有找到該類,就會(huì)拋出該異常;

        另一種拋出該異常的可能原因是某個(gè)類已經(jīng)由一個(gè)類加載器加載至內(nèi)存中,另一個(gè)加載器又嘗試去加載它。

        Java 常見異常有哪些?

        java.lang.IllegalAccessError:違法訪問錯(cuò)誤。當(dāng)一個(gè)應(yīng)用試圖訪問、修改某個(gè)類的域(Field)或者調(diào)用其方法,但是又違反域或方法的可見性聲明,則拋出該異常。

        java.lang.InstantiationError:實(shí)例化錯(cuò)誤。當(dāng)一個(gè)應(yīng)用試圖通過 Java 的 new 操作符構(gòu)造一個(gè)抽象類或者接口時(shí)拋出該異常.

        java.lang.OutOfMemoryError:內(nèi)存不足錯(cuò)誤。當(dāng)可用內(nèi)存不足以讓 Java 虛擬機(jī)分配給一個(gè)對(duì)象時(shí)拋出該錯(cuò)誤。

        java.lang.StackOverflowError:堆棧溢出錯(cuò)誤。當(dāng)一個(gè)應(yīng)用遞歸調(diào)用的層次太深而導(dǎo)致堆棧溢出或者陷入死循環(huán)時(shí)拋出該錯(cuò)誤。

        java.lang.ClassCastException:類造型異常。假設(shè)有類 A 和 B(A 不是 B 的父類或子類),O 是 A 的實(shí)例,那么當(dāng)強(qiáng)制將 O 構(gòu)造為類 B 的實(shí)例時(shí)拋出該異常。該異常經(jīng)常被稱為強(qiáng)制類型轉(zhuǎn)換異常。

        java.lang.ClassNotFoundException:找不到類異常。當(dāng)應(yīng)用試圖根據(jù)字符串形式的類名構(gòu)造類,而在遍歷 CLASSPAH 之后找不到對(duì)應(yīng)名稱的 class 文件時(shí),拋出該異常。

        java.lang.ArithmeticException:算術(shù)條件異常。譬如:整數(shù)除零等。

        java.lang.ArrayIndexOutOfBoundsException:數(shù)組索引越界異常。當(dāng)對(duì)數(shù)組的索引值為負(fù)數(shù)或大于等于數(shù)組大小時(shí)拋出。

        final、finally、finalize 區(qū)別?

        除了名字相似,他們毫無關(guān)系?。?!

        • final 可以修飾類、變量、方法,修飾類表示該類不能被繼承、修飾方法表示該方法不能被重寫、修飾變量表示該變量是一個(gè)常量不能被重新賦值。
        • finally 一般作用在 try-catch 代碼塊中,在處理異常的時(shí)候,通常我們將一定要執(zhí)行的代碼方法 finally 代碼塊中,表示不管是否出現(xiàn)異常,該代碼塊都會(huì)執(zhí)行,一般用來存放一些關(guān)閉資源的代碼。
        • finalize 是一個(gè)方法,屬于 Object 類的一個(gè)方法,而 Object 類是所有類的父類,Java 中允許使用 finalize()方法在垃圾收集器將對(duì)象從內(nèi)存中清除出去之前做必要的清理工作。
        ?

        final 有什么用?

        用于修飾類、屬性和方法;

        • 被 final 修飾的類不可以被繼承
        • 被 final 修飾的方法不可以被重寫
        • 被 final 修飾的變量不可以被改變,被 final 修飾不可變的是變量的引用,而不是引用指向的內(nèi)容,引用指向的內(nèi)容是可以改變的。
        ?

        try-catch-finally 中,如果 catch 中 return 了,finally 還會(huì)執(zhí)行嗎?

        答:會(huì)執(zhí)行,在 return 前執(zhí)行。

        注意:在 finally 中改變返回值的做法是不好的,因?yàn)槿绻嬖?finally 代碼塊,try 中的 return 語句不會(huì)立馬返回調(diào)用者,而是記錄下返回值待 finally 代碼塊執(zhí)行完畢之后再向調(diào)用者返回其值,然后如果在 finally 中修改了返回值,就會(huì)返回修改后的值。

        顯然,在 finally 中返回或者修改返回值會(huì)對(duì)程序造成很大的困擾,C#中直接用編譯錯(cuò)誤的方式來阻止程序員干這種齷齪的事情,Java 中也可以通過提升編譯器的語法檢查級(jí)別來產(chǎn)生警告或錯(cuò)誤。

        public static int getInt() {
            int a = 10;
            try {
                System.out.println(a / 0);
                a = 20;
            } catch (ArithmeticException e) {
                a = 30;
                return a;
                /*
                 * return a 在程序執(zhí)行到這一步的時(shí)候,這里不是return a 而是 return 30;這個(gè)返回路徑就形成了
                 * 但是呢,它發(fā)現(xiàn)后面還有finally,所以繼續(xù)執(zhí)行finally的內(nèi)容,a=40
                 * 再次回到以前的路徑,繼續(xù)走return 30,形成返回路徑之后,這里的a就不是a變量了,而是常量30
                 */

            } finally {
                a = 40;
            }
         return a;
        }

        執(zhí)行結(jié)果:30。

        public static int getInt() {
            int a = 10;
            try {
                System.out.println(a / 0);
                a = 20;
            } catch (ArithmeticException e) {
                a = 30;
                return a;
            } finally {
                a = 40;
                //如果這樣,就又重新形成了一條返回路徑,由于只能通過1個(gè)return返回,所以這里直接返回40
                return a;
            }

        }

        執(zhí)行結(jié)果:40。

        強(qiáng)引用、軟引用、弱引用、虛引用

        ?

        強(qiáng)引用、軟引用、弱引用、幻象引用有什么區(qū)別?具體使用場(chǎng)景是什么?

        不同的引用類型,主要體現(xiàn)的是對(duì)象不同的可達(dá)性(reachable)狀態(tài)和對(duì)垃圾收集的影響。

        強(qiáng)引用

        通過 new 創(chuàng)建的對(duì)象就是強(qiáng)引用,強(qiáng)引用指向一個(gè)對(duì)象,就表示這個(gè)對(duì)象還活著,垃圾回收不會(huì)去收集。

        軟引用

        是一種相對(duì)強(qiáng)引用弱化一些的引用,只有當(dāng) JVM 認(rèn)為內(nèi)存不足時(shí),才會(huì)去試圖回收軟引用指向的對(duì)象。

        JVM 會(huì)確保在拋出 OutOfMemoryError 之前,清理軟引用指向的對(duì)象。

        軟引用通常用來實(shí)現(xiàn)內(nèi)存敏感的緩存,如果還有空閑內(nèi)存,就可以暫時(shí)保留緩存,當(dāng)內(nèi)存不足時(shí)清理掉,這樣就保證了使用緩存的同時(shí),不會(huì)耗盡內(nèi)存。

        弱引用

        ThreadlocalMap 中的 key 就是用了弱引用,因?yàn)?ThreadlocalMap 被 thread 對(duì)象持有,所以如果是強(qiáng)引用的話,只有當(dāng) thread 結(jié)束時(shí)才能被回收,而弱引用則可以在使用完后立即回收,不必等待 thread 結(jié)束。

        虛引用

        “虛引用”顧名思義,就是形同虛設(shè),與其他幾種引用都不同,虛引用并不會(huì)決定對(duì)象的生命周期。如果一個(gè)對(duì)象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時(shí)候都可能被垃圾回收器回收。

        虛引用主要用來跟蹤對(duì)象被垃圾回收器回收的活動(dòng)。虛引用與軟引用和弱引用的一個(gè)區(qū)別在于:虛引用必須和引用隊(duì)列 (ReferenceQueue)聯(lián)合使用。

        當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象的內(nèi)存之前,把這個(gè)虛引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。

        String 篇章

        可變性

        String 類中使用字符數(shù)組保存字符串,private final char value[],所以 string 對(duì)象是不可變的。StringBuilderStringBuffer 都繼承自 AbstractStringBuilder 類,在 AbstractStringBuilder 中也是使用字符數(shù)組保存字符串,char[] value,這兩種象都是可變的。·線程安全性

        String 中的對(duì)象是不可變的,也就可以理解為常量,線程安全。AbstractStringBuilder 是 StringBuilder 與 StringBuffer 的公共父類,定義了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。

        StringBuffer 對(duì)方法加了同步鎖或者對(duì)調(diào)用的方法加了同步鎖,所以是線程安全的。StringBuilder 并沒有對(duì)方法進(jìn)行加同步鎖,所以是非線程安全的。

        性能

        每次對(duì) String 類型進(jìn)行改變的時(shí)候,都會(huì)生成一個(gè)新的 String 對(duì)象,然后將指針指向新的 String 對(duì)象。

        StringBuffer 每次都會(huì)對(duì) StringBuffer 對(duì)象本身進(jìn)行操作,而不是生成新的對(duì)象并改變對(duì)象引用。相同情況下使用 StirngBuilder 相比使用 StringBuffer 僅能獲得 10%~15% 左右的性能提升,但卻要冒多線程不安全的風(fēng)險(xiǎn)。

        對(duì)于三者使用的總結(jié)

        如果要操作少量的數(shù)據(jù)用 = String

        單線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù) = StringBuilder

        多線程操作字符串緩沖區(qū)下操作大量數(shù)據(jù) = StringBuffer

        String

        String 是 Java 語言非常基礎(chǔ)和重要的類,提供了構(gòu)造和管理字符串的各種基本邏輯。它是典型的 Immutable 類,被聲明成為 final class,所有屬性也都是 final 的。

        也由于它的不可變性,類似拼接、裁剪字符串等動(dòng)作,都會(huì)產(chǎn)生新的 String 對(duì)象。

        StringBuilder

        StringBuilder 是 Java 1.5 中新增的,在能力上和 StringBuffer 沒有本質(zhì)區(qū)別,但是它去掉了線程安全的部分,有效減小了開銷,是絕大部分情況下進(jìn)行字符串拼接的首選。

        StringBuffer

        StringBuffer 是為解決上面提到拼接產(chǎn)生太多中間對(duì)象的問題而提供的一個(gè)類,我們可以用 append 或者 add 方法,把字符串添加到已有序列的末尾或者指定位置。

        StringBuffer 本質(zhì)是一個(gè)線程安全的可修改字符序列,它保證了線程安全,也隨之帶來了額外的性能開銷,所以除非有線程安全的需要,不然還是推薦使用它的后繼者,也就是 StringBuilder

        HashMap 使用 String 作為 key 有什么好處

        HashMap 內(nèi)部實(shí)現(xiàn)是通過 keyhashcode 來確定 value 的存儲(chǔ)位置,因?yàn)樽址遣豢勺兊模援?dāng)創(chuàng)建字符串時(shí),它的 hashcode 被緩存下來,不需要再次計(jì)算,所以相比于其他對(duì)象更快。

        接口和抽象類有什么區(qū)別?

        抽象類是用來捕捉子類的通用特性的。接口是抽象方法的集合。

        接口和抽象類各有優(yōu)缺點(diǎn),在接口和抽象類的選擇上,必須遵守這樣一個(gè)原則:

        • 行為模型應(yīng)該總是通過接口而不是抽象類定義,所以通常是優(yōu)先選用接口,盡量少用抽象類。
        • 選擇抽象類的時(shí)候通常是如下情況:需要定義子類的行為,又要為子類提供通用的功能。

        相同點(diǎn)

        • 接口和抽象類都不能實(shí)例化
        • 都位于繼承的頂端,用于被其他實(shí)現(xiàn)或繼承
        • 都包含抽象方法,其子類都必須覆寫這些抽象方法

        接口

        接口定義了協(xié)議,是面向?qū)ο缶幊蹋ǚ庋b、繼承多態(tài))基礎(chǔ),通過接口我們能很好的實(shí)現(xiàn)單一職責(zé)、接口隔離、內(nèi)聚。

        • 不能實(shí)例化;
        • 不能包含任何非常量成員,任何 field 都是隱含著 public static final 的意義;
        • 同時(shí),沒有非靜態(tài)方法實(shí)現(xiàn),也就是說要么是抽象方法,要么是靜態(tài)方法。

        Java8 中接口中引入默認(rèn)方法和靜態(tài)方法,并且不用強(qiáng)制子類來實(shí)現(xiàn)它。以此來減少抽象類和接口之間的差異。

        抽象類

        抽象類是不能實(shí)例化的類,用 abstract 關(guān)鍵字修飾 class,其目的主要是代碼重用。

        從設(shè)計(jì)層面來說,抽象類是對(duì)類的抽象,是一種模板設(shè)計(jì),接口是行為的抽象,是一種行為的規(guī)范。

        除了不能實(shí)例化,形式上和一般的 Java 類并沒有太大區(qū)別。

        可以有一個(gè)或者多個(gè)抽象方法,也可以沒有抽象方法。抽象類大多用于抽取相關(guān) Java 類的共用方法實(shí)現(xiàn)或者是共同成員變量,然后通過繼承的方式達(dá)到代碼復(fù)用的目的。

        ?

        碼老濕,抽象類能用 final 修飾么?

        不能,定義抽象類就是讓其他類繼承的,如果定義為 final 該類就不能被繼承,這樣彼此就會(huì)產(chǎn)生矛盾,所以 final 不能修飾抽象類

        值傳遞

        ?

        當(dāng)一個(gè)對(duì)象被當(dāng)作參數(shù)傳遞到一個(gè)方法后,此方法可改變這個(gè)對(duì)象的屬性,并可返回變化后的結(jié)果,那么這里到底是值傳遞還是引用傳遞?

        是值傳遞。

        Java 語言的方法調(diào)用只支持參數(shù)的值傳遞。當(dāng)一個(gè)對(duì)象實(shí)例作為一個(gè)參數(shù)被傳遞到方法中時(shí),參數(shù)的值就是對(duì)該對(duì)象的引用。

        對(duì)象的屬性可以在被調(diào)用過程中被改變,但對(duì)對(duì)象引用的改變是不會(huì)影響到調(diào)用者的。

        ?

        為什么 Java 只有值傳遞?

        首先回顧一下在程序設(shè)計(jì)語言中有關(guān)將參數(shù)傳遞給方法(或函數(shù))的一些專業(yè)術(shù)語。按值調(diào)用(call by value)表示方法接收的是調(diào)用者提供的值,而按引用調(diào)用(call by reference)表示方法接收的是調(diào)用者提供的變量地址。

        一個(gè)方法可以修改傳遞引用所對(duì)應(yīng)的變量值,而不能修改傳遞值調(diào)用所對(duì)應(yīng)的變量值。

        它用來描述各種程序設(shè)計(jì)語言(不只是 Java)中方法參數(shù)傳遞方式。

        Java 程序設(shè)計(jì)語言總是采用按值調(diào)用。也就是說,方法得到的是所有參數(shù)值的一個(gè)拷貝,也就是說,方法不能修改傳遞給它的任何參數(shù)變量的內(nèi)容。

        基本數(shù)據(jù)類型

        例子如下:

        public static void main(String[] args) {
            int num1 = 10;
            int num2 = 20;

            swap(num1, num2);

            System.out.println("num1 = " + num1);
            System.out.println("num2 = " + num2);
        }

        public static void swap(int a, int b) {
            int temp = a;
            a = b;
            b = temp;

            System.out.println("a = " + a);
            System.out.println("b = " + b);
        }

        執(zhí)行結(jié)果:

        a = 20
        b = 10
        num1 = 10
        num2 = 20

        解析:

        在 swap 方法中,a、b 的值進(jìn)行交換,并不會(huì)影響到 num1、num2。

        因?yàn)?,a、b 中的值,只是從 num1、num2 的復(fù)制過來的。

        也就是說,a、b 相當(dāng)于 num1、num2 的副本,副本的內(nèi)容無論怎么修改,都不會(huì)影響到原件本身。

        對(duì)象引用類型

            public static void main(String[] args) {
                int[] arr = { 12345 };
                System.out.println(arr[0]);
                change(arr);
                System.out.println(arr[0]);
            }

            public static void change(int[] array) {
                // 將數(shù)組的第一個(gè)元素變?yōu)?
                array[0] = 0;
            }

        結(jié)果:

        1
        0

        解析:

        array 被初始化 arr 的拷貝也就是一個(gè)對(duì)象的引用,也就是說 array 和 arr 指向的時(shí)同一個(gè)數(shù)組對(duì)象。因此,外部對(duì)引用對(duì)象的改變會(huì)反映到所對(duì)應(yīng)的對(duì)象上。

        通過 example2 我們已經(jīng)看到,實(shí)現(xiàn)一個(gè)改變對(duì)象參數(shù)狀態(tài)的方法并不是一件難事。理由很簡(jiǎn)單,方法得到的是對(duì)象引用的拷貝,對(duì)象引用及其他的拷貝同時(shí)引用同一個(gè)對(duì)象。

        很多程序設(shè)計(jì)語言(特別是,C++和 Pascal)提供了兩種參數(shù)傳遞的方式:值調(diào)用和引用調(diào)用。

        有些程序員認(rèn)為 Java 程序設(shè)計(jì)語言對(duì)對(duì)象采用的是引用調(diào)用,實(shí)際上,這種理解是不對(duì)的。

        值傳遞和引用傳遞有什么區(qū)別?

        值傳遞:指的是在方法調(diào)用時(shí),傳遞的參數(shù)是按值的拷貝傳遞,傳遞的是值的拷貝,也就是說傳遞后就互不相關(guān)了。

        引用傳遞:指的是在方法調(diào)用時(shí),傳遞的參數(shù)是按引用進(jìn)行傳遞,其實(shí)傳遞的引用的地址,也就是變量所對(duì)應(yīng)的內(nèi)存空間的地址。傳遞的是值的引用,也就是說傳遞前和傳遞后都指向同一個(gè)引用(也就是同一個(gè)內(nèi)存空間)。


        瀏覽 26
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 青娱乐狠狠| 国产一级片网站 | 丝袜美女足交 | 国产乱淫a∨片免费视频 | 亚洲精品福利视频 |