日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > java >内容正文

java

java 多态判断非空_收藏Java 面试题全梳理

發(fā)布時(shí)間:2023/12/10 java 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java 多态判断非空_收藏Java 面试题全梳理 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

? 腳本之家

你與百萬(wàn)開(kāi)發(fā)者在一起

來(lái)源 | Java建設(shè)者(ID:javajianshe)

作者 |cxuan

如若轉(zhuǎn)載請(qǐng)聯(lián)系原公眾號(hào)

Java 基礎(chǔ)篇

Java 有哪些特點(diǎn)

  • 并發(fā)性的:你可以在其中執(zhí)行許多語(yǔ)句,而不必一次執(zhí)行它
  • 面向?qū)ο蟮?#xff1a;基于類(lèi)和面向?qū)ο蟮木幊陶Z(yǔ)言。
  • 獨(dú)立性的:支持一次編寫(xiě),到處運(yùn)行的獨(dú)立編程語(yǔ)言,即編譯后的代碼可以在支持 Java 的所有平臺(tái)上運(yùn)行。

Java 的特性

Java 的特性有如下這幾點(diǎn)

  • 簡(jiǎn)單,Java 會(huì)讓你的工作變得更加輕松,使你把關(guān)注點(diǎn)放在主要業(yè)務(wù)邏輯上,而不必關(guān)心指針、運(yùn)算符重載、內(nèi)存回收等與主要業(yè)務(wù)無(wú)關(guān)的功能。

  • 便攜性,Java 是平臺(tái)無(wú)關(guān)性的,這意味著在一個(gè)平臺(tái)上編寫(xiě)的任何應(yīng)用程序都可以輕松移植到另一個(gè)平臺(tái)上。

  • 安全性, 編譯后會(huì)將所有的代碼轉(zhuǎn)換為字節(jié)碼,人類(lèi)無(wú)法讀取。它使開(kāi)發(fā)無(wú)病毒,無(wú)篡改的系統(tǒng)/應(yīng)用成為可能。

  • 動(dòng)態(tài)性,它具有適應(yīng)不斷變化的環(huán)境的能力,它能夠支持動(dòng)態(tài)內(nèi)存分配,從而減少了內(nèi)存浪費(fèi),提高了應(yīng)用程序的性能。

  • 分布式,Java 提供的功能有助于創(chuàng)建分布式應(yīng)用。使用遠(yuǎn)程方法調(diào)用(RMI),程序可以通過(guò)網(wǎng)絡(luò)調(diào)用另一個(gè)程序的方法并獲取輸出。您可以通過(guò)從互聯(lián)網(wǎng)上的任何計(jì)算機(jī)上調(diào)用方法來(lái)訪問(wèn)文件。這是革命性的一個(gè)特點(diǎn),對(duì)于當(dāng)今的互聯(lián)網(wǎng)來(lái)說(shuō)太重要了。

  • 健壯性,Java 有強(qiáng)大的內(nèi)存管理功能,在編譯和運(yùn)行時(shí)檢查代碼,它有助于消除錯(cuò)誤。

  • 高性能,Java 最黑的科技就是字節(jié)碼編程,Java 代碼編譯成的字節(jié)碼可以輕松轉(zhuǎn)換為本地機(jī)器代碼。通過(guò) JIT 即時(shí)編譯器來(lái)實(shí)現(xiàn)高性能。

  • 解釋性,Java 被編譯成字節(jié)碼,由 Java 運(yùn)行時(shí)環(huán)境解釋。

  • 多線程性,Java支持多個(gè)執(zhí)行線程(也稱為輕量級(jí)進(jìn)程),包括一組同步原語(yǔ)。這使得使用線程編程更加容易,Java 通過(guò)管程模型來(lái)實(shí)現(xiàn)線程安全性。

描述一下值傳遞和引用傳遞的區(qū)別

要想真正理解的話,可以參考這篇文章 :https://www.zhihu.com/question/31203609

簡(jiǎn)單理解的話就是

值傳遞是指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制一份到函數(shù)中,這樣的話如果函數(shù)對(duì)其傳遞過(guò)來(lái)的形式參數(shù)進(jìn)行修改,將不會(huì)影響到實(shí)際參數(shù)

引用傳遞是指在調(diào)用函數(shù)時(shí)將對(duì)象的地址直接傳遞到函數(shù)中,如果在對(duì)形式參數(shù)進(jìn)行修改,將影響到實(shí)際參數(shù)的值。

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

==是 Java 中一種操作符,它有兩種比較方式

  • 對(duì)于基本數(shù)據(jù)類(lèi)型來(lái)說(shuō), == 判斷的是兩邊的值是否相等
public?class?DoubleCompareAndEquals?{

????Person?person1?=?new?Person(24,"boy");
????Person?person2?=?new?Person(24,"girl");
????int?c?=?10;

????private?void?doubleCompare(){

????????int?a?=?10;
????????int?b?=?10;

????????System.out.println(a?==?b);
????????System.out.println(a?==?c);
????????System.out.println(person1.getId()?==?person2.getId());

????}
}
  • 對(duì)于引用類(lèi)型來(lái)說(shuō), == 判斷的是兩邊的引用是否相等,也就是判斷兩個(gè)對(duì)象是否指向了同一塊內(nèi)存區(qū)域。
private?void?equals(){

??System.out.println(person1.getName().equals(person2.getName()));
}

equals是 Java 中所有對(duì)象的父類(lèi),即Object類(lèi)定義的一個(gè)方法。它只能比較對(duì)象,它表示的是引用雙方的值是否相等。所以記住,并不是說(shuō) == 比較的就是引用是否相等,equals 比較的就是值,這需要區(qū)分來(lái)說(shuō)的。

equals 用作對(duì)象之間的比較具有如下特性

  • 自反性:對(duì)于任何非空引用 x 來(lái)說(shuō),x.equals(x) 應(yīng)該返回 true。
  • 對(duì)稱性:對(duì)于任何非空引用 x 和 y 來(lái)說(shuō),若x.equals(y)為 true,則y.equals(x)也為 true。
  • 傳遞性:對(duì)于任何非空引用的值來(lái)說(shuō),有三個(gè)值,x、y 和 z,如果x.equals(y) 返回true,y.equals(z) 返回true,那么x.equals(z) 也應(yīng)該返回true。
  • 一致性:對(duì)于任何非空引用 x 和 y 來(lái)說(shuō),如果 x.equals(y) 相等的話,那么它們必須始終相等。
  • 非空性:對(duì)于任何非空引用的值 x 來(lái)說(shuō),x.equals(null) 必須返回 false。

String 中的 equals 是如何重寫(xiě)的

String 代表的是 Java 中的字符串,String 類(lèi)比較特殊,它整個(gè)類(lèi)都是被final修飾的,也就是說(shuō),String 不能被任何類(lèi)繼承,任何修改String 字符串的方法都是創(chuàng)建了一個(gè)新的字符串。

equals 方法是 Object 類(lèi)定義的方法,Object 是所有類(lèi)的父類(lèi),當(dāng)然也包括 String,String 重寫(xiě)了equals方法,下面我們來(lái)看看是怎么重寫(xiě)的

  • 首先會(huì)判斷要比較的兩個(gè)字符串它們的引用是否相等。如果引用相等的話,直接返回 true ,不相等的話繼續(xù)下面的判斷
  • 然后再判斷被比較的對(duì)象是否是 String 的實(shí)例,如果不是的話直接返回 false,如果是的話,再比較兩個(gè)字符串的長(zhǎng)度是否相等,如果長(zhǎng)度不想等的話也就沒(méi)有比較的必要了;長(zhǎng)度如果相同,會(huì)比較字符串中的每個(gè)字符是否相等,一旦有一個(gè)字符不相等,就會(huì)直接返回 false。

下面是它的流程圖

這里再提示一下,你可能有疑惑什么時(shí)候是

if?(this?==?anObject)?{
??return?true;
}

這個(gè)判斷語(yǔ)句如何才能返回 true?因?yàn)槎际亲址?#xff0c;字符串比較的不都是堆空間嗎,猛然一看發(fā)現(xiàn)好像永遠(yuǎn)也不會(huì)走,但是你忘記了String.intern()方法,它表示的概念在不同的 JDK 版本有不同的區(qū)分

