美國(guó)留學(xué)選擇什么專業(yè)好?留學(xué)美國(guó)熱門專業(yè)推薦
2019-06-26
更新時(shí)間:2024-06-10 21:22作者:小樂(lè)
Java基礎(chǔ)知識(shí)
Java 并發(fā)的特點(diǎn)是什么: 可以在其中執(zhí)行許多語(yǔ)句,而不必一次執(zhí)行所有語(yǔ)句面向?qū)ο螅?基于類和面向?qū)ο蟮木幊陶Z(yǔ)言。獨(dú)立:獨(dú)立的編程語(yǔ)言,支持一次編寫,隨處運(yùn)行,即編譯后的代碼可以在所有支持Java的平臺(tái)上運(yùn)行。 Java的特點(diǎn)Java的特點(diǎn)包括以下幾點(diǎn)
簡(jiǎn)單來(lái)說(shuō),Java會(huì)讓你的工作變得更加輕松,讓你專注于主要業(yè)務(wù)邏輯,而不必?fù)?dān)心指針、運(yùn)算符重載、內(nèi)存回收等與主要業(yè)務(wù)無(wú)關(guān)的功能??梢浦残?,Java是平臺(tái)無(wú)關(guān)的,這意味著在一個(gè)平臺(tái)上編寫的任何應(yīng)用程序都可以輕松移植到另一個(gè)平臺(tái)上。安全性,編譯后,所有代碼都會(huì)轉(zhuǎn)換為字節(jié)碼,人類無(wú)法讀取。它使得開發(fā)無(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ò)從Internet 上的任何計(jì)算機(jī)調(diào)用方法來(lái)訪問(wèn)文件。這是一項(xiàng)革命性的功能,對(duì)于當(dāng)今的互聯(lián)網(wǎng)來(lái)說(shuō)非常重要。健壯性,Java具有強(qiáng)大的內(nèi)存管理功能,可以在編譯和運(yùn)行時(shí)檢查代碼,這有助于消除錯(cuò)誤。高性能,Java最黑暗的技術(shù)就是字節(jié)碼編程。 Java代碼編譯后的字節(jié)碼可以很容易地轉(zhuǎn)換為本地機(jī)器碼。使用JIT 編譯器實(shí)現(xiàn)高性能。解釋型,Java被編譯成字節(jié)碼,由Java運(yùn)行時(shí)環(huán)境解釋。多線程,Java 支持多個(gè)執(zhí)行線程(也稱為輕量級(jí)進(jìn)程),包括一組同步原語(yǔ)。這使得使用線程進(jìn)行編程變得更加容易,并且Java通過(guò)監(jiān)視器模型實(shí)現(xiàn)了線程安全。描述值傳遞和引用傳遞之間的區(qū)別。如果想真正了解,可以參考這篇文章:https://www.zhihu.com/question/31203609
簡(jiǎn)單來(lái)說(shuō),就是
值傳遞是指在調(diào)用函數(shù)時(shí)將實(shí)際參數(shù)復(fù)制到函數(shù)中。這樣,如果函數(shù)修改了傳遞的形參,則不會(huì)影響實(shí)參。
引用傳遞是指在調(diào)用函數(shù)時(shí)將對(duì)象的地址直接傳遞給函數(shù)。如果修改形參,實(shí)參的值就會(huì)受到影響。
==和equals 有什么區(qū)別?==是Java中的運(yùn)算符。它有兩種比較方法。
對(duì)于基本數(shù)據(jù)類型,==判斷兩邊的值是否相等public class DoubleCompareAndEquals { Person person1=new Person(24,'boy');人person2=new Person(24,'女孩');整數(shù)c=10;私有無(wú)效doubleCompare(){ int a=10;整數(shù)b=10; System.out.println(a==b); System.out.println(a==c); System.out.println(person1.getId()==person2.getId());對(duì)于引用類型,==判斷兩邊的引用是否相等,即兩個(gè)對(duì)象是否指向同一個(gè)內(nèi)存區(qū)域。 private void equals(){ System.out.println(person1.getName().equals(person2.getName()));}equals是Java中所有對(duì)象的父類,即Object類定義的方法。它只能比較對(duì)象,并且指示兩個(gè)引用的值是否相等。所以記住,并不是說(shuō)==比較引用是否相等,equals比較值,這個(gè)需要區(qū)分。
equals用于對(duì)象之間的比較,具有以下特點(diǎn)
自反性:對(duì)于任何非空引用x,x.equals(x) 應(yīng)該返回true。對(duì)稱性:對(duì)于任何非空引用x 和y,如果x.equals(y) 為true,則y.equals(x) 也為true。傳遞性:對(duì)于任何非空參考值,都有三個(gè)值:x、y和z。如果x.equals(y) 返回true 并且y.equals(z) 返回true,則x.equals(z) 也應(yīng)該返回true。一致性:對(duì)于任何非空引用x 和y,如果x.equals(y) 相等,則它們必須始終相等。非空性:對(duì)于任何非空引用的值x,x.equals(null) 必須返回false。 String中的equals是如何重寫的? String在Java中表示字符串。 String 類很特殊。整個(gè)類都被final修飾了。也就是說(shuō)String不能被任何類繼承。對(duì)String string 的任何修改所有方法都會(huì)創(chuàng)建一個(gè)新字符串。
equals方法是Object類定義的方法。 Object是所有類的父類,當(dāng)然也包括String。字符串重寫equals 方法。我們來(lái)看看它是如何重寫的。
首先判斷兩個(gè)待比較字符串的引用是否相等。如果引用相等,則直接返回true。如果不相等,則繼續(xù)下面的判斷,然后判斷比較的對(duì)象是否是String的實(shí)例。如果不是,直接返回false。如果是,則比較兩個(gè)字符串的長(zhǎng)度,看看它們是否相等。如果長(zhǎng)度不想等待,則無(wú)需比較;如果長(zhǎng)度相同,則將比較字符串中的每個(gè)字符是否相等。一旦有一個(gè)字符不相等,則直接返回false。下面是它的流程圖
這是另一個(gè)提醒,您可能想知道什么時(shí)候
if (this==anObject) { return true;}這個(gè)判斷語(yǔ)句怎么會(huì)返回true呢?因?yàn)槎际亲址?,字符串比較不都是堆空間嗎?乍一看,似乎永遠(yuǎn)不會(huì)消失,但你忘記了String.intern() 方法。它所代表的概念有不同的JDK版本。不同的區(qū)別
在JDK1.7及以后版本中,調(diào)用intern方法是判斷運(yùn)行時(shí)常量池中是否存在指定的字符串。如果沒(méi)有,則將該字符串添加到常量池中,并返回常量池中的對(duì)象。
驗(yàn)證流程如下
私有無(wú)效StringOverrideEquals(){ String s1='aaa';字符串s2='aa' + new String('a');字符串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) 無(wú)論如何這都會(huì)返回true,因?yàn)閟1 字符串在創(chuàng)建時(shí)已經(jīng)存在于常量池中。那么第二條語(yǔ)句返回false,因?yàn)閟1返回常量池中的對(duì)象,s2返回堆中的對(duì)象。第三條語(yǔ)句s3.intern.equals(s1)返回true,因?yàn)殡m然s3對(duì)象在堆中創(chuàng)建了一個(gè)對(duì)象,但是s3中的'aaa'返回的是常量池中的對(duì)象。為什么重寫equals方法必須重寫hashcode方法。 equals方法和hashCode都是Object中定義的方法,它們經(jīng)常被一起重寫。
equals方法是用來(lái)比較對(duì)象大小是否相等的方法,hashcode方法是用來(lái)確定每個(gè)對(duì)象的哈希值的方法。如果只重寫equals方法而不重寫hashcode方法,很可能會(huì)導(dǎo)致兩個(gè)不同的對(duì)象的hashcode相等,從而造成沖突。例如
字符串str1='調(diào)用'; String str2='重位置';兩者的hashcode相等,但equals可能不相等。
我們看一下hashCode的官方定義
把它們加起來(lái):
如果Java運(yùn)行時(shí)對(duì)同一個(gè)對(duì)象調(diào)用hashCode方法,那么無(wú)論調(diào)用多少次,都應(yīng)該返回相同的hashCode。但是,在不同的Java程序中,hashCode方法返回的值可能不一致。如果兩個(gè)對(duì)象的equals 相等,那么hashCode 一定相同。如果兩個(gè)對(duì)象的equals不相等,那么hashCode也可能相同,所以需要重寫hashCode方法,因?yàn)槟悴恢纇ashCode的底層結(jié)構(gòu)(反正我也不知道,有很多牛都會(huì)教),所以需要重寫hashCode方法,為不同的對(duì)象生成不同的hashCode值,這樣可以提高不同對(duì)象的訪問(wèn)速度。 HashCode通常是通過(guò)將地址轉(zhuǎn)換為整數(shù)來(lái)實(shí)現(xiàn)的。 String s1=new String('abc') 在內(nèi)存中創(chuàng)建一兩個(gè)對(duì)象。 String s1 聲明了一個(gè)String 類型的s1 變量,它不是一個(gè)對(duì)象。使用new關(guān)鍵字會(huì)在堆中創(chuàng)建一個(gè)對(duì)象,另一個(gè)對(duì)象是abc,會(huì)在常量池中創(chuàng)建,所以一共創(chuàng)建了兩個(gè)對(duì)象;如果常量池中已經(jīng)存在abc,則將創(chuàng)建一個(gè)對(duì)象。
詳細(xì)內(nèi)容請(qǐng)閱讀作者的另一篇文章:String、StringBuffer、StringBuilde獨(dú)特詳解
為什么String 是不可變的? jdk源碼中String是如何定義的?為什么要這樣設(shè)計(jì)呢?首先,我們來(lái)了解一下什么是不可變對(duì)象。不可變對(duì)象意味著一旦創(chuàng)建,其內(nèi)部狀態(tài)就無(wú)法修改。這是什么意思?換句話說(shuō),不可變對(duì)象需要遵守以下原則:
不可變對(duì)象的內(nèi)部屬性都是最終的。不可變對(duì)象的內(nèi)部屬性都是私有的。不可變對(duì)象不能提供任何可以修改內(nèi)部狀態(tài)的方法,setter 方法也不能。不可變對(duì)象不能被繼承和擴(kuò)展。而不是問(wèn)String 為什么它是不可變的。更好講一下如何將String設(shè)計(jì)成不可變的。
String類是一個(gè)獨(dú)立于Java基本數(shù)據(jù)類型而存在的對(duì)象。您可以將String 視為字符串的集合。 String被設(shè)計(jì)成final的,這意味著一旦創(chuàng)建了String對(duì)象,它的值就不能被修改,任何修改String值的方法都是重新創(chuàng)建一個(gè)字符串。 String對(duì)象創(chuàng)建后,會(huì)存在于運(yùn)行時(shí)常量池中。運(yùn)行時(shí)常量池是方法區(qū)的一部分。 JDK1.7之后被移至堆中。
不可變對(duì)象并不是真正不可變的。它們的內(nèi)部屬性和值可以通過(guò)反射來(lái)修改,但一般我們不這樣做。
static關(guān)鍵字有什么用?談?wù)勀愕睦斫狻?static是Java中一個(gè)非常重要的關(guān)鍵字。 static所代表的概念就是靜態(tài)的。 Java中主要使用static
修改變量。用static修飾的變量稱為靜態(tài)變量,也稱為類變量。類變量屬于類。對(duì)于不同的類,靜態(tài)變量只有一份副本。 static修飾的變量位于方法區(qū); static 修飾的變量可以直接通過(guò)類名和變量名來(lái)訪問(wèn),而不必實(shí)例化類然后使用。修飾的方法,靜態(tài)修飾的方法稱為靜態(tài)方法。靜態(tài)方法可以通過(guò)類名和方法名直接使用。非靜態(tài)屬性和方法不能在靜態(tài)方法內(nèi)使用。 static可以修改代碼塊,主要分為兩種:一種是直接在類中定義,使用static{},稱為靜態(tài)代碼塊。另一種是在類中定義一個(gè)靜態(tài)內(nèi)部類,使用static class xxx來(lái)定義。 static 可用于通過(guò)import static xxx 靜態(tài)導(dǎo)入包。一般不推薦這種方法。 static 可以與單例模式一起使用,通過(guò)雙重檢查鎖實(shí)現(xiàn)線程安全的單例模式。詳細(xì)信息請(qǐng)參考這篇文章。靜態(tài)文章還能阻礙我嗎?
Final關(guān)鍵字的用途是什么?談?wù)勀愕睦斫狻?Final是Java中的關(guān)鍵字。意思是不可改變的。在Java中,final主要用來(lái)
修飾的類,被final修飾的類不能被繼承。這意味著你不能使用extends來(lái)繼承被final修飾的類。修改變量。由final修飾的變量不能被重寫。不被重寫有兩種含義。對(duì)于基本數(shù)據(jù)類型,被final修飾的變量的值是不能改變的。由final修飾的對(duì)象的引用不能被改變。但對(duì)象內(nèi)部的屬性是可以修改的。 Final修飾的變量在一定程度上具有不可變的作用,因此可以用來(lái)保護(hù)只讀數(shù)據(jù),尤其是在并發(fā)編程中,因?yàn)楹苊黠@final變量不能再被賦值,這有助于減少額外的同步。高架。修改后的方法、final修飾的方法不能被覆蓋。 Final修飾符與Java程序性能優(yōu)化沒(méi)有必然關(guān)系。抽象類和接口有什么區(qū)別?抽象類和接口是Java中的關(guān)鍵字。抽象類和接口都允許定義方法,而不需要具體的方法實(shí)現(xiàn)。抽象類和接口都允許被繼承,并且在JDK和框架的源代碼中廣泛使用它們來(lái)實(shí)現(xiàn)多態(tài)性和不同的設(shè)計(jì)模式。
區(qū)別在于
不同的抽象層次:類、抽象類、接口實(shí)際上是三種不同的抽象層次。抽象層次的順序是接口、抽象類、類。接口中只允許定義方法,不允許方法的實(shí)現(xiàn)。方法的定義和實(shí)現(xiàn)可以在抽象類中進(jìn)行;但類中只允許實(shí)現(xiàn)方法。我所說(shuō)的方法的定義在方法中是不允許的。 {}后面使用的關(guān)鍵字不同:類用class表示;抽象類用抽象類來(lái)表示;接口由接口表示:接口中定義的變量只能是公共靜態(tài)常量,抽象類中的變量是普通變量。重寫和重載的區(qū)別在Java中,重寫和重載是同一個(gè)方法的不同表達(dá)。讓我們簡(jiǎn)單區(qū)分一下重寫和重載。
孩子和父母之間的關(guān)系是不同的。重寫是針對(duì)子類和父類的不同表達(dá),而重載則是同一個(gè)類的不同表達(dá)。概念不同。子類重寫父類的方法一般用@override表示。被重寫方法的方法聲明、參數(shù)類型和順序必須與父類完全一致;重載是針對(duì)同一個(gè)類中的概念,它要求重載的方法必須滿足以下任何一個(gè)要求:方法參數(shù)的順序、參數(shù)的數(shù)量和參數(shù)的類型可以保持不同。字節(jié)的取值范圍是多少?如何計(jì)算byte的取值范圍?它在-128到127之間,總共256位。一個(gè)字節(jié)類型在計(jì)算機(jī)中占用一個(gè)字節(jié),即8位,所以最大為2^8=1111 1111。
Java 使用二進(jìn)制補(bǔ)碼來(lái)表示二進(jìn)制數(shù)。補(bǔ)碼的最高位是符號(hào)位。最高位為0 表示正數(shù),最高位1 表示負(fù)數(shù)。正數(shù)的補(bǔ)數(shù)就是它本身。由于最高位是符號(hào)位,所以正數(shù)代表0111 1111,即127。負(fù)數(shù)最大為1111 1111,涉及兩個(gè)0,一個(gè)+0,一個(gè)-0。 +0被歸類為正數(shù),即0,-0被歸類為負(fù)數(shù),即-128,所以字節(jié)的范圍是-128-127。
HashMap 和HashTable 的異同
HashMap和HashTable都是基于哈希表實(shí)現(xiàn)的,里面的每個(gè)元素都是一個(gè)鍵值對(duì)。 HashMap 和HashTable 都實(shí)現(xiàn)了Map、Cloneable 和Serialized 接口。
不同之處
父類不同:HashMap繼承AbstractMap類,而HashTable繼承Dictionary類??罩挡煌篐ashMap允許空的key和value值,而HashTable不允許空的key和value值。 HashMap 會(huì)將Null 鍵視為普通鍵。不允許重復(fù)空鍵。線程安全:HashMap不是線程安全的。如果多個(gè)外部操作同時(shí)修改HashMap的數(shù)據(jù)結(jié)構(gòu),比如添加、刪除等,就必須進(jìn)行同步操作。僅修改key或value并不是改變數(shù)據(jù)結(jié)構(gòu)的操作。您可以選擇構(gòu)造一個(gè)線程安全的Map,例如Collections.synchronizedMap或ConcurrentHashMap。而HashTable本身就是一個(gè)線程安全的容器。性能:雖然HashMap和HashTable都是基于單鏈表,但HashMap對(duì)于put或get操作可以達(dá)到恒定時(shí)間的性能;而HashTable的put和get操作都是synchronized鎖,所以效率很差。初始容量不同:HashTable的初始長(zhǎng)度為11,后續(xù)每次擴(kuò)容容量變?yōu)橹暗?n+1(n為之前的長(zhǎng)度),而HashMap的初始長(zhǎng)度為16,后續(xù)每次擴(kuò)容容量變?yōu)橹暗?倍原始長(zhǎng)度。創(chuàng)建時(shí),如果給定了一個(gè)容量的初始值,HashTable會(huì)直接使用你給定的大小,而HashMap會(huì)將其擴(kuò)展為2的冪。 HashMap和HashSet的區(qū)別HashSet繼承自AbstractSet接口,實(shí)現(xiàn)了Set、Cloneable和java.io.Serialized 接口。 HashSet不允許集合中有重復(fù)的值。 HashSet的底層實(shí)際上是HashMap,對(duì)HashSet的所有操作實(shí)際上都是對(duì)HashMap的操作。所以HashSet不保證集合的順序,也不是線程安全的容器。
在HashMap的底層結(jié)構(gòu)JDK1.7中,HashMap采用位桶+鏈表的實(shí)現(xiàn)方式,即用鏈表來(lái)處理沖突,將哈希值相同的鏈表存儲(chǔ)在一個(gè)數(shù)組中。但當(dāng)桶中元素較多時(shí),即哈希值相等的元素較多時(shí),按鍵值順序查找的效率較低。
因此,與JDK 1.7相比,JDK 1.8在底層結(jié)構(gòu)上做了一些改變。當(dāng)每個(gè)桶中的元素?cái)?shù)量大于8時(shí),將轉(zhuǎn)換為紅黑樹,以優(yōu)化查詢效率。
這幾天我一直在思考為什么HashMap的長(zhǎng)度是2的冪這個(gè)問(wèn)題。當(dāng)我和群里的朋友討論日常問(wèn)題時(shí),我問(wèn)他們?yōu)槭裁磍ength%hash==(n - 1) hash。他們相等的前提是長(zhǎng)度取2的次方。然后我回答說(shuō),長(zhǎng)度不能取2的次方嗎?其實(shí)我沒(méi)明白其中的因果關(guān)系,因?yàn)镠ashMap的長(zhǎng)度是2的冪,所以用余數(shù)來(lái)確定桶中的下標(biāo)。如果length的長(zhǎng)度不是2的冪,朋友們可以舉個(gè)例子試試。
比如長(zhǎng)度為9時(shí),3(9-1)=0、2(9-1)=0,都在0上,發(fā)生碰撞;
這會(huì)增加HashMap碰撞的概率。
HashMap多線程操作導(dǎo)致死循環(huán)問(wèn)題。 HashMap 不是線程安全的容器。高并發(fā)場(chǎng)景下,應(yīng)該使用ConcurrentHashMap。在多線程場(chǎng)景下使用HashMap會(huì)出現(xiàn)死循環(huán)問(wèn)題(基于JDK1.7)。問(wèn)題所在的位置是rehash地方,即
do { 條目下一個(gè)=e.next; //-- 假設(shè)線程一執(zhí)行到這里就被安排掛起int i=indexFor(e.hash, newCapacity); e.next=newTable[i]; newTable[i]=e; e=下一個(gè);} while (e !=null);這是JDK1.7的rehash代碼片段,在并發(fā)場(chǎng)景下會(huì)形成循環(huán)。
JDK1.8也會(huì)導(dǎo)致死循環(huán)問(wèn)題。
HashMap的線程安全實(shí)現(xiàn)有哪些?因?yàn)镠ashMap不是線程安全的容器,所以并發(fā)場(chǎng)景下建議使用ConcurrentHashMap,或者使用線程安全的HashMap或者Collections包下的線程安全容器,例如
Collections.synchronizedMap(new HashMap());還可以使用HashTable,它也是一個(gè)基于鍵值存儲(chǔ)的線程安全容器。 HashMap和HashTable經(jīng)常被比較,因?yàn)镠ashTable的數(shù)據(jù)結(jié)構(gòu)與HashMap相同。
上面最高效的是ConcurrentHashMap。
下面說(shuō)一下HashMap put的過(guò)程。首先使用哈希函數(shù)計(jì)算出key,然后執(zhí)行真正的插入方法。
Final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab;節(jié)點(diǎn)p;整數(shù)n,我; //如果表為null或者沒(méi)有為表分配內(nèi)存,則調(diào)整一次大小if ((tab=table)==null || (n=tab.length)==0) n=(tab=resize()) 。長(zhǎng)度; //如果指定hash值的節(jié)點(diǎn)為空,則直接插入。這個(gè)(n - 1) Hash 是表中真正的哈希if ((p=tab[i=(n - 1) hash])==null) tab[i]=newNode(hash, key, value, null) ; //如果不為空else { 節(jié)點(diǎn)e; K k; //計(jì)算表中真實(shí)的hash值,與要插入的key.hash進(jìn)行比較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) //使用紅黑樹存儲(chǔ)方法e=((TreeNode)p).putTreeVal(this, tab, hash, key, value) ; //key.hash不同,不再在TreeNode上,在鏈表上找到p.next==null else { for ( int binCount=0; ++binCount) { if ((e=p.next)==null) { //在表末尾插入p.next=newNode(hash, key, value, null); //添加新節(jié)點(diǎn)后的節(jié)點(diǎn)數(shù)如果達(dá)到閾值,則進(jìn)入treeifyBin()再次判斷if (binCount=TREEIFY_THRESHOLD - 1) //-1 for 1st treeifyBin(tab, hash);休息; } //如果找到hash和key相同的節(jié)點(diǎn),則直接退出循環(huán)if (e.hash==hash ((k=e.key)==key || (key !=null key.equals(k) ))) 休息; //更新p 使其指向下一個(gè)節(jié)點(diǎn)p=e; } } //映射包含舊值,返回舊值if (e !=null) { //key 的現(xiàn)有映射V oldValue=e.value; if (!onlyIfAbsent || oldValue==null ) e.value=value;節(jié)點(diǎn)訪問(wèn)后(e);返回舊值; } } //地圖調(diào)整次數(shù)+ 1 ++modCount; //鍵值對(duì)數(shù)量達(dá)到閾值,需要擴(kuò)容if (++size Threshold) resize() ; afterNodeInsertion(逐出); return null;} HashMap put方法的核心是putval方法。其插入過(guò)程如下
首先會(huì)判斷HashMap是否是新建的。如果是,則先調(diào)整大小,然后判斷要插入的元素是否已經(jīng)存在于HashMap中(說(shuō)明發(fā)生了碰撞)。如果不存在,則直接生成新的k-。
v 節(jié)點(diǎn)存放,再判斷是否需要擴(kuò)容。如果要插入的元素已經(jīng)存在的話,說(shuō)明發(fā)生了沖突,這就會(huì)轉(zhuǎn)換成鏈表或者紅黑樹來(lái)解決沖突,首先判斷鏈表中的 hash,key 是否相等,如果相等的話,就用新值替換舊值,如果節(jié)點(diǎn)是屬于 TreeNode 類型,會(huì)直接在紅黑樹中進(jìn)行處理,如果 hash ,key 不相等也不屬于 TreeNode 類型,會(huì)直接轉(zhuǎn)換為鏈表處理,進(jìn)行鏈表遍歷,如果鏈表的 next 節(jié)點(diǎn)是 null,判斷是否轉(zhuǎn)換為紅黑樹,如果不轉(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)部類。 它的默認(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: 0XXXXXXXU+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXXU+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXXU+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX可以看到,UTF-8 通過(guò)開頭的標(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)部類,而不是 java.util.ArrayList private static class ArrayList extends AbstractList implements RandomAccess, java.io.Serializable繼承 AbstractList 中對(duì) add、remove、set 方法是直接拋異常的,也就是說(shuō)如果繼承的子類沒(mé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)部類 ArrayList 重寫了 set 方法,所以支持其可以對(duì)元素進(jìn)行修改。 Arrays.asList 不支持基礎(chǔ)類型的轉(zhuǎn)換Java 中的基礎(chǔ)數(shù)據(jù)類型(byte,short,int,long,float,double,boolean)是不支持使用 Arrays.asList 方法去轉(zhuǎn)換的 Collection 和 Collections 的區(qū)別Collection 和 Collections 都是位于 java.util 包下的類 Collection 是集合類的父類,它是一個(gè)頂級(jí)接口,大部分抽象類比如說(shuō) AbstractList、AbstractSet 都繼承了 Collection 類,Collection 類只定義一節(jié)標(biāo)準(zhǔn)方法比如說(shuō) add、remove、set、equals 等,具體的方法由抽象類或者實(shí)現(xiàn)類去實(shí)現(xiàn)。 Collections 是集合類的工具類,Collections 提供了一些工具類的基本使用 sort 方法,對(duì)當(dāng)前集合進(jìn)行排序, 實(shí)現(xiàn) Comparable 接口的類,只能使用一種排序方案,這種方案叫做自然比較比如實(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 包下的工具類,它們都實(shí)現(xiàn)了 List 接口。 ArrayList 的底層是動(dòng)態(tài)數(shù)組,它是基于數(shù)組的特性而演變出來(lái)的,所以ArrayList 遍歷訪問(wèn)非??欤窃鰟h比較慢,因?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.synchronizedListVector 向量是最早出現(xiàn)的集合容器,Vector 是一個(gè)線程安全的容器,它的每個(gè)方法都粗暴的加上了 synchronized 鎖,所以它的增刪、遍歷效率都很低。Vector 在擴(kuò)容時(shí),它的容量會(huì)增加一倍。Exception 和 Error 有什么區(qū)別Exception 泛指的是 異常,Exception 主要分為兩種異常,一種是編譯期出現(xiàn)的異常,稱為 checkedException ,一種是程序運(yùn)行期間出現(xiàn)的異常,稱為 uncheckedException,常見的 checkedException 有 IOException,uncheckedException 統(tǒng)稱為 RuntimeException,常見的 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 類位于 java.lang 包下,String 類是由 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 類,AbstractStringBuilder 類實(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ì)代理類的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個(gè)代理類中的方法??梢哉f(shuō)動(dòng)態(tài)代理是基于 反射 實(shí)現(xiàn)的。通過(guò)反射我們可以直接操作類或者對(duì)象,比如獲取類的定義,獲取聲明的屬性和方法,調(diào)用方法,在運(yùn)行時(shí)可以修改類的定義。 動(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)代理是基于類的繼承。 int 和 Integer 的區(qū)別int 和 Integer 區(qū)別可就太多了 int 是 Java 中的基本數(shù)據(jù)類型,int 代表的是 整型,一個(gè) int 占 4 字節(jié),也就是 32 位,int 的初始值是默認(rèn)值是 0 ,int 在 Java 內(nèi)存模型中被分配在棧中,int 沒(méi)有方法。Integer 是 Java 中的基本數(shù)據(jù)類型的包裝類,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)的類用 適配器模式 使用 組合 而不是繼承 使用 裝飾器可以制作加糖、加奶酪的咖啡 代理 可以用于任何中間商...... Comparator 和 Comparable 有什么不同Comparable 更像是自然排序Comparator 更像是定制排序同時(shí)存在時(shí)采用 Comparator(定制排序)的規(guī)則進(jìn)行比較。 對(duì)于一些普通的數(shù)據(jù)類型(比如 String, Integer, Double…),它們默認(rèn)實(shí)現(xiàn)了Comparable 接口,實(shí)現(xiàn)了 compareTo 方法,我們可以直接使用。 而對(duì)于一些自定義類,它們可能在不同情況下需要實(shí)現(xiàn)不同的比較策略,我們可以新創(chuàng)建 Comparator 接口,然后使用特定的 Comparator 實(shí)現(xiàn)進(jìn)行比較。 Object 類中一般都有哪些方法Object 類是所有對(duì)象的父類,它里面包含一些所有對(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ì)象類Java 泛型和類型擦除關(guān)于 Java 泛型和擦除看著一篇就夠了。 反射的基本原理,反射創(chuàng)建類實(shí)例的三種方式是什么反射機(jī)制就是使 Java 程序在運(yùn)行時(shí)具有自省(introspect) 的能力,通過(guò)反射我們可以直接操作類和對(duì)象,比如獲取某個(gè)類的定義,獲取類的屬性和方法,構(gòu)造方法等。 創(chuàng)建類實(shí)例的三種方式是 對(duì)象實(shí)例.getClass();通過(guò) Class.forName() 創(chuàng)建對(duì)象實(shí)例.newInstance() 方法創(chuàng)建強(qiáng)引用、若引用、虛引用和幻象引用的區(qū)別我們說(shuō)的不同的引用類型其實(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í)被使用,然后離開對(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 的子類,它里面有一個(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)修飾類、變量和方法,可以參考上面 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)閉即可,就不用寫 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)部類有哪些分類,分別解釋一下在 Java 中,可以將一個(gè)類的定義放在另外一個(gè)類的定義內(nèi)部,這就是內(nèi)部類。內(nèi)部類本身就是類的一個(gè)屬性,與其他屬性定義方式一致。 內(nèi)部類的分類一般主要有四種 成員內(nèi)部類局部?jī)?nèi)部類匿名內(nèi)部類靜態(tài)內(nèi)部類靜態(tài)內(nèi)部類就是定義在類內(nèi)部的靜態(tài)類,靜態(tài)內(nèi)部類可以訪問(wèn)外部類所有的靜態(tài)變量,而不可訪問(wèn)外部類的非靜態(tài)變量; 成員內(nèi)部類 就是定義在類內(nèi)部,成員位置上的非靜態(tài)類,就是成員內(nèi)部類。成員內(nèi)部類可以訪問(wèn)外部類所有的變量和方法,包括靜態(tài)和非靜態(tài),私有和公有。 定義在方法中的內(nèi)部類,就是局部?jī)?nèi)部類。定義在實(shí)例方法中的局部類可以訪問(wèn)外部類的所有變量和方法,定義在靜態(tài)方法中的局部類只能訪問(wèn)外部類的靜態(tài)變量和方法。 匿名內(nèi)部類 就是沒(méi)有名字的內(nèi)部類,除了沒(méi)有名字,匿名內(nèi)部類還有以下特點(diǎn): 匿名內(nèi)部類必須繼承一個(gè)抽象類或者實(shí)現(xiàn)一個(gè)接口匿名內(nèi)部類不能定義任何靜態(tài)成員和靜態(tài)方法。當(dāng)所在的方法的形參需要被匿名內(nèi)部類使用時(shí),必須聲明為 final。匿名內(nèi)部類不能是抽象的,它必須要實(shí)現(xiàn)繼承的類或者實(shí)現(xiàn)的接口的所有抽象方法。說(shuō)出幾種常用的異常NullPointerException: 空指針異常NoSuchMethodException:找不到方法IllegalArgumentException:不合法的參數(shù)異常IndexOutOfBoundException: 數(shù)組下標(biāo)越界異常IOException:由于文件未找到、未打開或者I/O操作不能進(jìn)行而引起異常ClassNotFoundException :找不到文件所拋出的異常NumberFormatException: 字符的UTF代碼數(shù)據(jù)格式有錯(cuò)引起異常;InterruptedException: 線程中斷拋出的異常靜態(tài)綁定和動(dòng)態(tài)綁定的區(qū)別一個(gè)Java 程序要經(jīng)過(guò)編寫、編譯、運(yùn)行三個(gè)步驟,其中編寫代碼不在我們討論的范圍之內(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è)方法的類連接在一起的過(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ú)法由本類之外的其他類所調(diào)用的,也就是本類所特有的方法,所以也就由編譯器識(shí)別此方法是屬于哪個(gè)類的 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類獨(dú)有的,所以Animal類無(wú)法訪問(wèn)(動(dòng)物本來(lái)就不能說(shuō)話)// p.canTalk(); }}final:final 修飾的方法不能被重寫,但是可以由子類進(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{ // 不能重寫final方法,eatingFruit方法只屬于Fruit類,Apple類無(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)調(diào)用,由類名.變量名直接調(diào)用該方法,這個(gè)就很關(guān)鍵了,new 很關(guān)鍵,也可以認(rèn)為是開啟多態(tài)的導(dǎo)火索,而由類名.變量名直接調(diào)用的話,此時(shí)的類名是確定的,并不會(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ú)法重寫 sayHello 方法的,也就是說(shuō) sayHello() 方法是對(duì)子類隱藏的,但是你可以編寫自己的 sayHello() 方法,也就是子類 SubClass 的sayHello() 方法,由此可見,方法由 static 關(guān)鍵詞所修飾,也是編譯時(shí)綁定 動(dòng)態(tài)綁定在運(yùn)行時(shí)根據(jù)具體對(duì)象的類型進(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 類繼承 Father 類,并重寫了父類的 dringMilk() 方法,在輸出結(jié)果得出的是兒子喜歡喝牛奶。那么上面的綁定方式是什么呢? 上面的綁定方式稱之為動(dòng)態(tài)綁定,因?yàn)樵谀憔帉?Father son = new Son() 的時(shí)候,編譯器并不知道 son 對(duì)象真正引用的是誰(shuí),在程序運(yùn)行時(shí)期才知道,這個(gè) son 是一個(gè) Father 類的對(duì)象,但是卻指向了 Son 的引用,這種概念稱之為多態(tài),那么我們就能夠整理出來(lái)多態(tài)的三個(gè)原則: 繼承重寫父類引用指向子類對(duì)象也就是說(shuō),在 Father son = new Son() ,觸發(fā)了動(dòng)態(tài)綁定機(jī)制。 動(dòng)態(tài)綁定的過(guò)程 虛擬機(jī)提取對(duì)象的實(shí)際類型的方法表;虛擬機(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ǔ),能夠降低耦合性。