1. 離職在家學(xué)習(xí)Java的第一天

        共 3755字,需瀏覽 8分鐘

         ·

        2022-03-03 01:12

        大家好,我是二哥呀。今天在好朋友 Guide 哥的星球閑逛,看到一位球友離職在家學(xué)習(xí) Java,第一天的目標(biāo)是 Java 的面向?qū)ο?,希望能寫出一個(gè)五子棋游戲。

        那針對(duì)學(xué)習(xí)?Java 的同學(xué)來(lái)說(shuō),二哥的小破站《Java程序員進(jìn)階之路》應(yīng)該是一個(gè)非常不錯(cuò)的選擇。天我們來(lái)回到夢(mèng)開(kāi)始的地方——Java 基礎(chǔ),其實(shí)過(guò)了萌新階段,面試問(wèn)基礎(chǔ)問(wèn)的不多,但是保不齊突然問(wèn)這么一下。

        想一下,總不能張口高并發(fā)、閉口分布式,正嗨著呢?突然面試官問(wèn)了一下什么是面向?qū)ο?,說(shuō)不清,就有點(diǎn)魔幻。所以今天就給大家整理了 53 道 Java 基礎(chǔ)高頻面試題!

        一、Java 概述

        1.什么是 Java?

        PS:碎慫 Java,有啥好介紹的。哦,面試啊。

        Java 是一門面向?qū)ο蟮木幊陶Z(yǔ)言,不僅吸收了 C++語(yǔ)言的各種優(yōu)點(diǎn),還摒棄了 C++里難以理解的多繼承、指針等概念,因此 Java 語(yǔ)言具有功能強(qiáng)大和簡(jiǎn)單易用兩個(gè)特征。Java 語(yǔ)言作為靜態(tài)面向?qū)ο缶幊陶Z(yǔ)言的優(yōu)秀代表,極好地實(shí)現(xiàn)了面向?qū)ο罄碚?,允許程序員以優(yōu)雅的思維方式進(jìn)行復(fù)雜的編程 。

        2.Java 語(yǔ)言有哪些特點(diǎn)?

        Java 語(yǔ)言有很多優(yōu)秀(可吹)的特點(diǎn),以下幾個(gè)是比較突出的:

        Java語(yǔ)言特點(diǎn)
        • 面向?qū)ο螅ǚ庋b,繼承,多態(tài));
        • 平臺(tái)無(wú)關(guān)性,平臺(tái)無(wú)關(guān)性的具體表現(xiàn)在于,Java 是“一次編寫,到處運(yùn)行(Write Once,Run any Where)”的語(yǔ)言,因此采用 Java 語(yǔ)言編寫的程序具有很好的可移植性,而保證這一點(diǎn)的正是 Java 的虛擬機(jī)機(jī)制。在引入虛擬機(jī)之后,Java 語(yǔ)言在不同的平臺(tái)上運(yùn)行不需要重新編譯。
        • 支持多線程。C++ 語(yǔ)言沒(méi)有內(nèi)置的多線程機(jī)制,因此必須調(diào)用操作系統(tǒng)的多線程功能來(lái)進(jìn)行多線程程序設(shè)計(jì),而 Java 語(yǔ)言卻提供了多線程支持;
        • 編譯與解釋并存;

        3.JVM、JDK 和 JRE 有什么區(qū)別?

        JVM:Java Virtual Machine,Java 虛擬機(jī),Java 程序運(yùn)行在 Java 虛擬機(jī)上。針對(duì)不同系統(tǒng)的實(shí)現(xiàn)(Windows,Linux,macOS)不同的 JVM,因此 Java 語(yǔ)言可以實(shí)現(xiàn)跨平臺(tái)。

        JRE:Java 運(yùn)?時(shí)環(huán)境。它是運(yùn)?已編譯 Java 程序所需的所有內(nèi)容的集合,包括 Java 虛擬機(jī)(JVM),Java 類庫(kù),Java 命令和其他的?些基礎(chǔ)構(gòu)件。但是,它不能?于創(chuàng)建新程序。

        JDK: Java Development Kit,它是功能?全的 Java SDK。它擁有 JRE 所擁有的?切,還有編譯器(javac)和?具(如 javadoc 和 jdb)。它能夠創(chuàng)建和編譯程序。

        簡(jiǎn)單來(lái)說(shuō),JDK 包含 JRE,JRE 包含 JVM。

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

        4.說(shuō)說(shuō)什么是跨平臺(tái)性?原理是什么

        所謂跨平臺(tái)性,是指 Java 語(yǔ)言編寫的程序,一次編譯后,可以在多個(gè)系統(tǒng)平臺(tái)上運(yùn)行。

        實(shí)現(xiàn)原理:Java 程序是通過(guò) Java 虛擬機(jī)在系統(tǒng)平臺(tái)上運(yùn)行的,只要該系統(tǒng)可以安裝相應(yīng)的 Java 虛擬機(jī),該系統(tǒng)就可以運(yùn)行 java 程序。

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

        所謂的字節(jié)碼,就是 Java 程序經(jīng)過(guò)編譯之類產(chǎn)生的.class 文件,字節(jié)碼能夠被虛擬機(jī)識(shí)別,從而實(shí)現(xiàn) Java 程序的跨平臺(tái)性。

        Java 程序從源代碼到運(yùn)行主要有三步:

        • 編譯:將我們的代碼(.java)編譯成虛擬機(jī)可以識(shí)別理解的字節(jié)碼(.class)
        • 解釋:虛擬機(jī)執(zhí)行 Java 字節(jié)碼,將字節(jié)碼翻譯成機(jī)器能識(shí)別的機(jī)器碼
        • 執(zhí)行:對(duì)應(yīng)的機(jī)器執(zhí)行二進(jìn)制機(jī)器碼
        Java程序執(zhí)行過(guò)程

        只需要把 Java 程序編譯成 Java 虛擬機(jī)能識(shí)別的 Java 字節(jié)碼,不同的平臺(tái)安裝對(duì)應(yīng)的 Java 虛擬機(jī),這樣就可以可以實(shí)現(xiàn) Java 語(yǔ)言的平臺(tái)無(wú)關(guān)性。

        6.為什么說(shuō) Java 語(yǔ)言“編譯與解釋并存”?

        高級(jí)編程語(yǔ)言按照程序的執(zhí)行方式分為編譯型解釋型兩種。

        簡(jiǎn)單來(lái)說(shuō),編譯型語(yǔ)言是指編譯器針對(duì)特定的操作系統(tǒng)將源代碼一次性翻譯成可被該平臺(tái)執(zhí)行的機(jī)器碼;解釋型語(yǔ)言是指解釋器對(duì)源程序逐行解釋成特定平臺(tái)的機(jī)器碼并立即執(zhí)行。

        比如,你想讀一本外國(guó)的小說(shuō),你可以找一個(gè)翻譯人員幫助你翻譯,有兩種選擇方式,你可以先等翻譯人員將全本的小說(shuō)(也就是源碼)都翻譯成漢語(yǔ),再去閱讀,也可以讓翻譯人員翻譯一段,你在旁邊閱讀一段,慢慢把書讀完。

        Java 語(yǔ)言既具有編譯型語(yǔ)言的特征,也具有解釋型語(yǔ)言的特征,因?yàn)?Java 程序要經(jīng)過(guò)先編譯,后解釋兩個(gè)步驟,由 Java 編寫的程序需要先經(jīng)過(guò)編譯步驟,生成字節(jié)碼(\*.class 文件),這種字節(jié)碼必須再經(jīng)過(guò) JVM,解釋成操作系統(tǒng)能識(shí)別的機(jī)器碼,在由操作系統(tǒng)執(zhí)行。因此,我們可以認(rèn)為 Java 語(yǔ)言編譯解釋并存。

        編譯與解釋

        二、基礎(chǔ)語(yǔ)法

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

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

        Java 語(yǔ)言數(shù)據(jù)類型分為兩種:基本數(shù)據(jù)類型引用數(shù)據(jù)類型。

        Java數(shù)據(jù)類型

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

        • 數(shù)值型
          • 整數(shù)類型(byte、short、long)
          • 浮點(diǎn)類型(float、long)
        • 字符型(char)
        • 布爾型(boolean)

        Java 基本數(shù)據(jù)類型范圍和默認(rèn)值:

        基本類型位數(shù)字節(jié)默認(rèn)值
        int3240
        short1620
        long6480L
        byte810
        char162'u0000'
        float3240f
        double6480d
        boolean1
        false

        引用數(shù)據(jù)類型:

        • 類(class)
        • 接口(interface)
        • 數(shù)組([])

        8.自動(dòng)類型轉(zhuǎn)換、強(qiáng)制類型轉(zhuǎn)換?看看這幾行代碼?

        Java 所有的數(shù)值型變量可以相互轉(zhuǎn)換,當(dāng)把一個(gè)表數(shù)范圍小的數(shù)值或變量直接賦給另一個(gè)表數(shù)范圍大的變量時(shí),可以進(jìn)行自動(dòng)類型轉(zhuǎn)換;反之,需要強(qiáng)制轉(zhuǎn)換。

        Java自動(dòng)類型轉(zhuǎn)換方向

        這就好像,小杯里的水倒進(jìn)大杯沒(méi)問(wèn)題,但大杯的水倒進(jìn)小杯就不行了,可能會(huì)溢出。

        float f=3.4,對(duì)嗎?

        不正確。3.4 是單精度數(shù),將雙精度型(double)賦值給浮點(diǎn)型(float)屬于下轉(zhuǎn)型(down-casting,也稱為窄化)會(huì)造成精度損失,因此需要強(qiáng)制類型轉(zhuǎn)換float f =(float)3.4;或者寫成float f =3.4F

        short s1 = 1; s1 = s1 + 1;對(duì)嗎?short s1 = 1; s1 += 1;對(duì)嗎?

        對(duì)于 short s1 = 1; s1 = s1 + 1;編譯出錯(cuò),由于 1 是 int 類型,因此 s1+1 運(yùn)算結(jié)果也是 int 型,需要強(qiáng)制轉(zhuǎn)換類型才能賦值給 short 型。

        而 short s1 = 1; s1 += 1;可以正確編譯,因?yàn)?s1+= 1;相當(dāng)于 s1 = (short(s1 + 1);其中有隱含的強(qiáng)制類型轉(zhuǎn)換。

        9.什么是自動(dòng)拆箱/封箱?

        • 裝箱:將基本類型用它們對(duì)應(yīng)的引用類型包裝起來(lái);
        • 拆箱:將包裝類型轉(zhuǎn)換為基本數(shù)據(jù)類型;

        Java 可以自動(dòng)對(duì)基本數(shù)據(jù)類型和它們的包裝類進(jìn)行裝箱和拆箱。

        裝箱和拆箱

        舉例:

        Integer?i?=?10;??//裝箱
        int?n?=?i;???//拆箱

        10.&和&&有什么區(qū)別?

        &運(yùn)算符有兩種用法:短路與、邏輯與。

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

        &&之所以稱為短路運(yùn)算是因?yàn)?,如?&左邊的表達(dá)式的值是 false,右邊的表達(dá)式會(huì)被直接短路掉,不會(huì)進(jìn)行運(yùn)算。很多時(shí)候我們可能都需要用&&而不是&。

        例如在驗(yàn)證用戶登錄時(shí)判定用戶名不是 null 而且不是空字符串,應(yīng)當(dāng)寫為username != null &&!username.equals(""),二者的順序不能交換,更不能用&運(yùn)算符,因?yàn)榈谝粋€(gè)條件如果不成立,根本不能進(jìn)行字符串的 equals 比較,否則會(huì)產(chǎn)生 NullPointerException 異常。

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

        11.switch 是否能作用在 byte/long/String 上?

        Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。

        從 Java 5 開(kāi)始,Java 中引入了枚舉類型, expr 也可以是 enum 類型。

        從 Java 7 開(kāi)始,expr 還可以是字符串(String),但是長(zhǎng)整型(long)在目前所有的版本中都是不可以的。

        12.break ,continue ,return 的區(qū)別及作用?

        • break 跳出整個(gè)循環(huán),不再執(zhí)行循環(huán)(結(jié)束當(dāng)前的循環(huán)體)
        • continue 跳出本次循環(huán),繼續(xù)執(zhí)行下次循環(huán)(結(jié)束正在執(zhí)行的循環(huán) 進(jìn)入下一個(gè)循環(huán)條件)
        • return 程序返回,不再執(zhí)行下面的代碼(結(jié)束當(dāng)前的方法 直接返回)
        break 、continue 、return

        13.用最有效率的方法計(jì)算 2 乘以 8?

        2 << 3。位運(yùn)算,數(shù)字的二進(jìn)制位左移三位相當(dāng)于乘以 2 的三次方。

        14.說(shuō)說(shuō)自增自減運(yùn)算?看下這幾個(gè)代碼運(yùn)行結(jié)果?

        在寫代碼的過(guò)程中,常見(jiàn)的一種情況是需要某個(gè)整數(shù)類型變量增加 1 或減少 1,Java 提供了一種特殊的運(yùn)算符,用于這種表達(dá)式,叫做自增運(yùn)算符(++)和自減運(yùn)算符(--)。

        ++和--運(yùn)算符可以放在變量之前,也可以放在變量之后。

        當(dāng)運(yùn)算符放在變量之前時(shí)(前綴),先自增/減,再賦值;當(dāng)運(yùn)算符放在變量之后時(shí)(后綴),先賦值,再自增/減。

        例如,當(dāng) b = ++a 時(shí),先自增(自己增加 1),再賦值(賦值給 b);當(dāng) b = a++ 時(shí),先賦值(賦值給 b),再自增(自己增加 1)。也就是,++a 輸出的是 a+1 的值,a++輸出的是 a 值。

        用一句口訣就是:“符號(hào)在前就先加/減,符號(hào)在后就后加/減”。

        看一下這段代碼運(yùn)行結(jié)果?

        int?i??=?1;
        i?=?i++;
        System.out.println(i);

        答案是 1。有點(diǎn)離譜對(duì)不對(duì)。

        對(duì)于 JVM 而言,它對(duì)自增運(yùn)算的處理,是會(huì)先定義一個(gè)臨時(shí)變量來(lái)接收 i 的值,然后進(jìn)行自增運(yùn)算,最后又將臨時(shí)變量賦給了值為 2 的 i,所以最后的結(jié)果為 1。

        相當(dāng)于這樣的代碼:

        int?i?=?1;
        int?temp?=?i;
        i++;
        i?=?temp;
        System.out.println(i);

        這段代碼會(huì)輸出什么?

        int?count?=?0;
        for(int?i?=?0;i?100;i++)
        {
        ????count?=?count++;
        }
        System.out.println("count?=?"+count);

        答案是 0。

        和上面的題目一樣的道理,同樣是用了臨時(shí)變量,count 實(shí)際是等于臨時(shí)變量的值。

        int?autoAdd(int?count)
        {
        ????int?temp?=?count;
        ????count?=?coutn?+?1;
        ????return?temp;
        }

        PS:筆試面試可能會(huì)碰到的奇葩題,開(kāi)發(fā)這么寫,見(jiàn)一次吊一次。

        三、面向?qū)ο?/h1>

        15.?向?qū)ο蠛?向過(guò)程的區(qū)別?

        • ?向過(guò)程 :面向過(guò)程就是分析出解決問(wèn)題所需要的步驟,然后用函數(shù)把這些步驟一步一步實(shí)現(xiàn),使用的時(shí)候再一個(gè)一個(gè)的一次調(diào)用就可以。
        • ?向?qū)ο?/strong> :面向?qū)ο?,把?gòu)成問(wèn)題的事務(wù)分解成各個(gè)對(duì)象,而建立對(duì)象的目的也不是為了完成一個(gè)個(gè)步驟,而是為了描述某個(gè)事件在解決整個(gè)問(wèn)題的過(guò)程所發(fā)生的行為。目的是為了寫出通用的代碼,加強(qiáng)代碼的重用,屏蔽差異性。

        用一個(gè)比喻:面向過(guò)程是編年體;面向?qū)ο笫羌o(jì)傳體。

        面向?qū)ο蠛兔嫦蜻^(guò)程的區(qū)別

        16.面向?qū)ο笥心男┨匦?span style="display: none;">

        面向?qū)ο笕筇卣?/figcaption>
        • 封裝

          封裝把?個(gè)對(duì)象的屬性私有化,同時(shí)提供?些可以被外界訪問(wèn)的屬性的?法。

        • 繼承

          繼承是使?已存在的類的定義作為基礎(chǔ)創(chuàng)建新的類,新類的定義可以增加新的屬性或新的方法,也可以繼承父類的屬性和方法。通過(guò)繼承可以很方便地進(jìn)行代碼復(fù)用。

        關(guān)于繼承有以下三個(gè)要點(diǎn):

        1. ?類擁有?類對(duì)象所有的屬性和?法(包括私有屬性和私有?法),但是?類中的私有屬性和?法?類是?法訪問(wèn),只是擁有。

        2. ?類可以擁有??屬性和?法,即?類可以對(duì)?類進(jìn)?擴(kuò)展。

        3. ?類可以???的?式實(shí)現(xiàn)?類的?法。

        • 多態(tài)

          所謂多態(tài)就是指程序中定義的引?變量所指向的具體類型和通過(guò)該引?變量發(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)接?并覆蓋接?中同??法)。

        17.重載(overload)和重寫(override)的區(qū)別?

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

        • 重載發(fā)生在一個(gè)類中,同名的方法如果有不同的參數(shù)列表(參數(shù)類型不同、參數(shù)個(gè)數(shù)不同或者二者都不同)則視為重載;

        • 重寫發(fā)生在子類與父類之間,重寫要求子類被重寫方法與父類被重寫方法有相同的返回類型,比父類被重寫方法更好訪問(wèn),不能比父類被重寫方法聲明更多的異常(里氏代換原則)。

        方法重載的規(guī)則:

        1. 方法名一致,參數(shù)列表中參數(shù)的順序,類型,個(gè)數(shù)不同。
        2. 重載與方法的返回值無(wú)關(guān),存在于父類和子類,同類中。
        3. 可以拋出不同的異常,可以有不同修飾符。

        18.訪問(wèn)修飾符 public、private、protected、以及不寫(默認(rèn))時(shí)的區(qū)別?

        Java 中,可以使用訪問(wèn)控制符來(lái)保護(hù)對(duì)類、變量、方法和構(gòu)造方法的訪問(wèn)。Java 支持 4 種不同的訪問(wèn)權(quán)限。

        • default (即默認(rèn),什么也不寫): 在同一包內(nèi)可見(jiàn),不使用任何修飾符??梢孕揎椩陬?、接口、變量、方法。
        • private : 在同一類內(nèi)可見(jiàn)??梢孕揎椬兞?、方法。注意:不能修飾類(外部類)
        • public : 對(duì)所有類可見(jiàn)??梢孕揎楊?、接口、變量、方法
        • protected : 對(duì)同一包內(nèi)的類和所有子類可見(jiàn)。可以修飾變量、方法。注意:不能修飾類(外部類)
        訪問(wèn)修飾符和可見(jiàn)性

        19.this 關(guān)鍵字有什么作用?

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

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

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

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

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

        20.抽象類(abstract class)和接口(interface)有什么區(qū)別?

        1. 接?的?法默認(rèn)是 public ,所有?法在接?中不能有實(shí)現(xiàn)(Java 8 開(kāi)始接??法可以有默認(rèn)實(shí)現(xiàn)),?抽象類可以有?抽象的?法。

        2. 接?中除了 static 、 final 變量,不能有其他變量,?抽象類中則不?定。

        3. ?個(gè)類可以實(shí)現(xiàn)多個(gè)接?,但只能實(shí)現(xiàn)?個(gè)抽象類。接???本身可以通過(guò) extends 關(guān)鍵字?jǐn)U展多個(gè)接?。

        4. 接??法默認(rèn)修飾符是 public ,抽象?法可以有 public 、 protected 和 default 這些修飾符(抽象?法就是為了被重寫所以不能使? private 關(guān)鍵字修飾?。?。

        5. 從設(shè)計(jì)層?來(lái)說(shuō),抽象是對(duì)類的抽象,是?種模板設(shè)計(jì),?接?是對(duì)?為的抽象,是?種?為的規(guī)范。

        1. 在 JDK8 中,接?也可以定義靜態(tài)?法,可以直接?接?名調(diào)?。實(shí)現(xiàn)類和實(shí)現(xiàn)是不可以調(diào)?的。如果同時(shí)實(shí)現(xiàn)兩個(gè)接?,接?中定義了?樣的默認(rèn)?法,則必須重寫,不然會(huì)報(bào)錯(cuò)。

        2. jdk9 的接?被允許定義私有?法 。

        總結(jié)?下 jdk7~jdk9 Java 中接?的變化:

        1. 在 jdk 7 或更早版本中,接???只能有常量變量和抽象?法。這些接??法必須由選擇實(shí)現(xiàn)接?的類實(shí)現(xiàn)。

        2. jdk 8 的時(shí)候接?可以有默認(rèn)?法和靜態(tài)?法功能。

        3. jdk 9 在接?中引?了私有?法和私有靜態(tài)?法。

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

        1. 從語(yǔ)法形式上看:成員變量是屬于類的,?局部變量是在?法中定義的變量或是?法的參數(shù);成員變量可以被 public , private , static 等修飾符所修飾,?局部變量不能被訪問(wèn)控制修飾符及 static 所修飾;但是,成員變量和局部變量都能被 final 所修飾。

        2. 從變量在內(nèi)存中的存儲(chǔ)?式來(lái)看:如果成員變量是使? static 修飾的,那么這個(gè)成員變量是屬于類的,如果沒(méi)有使? static 修飾,這個(gè)成員變量是屬于實(shí)例的。對(duì)象存于堆內(nèi)存,如果局部變量類型為基本數(shù)據(jù)類型,那么存儲(chǔ)在棧內(nèi)存,如果為引?數(shù)據(jù)類型,那存放的是指向堆內(nèi)存對(duì)象的引?或者是指向常量池中的地址。

        3. 從變量在內(nèi)存中的?存時(shí)間上看:成員變量是對(duì)象的?部分,它隨著對(duì)象的創(chuàng)建?存在,?局部變量隨著?法的調(diào)???動(dòng)消失。

        4. 成員變量如果沒(méi)有被賦初值:則會(huì)?動(dòng)以類型的默認(rèn)值?賦值(?種情況例外:被 final 修飾的成員變量也必須顯式地賦值),?局部變量則不會(huì)?動(dòng)賦值。

        22.靜態(tài)變量和實(shí)例變量的區(qū)別?靜態(tài)方法、實(shí)例方法呢?

        靜態(tài)變量和實(shí)例變量的區(qū)別?

        靜態(tài)變量: 是被 static 修飾符修飾的變量,也稱為類變量,它屬于類,不屬于類的任何一個(gè)對(duì)象,一個(gè)類不管創(chuàng)建多少個(gè)對(duì)象,靜態(tài)變量在內(nèi)存中有且僅有一個(gè)副本。

        實(shí)例變量: 必須依存于某一實(shí)例,需要先創(chuàng)建對(duì)象然后通過(guò)對(duì)象才能訪問(wèn)到它。靜態(tài)變量可以實(shí)現(xiàn)讓多個(gè)對(duì)象共享內(nèi)存。

        靜態(tài)?法和實(shí)例?法有何不同?

        類似地。

        靜態(tài)方法:static 修飾的方法,也被稱為類方法。在外部調(diào)?靜態(tài)?法時(shí),可以使?"類名.?法名"的?式,也可以使?"對(duì)象名.?法名"的?式。靜態(tài)方法里不能訪問(wèn)類的非靜態(tài)成員變量和方法。

        實(shí)例?法:依存于類的實(shí)例,需要使用"對(duì)象名.?法名"的?式調(diào)用;可以訪問(wèn)類的所有成員變量和方法。

        24.final 關(guān)鍵字有什么作用?

        final 表示不可變的意思,可用于修飾類、屬性和方法:

        • 被 final 修飾的類不可以被繼承

        • 被 final 修飾的方法不可以被重寫

        • 被 final 修飾的變量不可變,被 final 修飾的變量必須被顯式第指定初始值,還得注意的是,這里的不可變指的是變量的引用不可變,不是引用指向的內(nèi)容的不可變。

          例如:

          ????????final?StringBuilder?sb?=?new?StringBuilder("abc");
          ????????sb.append("d");
          ????????System.out.println(sb);??//abcd

          一張圖說(shuō)明:

          final修飾變量

        25.final、finally、finalize 的區(qū)別?

        • final 用于修飾變量、方法和類:final 修飾的類不可被繼承;修飾的方法不可被重寫;修飾的變量不可變。

        • finally 作為異常處理的一部分,它只能在 try/catch 語(yǔ)句中,并且附帶一個(gè)語(yǔ)句塊表示這段語(yǔ)句最終一定被執(zhí)行(無(wú)論是否拋出異常),經(jīng)常被用在需要釋放資源的情況下,System.exit (0) 可以阻斷 finally 執(zhí)行。

        • finalize 是在 java.lang.Object 里定義的方法,也就是說(shuō)每一個(gè)對(duì)象都有這么個(gè)方法,這個(gè)方法在 gc 啟動(dòng),該對(duì)象被回收的時(shí)候被調(diào)用。

          一個(gè)對(duì)象的 finalize 方法只會(huì)被調(diào)用一次,finalize 被調(diào)用不一定會(huì)立即回收該對(duì)象,所以有可能調(diào)用 finalize 后,該對(duì)象又不需要被回收了,然后到了真正要被回收的時(shí)候,因?yàn)榍懊嬲{(diào)用過(guò)一次,所以不會(huì)再次調(diào)用 finalize 了,進(jìn)而產(chǎn)生問(wèn)題,因此不推薦使用 finalize 方法。

        26.==和 equals 的區(qū)別?

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

        equals() : 它的作?也是判斷兩個(gè)對(duì)象是否相等。但是這個(gè)“相等”一般也分兩種情況:

        • 默認(rèn)情況:類沒(méi)有覆蓋 equals() ?法。則通過(guò) equals() 比較該類的兩個(gè)對(duì)象時(shí),等價(jià)于通過(guò)“ == ”比較這兩個(gè)對(duì)象,還是相當(dāng)于比較內(nèi)存地址。

        • 自定義情況:類覆蓋了 equals() ?法。我們平時(shí)覆蓋的 equals()方法一般是比較兩個(gè)對(duì)象的內(nèi)容是否相同,自定義了一個(gè)相等的標(biāo)準(zhǔn),也就是兩個(gè)對(duì)象的值是否相等。

        舉個(gè)例?,Person,我們認(rèn)為兩個(gè)人的編號(hào)和姓名相同,就是一個(gè)人:

        public?class?Person?{
        ????private?String?no;
        ????private?String?name;

        ????@Override
        ????public?boolean?equals(Object?o)?{
        ????????if?(this?==?o)?return?true;
        ????????if?(!(o?instanceof?Person))?return?false;
        ????????Person?person?=?(Person)?o;
        ????????return?Objects.equals(no,?person.no)?&&
        ????????????????Objects.equals(name,?person.name);
        ????}

        ????@Override
        ????public?int?hashCode()?{
        ????????return?Objects.hash(no,?name);
        ????}
        }

        27.hashCode 與 equals?

        這個(gè)也是面試常問(wèn)——“你重寫過(guò) hashcode 和 equals 么,為什么重寫 equals 時(shí)必須重寫 hashCode ?法?”

        什么是 HashCode?

        hashCode() 的作?是獲取哈希碼,也稱為散列碼;它實(shí)際上是返回?個(gè) int 整數(shù),定義在 Object 類中, 是一個(gè)本地?法,這個(gè)?法通常?來(lái)將對(duì)象的內(nèi)存地址轉(zhuǎn)換為整數(shù)之后返回。

        public?native?int?hashCode();

        哈希碼主要在哈希表這類集合映射的時(shí)候用到,哈希表存儲(chǔ)的是鍵值對(duì)(key-value),它的特點(diǎn)是:能根據(jù)“鍵”快速的映射到對(duì)應(yīng)的“值”。這其中就利?到了哈希碼!

        為什么要有 hashCode?

        上面已經(jīng)講了,主要是在哈希表這種結(jié)構(gòu)中用的到。

        例如 HashMap 怎么把 key 映射到對(duì)應(yīng)的 value 上呢?用的就是哈希取余法,也就是拿哈希碼和存儲(chǔ)元素的數(shù)組的長(zhǎng)度取余,獲取 key 對(duì)應(yīng)的 value 所在的下標(biāo)位置。

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

        如果兩個(gè)對(duì)象相等,則 hashcode ?定也是相同的。兩個(gè)對(duì)象相等,對(duì)兩個(gè)對(duì)象分別調(diào)? equals ?法都返回 true。反之,兩個(gè)對(duì)象有相同的 hashcode 值,它們也不?定是相等的 。因此,equals ?法被覆蓋過(guò),則 hashCode ?法也必須被覆蓋。

        hashCode() 的默認(rèn)?為是對(duì)堆上的對(duì)象產(chǎn)?獨(dú)特值。如果沒(méi)有重寫 hashCode() ,則該 class 的兩個(gè)對(duì)象?論如何都不會(huì)相等(即使這兩個(gè)對(duì)象指向相同的數(shù)據(jù))

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

        因?yàn)榭赡軙?huì)碰撞, hashCode() 所使?的散列算法也許剛好會(huì)讓多個(gè)對(duì)象傳回相同的散列值。越糟糕的散列算法越容易碰撞,但這也與數(shù)據(jù)值域分布的特性有關(guān)(所謂碰撞也就是指的是不同的對(duì)象得到相同的 hashCode )。

        28.Java 是值傳遞,還是引用傳遞?

        Java 語(yǔ)言是值傳遞。Java 語(yǔ)言的方法調(diào)用只支持參數(shù)的值傳遞。當(dāng)一個(gè)對(duì)象實(shí)例作為一個(gè)參數(shù)被傳遞到方法中時(shí),參數(shù)的值就是對(duì)該對(duì)象的引用。對(duì)象的屬性可以在被調(diào)用過(guò)程中被改變,但對(duì)對(duì)象引用的改變是不會(huì)影響到調(diào)用者的。

        JVM 的內(nèi)存分為堆和棧,其中棧中存儲(chǔ)了基本數(shù)據(jù)類型和引用數(shù)據(jù)類型實(shí)例的地址,也就是對(duì)象地址。

        而對(duì)象所占的空間是在堆中開(kāi)辟的,所以傳遞的時(shí)候可以理解為把變量存儲(chǔ)的對(duì)象地址給傳遞過(guò)去,因此引用類型也是值傳遞。

        Java引用數(shù)據(jù)值傳遞示意圖

        29.深拷貝和淺拷貝?

        • 淺拷貝:僅拷貝被拷貝對(duì)象的成員變量的值,也就是基本數(shù)據(jù)類型變量的值,和引用數(shù)據(jù)類型變量的地址值,而對(duì)于引用類型變量指向的堆中的對(duì)象不會(huì)拷貝。
        • 深拷貝:完全拷貝一個(gè)對(duì)象,拷貝被拷貝對(duì)象的成員變量的值,堆中的對(duì)象也會(huì)拷貝一份。

        例如現(xiàn)在有一個(gè) order 對(duì)象,里面有一個(gè) products 列表,它的淺拷貝和深拷貝的示意圖:

        淺拷貝和深拷貝示意圖

        因此深拷貝是安全的,淺拷貝的話如果有引用類型,那么拷貝后對(duì)象,引用類型變量修改,會(huì)影響原對(duì)象。

        淺拷貝如何實(shí)現(xiàn)呢?

        Object 類提供的 clone()方法可以非常簡(jiǎn)單地實(shí)現(xiàn)對(duì)象的淺拷貝。

        深拷貝如何實(shí)現(xiàn)呢?

        • 重寫克隆方法:重寫克隆方法,引用類型變量單獨(dú)克隆,這里可能會(huì)涉及多層遞歸。
        • 序列化:可以先講原對(duì)象序列化,再反序列化成拷貝對(duì)象。

        30.Java 創(chuàng)建對(duì)象有哪幾種方式?

        Java 中有以下四種創(chuàng)建對(duì)象的方式:

        Java創(chuàng)建對(duì)象的四種方式
        • new 創(chuàng)建新對(duì)象
        • 通過(guò)反射機(jī)制
        • 采用 clone 機(jī)制
        • 通過(guò)序列化機(jī)制

        前兩者都需要顯式地調(diào)用構(gòu)造方法。對(duì)于 clone 機(jī)制,需要注意淺拷貝和深拷貝的區(qū)別,對(duì)于序列化機(jī)制需要明確其實(shí)現(xiàn)原理,在 Java 中序列化可以通過(guò)實(shí)現(xiàn) Externalizable 或者 Serializable 來(lái)實(shí)現(xiàn)。

        四、常用類

        1)String

        31.String 是 Java 基本數(shù)據(jù)類型嗎?可以被繼承嗎?

        String 是 Java 基本數(shù)據(jù)類型嗎?

        不是。Java 中的基本數(shù)據(jù)類型只有 8 個(gè):byte、short、int、long、float、double、char、boolean;除了基本類型(primitive type),剩下的都是引用類型(reference type)。

        String 是一個(gè)比較特殊的引用數(shù)據(jù)類型。

        String 類可以繼承嗎?

        不行。String 類使用 final 修飾,是所謂的不可變類,無(wú)法被繼承。

        32.String 和 StringBuilder、StringBuffer 的區(qū)別?

        • String:String 的值被創(chuàng)建后不能修改,任何對(duì) String 的修改都會(huì)引發(fā)新的 String 對(duì)象的生成。
        • StringBuffer:跟 String 類似,但是值可以被修改,使用 synchronized 來(lái)保證線程安全。
        • StringBuilder:StringBuffer 的非線程安全版本,性能上更高一些。

        33.String str1 = new String("abc")和 String str2 = "abc" 和 區(qū)別?

        兩個(gè)語(yǔ)句都會(huì)去字符串常量池中檢查是否已經(jīng)存在 “abc”,如果有則直接使用,如果沒(méi)有則會(huì)在常量池中創(chuàng)建 “abc” 對(duì)象。

        堆與常量池中的String

        但是不同的是,String str1 = new String("abc") 還會(huì)通過(guò) new String() 在堆里創(chuàng)建一個(gè) "abc" 字符串對(duì)象實(shí)例。所以后者可以理解為被前者包含。

        String s = new String("abc")創(chuàng)建了幾個(gè)對(duì)象?

        很明顯,一個(gè)或兩個(gè)。如果字符串常量池已經(jīng)有“abc”,則是一個(gè);否則,兩個(gè)。

        當(dāng)字符創(chuàng)常量池沒(méi)有 “abc”,此時(shí)會(huì)創(chuàng)建如下兩個(gè)對(duì)象:

        • 一個(gè)是字符串字面量 "abc" 所對(duì)應(yīng)的、字符串常量池中的實(shí)例
        • 另一個(gè)是通過(guò) new String() 創(chuàng)建并初始化的,內(nèi)容與"abc"相同的實(shí)例,在堆中。

        34.String 不是不可變類嗎?字符串拼接是如何實(shí)現(xiàn)的?

        String 的確是不可變的,“**+**”的拼接操作,其實(shí)是會(huì)生成新的對(duì)象。

        例如:

        String?a?=?"hello?";
        String?b?=?"world!";
        String?ab?=?a?+?b;

        jdk1.8 之前,a 和 b 初始化時(shí)位于字符串常量池,ab 拼接后的對(duì)象位于堆中。經(jīng)過(guò)拼接新生成了 String 對(duì)象。如果拼接多次,那么會(huì)生成多個(gè)中間對(duì)象。

        內(nèi)存如下:

        jdk1.8之前的字符串拼接

        Java8 時(shí)JDK 對(duì)“+”號(hào)拼接進(jìn)行了優(yōu)化,上面所寫的拼接方式會(huì)被優(yōu)化為基于 StringBuilder 的 append 方法進(jìn)行處理。Java 會(huì)在編譯期對(duì)“+”號(hào)進(jìn)行處理。

        下面是通過(guò) javap -verbose 命令反編譯字節(jié)碼的結(jié)果,很顯然可以看到 StringBuilder 的創(chuàng)建和 append 方法的調(diào)用。

        stack=2,?locals=4,?args_size=1
        ?????0:?ldc???????????#2??????????????????//?String?hello
        ?????2:?astore_1
        ?????3:?ldc???????????#3??????????????????//?String?world!
        ?????5:?astore_2
        ?????6:?new???????????#4??????????????????//?class?java/lang/StringBuilder
        ?????9:?dup
        ????10:?invokespecial?#5??????????????????//?Method?java/lang/StringBuilder."":()V
        ????13:?aload_1
        ????14:?invokevirtual?#6??????????????????//?Method?java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        ????17:?aload_2
        ????18:?invokevirtual?#6??????????????????//?Method?java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        ????21:?invokevirtual?#7??????????????????//?Method?java/lang/StringBuilder.toString:()Ljava/lang/String;
        ????24:?astore_3
        ????25:?return

        也就是說(shuō)其實(shí)上面的代碼其實(shí)相當(dāng)于:

        String?a?=?"hello?";
        String?b?=?"world!";
        StringBuilder?sb?=?new?StringBuilder();
        sb.append(a);
        sb.append(b);
        String?ab?=?sb.toString();

        此時(shí),如果再籠統(tǒng)的回答:通過(guò)加號(hào)拼接字符串會(huì)創(chuàng)建多個(gè) String 對(duì)象,因此性能比 StringBuilder 差,就是錯(cuò)誤的了。因?yàn)楸举|(zhì)上加號(hào)拼接的效果最終經(jīng)過(guò)編譯器處理之后和 StringBuilder 是一致的。

        當(dāng)然,循環(huán)里拼接還是建議用 StringBuilder,為什么,因?yàn)檠h(huán)一次就會(huì)創(chuàng)建一個(gè)新的 StringBuilder 對(duì)象,大家可以自行實(shí)驗(yàn)。

        35.intern 方法有什么作用?

        JDK 源碼里已經(jīng)對(duì)這個(gè)方法進(jìn)行了說(shuō)明:

        ?????*?


        ?????*?When?the?intern?method?is?invoked,?if?the?pool?already?contains?a
        ?????*?string?equal?to?this?{@code?String}?object?as?determined?by
        ?????*?the?{@link?#equals(Object)}?method,?then?the?string?from?the?pool?is
        ?????*?returned.?Otherwise,?this?{@code?String}?object?is?added?to?the
        ?????*?pool?and?a?reference?to?this?{@code?String}?object?is?returned.
        ?????*?


        意思也很好懂:

        • 如果當(dāng)前字符串內(nèi)容存在于字符串常量池(即 equals()方法為 true,也就是內(nèi)容一樣),直接返回字符串常量池中的字符串
        • 否則,將此 String 對(duì)象添加到池中,并返回 String 對(duì)象的引用

        2)Integer

        36.Integer a= 127,Integer b = 127;Integer c= 128,Integer d = 128;,相等嗎?

        答案是 a 和 b 相等,c 和 d 不相等。

        • 對(duì)于基本數(shù)據(jù)類型==比較的值
        • 對(duì)于引用數(shù)據(jù)類型==比較的是地址

        Integer a= 127 這種賦值,是用到了 Integer 自動(dòng)裝箱的機(jī)制。自動(dòng)裝箱的時(shí)候會(huì)去緩存池里取 Integer 對(duì)象,沒(méi)有取到才會(huì)創(chuàng)建新的對(duì)象。

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

        ????public?static?void?main(String[]?args)?{
        ????????Integer?a?=?127;
        ????????Integer?b?=?127;
        ????????Integer?b1?=?new?Integer(127);
        ????????System.out.println(a?==?b);?//true
        ????????System.out.println(b==b1);??//false

        ????????Integer?c?=?128;
        ????????Integer?d?=?128;
        ????????System.out.println(c?==?d);??//false
        ????}

        什么是 Integer 緩存?

        因?yàn)楦鶕?jù)實(shí)踐發(fā)現(xiàn)大部分的數(shù)據(jù)操作都集中在值比較小的范圍,因此 Integer 搞了個(gè)緩存池,默認(rèn)范圍是 -128 到 127,可以根據(jù)通過(guò)設(shè)置JVM-XX:AutoBoxCacheMax=來(lái)修改緩存的最大值,最小值改不了。

        實(shí)現(xiàn)的原理是 int 在自動(dòng)裝箱的時(shí)候會(huì)調(diào)用 Integer.valueOf,進(jìn)而用到了 IntegerCache。

        Integer.valueOf

        很簡(jiǎn)單,就是判斷下值是否在緩存范圍之內(nèi),如果是的話去 IntegerCache 中取,不是的話就創(chuàng)建一個(gè)新的 Integer 對(duì)象。

        IntegerCache 是一個(gè)靜態(tài)內(nèi)部類, 在靜態(tài)塊中會(huì)初始化好緩存值。

        ?private?static?class?IntegerCache?{
        ?????……
        ?????static?{
        ????????????//創(chuàng)建Integer對(duì)象存儲(chǔ)
        ????????????for(int?k?=?0;?k?????????????????cache[k]?=?new?Integer(j++);
        ?????????……
        ?????}
        ?}

        37.String 怎么轉(zhuǎn)成 Integer 的?原理?

        PS:這道題印象中在一些面經(jīng)中出場(chǎng)過(guò)幾次。

        String 轉(zhuǎn)成 Integer,主要有兩個(gè)方法:

        • Integer.parseInt(String s)
        • Integer.valueOf(String s)

        不管哪一種,最終還是會(huì)調(diào)用 Integer 類內(nèi)中的parseInt(String s, int radix)方法。

        拋去一些邊界之類的看看核心代碼:

        public?static?int?parseInt(String?s,?int?radix)
        ????????????????throws?NumberFormatException
        ????
        {

        ????????int?result?=?0;
        ????????//是否是負(fù)數(shù)
        ????????boolean?negative?=?false;
        ????????//char字符數(shù)組下標(biāo)和長(zhǎng)度
        ????????int?i?=?0,?len?=?s.length();
        ????????……
        ????????int?digit;
        ????????//判斷字符長(zhǎng)度是否大于0,否則拋出異常
        ????????if?(len?>?0)?{
        ????????????……
        ????????????while?(i?????????????????//?Accumulating?negatively?avoids?surprises?near?MAX_VALUE
        ????????????????//返回指定基數(shù)中字符表示的數(shù)值。(此處是十進(jìn)制數(shù)值)
        ????????????????digit?=?Character.digit(s.charAt(i++),radix);
        ????????????????//進(jìn)制位乘以數(shù)值
        ????????????????result?*=?radix;
        ????????????????result?-=?digit;
        ????????????}
        ????????}
        ????????//根據(jù)上面得到的是否負(fù)數(shù),返回相應(yīng)的值
        ????????return?negative???result?:?-result;
        ????}

        去掉枝枝蔓蔓(當(dāng)然這些枝枝蔓蔓可以去看看,源碼 cover 了很多情況),其實(shí)剩下的就是一個(gè)簡(jiǎn)單的字符串遍歷計(jì)算,不過(guò)計(jì)算方式有點(diǎn)反常規(guī),是用負(fù)的值累減。

        parseInt示意圖

        3)Object

        38.Object 類的常見(jiàn)方法?

        Object 類是一個(gè)特殊的類,是所有類的父類,也就是說(shuō)所有類都可以調(diào)用它的方法。它主要提供了以下 11 個(gè)方法,大概可以分為六類:

        Object類的方法

        對(duì)象比較

        • public native int hashCode() :native 方法,用于返回對(duì)象的哈希碼,主要使用在哈希表中,比如 JDK 中的 HashMap。
        • public boolean equals(Object obj):用于比較 2 個(gè)對(duì)象的內(nèi)存地址是否相等,String 類對(duì)該方法進(jìn)行了重寫用戶比較字符串的值是否相等。

        對(duì)象拷貝

        • protected native Object clone() throws CloneNotSupportedException:naitive 方法,用于創(chuàng)建并返回當(dāng)前對(duì)象的一份拷貝。一般情況下,對(duì)于任何對(duì)象 x,表達(dá)式 x.clone() != x 為 true,x.clone().getClass() == x.getClass() 為 true。Object 本身沒(méi)有實(shí)現(xiàn) Cloneable 接口,所以不重寫 clone 方法并且進(jìn)行調(diào)用的話會(huì)發(fā)生 CloneNotSupportedException 異常。

        對(duì)象轉(zhuǎn)字符串:

        • public String toString():返回類的名字@實(shí)例的哈希碼的 16 進(jìn)制的字符串。建議 Object 所有的子類都重寫這個(gè)方法。

        多線程調(diào)度:

        • public final native void notify():native 方法,并且不能重寫。喚醒一個(gè)在此對(duì)象監(jiān)視器上等待的線程(監(jiān)視器相當(dāng)于就是鎖的概念)。如果有多個(gè)線程在等待只會(huì)任意喚醒一個(gè)。
        • public final native void notifyAll():native 方法,并且不能重寫。跟 notify 一樣,唯一的區(qū)別就是會(huì)喚醒在此對(duì)象監(jiān)視器上等待的所有線程,而不是一個(gè)線程。
        • public final native void wait(long timeout) throws InterruptedException:native 方法,并且不能重寫。暫停線程的執(zhí)行。注意:sleep 方法沒(méi)有釋放鎖,而 wait 方法釋放了鎖 。timeout 是等待時(shí)間。
        • public final void wait(long timeout, int nanos) throws InterruptedException:多了 nanos 參數(shù),這個(gè)參數(shù)表示額外時(shí)間(以毫微秒為單位,范圍是 0-999999)。所以超時(shí)的時(shí)間還需要加上 nanos 毫秒。
        • public final void wait() throws InterruptedException:跟之前的 2 個(gè) wait 方法一樣,只不過(guò)該方法一直等待,沒(méi)有超時(shí)時(shí)間這個(gè)概念

        反射:

        • public final native Class getClass():native 方法,用于返回當(dāng)前運(yùn)行時(shí)對(duì)象的 Class 對(duì)象,使用了 final 關(guān)鍵字修飾,故不允許子類重寫。

        垃圾回收:

        • protected void finalize() throws Throwable :通知垃圾收集器回收對(duì)象。

        五、異常處理

        39.Java 中異常處理體系?

        Java 的異常體系是分為多層的。

        Java異常體系

        Throwable是 Java 語(yǔ)言中所有錯(cuò)誤或異常的基類。Throwable 又分為ErrorException,其中 Error 是系統(tǒng)內(nèi)部錯(cuò)誤,比如虛擬機(jī)異常,是程序無(wú)法處理的。Exception是程序問(wèn)題導(dǎo)致的異常,又分為兩種:

        • CheckedException 受檢異常:編譯器會(huì)強(qiáng)制檢查并要求處理的異常。
        • RuntimeException 運(yùn)行時(shí)異常:程序運(yùn)行中出現(xiàn)異常,比如我們熟悉的空指針、數(shù)組下標(biāo)越界等等

        40.異常的處理方式?

        針對(duì)異常的處理主要有兩種方式:

        異常處理
        • 遇到異常不進(jìn)行具體處理,而是繼續(xù)拋給調(diào)用者 (throw,throws)

        拋出異常有三種形式,一是 throw,一個(gè) throws,還有一種系統(tǒng)自動(dòng)拋異常。

        throws 用在方法上,后面跟的是異常類,可以跟多個(gè);而 throw 用在方法內(nèi),后面跟的是異常對(duì)象。

        • try catch 捕獲異常

        在 catch 語(yǔ)句塊中補(bǔ)貨發(fā)生的異常,并進(jìn)行處理。

        ???????try?{
        ????????????//包含可能會(huì)出現(xiàn)異常的代碼以及聲明異常的方法
        ????????}catch(Exception?e)?{
        ????????????//捕獲異常并進(jìn)行處理
        ????????}finally?{???????????????????????????????????????????????????????}
        ????????????//可選,必執(zhí)行的代碼
        ????????}

        try-catch 捕獲異常的時(shí)候還可以選擇加上 finally 語(yǔ)句塊,finally 語(yǔ)句塊不管程序是否正常執(zhí)行,最終它都會(huì)必然執(zhí)行。

        41.三道經(jīng)典異常處理代碼題

        題目 1

        public?class?TryDemo?{
        ????public?static?void?main(String[]?args)?{
        ????????System.out.println(test());
        ????}
        ????public?static?int?test()?{
        ????????try?{
        ????????????return?1;
        ????????}?catch?(Exception?e)?{
        ????????????return?2;
        ????????}?finally?{
        ????????????System.out.print("3");
        ????????}
        ????}
        }

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

        try、catch。finally 的基礎(chǔ)用法,在 return 前會(huì)先執(zhí)行 finally 語(yǔ)句塊,所以是先輸出 finally 里的 3,再輸出 return 的 1。

        題目 2

        public?class?TryDemo?{
        ????public?static?void?main(String[]?args)?{
        ????????System.out.println(test1());
        ????}
        ????public?static?int?test1()?{
        ????????try?{
        ????????????return?2;
        ????????}?finally?{
        ????????????return?3;
        ????????}
        ????}
        }

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

        try 返回前先執(zhí)行 finally,結(jié)果 finally 里不按套路出牌,直接 return 了,自然也就走不到 try 里面的 return 了。

        finally 里面使用 return 僅存在于面試題中,實(shí)際開(kāi)發(fā)這么寫要挨吊的。

        題目 3

        public?class?TryDemo?{
        ????public?static?void?main(String[]?args)?{
        ????????System.out.println(test1());
        ????}
        ????public?static?int?test1()?{
        ????????int?i?=?0;
        ????????try?{
        ????????????i?=?2;
        ????????????return?i;
        ????????}?finally?{
        ????????????i?=?3;
        ????????}
        ????}
        }

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

        大家可能會(huì)以為結(jié)果應(yīng)該是 3,因?yàn)樵?return 前會(huì)執(zhí)行 finally,而 i 在 finally 中被修改為 3 了,那最終返回 i 不是應(yīng)該為 3 嗎?

        但其實(shí),在執(zhí)行 finally 之前,JVM 會(huì)先將 i 的結(jié)果暫存起來(lái),然后 finally 執(zhí)行完畢后,會(huì)返回之前暫存的結(jié)果,而不是返回 i,所以即使 i 已經(jīng)被修改為 3,最終返回的還是之前暫存起來(lái)的結(jié)果 2。

        六、I/O

        42.Java 中 IO 流分為幾種?

        流按照不同的特點(diǎn),有很多種劃分方式。

        • 按照流的流向分,可以分為輸入流輸出流;
        • 按照操作單元?jiǎng)澐?,可以劃分?strong style="color: rgb(53, 179, 120);">字節(jié)流字符流;
        • 按照流的角色劃分為節(jié)點(diǎn)流處理流

        Java Io 流共涉及 40 多個(gè)類,看上去雜亂,其實(shí)都存在一定的關(guān)聯(lián), Java I0 流的 40 多個(gè)類都是從如下 4 個(gè)抽象類基類中派生出來(lái)的。

        • InputStream/Reader: 所有的輸入流的基類,前者是字節(jié)輸入流,后者是字符輸入流。
        • OutputStream/Writer: 所有輸出流的基類,前者是字節(jié)輸出流,后者是字符輸出流。
        IO-操作方式分類-圖片來(lái)源參考[2]

        IO 流用到了什么設(shè)計(jì)模式?

        其實(shí),Java 的 IO 流體系還用到了一個(gè)設(shè)計(jì)模式——裝飾器模式。

        InputStream 相關(guān)的部分類圖如下,篇幅有限,裝飾器模式就不展開(kāi)說(shuō)了。

        Java IO流用到裝飾器模式

        43.既然有了字節(jié)流,為什么還要有字符流?

        其實(shí)字符流是由 Java 虛擬機(jī)將字節(jié)轉(zhuǎn)換得到的,問(wèn)題就出在這個(gè)過(guò)程還比較耗時(shí),并且,如果我們不知道編碼類型就很容易出現(xiàn)亂碼問(wèn)題。

        所以, I/O 流就干脆提供了一個(gè)直接操作字符的接口,方便我們平時(shí)對(duì)字符進(jìn)行流操作。如果音頻文件、圖片等媒體文件用字節(jié)流比較好,如果涉及到字符的話使用字符流比較好。

        44.BIO、NIO、AIO?

        BIO、NIO、AIO

        BIO(blocking I/O) :就是傳統(tǒng)的 IO,同步阻塞,服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程,即客戶端有連接請(qǐng)求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理,如果這個(gè)連接不做任何事情會(huì)造成不必要的線程開(kāi)銷,可以通過(guò)連接池機(jī)制改善(實(shí)現(xiàn)多個(gè)客戶連接服務(wù)器)。

        BIO、NIO、AIO

        BIO 方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對(duì)服務(wù)器資源要求比較高,并發(fā)局限于應(yīng)用中,JDK1.4 以前的唯一選擇,程序簡(jiǎn)單易理解。

        NIO :全稱 java non-blocking IO,是指 JDK 提供的新 API。從 JDK1.4 開(kāi)始,Java 提供了一系列改進(jìn)的輸入/輸出的新特性,被統(tǒng)稱為 NIO(即 New IO)。

        NIO 是同步非阻塞的,服務(wù)器端用一個(gè)線程處理多個(gè)連接,客戶端發(fā)送的連接請(qǐng)求會(huì)注冊(cè)到多路復(fù)用器上,多路復(fù)用器輪詢到連接有 IO 請(qǐng)求就進(jìn)行處理:

        NIO線程

        NIO 的數(shù)據(jù)是面向緩沖區(qū) Buffer的,必須從 Buffer 中讀取或?qū)懭搿?/p>

        所以完整的 NIO 示意圖:

        NIO完整示意圖

        可以看出,NIO 的運(yùn)行機(jī)制:

        • 每個(gè) Channel 對(duì)應(yīng)一個(gè) Buffer。
        • Selector 對(duì)應(yīng)一個(gè)線程,一個(gè)線程對(duì)應(yīng)多個(gè) Channel。
        • Selector 會(huì)根據(jù)不同的事件,在各個(gè)通道上切換。
        • Buffer 是內(nèi)存塊,底層是數(shù)據(jù)。

        AIO:JDK 7 引入了 Asynchronous I/O,是異步不阻塞的 IO。在進(jìn)行 I/O 編程中,常用到兩種模式:Reactor 和 Proactor。Java 的 NIO 就是 Reactor,當(dāng)有事件觸發(fā)時(shí),服務(wù)器端得到通知,進(jìn)行相應(yīng)的處理,完成后才通知服務(wù)端程序啟動(dòng)線程去處理,一般適用于連接數(shù)較多且連接時(shí)間較長(zhǎng)的應(yīng)用。

        七、序列化

        45.什么是序列化?什么是反序列化?

        什么是序列化,序列化就是把 Java 對(duì)象轉(zhuǎn)為二進(jìn)制流,方便存儲(chǔ)和傳輸。

        所以反序列化就是把二進(jìn)制流恢復(fù)成對(duì)象

        序列化和反序列化

        類比我們生活中一些大件物品的運(yùn)輸,運(yùn)輸?shù)臅r(shí)候把它拆了打包,用的時(shí)候再拆包組裝。

        Serializable 接口有什么用?

        這個(gè)接口只是一個(gè)標(biāo)記,沒(méi)有具體的作用,但是如果不實(shí)現(xiàn)這個(gè)接口,在有些序列化場(chǎng)景會(huì)報(bào)錯(cuò),所以一般建議,創(chuàng)建的 JavaBean 類都實(shí)現(xiàn) Serializable。

        serialVersionUID 又有什么用?

        serialVersionUID 就是起驗(yàn)證作用。

        private?static?final?long?serialVersionUID?=?1L;

        我們經(jīng)常會(huì)看到這樣的代碼,這個(gè) ID 其實(shí)就是用來(lái)驗(yàn)證序列化的對(duì)象和反序列化對(duì)應(yīng)的對(duì)象 ID 是否一致。

        這個(gè) ID 的數(shù)字其實(shí)不重要,無(wú)論是 1L 還是 IDE 自動(dòng)生成的,只要序列化時(shí)候?qū)ο蟮?serialVersionUID 和反序列化時(shí)候?qū)ο蟮?serialVersionUID 一致的話就行。

        如果沒(méi)有顯示指定 serialVersionUID ,則編譯器會(huì)根據(jù)類的相關(guān)信息自動(dòng)生成一個(gè),可以認(rèn)為是一個(gè)指紋。

        所以如果你沒(méi)有定義一個(gè) serialVersionUID, 結(jié)果序列化一個(gè)對(duì)象之后,在反序列化之前把對(duì)象的類的結(jié)構(gòu)改了,比如增加了一個(gè)成員變量,則此時(shí)的反序列化會(huì)失敗。

        因?yàn)轭惖慕Y(jié)構(gòu)變了,所以 serialVersionUID 就不一致。

        Java 序列化不包含靜態(tài)變量?

        序列化的時(shí)候是不包含靜態(tài)變量的。

        如果有些變量不想序列化,怎么辦?

        對(duì)于不想進(jìn)行序列化的變量,使用transient關(guān)鍵字修飾。

        transient 關(guān)鍵字的作用是:阻止實(shí)例中那些用此關(guān)鍵字修飾的的變量序列化;當(dāng)對(duì)象被反序列化時(shí),被 transient 修飾的變量值不會(huì)被持久化和恢復(fù)。transient 只能修飾變量,不能修飾類和方法。

        46.說(shuō)說(shuō)有幾種序列化方式?

        Java 序列化方式有很多,常見(jiàn)的有三種:

        Java常見(jiàn)序列化方式
        • Java 對(duì)象流列化 :Java 原生序列化方法即通過(guò) Java 原生流(InputStream 和 OutputStream 之間的轉(zhuǎn)化)的方式進(jìn)行轉(zhuǎn)化,一般是對(duì)象輸出流 ObjectOutputStream和對(duì)象輸入流ObjectI叩utStream
        • Json 序列化:這個(gè)可能是我們最常用的序列化方式,Json 序列化的選擇很多,一般會(huì)使用 jackson 包,通過(guò) ObjectMapper 類來(lái)進(jìn)行一些操作,比如將對(duì)象轉(zhuǎn)化為 byte 數(shù)組或者將 json 串轉(zhuǎn)化為對(duì)象。
        • ProtoBuff 序列化:ProtocolBuffer 是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式,ProtoBuff 序列化對(duì)象可以很大程度上將其壓縮,可以大大減少數(shù)據(jù)傳輸大小,提高系統(tǒng)性能。

        八、泛型

        47.Java 泛型了解么?什么是類型擦除?介紹一下常用的通配符?

        什么是泛型?

        Java 泛型(generics)是 JDK 5 中引入的一個(gè)新特性, 泛型提供了編譯時(shí)類型安全檢測(cè)機(jī)制,該機(jī)制允許程序員在編譯時(shí)檢測(cè)到非法的類型。泛型的本質(zhì)是參數(shù)化類型,也就是說(shuō)所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù)。

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

        list.add(12);
        //這里直接添加會(huì)報(bào)錯(cuò)
        list.add("a");
        Class?clazz?=?list.getClass();
        Method?add?=?clazz.getDeclaredMethod("add",?Object.class);
        //但是通過(guò)反射添加,是可以的
        add.invoke(list,?"kl");

        System.out.println(list);

        泛型一般有三種使用方式:泛型類泛型接口、泛型方法

        泛型類、泛型接口、泛型方法

        1.泛型類

        //此處T可以隨便寫為任意標(biāo)識(shí),常見(jiàn)的如T、E、K、V等形式的參數(shù)常用于表示泛型
        //在實(shí)例化泛型類時(shí),必須指定T的具體類型
        public?class?Generic<T>{

        ????private?T?key;

        ????public?Generic(T?key)?{
        ????????this.key?=?key;
        ????}

        ????public?T?getKey(){
        ????????return?key;
        ????}
        }

        如何實(shí)例化泛型類:

        Generic?genericInteger?=?new?Generic(123456);

        2.泛型接口

        class?GeneratorImpl<T>?implements?Generator<T>{
        ????@Override
        ????public?T?method()?{
        ????????return?null;
        ????}
        }

        實(shí)現(xiàn)泛型接口,指定類型:

        class?GeneratorImpl<T>?implements?Generator<String>{
        ????@Override
        ????public?String?method()?{
        ????????return?"hello";
        ????}
        }

        3.泛型方法

        ???public?static??void?printArray(?E[]?inputArray?)
        ???
        {
        ?????????for?(?E?element?:?inputArray?){
        ????????????System.out.printf(?"%s?",?element?);
        ?????????}
        ?????????System.out.println();
        ????}

        使用:

        //?創(chuàng)建不同類型數(shù)組:Integer, Double 和 Character
        Integer[]?intArray?=?{?1,?2,?3?};
        String[]?stringArray?=?{?"Hello",?"World"?};
        printArray(?intArray??);
        printArray(?stringArray??);

        泛型常用的通配符有哪些?

        常用的通配符為:T,E,K,V,?

        • ?表示不確定的 java 類型
        • T (type) 表示具體的一個(gè) java 類型
        • K V (key value) 分別代表 java 鍵值中的 Key Value
        • E (element) 代表 Element

        什么是泛型擦除?

        所謂的泛型擦除,官方名叫“類型擦除”。

        Java 的泛型是偽泛型,這是因?yàn)?Java 在編譯期間,所有的類型信息都會(huì)被擦掉。

        也就是說(shuō),在運(yùn)行的時(shí)候是沒(méi)有泛型的。

        例如這段代碼,往一群貓里放條狗:

        LinkedList?cats?=?new?LinkedList();
        LinkedList?list?=?cats;??//?注意我在這里把范型去掉了,但是list和cats是同一個(gè)鏈表!
        list.add(new?Dog());??//?完全沒(méi)問(wèn)題!

        因?yàn)?Java 的范型只存在于源碼里,編譯的時(shí)候給你靜態(tài)地檢查一下范型類型是否正確,而到了運(yùn)行時(shí)就不檢查了。上面這段代碼在 JRE(Java運(yùn)行環(huán)境)看來(lái)和下面這段沒(méi)區(qū)別:

        LinkedList?cats?=?new?LinkedList();??//?注意:沒(méi)有范型!
        LinkedList?list?=?cats;
        list.add(new?Dog());

        為什么要類型擦除呢?

        主要是為了向下兼容,因?yàn)?JDK5 之前是沒(méi)有泛型的,為了讓 JVM 保持向下兼容,就出了類型擦除這個(gè)策略。

        九、注解

        48.說(shuō)一下你對(duì)注解的理解?

        Java 注解本質(zhì)上是一個(gè)標(biāo)記,可以理解成生活中的一個(gè)人的一些小裝扮,比如戴什么什么帽子,戴什么眼鏡。

        Java注解和帽子

        注解可以標(biāo)記在類上、方法上、屬性上等,標(biāo)記自身也可以設(shè)置一些值,比如帽子顏色是綠色。

        有了標(biāo)記之后,我們就可以在編譯或者運(yùn)行階段去識(shí)別這些標(biāo)記,然后搞一些事情,這就是注解的用處。

        例如我們常見(jiàn)的 AOP,使用注解作為切點(diǎn)就是運(yùn)行期注解的應(yīng)用;比如 lombok,就是注解在編譯期的運(yùn)行。

        注解生命周期有三大類,分別是:

        • RetentionPolicy.SOURCE:給編譯器用的,不會(huì)寫入 class 文件
        • RetentionPolicy.CLASS:會(huì)寫入 class 文件,在類加載階段丟棄,也就是運(yùn)行的時(shí)候就沒(méi)這個(gè)信息了
        • RetentionPolicy.RUNTIME:會(huì)寫入 class 文件,永久保存,可以通過(guò)反射獲取注解信息

        所以我上文寫的是解析的時(shí)候,沒(méi)寫具體是解析啥,因?yàn)椴煌纳芷诘慕馕鰟?dòng)作是不同的。

        像常見(jiàn)的:

        Override注解

        就是給編譯器用的,編譯器編譯的時(shí)候檢查沒(méi)問(wèn)題就 over 了,class 文件里面不會(huì)有 Override 這個(gè)標(biāo)記。

        再比如 Spring 常見(jiàn)的 Autowired ,就是 RUNTIME 的,所以在運(yùn)行的時(shí)候可以通過(guò)反射得到注解的信息,還能拿到標(biāo)記的值 required 。

        Autowired注解

        十、反射

        49.什么是反射?應(yīng)用?原理?

        什么是反射?

        我們通常都是利用new方式來(lái)創(chuàng)建對(duì)象實(shí)例,這可以說(shuō)就是一種“正射”,這種方式在編譯時(shí)候就確定了類型信息。

        而如果,我們想在時(shí)候動(dòng)態(tài)地獲取類信息、創(chuàng)建類實(shí)例、調(diào)用類方法這時(shí)候就要用到反射。

        通過(guò)反射你可以獲取任意一個(gè)類的所有屬性和方法,你還可以調(diào)用這些方法和屬性。

        反射最核心的四個(gè)類:

        Java反射相關(guān)類

        反射的應(yīng)用場(chǎng)景?

        一般我們平時(shí)都是在在寫業(yè)務(wù)代碼,很少會(huì)接觸到直接使用反射機(jī)制的場(chǎng)景。

        但是,這并不代表反射沒(méi)有用。相反,正是因?yàn)榉瓷?,你才能這么輕松地使用各種框架。像 Spring/Spring Boot、MyBatis 等等框架中都大量使用了反射機(jī)制。

        像 Spring 里的很多 注解 ,它真正的功能實(shí)現(xiàn)就是利用反射。

        就像為什么我們使用 Spring 的時(shí)候 ,一個(gè)@Component注解就聲明了一個(gè)類為 Spring Bean 呢?為什么通過(guò)一個(gè) @Value注解就讀取到配置文件中的值呢?究竟是怎么起作用的呢?

        這些都是因?yàn)槲覀兛梢曰诜瓷洳僮黝?,然后獲取到類/屬性/方法/方法的參數(shù)上的注解,注解這里就有兩個(gè)作用,一是標(biāo)記,我們對(duì)注解標(biāo)記的類/屬性/方法進(jìn)行對(duì)應(yīng)的處理;二是注解本身有一些信息,可以參與到處理的邏輯中。

        反射的原理?

        我們都知道 Java 程序的執(zhí)行分為編譯和運(yùn)行兩步,編譯之后會(huì)生成字節(jié)碼(.class)文件,JVM 進(jìn)行類加載的時(shí)候,會(huì)加載字節(jié)碼文件,將類型相關(guān)的所有信息加載進(jìn)方法區(qū),反射就是去獲取這些信息,然后進(jìn)行各種操作。

        十一、JDK1.8 新特性

        JDK 已經(jīng)出到 17 了,但是你迭代你的版本,我用我的 8。JDK1.8 的一些新特性,當(dāng)然現(xiàn)在也不新了,其實(shí)在工作中已經(jīng)很常用了。

        50.JDK1.8 都有哪些新特性?

        JDK1.8 有不少新特性,我們經(jīng)常接觸到的新特性如下:

        JDK1.8主要新特性
        • 接口默認(rèn)方法:Java 8 允許我們給接口添加一個(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ì)有可讀性等問(wèn)題,《Effective Java》作者 Josh Bloch 建議使用 Lambda 表達(dá)式最好不要超過(guò) 3 行。

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

          Java8 中處理集合的關(guān)鍵抽象概念,它可以指定你希望對(duì)集合進(jìn)行的操作,可以執(zhí)行非常復(fù)雜的查找、過(guò)濾和映射數(shù)據(jù)等操作。使用 Stream API 對(duì)集合數(shù)據(jù)進(jìn)行操作,就類似于使用 SQL 執(zhí)行的數(shù)據(jù)庫(kù)查詢。也可以使用 Stream API 來(lái)并行執(zhí)行操作。

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

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

        • Optional 類:用來(lái)解決空指針異常的問(wèn)題。很久以前 Google Guava 項(xiàng)目引入了 Optional 作為解決空指針異常的一種方式,不贊成代碼被 null 檢查的代碼污染,期望程序員寫整潔的代碼。受 Google Guava 的鼓勵(lì),Optional 現(xiàn)在是 Java 8 庫(kù)的一部分。

        51.Lambda 表達(dá)式了解多少?

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

        比如我們以前使用 Runnable 創(chuàng)建并運(yùn)行線程:

        ????????new?Thread(new?Runnable()?{
        ????????????@Override
        ????????????public?void?run()?{
        ????????????????System.out.println("Thread?is?running?before?Java8!");
        ????????????}
        ????????}).start();

        這是通過(guò)內(nèi)部類的方式來(lái)重寫 run 方法,使用 Lambda 表達(dá)式,還可以更加簡(jiǎn)潔:

        new?Thread(?()?->?System.out.println("Thread?is?running?since?Java8!")?).start();

        當(dāng)然不是每個(gè)接口都可以縮寫成 Lambda 表達(dá)式。只有那些函數(shù)式接口(Functional Interface)才能縮寫成 Lambda 表示式。

        所謂函數(shù)式接口(Functional Interface)就是只包含一個(gè)抽象方法的聲明。針對(duì)該接口類型的所有 Lambda 表達(dá)式都會(huì)與這個(gè)抽象方法匹配。

        Java8 有哪些內(nèi)置函數(shù)式接口?

        JDK 1.8 API 包含了很多內(nèi)置的函數(shù)式接口。其中就包括我們?cè)诶习姹局薪?jīng)常見(jiàn)到的 ComparatorRunnable,Java 8 為他們都添加了 @FunctionalInterface 注解,以用來(lái)支持 Lambda 表達(dá)式。

        除了這兩個(gè)之外,還有 Callable、Predicate、Function、Supplier、Consumer 等等。

        52.Optional 了解嗎?

        Optional是用于防范NullPointerException

        可以將 Optional 看做是包裝對(duì)象(可能是 null, 也有可能非 null)的容器。當(dāng)我們定義了 一個(gè)方法,這個(gè)方法返回的對(duì)象可能是空,也有可能非空的時(shí)候,我們就可以考慮用 Optional 來(lái)包裝它,這也是在 Java 8 被推薦使用的做法。

        Optional?optional?=?Optional.of("bam");

        optional.isPresent();???????????//?true
        optional.get();?????????????????//?"bam"
        optional.orElse("fallback");????//?"bam"

        optional.ifPresent((s)?->?System.out.println(s.charAt(0)));?????//?"b"

        53.Stream 流用過(guò)嗎?

        Stream 流,簡(jiǎn)單來(lái)說(shuō),使用 java.util.Stream 對(duì)一個(gè)包含一個(gè)或多個(gè)元素的集合做各種操作。這些操作可能是 中間操作 亦或是 _終端操作_。終端操作會(huì)返回一個(gè)結(jié)果,而中間操作會(huì)返回一個(gè) Stream 流。

        Stream 流一般用于集合,我們對(duì)一個(gè)集合做幾個(gè)常見(jiàn)操作:

        List?stringCollection?=?new?ArrayList<>();
        stringCollection.add("ddd2");
        stringCollection.add("aaa2");
        stringCollection.add("bbb1");
        stringCollection.add("aaa1");
        stringCollection.add("bbb3");
        stringCollection.add("ccc");
        stringCollection.add("bbb2");
        stringCollection.add("ddd1");
        • Filter 過(guò)濾
        stringCollection
        ????.stream()
        ????.filter((s)?->?s.startsWith("a"))
        ????.forEach(System.out::println);

        //?"aaa2",?"aaa1"

        • Sorted 排序
        stringCollection
        ????.stream()
        ????.sorted()
        ????.filter((s)?->?s.startsWith("a"))
        ????.forEach(System.out::println);

        //?"aaa1",?"aaa2"
        • Map 轉(zhuǎn)換
        stringCollection
        ????.stream()
        ????.map(String::toUpperCase)
        ????.sorted((a,?b)?->?b.compareTo(a))
        ????.forEach(System.out::println);

        //?"DDD2",?"DDD1",?"CCC",?"BBB3",?"BBB2",?"AAA2",?"AAA1"
        • Match 匹配
        //?驗(yàn)證?list?中?string?是否有以?a?開(kāi)頭的,?匹配到第一個(gè),即返回?true
        boolean?anyStartsWithA?=
        ????stringCollection
        ????????.stream()
        ????????.anyMatch((s)?->?s.startsWith("a"));

        System.out.println(anyStartsWithA);??????//?true

        //?驗(yàn)證?list?中?string?是否都是以?a?開(kāi)頭的
        boolean?allStartsWithA?=
        ????stringCollection
        ????????.stream()
        ????????.allMatch((s)?->?s.startsWith("a"));

        System.out.println(allStartsWithA);??????//?false

        //?驗(yàn)證?list?中?string?是否都不是以?z?開(kāi)頭的,
        boolean?noneStartsWithZ?=
        ????stringCollection
        ????????.stream()
        ????????.noneMatch((s)?->?s.startsWith("z"));

        System.out.println(noneStartsWithZ);??????//?true
        • Count 計(jì)數(shù)

        count 是一個(gè)終端操作,它能夠統(tǒng)計(jì) stream 流中的元素總數(shù),返回值是 long 類型。

        //?先對(duì)?list?中字符串開(kāi)頭為?b?進(jìn)行過(guò)濾,讓后統(tǒng)計(jì)數(shù)量
        long?startsWithB?=
        ????stringCollection
        ????????.stream()
        ????????.filter((s)?->?s.startsWith("b"))
        ????????.count();

        System.out.println(startsWithB);????//?3
        • Reduce

        Reduce 中文翻譯為:_減少、縮小_。通過(guò)入?yún)⒌?Function,我們能夠?qū)?list 歸約成一個(gè)值。它的返回類型是 Optional 類型。

        Optional?reduced?=
        ????stringCollection
        ????????.stream()
        ????????.sorted()
        ????????.reduce((s1,?s2)?->?s1?+?"#"?+?s2);

        reduced.ifPresent(System.out::println);
        //?"aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

        以上是常見(jiàn)的幾種流式操作,還有其它的一些流式操作,可以幫助我們更便捷地處理集合數(shù)據(jù)。

        Java Stream流

        沒(méi)有什么使我停留——除了目的,縱然岸旁有玫瑰、有綠蔭、有寧?kù)o的港灣,我是不系之舟。

        推薦閱讀

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

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 国产熟妇 码视频黑料 | 国精品无码一区二区三区 | 做爱网站网站入口在线观看 | 波多野结衣视频网 | 日韩猛交 |