在 JDK1.7 及以后調(diào)用 intern 方法是判斷運(yùn)行時(shí)常量池中是否有指定的字符串,如果沒(méi)有的話,就把字符串添加到常量池中,并返回常量池中的對(duì)象。

驗(yàn)證過(guò)程如下

private?void?StringOverrideEquals(){

??String?s1?=?"aaa";
??String?s2?=?"aa"?+?new?String("a");
??String?s3?=?new?String("aaa");

??System.out.println(s1.intern().equals(s1));
??System.out.println(s1.intern().equals(s2));
??System.out.println(s3.intern().equals(s1));

}
  • 首先 s1.intern.equals(s1) 這個(gè)無(wú)論如何都返回 true,因?yàn)?s1 字符串創(chuàng)建出來(lái)就已經(jīng)在常量池中存在了。

  • 然后第二條語(yǔ)句返回 false,因?yàn)?s1 返回的是常量池中的對(duì)象,而 s2 返回的是堆中的對(duì)象

  • 第三條語(yǔ)句 s3.intern.equals(s1),返回 true ,因?yàn)?s3 對(duì)象雖然在堆中創(chuàng)建了一個(gè)對(duì)象,但是 s3 中的 "aaa" 返回的是常量池中的對(duì)象。

為什么重寫(xiě) equals 方法必須重寫(xiě) hashcode 方法

equals 方法和 hashCode 都是 Object 中定義的方法,它們經(jīng)常被一起重寫(xiě)。

equals 方法是用來(lái)比較對(duì)象大小是否相等的方法,hashcode 方法是用來(lái)判斷每個(gè)對(duì)象 hash 值的一種方法。如果只重寫(xiě) equals 方法而不重寫(xiě) hashcode 方法,很可能會(huì)造成兩個(gè)不同的對(duì)象,它們的 hashcode 也相等,造成沖突。比如

String?str1?=?"通話";
String?str2?=?"重地";

它們兩個(gè)的 hashcode 相等,但是 equals 可不相等。

我們來(lái)看一下 hashCode 官方的定義

總結(jié)起來(lái)就是

  • 如果在 Java 運(yùn)行期間對(duì)同一個(gè)對(duì)象調(diào)用 hashCode 方法后,無(wú)論調(diào)用多少次,都應(yīng)該返回相同的 hashCode,但是在不同的 Java 程序中,執(zhí)行 hashCode 方法返回的值可能不一致。
  • 如果兩個(gè)對(duì)象的 equals 相等,那么 hashCode 必須相同
  • 如果兩個(gè)對(duì)象 equals 不相等,那么 hashCode 也有可能相同,所以需要重寫(xiě) hashCode 方法,因?yàn)槟悴恢?hashCode 的底層構(gòu)造(反正我是不知道,有大牛可以傳授傳授),所以你需要重寫(xiě) hashCode 方法,來(lái)為不同的對(duì)象生成不同的 hashCode 值,這樣能夠提高不同對(duì)象的訪問(wèn)速度。
  • hashCode 通常是將地址轉(zhuǎn)換為整數(shù)來(lái)實(shí)現(xiàn)的。

String s1 = new String("abc") 在內(nèi)存中創(chuàng)建了幾個(gè)對(duì)象

一個(gè)或者兩個(gè),String s1 是聲明了一個(gè) String 類(lèi)型的 s1 變量,它不是對(duì)象。使用new關(guān)鍵字會(huì)在堆中創(chuàng)建一個(gè)對(duì)象,另外一個(gè)對(duì)象是abc,它會(huì)在常量池中創(chuàng)建,所以一共創(chuàng)建了兩個(gè)對(duì)象;如果 abc 在常量池中已經(jīng)存在的話,那么就會(huì)創(chuàng)建一個(gè)對(duì)象。

詳細(xì)請(qǐng)翻閱筆者的另外一篇文章一篇與眾不同的 String、StringBuffer、StringBuilder 詳解

String 為什么是不可變的、jdk 源碼中的 String 如何定義的、為什么這么設(shè)計(jì)。

首先了解一下什么是不可變對(duì)象,不可變對(duì)象就是一經(jīng)創(chuàng)建后,其對(duì)象的內(nèi)部狀態(tài)不能被修改,啥意思呢?也就是說(shuō)不可變對(duì)象需要遵守下面幾條原則

  • 不可變對(duì)象的內(nèi)部屬性都是 final 的
  • 不可變對(duì)象的內(nèi)部屬性都是 private 的
  • 不可變對(duì)象不能提供任何可以修改內(nèi)部狀態(tài)的方法、setter 方法也不行
  • 不可變對(duì)象不能被繼承和擴(kuò)展

與其說(shuō)問(wèn) String 為什么是不可變的,不如說(shuō)如何把 String 設(shè)計(jì)成不可變的。

String 類(lèi)是一種對(duì)象,它是獨(dú)立于 Java 基本數(shù)據(jù)類(lèi)型而存在的,String 你可以把它理解為字符串的集合,String 被設(shè)計(jì)為 final 的,表示 String 對(duì)象一經(jīng)創(chuàng)建后,它的值就不能再被修改,任何對(duì) String 值進(jìn)行修改的方法就是重新創(chuàng)建一個(gè)字符串。String 對(duì)象創(chuàng)建后會(huì)存在于運(yùn)行時(shí)常量池中,運(yùn)行時(shí)常量池是屬于方法區(qū)的一部分,JDK1.7 后把它移到了堆中。

不可變對(duì)象不是真的不可變,可以通過(guò)反射來(lái)對(duì)其內(nèi)部的屬性和值進(jìn)行修改,不過(guò)一般我們不這樣做。

static 關(guān)鍵字是干什么用的?談?wù)勀愕睦斫?span style="font-weight:bold;">

static 是 Java 中非常重要的關(guān)鍵字,static 表示的概念是靜態(tài)的,在 Java 中,static 主要用來(lái)

  • 修飾變量,static 修飾的變量稱為靜態(tài)變量、也稱為類(lèi)變量,類(lèi)變量屬于類(lèi)所有,對(duì)于不同的類(lèi)來(lái)說(shuō),static 變量只有一份,static 修飾的變量位于方法區(qū)中;static 修飾的變量能夠直接通過(guò)類(lèi)名.變量名來(lái)進(jìn)行訪問(wèn),不用通過(guò)實(shí)例化類(lèi)再進(jìn)行使用。
  • 修飾方法,static 修飾的方法被稱為靜態(tài)方法,靜態(tài)方法能夠直接通過(guò)類(lèi)名.方法名來(lái)使用,在靜態(tài)方法內(nèi)部不能使用非靜態(tài)屬性和方法
  • static 可以修飾代碼塊,主要分為兩種,一種直接定義在類(lèi)中,使用static{},這種被稱為靜態(tài)代碼塊,一種是在類(lèi)中定義靜態(tài)內(nèi)部類(lèi),使用static class xxx來(lái)進(jìn)行定義。
  • static 可以用于靜態(tài)導(dǎo)包,通過(guò)使用import static xxx?來(lái)實(shí)現(xiàn),這種方式一般不推薦使用
  • static 可以和單例模式一起使用,通過(guò)雙重檢查鎖來(lái)實(shí)現(xiàn)線程安全的單例模式。

詳情請(qǐng)參考這篇文章一篇 static 還能難得住我?

final 關(guān)鍵字是干什么用的?談?wù)勀愕睦斫?span style="font-weight:bold;">

final 是 Java 中的關(guān)鍵字,它表示的意思是不可變的,在 Java 中,final 主要用來(lái)

  • 修飾類(lèi),final 修飾的類(lèi)不能被繼承,不能被繼承的意思就是不能使用extends來(lái)繼承被 final 修飾的類(lèi)。
  • 修飾變量,final 修飾的變量不能被改寫(xiě),不能被改寫(xiě)的意思有兩種,對(duì)于基本數(shù)據(jù)類(lèi)型來(lái)說(shuō),final 修飾的變量,其值不能被改變,final 修飾的對(duì)象,對(duì)象的引用不能被改變,但是對(duì)象內(nèi)部的屬性可以被修改。final 修飾的變量在某種程度上起到了不可變的效果,所以,可以用來(lái)保護(hù)只讀數(shù)據(jù),尤其是在并發(fā)編程中,因?yàn)槊鞔_的不能再為 final 變量進(jìn)行賦值,有利于減少額外的同步開(kāi)銷(xiāo)。
  • 修飾方法,final 修飾的方法不能被重寫(xiě)。
  • final 修飾符和 Java 程序性能優(yōu)化沒(méi)有必然聯(lián)系

