離職在家學(xué)習(xí)Java的第一天
大家好,我是二哥呀。今天在好朋友 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è)是比較突出的:

面向?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。

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 程序編譯成 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ù)類型。

基本數(shù)據(jù)類型:
數(shù)值型 整數(shù)類型(byte、short、long) 浮點(diǎn)類型(float、long) 字符型(char) 布爾型(boolean)
Java 基本數(shù)據(jù)類型范圍和默認(rèn)值:
| 基本類型 | 位數(shù) | 字節(jié) | 默認(rèn)值 |
|---|---|---|---|
int | 32 | 4 | 0 |
short | 16 | 2 | 0 |
long | 64 | 8 | 0L |
byte | 8 | 1 | 0 |
char | 16 | 2 | 'u0000' |
float | 32 | 4 | 0f |
double | 64 | 8 | 0d |
boolean | 1 | 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)換。

這就好像,小杯里的水倒進(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)前的方法 直接返回)

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ì)傳體。

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

封裝
封裝把?個(gè)對(duì)象的屬性私有化,同時(shí)提供?些可以被外界訪問(wèn)的屬性的?法。
繼承
繼承是使?已存在的類的定義作為基礎(chǔ)創(chuàng)建新的類,新類的定義可以增加新的屬性或新的方法,也可以繼承父類的屬性和方法。通過(guò)繼承可以很方便地進(jìn)行代碼復(fù)用。
關(guān)于繼承有以下三個(gè)要點(diǎn):
?類擁有?類對(duì)象所有的屬性和?法(包括私有屬性和私有?法),但是?類中的私有屬性和?法?類是?法訪問(wèn),只是擁有。
?類可以擁有??屬性和?法,即?類可以對(duì)?類進(jìn)?擴(kuò)展。
?類可以???的?式實(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ī)則:
方法名一致,參數(shù)列表中參數(shù)的順序,類型,個(gè)數(shù)不同。 重載與方法的返回值無(wú)關(guān),存在于父類和子類,同類中。 可以拋出不同的異常,可以有不同修飾符。
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)。可以修飾變量、方法。注意:不能修飾類(外部類)。

19.this 關(guān)鍵字有什么作用?
this 是自身的一個(gè)對(duì)象,代表對(duì)象本身,可以理解為:指向?qū)ο蟊旧淼囊粋€(gè)指針。
this 的用法在 Java 中大體可以分為 3 種:
普通的直接引用,this 相當(dāng)于是指向當(dāng)前對(duì)象本身
形參與成員變量名字重名,用 this 來(lái)區(qū)分:
public?Person(String?name,int?age){
????this.name=name;
????this.age=age;
}
引用本類的構(gòu)造函數(shù)
20.抽象類(abstract class)和接口(interface)有什么區(qū)別?
接?的?法默認(rèn)是 public ,所有?法在接?中不能有實(shí)現(xiàn)(Java 8 開(kāi)始接??法可以有默認(rèn)實(shí)現(xiàn)),?抽象類可以有?抽象的?法。
接?中除了 static 、 final 變量,不能有其他變量,?抽象類中則不?定。
?個(gè)類可以實(shí)現(xiàn)多個(gè)接?,但只能實(shí)現(xiàn)?個(gè)抽象類。接???本身可以通過(guò) extends 關(guān)鍵字?jǐn)U展多個(gè)接?。
接??法默認(rèn)修飾符是 public ,抽象?法可以有 public 、 protected 和 default 這些修飾符(抽象?法就是為了被重寫所以不能使? private 關(guān)鍵字修飾?。?。
從設(shè)計(jì)層?來(lái)說(shuō),抽象是對(duì)類的抽象,是?種模板設(shè)計(jì),?接?是對(duì)?為的抽象,是?種?為的規(guī)范。
在 JDK8 中,接?也可以定義靜態(tài)?法,可以直接?接?名調(diào)?。實(shí)現(xiàn)類和實(shí)現(xiàn)是不可以調(diào)?的。如果同時(shí)實(shí)現(xiàn)兩個(gè)接?,接?中定義了?樣的默認(rèn)?法,則必須重寫,不然會(huì)報(bào)錯(cuò)。
jdk9 的接?被允許定義私有?法 。
總結(jié)?下 jdk7~jdk9 Java 中接?的變化:
在 jdk 7 或更早版本中,接???只能有常量變量和抽象?法。這些接??法必須由選擇實(shí)現(xiàn)接?的類實(shí)現(xiàn)。
jdk 8 的時(shí)候接?可以有默認(rèn)?法和靜態(tài)?法功能。
jdk 9 在接?中引?了私有?法和私有靜態(tài)?法。
21.成員變量與局部變量的區(qū)別有哪些?
從語(yǔ)法形式上看:成員變量是屬于類的,?局部變量是在?法中定義的變量或是?法的參數(shù);成員變量可以被 public , private , static 等修飾符所修飾,?局部變量不能被訪問(wèn)控制修飾符及 static 所修飾;但是,成員變量和局部變量都能被 final 所修飾。
從變量在內(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ì)象的引?或者是指向常量池中的地址。
從變量在內(nèi)存中的?存時(shí)間上看:成員變量是對(duì)象的?部分,它隨著對(duì)象的創(chuàng)建?存在,?局部變量隨著?法的調(diào)???動(dòng)消失。
成員變量如果沒(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ò)去,因此引用類型也是值傳遞。

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ì)象的方式:

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 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)存如下:

在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。

很簡(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ù)的值累減。

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

對(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 的異常體系是分為多層的。

Throwable是 Java 語(yǔ)言中所有錯(cuò)誤或異常的基類。Throwable 又分為Error和Exception,其中 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 流用到了什么設(shè)計(jì)模式?
其實(shí),Java 的 IO 流體系還用到了一個(gè)設(shè)計(jì)模式——裝飾器模式。
InputStream 相關(guān)的部分類圖如下,篇幅有限,裝飾器模式就不展開(kāi)說(shuō)了。

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(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 方式適用于連接數(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 的數(shù)據(jù)是面向緩沖區(qū) Buffer的,必須從 Buffer 中讀取或?qū)懭搿?/p>
所以完整的 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 對(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?extends?List>?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è)人的一些小裝扮,比如戴什么什么帽子,戴什么眼鏡。

注解可以標(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)的:

就是給編譯器用的,編譯器編譯的時(shí)候檢查沒(méi)問(wèn)題就 over 了,class 文件里面不會(huì)有 Override 這個(gè)標(biāo)記。
再比如 Spring 常見(jiàn)的 Autowired ,就是 RUNTIME 的,所以在運(yùn)行的時(shí)候可以通過(guò)反射得到注解的信息,還能拿到標(biāo)記的值 required 。

十、反射
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è)類:

反射的應(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)常接觸到的新特性如下:

接口默認(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)到的 Comparator 和 Runnable,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ù)。


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