抽象類(lèi)和接口的區(qū)別是什么

抽象類(lèi)和接口都是 Java 中的關(guān)鍵字,抽象類(lèi)和接口中都允許進(jìn)行方法的定義,而不用具體的方法實(shí)現(xiàn)。抽象類(lèi)和接口都允許被繼承,它們廣泛的應(yīng)用于 JDK 和框架的源碼中,來(lái)實(shí)現(xiàn)多態(tài)和不同的設(shè)計(jì)模式。

不同點(diǎn)在于

  • 抽象級(jí)別不同:類(lèi)、抽象類(lèi)、接口其實(shí)是三種不同的抽象級(jí)別,抽象程度依次是 接口 > 抽象類(lèi) > 類(lèi)。在接口中,只允許進(jìn)行方法的定義,不允許有方法的實(shí)現(xiàn),抽象類(lèi)中可以進(jìn)行方法的定義和實(shí)現(xiàn);而類(lèi)中只允許進(jìn)行方法的實(shí)現(xiàn),我說(shuō)的方法的定義是不允許在方法后面出現(xiàn){}
  • 使用的關(guān)鍵字不同:類(lèi)使用class來(lái)表示;抽象類(lèi)使用abstract class來(lái)表示;接口使用interface來(lái)表示
  • 變量:接口中定義的變量只能是公共的靜態(tài)常量,抽象類(lèi)中的變量是普通變量。

重寫(xiě)和重載的區(qū)別

在 Java 中,重寫(xiě)和重載都是對(duì)同一方法的不同表現(xiàn)形式,下面我們針對(duì)重寫(xiě)和重載做一下簡(jiǎn)單的區(qū)分

  • 子父級(jí)關(guān)系不同,重寫(xiě)是針對(duì)子級(jí)和父級(jí)的不同表現(xiàn)形式,而重載是在同一類(lèi)中的不同表現(xiàn)形式;
  • 概念不同,子類(lèi)重寫(xiě)父類(lèi)的方法一般使用@override來(lái)表示;重寫(xiě)后的方法其方法的聲明和參數(shù)類(lèi)型、順序必須要與父類(lèi)完全一致;重載是針對(duì)同一類(lèi)中概念,它要求重載的方法必須滿足下面任何一個(gè)要求:方法參數(shù)的順序,參數(shù)的個(gè)數(shù),參數(shù)的類(lèi)型任意一個(gè)保持不同即可。

byte的取值范圍是多少,怎么計(jì)算出來(lái)的

byte 的取值范圍是 -128 -> 127 之間,一共是 256 。一個(gè) byte 類(lèi)型在計(jì)算機(jī)中占據(jù)一個(gè)字節(jié),那么就是 8 bit,所以最大就是 2^7 = 1111 1111。

Java 中用補(bǔ)碼來(lái)表示二進(jìn)制數(shù),補(bǔ)碼的最高位是符號(hào)位,最高位用 0 表示正數(shù),最高位 1 表示負(fù)數(shù),正數(shù)的補(bǔ)碼就是其本身,由于最高位是符號(hào)位,所以正數(shù)表示的就是 0111 1111 ,也就是 127。最大負(fù)數(shù)就是 1111 1111,這其中會(huì)涉及到兩個(gè) 0 ,一個(gè) +0 ,一個(gè) -0 ,+0 歸為正數(shù),也就是 0 ,-0 歸為負(fù)數(shù),也就是 -128,所以 byte 的范圍就是 -128 - 127。

HashMap 和 HashTable 的區(qū)別

相同點(diǎn)

HashMap 和 HashTable 都是基于哈希表實(shí)現(xiàn)的,其內(nèi)部每個(gè)元素都是key-value鍵值對(duì),HashMap 和 HashTable 都實(shí)現(xiàn)了 Map、Cloneable、Serializable 接口。

不同點(diǎn)

  • 父類(lèi)不同:HashMap 繼承了AbstractMap類(lèi),而 HashTable 繼承了Dictionary類(lèi)

  • 空值不同:HashMap 允許空的 key 和 value 值,HashTable 不允許空的 key 和 value 值。HashMap 會(huì)把 Null key 當(dāng)做普通的 key 對(duì)待。不允許 null key 重復(fù)。

  • 線程安全性:HashMap 不是線程安全的,如果多個(gè)外部操作同時(shí)修改 HashMap 的數(shù)據(jù)結(jié)構(gòu)比如 add 或者是 delete,必須進(jìn)行同步操作,僅僅對(duì) key 或者 value 的修改不是改變數(shù)據(jù)結(jié)構(gòu)的操作。可以選擇構(gòu)造線程安全的 Map 比如Collections.synchronizedMap或者是ConcurrentHashMap。而 HashTable 本身就是線程安全的容器。
  • 性能方面:雖然 HashMap 和 HashTable 都是基于單鏈表的,但是 HashMap 進(jìn)行 put 或者 get? 操作,可以達(dá)到常數(shù)時(shí)間的性能;而 HashTable 的 put 和 get 操作都是加了synchronized鎖的,所以效率很差。

  • 初始容量不同:HashTable 的初始長(zhǎng)度是11,之后每次擴(kuò)充容量變?yōu)橹暗?2n+1(n為上一次的長(zhǎng)度)而 HashMap 的初始長(zhǎng)度為16,之后每次擴(kuò)充變?yōu)樵瓉?lái)的兩倍。創(chuàng)建時(shí),如果給定了容量初始值,那么HashTable 會(huì)直接使用你給定的大小,而 HashMap 會(huì)將其擴(kuò)充為2的冪次方大小。

HashMap 和 HashSet 的區(qū)別

HashSet 繼承于 AbstractSet 接口,實(shí)現(xiàn)了 Set、Cloneable,、java.io.Serializable 接口。HashSet 不允許集合中出現(xiàn)重復(fù)的值。HashSet 底層其實(shí)就是 HashMap,所有對(duì) HashSet 的操作其實(shí)就是對(duì) HashMap 的操作。所以 HashSet 也不保證集合的順序,也不是線程安全的容器。

HashMap 的底層結(jié)構(gòu)

JDK1.7 中,HashMap 采用位桶 + 鏈表的實(shí)現(xiàn),即使用鏈表來(lái)處理沖突,同一 hash 值的鏈表都存儲(chǔ)在一個(gè)數(shù)組中。但是當(dāng)位于一個(gè)桶中的元素較多,即 hash 值相等的元素較多時(shí),通過(guò) key 值依次查找的效率較低。

所以,與 JDK 1.7 相比,JDK 1.8 在底層結(jié)構(gòu)方面做了一些改變,當(dāng)每個(gè)桶中元素大于 8 的時(shí)候,會(huì)轉(zhuǎn)變?yōu)榧t黑樹(shù),目的就是優(yōu)化查詢效率。

HashMap 的長(zhǎng)度為什么是 2 的冪次方

這道題我想了幾天,之前和群里小伙伴們探討每日一題的時(shí)候,問(wèn)他們?yōu)槭裁?length%hash == (n - 1) & hash,它們說(shuō)相等的前提是 length 的長(zhǎng)度 2 的冪次方,然后我回了一句難道 length 還能不是 2 的冪次方嗎?其實(shí)是我沒(méi)有搞懂因果關(guān)系,因?yàn)?HashMap 的長(zhǎng)度是 2 的冪次方,所以使用余數(shù)來(lái)判斷在桶中的下標(biāo)。如果 length 的長(zhǎng)度不是 2 的冪次方,小伙伴們可以舉個(gè)例子來(lái)試試

例如長(zhǎng)度為 9 時(shí)候,3 & (9-1) = 0,2 & (9-1) = 0 ,都在 0 上,碰撞了;

這樣會(huì)增大 HashMap 碰撞的幾率。

HashMap 多線程操作導(dǎo)致死循環(huán)問(wèn)題

HashMap 不是一個(gè)線程安全的容器,在高并發(fā)場(chǎng)景下,應(yīng)該使用ConcurrentHashMap,在多線程場(chǎng)景下使用 HashMap 會(huì)造成死循環(huán)問(wèn)題(基于 JDK1.7),出現(xiàn)問(wèn)題的位置在rehash處,也就是

do?{
????Entry?next?=?e.next;?//?int?i?=?indexFor(e.hash,?newCapacity);
????e.next?=?newTable[i];
????newTable[i]?=?e;
????e?=?next;
}?while?(e?!=?null);

這是 JDK1.7 的 rehash 代碼片段,在并發(fā)的場(chǎng)景下會(huì)形成環(huán)。

JDK1.8 也會(huì)造成死循環(huán)問(wèn)題。

HashMap 線程安全的實(shí)現(xiàn)有哪些

因?yàn)?HashMap 不是一個(gè)線程安全的容器,所以并發(fā)場(chǎng)景下推薦使用ConcurrentHashMap,或者使用線程安全的 HashMap,使用Collections包下的線程安全的容器,比如說(shuō)

Collections.synchronizedMap(new?HashMap());

還可以使用 HashTable ,它也是線程安全的容器,基于 key-value 存儲(chǔ),經(jīng)常用 HashMap 和 HashTable 做比較就是因?yàn)?HashTable 的數(shù)據(jù)結(jié)構(gòu)和 HashMap 相同。

上面效率最高的就是 ConcurrentHashMap。

講一下 HashMap put 的過(guò)程

首先會(huì)使用 hash 函數(shù)來(lái)計(jì)算 key,然后執(zhí)行真正的插入方法

final?V?putVal(int?hash,?K?key,?V?value,?boolean?onlyIfAbsent,boolean?evict)?{
??Node[]?tab;?Node?p;?int?n,?i;//?如果table?為null?或者沒(méi)有為table分配內(nèi)存,就resize一次if?((tab?=?table)?==?null?||?(n?=?tab.length)?==?0)
????n?=?(tab?=?resize()).length;//?指定hash值節(jié)點(diǎn)為空則直接插入,這個(gè)(n?-?1)?&?hash才是表中真正的哈希if?((p?=?tab[i?=?(n?-?1)?&?hash])?==?null)
????tab[i]?=?newNode(hash,?key,?value,?null);//?如果不為空else?{
????Node?e;?K?k;//?計(jì)算表中的這個(gè)真正的哈希值與要插入的key.hash相比if?(p.hash?==?hash?&&
????????((k?=?p.key)?==?key?||?(key?!=?null?&&?key.equals(k))))
??????e?=?p;//?若不同的話,并且當(dāng)前節(jié)點(diǎn)已經(jīng)在?TreeNode?上了else?if?(p?instanceof?TreeNode)//?采用紅黑樹(shù)存儲(chǔ)方式
??????e?=?((TreeNode)p).putTreeVal(this,?tab,?hash,?key,?value);//?key.hash?不同并且也不再?TreeNode?上,在鏈表上找到?p.next==nullelse?{for?(int?binCount?=?0;?;?++binCount)?{if?((e?=?p.next)?==?null)?{//?在表尾插入
??????????p.next?=?newNode(hash,?key,?value,?null);//?新增節(jié)點(diǎn)后如果節(jié)點(diǎn)個(gè)數(shù)到達(dá)閾值,則進(jìn)入?treeifyBin()?進(jìn)行再次判斷if?(binCount?>=?TREEIFY_THRESHOLD?-?1)?//?-1?for?1st
????????????treeifyBin(tab,?hash);break;
????????}//?如果找到了同hash、key的節(jié)點(diǎn),那么直接退出循環(huán)if?(e.hash?==?hash?&&
????????????((k?=?e.key)?==?key?||?(key?!=?null?&&?key.equals(k))))break;//?更新?p?指向下一節(jié)點(diǎn)
????????p?=?e;
??????}
????}//?map中含有舊值,返回舊值if?(e?!=?null)?{?//?existing?mapping?for?key
??????V?oldValue?=?e.value;if?(!onlyIfAbsent?||?oldValue?==?null)
????????e.value?=?value;
??????afterNodeAccess(e);return?oldValue;
????}
??}//?map調(diào)整次數(shù)?+?1
??++modCount;//?鍵值對(duì)的數(shù)量達(dá)到閾值,需要擴(kuò)容if?(++size?>?threshold)
????resize();
??afterNodeInsertion(evict);return?null;
}

HashMap put 方法的核心就是在putval方法,它的插入過(guò)程如下

  • 首先會(huì)判斷 HashMap 中是否是新構(gòu)建的,如果是的話會(huì)首先進(jìn)行 resize
  • 然后判斷需要插入的元素在 HashMap 中是否已經(jīng)存在(說(shuō)明出現(xiàn)了碰撞情況),如果不存在,直接生成新的k-v 節(jié)點(diǎn)存放,再判斷是否需要擴(kuò)容。
  • 如果要插入的元素已經(jīng)存在的話,說(shuō)明發(fā)生了沖突,這就會(huì)轉(zhuǎn)換成鏈表或者紅黑樹(shù)來(lái)解決沖突,首先判斷鏈表中的 hash,key 是否相等,如果相等的話,就用新值替換舊值,如果節(jié)點(diǎn)是屬于 TreeNode 類(lèi)型,會(huì)直接在紅黑樹(shù)中進(jìn)行處理,如果 hash ,key 不相等也不屬于 TreeNode 類(lèi)型,會(huì)直接轉(zhuǎn)換為鏈表處理,進(jìn)行鏈表遍歷,如果鏈表的 next 節(jié)點(diǎn)是 null,判斷是否轉(zhuǎn)換為紅黑樹(shù),如果不轉(zhuǎn)換的話,在遍歷過(guò)程中找到 key 完全相等的節(jié)點(diǎn),則用新節(jié)點(diǎn)替換老節(jié)點(diǎn)

ConcurrentHashMap 底層實(shí)現(xiàn)

ConcurrentHashMap 是線程安全的 Map,它也是高并發(fā)場(chǎng)景下的首選數(shù)據(jù)結(jié)構(gòu),ConcurrentHashMap 底層是使用分段鎖來(lái)實(shí)現(xiàn)的。

Integer 緩存池

Integer 緩存池也就是IntegerCache,它是 Integer 的靜態(tài)內(nèi)部類(lèi)。

它的默認(rèn)值用于緩存 -128 - 127 之間的數(shù)字,如果有 -128 - 127 之間的數(shù)字的話,使用 new Integer 不用創(chuàng)建對(duì)象,會(huì)直接從緩存池中取,此操作會(huì)減少堆中對(duì)象的分配,有利于提高程序的運(yùn)行效率。

例如創(chuàng)建一個(gè) Integer a = 24,其實(shí)是調(diào)用 Integer 的valueOf,可以通過(guò)反編譯得出這個(gè)結(jié)論

然后我們看一下 valueOf 方法

如果在指定緩存池范圍內(nèi)的話,會(huì)直接返回緩存的值而不用創(chuàng)建新的 Integer 對(duì)象。

緩存的大小可以使用XX:AutoBoxCacheMax來(lái)指定,在 VM 初始化時(shí),java.lang.Integer.IntegerCache.high屬性會(huì)設(shè)置和保存在sun.misc.VM的私有系統(tǒng)屬性中。

UTF-8 和 Unicode 的關(guān)系

由于每個(gè)國(guó)家都有自己獨(dú)有的字符編碼,所以Unicode 的發(fā)展旨在創(chuàng)建一個(gè)新的標(biāo)準(zhǔn),用來(lái)映射當(dāng)今使用的大多數(shù)語(yǔ)言中的字符,這些字符有一些不是必要的,但是對(duì)于創(chuàng)建文本來(lái)說(shuō)卻是不可或缺的。Unicode 統(tǒng)一了所有字符的編碼,是一個(gè) Character Set,也就是字符集,字符集只是給所有的字符一個(gè)唯一編號(hào),但是卻沒(méi)有規(guī)定如何存儲(chǔ),不同的字符其存儲(chǔ)空間不一樣,有的需要一個(gè)字節(jié)就能存儲(chǔ),有的則需要2、3、4個(gè)字節(jié)。

UTF-8 只是眾多能夠?qū)ξ谋咀址M(jìn)行解碼的一種方式,它是一種變長(zhǎng)的方式。UTF-8 代表 8 位一組表示 Unicode 字符的格式,使用 1 - 4 個(gè)字節(jié)來(lái)表示字符。

U+ 0000 ~ U+ 007F: 0XXXXXXX
U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX
U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX
U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

可以看到,UTF-8 通過(guò)開(kāi)頭的標(biāo)志位位數(shù)實(shí)現(xiàn)了變長(zhǎng)。對(duì)于單字節(jié)字符,只占用一個(gè)字節(jié),實(shí)現(xiàn)了向下兼容 ASCII,并且能和 UTF-32 一樣,包含 Unicode 中的所有字符,又能有效減少存儲(chǔ)傳輸過(guò)程中占用的空間。

項(xiàng)目為 UTF-8 環(huán)境,char c = '中',是否合法

可以,因?yàn)?Unicode 編碼采用 2 個(gè)字節(jié)的編碼,UTF-8 是 Unicode 的一種實(shí)現(xiàn),它使用可變長(zhǎng)度的字符集進(jìn)行編碼,char c = '中' 是兩個(gè)字節(jié),所以能夠存儲(chǔ)。合法。

Arrays.asList 獲得的 List 應(yīng)該注意什么

Arrays.asList是 Array 中的一個(gè)靜態(tài)方法,它能夠?qū)崿F(xiàn)把數(shù)組轉(zhuǎn)換成為 List 序列,需要注意下面幾點(diǎn)

  • Arrays.asList 轉(zhuǎn)換完成后的 List 不能再進(jìn)行結(jié)構(gòu)化的修改,什么是結(jié)構(gòu)化的修改?就是不能再進(jìn)行任何 List 元素的增加或者減少的操作。
public?static?void?main(String[]?args)?{
??Integer[]?integer?=?new?Integer[]?{?1,?2,?3,?4?};
??List?integetList?=?Arrays.asList(integer);
??integetList.add(5);
}

結(jié)果會(huì)直接拋出

Exception?in?thread?"main"?java.lang.UnsupportedOperationException

我們看一下源碼就能發(fā)現(xiàn)問(wèn)題

//?這是?java.util.Arrays?的內(nèi)部類(lèi),而不是?java.util.ArrayList?
private?static?class?ArrayList<E>?extends?AbstractList<E>implements?RandomAccess,?java.io.Serializable

繼承 AbstractList 中對(duì) add、remove、set 方法是直接拋異常的,也就是說(shuō)如果繼承的子類(lèi)沒(méi)有去重寫(xiě)這些方法,那么子類(lèi)的實(shí)例去調(diào)用這些方法是會(huì)直接拋異常的。

下面是AbstractList中方法的定義,我們可以看到具體拋出的異常:

public?void?add(int?index,?E?element)?{
??throw?new?UnsupportedOperationException();
}
public?E?remove(int?index)?{
??throw?new?UnsupportedOperationException();
}
public?E?set(int?index,?E?element)?{
??throw?new?UnsupportedOperationException();
}

雖然 set 方法也拋出了一場(chǎng),但是由于 內(nèi)部類(lèi) ArrayList 重寫(xiě)了 set 方法,所以支持其可以對(duì)元素進(jìn)行修改。

  • Arrays.asList 不支持基礎(chǔ)類(lèi)型的轉(zhuǎn)換

Java 中的基礎(chǔ)數(shù)據(jù)類(lèi)型(byte,short,int,long,float,double,boolean)是不支持使用 Arrays.asList 方法去轉(zhuǎn)換的

Collection 和 Collections 的區(qū)別

Collection 和 Collections 都是位于java.util包下的類(lèi)

Collection 是集合類(lèi)的父類(lèi),它是一個(gè)頂級(jí)接口,大部分抽象類(lèi)比如說(shuō)AbstractList、AbstractSet都繼承了 Collection 類(lèi),Collection 類(lèi)只定義一節(jié)標(biāo)準(zhǔn)方法比如說(shuō) add、remove、set、equals 等,具體的方法由抽象類(lèi)或者實(shí)現(xiàn)類(lèi)去實(shí)現(xiàn)。

Collections 是集合類(lèi)的工具類(lèi),Collections 提供了一些工具類(lèi)的基本使用

  • sort 方法,對(duì)當(dāng)前集合進(jìn)行排序, 實(shí)現(xiàn) Comparable 接口的類(lèi),只能使用一種排序方案,這種方案叫做自然比較
  • 比如實(shí)現(xiàn)線程安全的容器Collections.synchronizedList、Collections.synchronizedMap等
  • reverse 反轉(zhuǎn),使用 reverse 方法可以根據(jù)元素的自然順序 對(duì)指定列表按降序進(jìn)行排序。
  • fill,使用指定元素替換指定列表中的所有元素。

有很多用法,讀者可以翻閱 Collections 的源碼查看,Collections 不能進(jìn)行實(shí)例化,所以 Collections 中的方法都是由Collections.方法直接調(diào)用。

你知道 fail-fast 和 fail-safe 嗎

fail-fast是 Java 中的一種快速失敗機(jī)制,java.util 包下所有的集合都是快速失敗的,快速失敗會(huì)拋出ConcurrentModificationException異常,fail-fast 你可以把它理解為一種快速檢測(cè)機(jī)制,它只能用來(lái)檢測(cè)錯(cuò)誤,不會(huì)對(duì)錯(cuò)誤進(jìn)行恢復(fù),fail-fast 不一定只在多線程環(huán)境下存在,ArrayList 也會(huì)拋出這個(gè)異常,主要原因是由于modCount 不等于 expectedModCount。

fail-safe是 Java 中的一種安全失敗機(jī)制,它表示的是在遍歷時(shí)不是直接在原集合上進(jìn)行訪問(wèn),而是先復(fù)制原有集合內(nèi)容,在拷貝的集合上進(jìn)行遍歷。由于迭代時(shí)是對(duì)原集合的拷貝進(jìn)行遍歷,所以在遍歷過(guò)程中對(duì)原集合所作的修改并不能被迭代器檢測(cè)到,所以不會(huì)觸發(fā) ConcurrentModificationException。java.util.concurrent包下的容器都是安全失敗的,可以在多線程條件下使用,并發(fā)修改。

ArrayList、LinkedList 和 Vector 的區(qū)別

這也是一道老生常談的問(wèn)題了

ArrayList、LinkedList、Vector都是位于java.util包下的工具類(lèi),它們都實(shí)現(xiàn)了 List 接口。

  • ArrayList 的底層是動(dòng)態(tài)數(shù)組,它是基于數(shù)組的特性而演變出來(lái)的,所以ArrayList 遍歷訪問(wèn)非常快,但是增刪比較慢,因?yàn)闀?huì)涉及到數(shù)組的拷貝。ArrayList 是一個(gè)非線程安全的容器,在并發(fā)場(chǎng)景下會(huì)造成問(wèn)題,如果想使用線程安全的容器的話,推薦使用Collections.synchronizedList;ArrayList 在擴(kuò)容時(shí)會(huì)增加 50% 的容量。
  • LinkedList 的底層是雙向鏈表,所以 LinkedList 的增加和刪除非常快,只需把元素刪除,把各自的指針指向新的元素即可。但是 LinkedList 遍歷比較慢,因?yàn)橹挥忻看卧L問(wèn)一個(gè)元素才能知道下一個(gè)元素的值。LinkedList 也是一個(gè)非線程安全的容器,推薦使用Collections.synchronizedList
  • Vector 向量是最早出現(xiàn)的集合容器,Vector 是一個(gè)線程安全的容器,它的每個(gè)方法都粗暴的加上了synchronized鎖,所以它的增刪、遍歷效率都很低。Vector 在擴(kuò)容時(shí),它的容量會(huì)增加一倍。

Exception 和 Error 有什么區(qū)別

Exception 泛指的是異常,Exception 主要分為兩種異常,一種是編譯期出現(xiàn)的異常,稱為checkedException,一種是程序運(yùn)行期間出現(xiàn)的異常,稱為uncheckedException,常見(jiàn)的 checkedException 有IOException,uncheckedException 統(tǒng)稱為RuntimeException,常見(jiàn)的 RuntimeException 主要有NullPointerException、IllegalArgumentException、ArrayIndexOutofBoundException等,Exception 可以被捕獲。

Error 是指程序運(yùn)行過(guò)程中出現(xiàn)的錯(cuò)誤,通常情況下會(huì)造成程序的崩潰,Error 通常是不可恢復(fù)的,Error 不能被捕獲。

詳細(xì)可以參考這篇文章看完這篇 Exception 和 Error ,和面試官扯皮就沒(méi)問(wèn)題了

String、StringBuilder 和 StringBuffer 有什么區(qū)別

String 特指的是 Java 中的字符串,String 類(lèi)位于java.lang包下,String 類(lèi)是由 final 修飾的,String 字符串一旦創(chuàng)建就不能被修改,任何對(duì) String 進(jìn)行修改的操作都相當(dāng)于重新創(chuàng)建了一個(gè)字符串。String 字符串的底層使用 StringBuilder 來(lái)實(shí)現(xiàn)的

StringBuilder 位于java.util包下,StringBuilder 是一非線程安全的容器,StringBuilder 的 append 方法常用于字符串拼接,它的拼接效率要比 String 中+號(hào)的拼接效率高。StringBuilder 一般不用于并發(fā)環(huán)境

StringBuffer 位于java.util包下,StringBuffer 是一個(gè)線程安全的容器,多線程場(chǎng)景下一般使用 StringBuffer 用作字符串的拼接

StringBuilder 和 StringBuffer 都是繼承于AbstractStringBuilder類(lèi),AbstractStringBuilder 類(lèi)實(shí)現(xiàn)了 StringBuffer 和 StringBuilder 的常規(guī)操作。

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

代理一般分為靜態(tài)代理和動(dòng)態(tài)代理,它們都是代理模式的一種應(yīng)用,靜態(tài)代理指的是在程序運(yùn)行前已經(jīng)編譯好,程序知道由誰(shuí)來(lái)執(zhí)行代理方法。

而動(dòng)態(tài)代理只有在程序運(yùn)行期間才能確定,相比于靜態(tài)代理, 動(dòng)態(tài)代理的優(yōu)勢(shì)在于可以很方便的對(duì)代理類(lèi)的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個(gè)代理類(lèi)中的方法。可以說(shuō)動(dòng)態(tài)代理是基于反射實(shí)現(xiàn)的。通過(guò)反射我們可以直接操作類(lèi)或者對(duì)象,比如獲取類(lèi)的定義,獲取聲明的屬性和方法,調(diào)用方法,在運(yùn)行時(shí)可以修改類(lèi)的定義。

動(dòng)態(tài)代理是一種在運(yùn)行時(shí)構(gòu)建代理、動(dòng)態(tài)處理方法調(diào)用的機(jī)制。動(dòng)態(tài)代理的實(shí)現(xiàn)方式有很多,Java 提供的代理被稱為JDK 動(dòng)態(tài)代理,JDK 動(dòng)態(tài)代理是基于類(lèi)的繼承。

int 和 Integer 的區(qū)別

int 和 Integer 區(qū)別可就太多了

  • int 是 Java 中的基本數(shù)據(jù)類(lèi)型,int 代表的是整型,一個(gè) int 占 4 字節(jié),也就是 32 位,int 的初始值是默認(rèn)值是 0 ,int 在 Java 內(nèi)存模型中被分配在棧中,int 沒(méi)有方法。
  • Integer 是 Java 中的基本數(shù)據(jù)類(lèi)型的包裝類(lèi),Integer 是一個(gè)對(duì)象,Integer 可以進(jìn)行方法調(diào)用,Integer 的默認(rèn)值是 null,Integer 在 Java 內(nèi)存模型中被分配在堆中。int 和 Integer 在計(jì)算時(shí)可以進(jìn)行相互轉(zhuǎn)換,int -> Integer 的過(guò)程稱為裝箱,Integer -> int 的過(guò)程稱為拆箱,Integer 還有 IntegerCache ,會(huì)自動(dòng)緩存 -128 - 127 中的值

Java 提供了哪些 I/O 方式

Java I/O 方式有很多種,傳統(tǒng)的 I/O 也稱為BIO,主要流有如下幾種

Java I/O 包的實(shí)現(xiàn)比較簡(jiǎn)單,但是容易出現(xiàn)性能瓶頸,傳統(tǒng)的 I/O 是基于同步阻塞的。

JDK 1.4 之后提供了NIO,也就是位于java.nio包下,提供了基于channel、Selector、Buffer的抽象,可以構(gòu)建多路復(fù)用、同步非阻塞 I/O 程序。

JDK 1.7 之后對(duì) NIO 進(jìn)行了進(jìn)一步改進(jìn),引入了異步非阻塞的方式,也被稱為AIO(Asynchronous IO)。可以用生活中的例子來(lái)說(shuō)明:項(xiàng)目經(jīng)理交給手下員工去改一個(gè) bug,那么項(xiàng)目經(jīng)理不會(huì)一直等待員工解決 bug,他肯定在員工解決 bug 的期間給其他手下分配 bug 或者做其他事情,員工解決完 bug 之后再告訴項(xiàng)目經(jīng)理 bug 解決完了。

談?wù)勀阒赖脑O(shè)計(jì)模式

一張思維導(dǎo)圖鎮(zhèn)場(chǎng)

比如全局唯一性可以用單例模式。

可以使用策略模式優(yōu)化過(guò)多的 if...else...

制定標(biāo)準(zhǔn)用模版模式

接手其他人的鍋,但不想改原來(lái)的類(lèi)用適配器模式

使用組合而不是繼承

使用裝飾器可以制作加糖、加奶酪的咖啡

代理可以用于任何中間商......

Comparator 和 Comparable 有什么不同

  • Comparable 更像是自然排序

  • Comparator 更像是定制排序

同時(shí)存在時(shí)采用 Comparator(定制排序)的規(guī)則進(jìn)行比較。

對(duì)于一些普通的數(shù)據(jù)類(lèi)型(比如 String, Integer, Double…),它們默認(rèn)實(shí)現(xiàn)了Comparable 接口,實(shí)現(xiàn)了 compareTo 方法,我們可以直接使用。

而對(duì)于一些自定義類(lèi),它們可能在不同情況下需要實(shí)現(xiàn)不同的比較策略,我們可以新創(chuàng)建 Comparator 接口,然后使用特定的 Comparator 實(shí)現(xiàn)進(jìn)行比較。

Object 類(lèi)中一般都有哪些方法

Object 類(lèi)是所有對(duì)象的父類(lèi),它里面包含一些所有對(duì)象都能夠使用的方法

  • hashCode():用于計(jì)算對(duì)象的哈希碼
  • equals():用于對(duì)象之間比較值是否相等
  • toString(): 用于把對(duì)象轉(zhuǎn)換成為字符串
  • clone(): 用于對(duì)象之間的拷貝
  • wait(): 用于實(shí)現(xiàn)對(duì)象之間的等待
  • notify(): 用于通知對(duì)象釋放資源
  • notifyAll(): 用于通知所有對(duì)象釋放資源
  • finalize(): 用于告知垃圾回收器進(jìn)行垃圾回收
  • getClass(): 用于獲得對(duì)象類(lèi)

Java 泛型和類(lèi)型擦除

關(guān)于 Java 泛型和擦除看著一篇就夠了。http://softlab.sdut.edu.cn/blog/subaochen/2017/01/generics-type-erasure/

反射的基本原理,反射創(chuàng)建類(lèi)實(shí)例的三種方式是什么

反射機(jī)制就是使 Java 程序在運(yùn)行時(shí)具有自省(introspect)的能力,通過(guò)反射我們可以直接操作類(lèi)和對(duì)象,比如獲取某個(gè)類(lèi)的定義,獲取類(lèi)的屬性和方法,構(gòu)造方法等。

創(chuàng)建類(lèi)實(shí)例的三種方式是

  • 對(duì)象實(shí)例.getClass();
  • 通過(guò) Class.forName() 創(chuàng)建
  • 對(duì)象實(shí)例.newInstance() 方法創(chuàng)建

強(qiáng)引用、若引用、虛引用和幻象引用的區(qū)別

我們說(shuō)的不同的引用類(lèi)型其實(shí)都是邏輯上的,而對(duì)于虛擬機(jī)來(lái)說(shuō),主要體現(xiàn)的是對(duì)象的不同的可達(dá)性(reachable)狀態(tài)和對(duì)垃圾收集(garbage collector)的影響。

可以通過(guò)下面的流程來(lái)對(duì)對(duì)象的生命周期做一個(gè)總結(jié)

對(duì)象被創(chuàng)建并初始化,對(duì)象在運(yùn)行時(shí)被使用,然后離開(kāi)對(duì)象的作用域,對(duì)象會(huì)變成不可達(dá)并會(huì)被垃圾收集器回收。圖中用紅色標(biāo)明的區(qū)域表示對(duì)象處于強(qiáng)可達(dá)階段。

JDK1.2 介紹了java.lang.ref包,對(duì)象的生命周期有四個(gè)階段:?強(qiáng)可達(dá)?(Strongly Reachable?)、軟可達(dá)(Soft Reachable?)、弱可達(dá)(Weak Reachable?)、幻象可達(dá)(Phantom Reachable?)。

如果只討論符合垃圾回收條件的對(duì)象,那么只有三種:軟可達(dá)、弱可達(dá)和幻象可達(dá)。

  • 軟可達(dá):軟可達(dá)就是?我們只能通過(guò)軟引用?才能訪問(wèn)的狀態(tài),軟可達(dá)的對(duì)象是由SoftReference引用的對(duì)象,并且沒(méi)有強(qiáng)引用的對(duì)象。軟引用是用來(lái)描述一些還有用但是非必須的對(duì)象。垃圾收集器會(huì)盡可能長(zhǎng)時(shí)間的保留軟引用的對(duì)象,但是會(huì)在發(fā)生OutOfMemoryError之前,回收軟引用的對(duì)象。如果回收完軟引用的對(duì)象,內(nèi)存還是不夠分配的話,就會(huì)直接拋出 OutOfMemoryError。

  • 弱可達(dá):弱可達(dá)的對(duì)象是WeakReference引用的對(duì)象。垃圾收集器可以隨時(shí)收集弱引用的對(duì)象,不會(huì)嘗試保留軟引用的對(duì)象。

  • 幻象可達(dá):幻象可達(dá)是由PhantomReference引用的對(duì)象,幻象可達(dá)就是沒(méi)有強(qiáng)、軟、弱引用進(jìn)行關(guān)聯(lián),并且已經(jīng)被 finalize 過(guò)了,只有幻象引用指向這個(gè)對(duì)象的時(shí)候。

除此之外,還有強(qiáng)可達(dá)和不可達(dá)的兩種可達(dá)性判斷條件

  • 強(qiáng)可達(dá):就是一個(gè)對(duì)象剛被創(chuàng)建、初始化、使用中的對(duì)象都是處于強(qiáng)可達(dá)的狀態(tài)
  • 不可達(dá)(unreachable):處于不可達(dá)的對(duì)象就意味著對(duì)象可以被清除了。

下面是一個(gè)不同可達(dá)性狀態(tài)的轉(zhuǎn)換圖

判斷可達(dá)性條件,也是 JVM 垃圾收集器決定如何處理對(duì)象的一部分考慮因素。

所有的對(duì)象可達(dá)性引用都是java.lang.ref.Reference的子類(lèi),它里面有一個(gè)get()方法,返回引用對(duì)象。如果已通過(guò)程序或垃圾收集器清除了此引用對(duì)象,則此方法返回 null 。也就是說(shuō),除了幻象引用外,軟引用和弱引用都是可以得到對(duì)象的。而且這些對(duì)象可以人為拯救,變?yōu)閺?qiáng)引用,例如把 this 關(guān)鍵字賦值給對(duì)象,只要重新和引用鏈上的任意一個(gè)對(duì)象建立關(guān)聯(lián)即可。

final、finally 和 finalize() 的區(qū)別

這三者可以說(shuō)是沒(méi)有任何關(guān)聯(lián)之處,我們上面談到了,final 可以用來(lái)修飾類(lèi)、變量和方法,可以參考上面 final 的那道面試題。

finally 是一個(gè)關(guān)鍵字,它經(jīng)常和 try 塊一起使用,用于異常處理。使用 try...finally 的代碼塊種,finally 部分的代碼一定會(huì)被執(zhí)行,所以我們經(jīng)常在 finally 方法中用于資源的關(guān)閉操作。

JDK1.7 中,推薦使用try-with-resources優(yōu)雅的關(guān)閉資源,它直接使用 try(){} 進(jìn)行資源的關(guān)閉即可,就不用寫(xiě) finally 關(guān)鍵字了。

finalize 是 Object 對(duì)象中的一個(gè)方法,用于對(duì)象的回收方法,這個(gè)方法我們一般不推薦使用,finalize 是和垃圾回收關(guān)聯(lián)在一起的,在 Java 9 中,將 finalize 標(biāo)記為了deprecated, 如果沒(méi)有特別原因,不要實(shí)現(xiàn) finalize 方法,也不要指望他來(lái)進(jìn)行垃圾回收。

內(nèi)部類(lèi)有哪些分類(lèi),分別解釋一下

在 Java 中,可以將一個(gè)類(lèi)的定義放在另外一個(gè)類(lèi)的定義內(nèi)部,這就是內(nèi)部類(lèi)。內(nèi)部類(lèi)本身就是類(lèi)的一個(gè)屬性,與其他屬性定義方式一致。

內(nèi)部類(lèi)的分類(lèi)一般主要有四種

  • 成員內(nèi)部類(lèi)
  • 局部?jī)?nèi)部類(lèi)
  • 匿名內(nèi)部類(lèi)
  • 靜態(tài)內(nèi)部類(lèi)

靜態(tài)內(nèi)部類(lèi)就是定義在類(lèi)內(nèi)部的靜態(tài)類(lèi),靜態(tài)內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)所有的靜態(tài)變量,而不可訪問(wèn)外部類(lèi)的非靜態(tài)變量;

成員內(nèi)部類(lèi)就是定義在類(lèi)內(nèi)部,成員位置上的非靜態(tài)類(lèi),就是成員內(nèi)部類(lèi)。成員內(nèi)部類(lèi)可以訪問(wèn)外部類(lèi)所有的變量和方法,包括靜態(tài)和非靜態(tài),私有和公有。

定義在方法中的內(nèi)部類(lèi),就是局部?jī)?nèi)部類(lèi)。定義在實(shí)例方法中的局部類(lèi)可以訪問(wèn)外部類(lèi)的所有變量和方法,定義在靜態(tài)方法中的局部類(lèi)只能訪問(wèn)外部類(lèi)的靜態(tài)變量和方法。

匿名內(nèi)部類(lèi)就是沒(méi)有名字的內(nèi)部類(lèi),除了沒(méi)有名字,匿名內(nèi)部類(lèi)還有以下特點(diǎn):

  • 匿名內(nèi)部類(lèi)必須繼承一個(gè)抽象類(lèi)或者實(shí)現(xiàn)一個(gè)接口
  • 匿名內(nèi)部類(lèi)不能定義任何靜態(tài)成員和靜態(tài)方法。
  • 當(dāng)所在的方法的形參需要被匿名內(nèi)部類(lèi)使用時(shí),必須聲明為 final。
  • 匿名內(nèi)部類(lèi)不能是抽象的,它必須要實(shí)現(xiàn)繼承的類(lèi)或者實(shí)現(xiàn)的接口的所有抽象方法。

說(shuō)出幾種常用的異常

  • NullPointerException: 空指針異常
  • NoSuchMethodException:找不到方法
  • IllegalArgumentException:不合法的參數(shù)異常
  • IndexOutOfBoundException: 數(shù)組下標(biāo)越界異常
  • IOException:由于文件未找到、未打開(kāi)或者I/O操作不能進(jìn)行而引起異常
  • ClassNotFoundException :找不到文件所拋出的異常
  • NumberFormatException:字符的UTF代碼數(shù)據(jù)格式有錯(cuò)引起異常;
  • InterruptedException:線程中斷拋出的異常

靜態(tài)綁定和動(dòng)態(tài)綁定的區(qū)別

一個(gè)Java 程序要經(jīng)過(guò)編寫(xiě)、編譯、運(yùn)行三個(gè)步驟,其中編寫(xiě)代碼不在我們討論的范圍之內(nèi),那么我們的重點(diǎn)自然就放在了編譯和運(yùn)行這兩個(gè)階段,由于編譯和運(yùn)行階段過(guò)程相當(dāng)繁瑣,下面就我的理解來(lái)進(jìn)行解釋:

Java 程序從源文件創(chuàng)建到程序運(yùn)行要經(jīng)過(guò)兩大步驟:

1、編譯時(shí)期是由編譯器將源文件編譯成字節(jié)碼的過(guò)程

2、字節(jié)碼文件由Java虛擬機(jī)解釋執(zhí)行

綁定

綁定就是一個(gè)方法的調(diào)用與調(diào)用這個(gè)方法的類(lèi)連接在一起的過(guò)程被稱為綁定。

綁定主要分為兩種:

靜態(tài)綁定 和 動(dòng)態(tài)綁定

綁定的其他叫法

靜態(tài)綁定 ?== 前期綁定 == 編譯時(shí)綁定

動(dòng)態(tài)綁定 ?== 后期綁定 == 運(yùn)行時(shí)綁定

為了方便區(qū)分:下面統(tǒng)一稱呼為靜態(tài)綁定和動(dòng)態(tài)綁定

靜態(tài)綁定

在程序運(yùn)行前,也就是編譯時(shí)期 JVM 就能夠確定方法由誰(shuí)調(diào)用,這種機(jī)制稱為靜態(tài)綁定

識(shí)別靜態(tài)綁定的三個(gè)關(guān)鍵字以及各自的理解

如果一個(gè)方法由 private、static、final 任意一個(gè)關(guān)鍵字所修飾,那么這個(gè)方法是前期綁定的

構(gòu)造方法也是前期綁定

private:private 關(guān)鍵字是私有的意思,如果被 private 修飾的方法是無(wú)法由本類(lèi)之外的其他類(lèi)所調(diào)用的,也就是本類(lèi)所特有的方法,所以也就由編譯器識(shí)別此方法是屬于哪個(gè)類(lèi)的

public?class?Person?{

????private?String?talk;

????private?String?canTalk(){
????????return?talk;
????}
}

class?Animal{

????public?static?void?main(String[]?args)?{
????????Person?p?=?new?Person();
????????//?private?修飾的方法是Person類(lèi)獨(dú)有的,所以Animal類(lèi)無(wú)法訪問(wèn)(動(dòng)物本來(lái)就不能說(shuō)話)
//????????p.canTalk();
????}
}

final:final 修飾的方法不能被重寫(xiě),但是可以由子類(lèi)進(jìn)行調(diào)用,如果將方法聲明為 final 可以有效的關(guān)閉動(dòng)態(tài)綁定

public?class?Fruit?{

????private?String?fruitName;

????final?String?eatingFruit(String?name){
????????System.out.println("eating?"?+?name);
????????return?fruitName;
????}
}

class?Apple?extends?Fruit{

??????//?不能重寫(xiě)final方法,eatingFruit方法只屬于Fruit類(lèi),Apple類(lèi)無(wú)法調(diào)用
//????String?eatingFruit(String?name){
//????????super.eatingFruit(name);
//????}

????String?eatingApple(String?name){
????????return?super.eatingFruit(name);
????}
}

static:static 修飾的方法比較特殊,不用通過(guò) new 出某個(gè)類(lèi)來(lái)調(diào)用,由類(lèi)名.變量名直接調(diào)用該方法,這個(gè)就很關(guān)鍵了,new 很關(guān)鍵,也可以認(rèn)為是開(kāi)啟多態(tài)的導(dǎo)火索,而由類(lèi)名.變量名直接調(diào)用的話,此時(shí)的類(lèi)名是確定的,并不會(huì)產(chǎn)生多態(tài),如下代碼:

public?class?SuperClass?{

????public?static?void?sayHello(){
????????
????????System.out.println("由?superClass?說(shuō)你好");
????}
}

public?class?SubClass?extends?SuperClass{

????public?static?void?sayHello(){
????????System.out.println("由?SubClass?說(shuō)你好");
????}

????public?static?void?main(String[]?args)?{
????????SuperClass.sayHello();
????????SubClass.sayHello();
????}
}

SubClass 繼承 SuperClass 后,在

是無(wú)法重寫(xiě) sayHello 方法的,也就是說(shuō) sayHello() 方法是對(duì)子類(lèi)隱藏的,但是你可以編寫(xiě)自己的 sayHello() 方法,也就是子類(lèi) SubClass 的sayHello() 方法,由此可見(jiàn),方法由 static 關(guān)鍵詞所修飾,也是編譯時(shí)綁定

動(dòng)態(tài)綁定

在運(yùn)行時(shí)根據(jù)具體對(duì)象的類(lèi)型進(jìn)行綁定

除了由 private、final、static 所修飾的方法和構(gòu)造方法外,JVM 在運(yùn)行期間決定方法由哪個(gè)對(duì)象調(diào)用的過(guò)程稱為動(dòng)態(tài)綁定

如果把編譯、運(yùn)行看成一條時(shí)間線的話,在運(yùn)行前必須要進(jìn)行程序的編譯過(guò)程,那么在編譯期進(jìn)行的綁定是前期綁定,在程序運(yùn)行了,發(fā)生的綁定就是后期綁定

public?class?Father?{

????void?drinkMilk(){
????????System.out.println("父親喜歡喝牛奶");
????}
}

public?class?Son?extends?Father{

????@Override
????void?drinkMilk()?{
????????System.out.println("兒子喜歡喝牛奶");
????}

????public?static?void?main(String[]?args)?{
????????Father?son?=?new?Son();
????????son.drinkMilk();
????}
}

Son 類(lèi)繼承 Father 類(lèi),并重寫(xiě)了父類(lèi)的 dringMilk() 方法,在輸出結(jié)果得出的是兒子喜歡喝牛奶。那么上面的綁定方式是什么呢?

上面的綁定方式稱之為動(dòng)態(tài)綁定,因?yàn)樵谀憔帉?xiě) Father son = new Son() 的時(shí)候,編譯器并不知道 son 對(duì)象真正引用的是誰(shuí),在程序運(yùn)行時(shí)期才知道,這個(gè) son 是一個(gè) Father 類(lèi)的對(duì)象,但是卻指向了 Son 的引用,這種概念稱之為多態(tài),那么我們就能夠整理出來(lái)多態(tài)的三個(gè)原則:

  • 繼承

  • 重寫(xiě)

  • 父類(lèi)對(duì)象指向子類(lèi)引用

也就是說(shuō),在 Father son = new Son() ,觸發(fā)了動(dòng)態(tài)綁定機(jī)制。

動(dòng)態(tài)綁定的過(guò)程

  • 虛擬機(jī)提取對(duì)象的實(shí)際類(lèi)型的方法表;
  • 虛擬機(jī)搜索方法簽名;
  • 調(diào)用方法。
  • 動(dòng)態(tài)綁定和靜態(tài)綁定的特點(diǎn)

    靜態(tài)綁定

    靜態(tài)綁定在編譯時(shí)期觸發(fā),那么它的主要特點(diǎn)是

    1、編譯期觸發(fā),能夠提早知道代碼錯(cuò)誤

    2、提高程序運(yùn)行效率

    動(dòng)態(tài)綁定

    1、使用動(dòng)態(tài)綁定的前提條件能夠提高代碼的可用性,使代碼更加靈活。

    2、多態(tài)是設(shè)計(jì)模式的基礎(chǔ),能夠降低耦合性。

    - END -點(diǎn)擊圖片參與活動(dòng),免費(fèi)包郵送書(shū)??????

    ●??人人都欠微軟一個(gè)正版?

    ●??腳本之家粉絲福利,請(qǐng)查看!

    ●??2020年開(kāi)發(fā)者生態(tài)報(bào)告:Python超越Java

    ●?Java 中的 String 有沒(méi)有長(zhǎng)度限制?

    ●?小白,你要的Java抽象類(lèi),操碎了心!

    C、Java:我看著你們,滿懷敬意

    總結(jié)

    以上是生活随笔為你收集整理的java 多态判断非空_收藏Java 面试题全梳理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。