美國留學(xué)選擇什么專業(yè)好?留學(xué)美國熱門專業(yè)推薦
2019-06-26
更新時間:2024-08-09 22:28作者:小樂
Java基礎(chǔ)知識
Java 并發(fā)的特點是什么: 可以在其中執(zhí)行許多語句,而不必一次執(zhí)行所有語句面向?qū)ο螅?基于類和面向?qū)ο蟮木幊陶Z言。獨立:獨立的編程語言,支持一次編寫,隨處運行,即編譯后的代碼可以在所有支持Java的平臺上運行。 Java的特點Java的特點包括以下幾點
簡單,Java會讓你的工作變得更加輕松,讓你專注于主要業(yè)務(wù)邏輯,而不必擔心指針、運算符重載、內(nèi)存回收等與主要業(yè)務(wù)無關(guān)的功能??梢浦残裕琂ava是平臺無關(guān)的,這意味著在一個平臺上編寫的任何應(yīng)用程序都可以輕松移植到另一個平臺上。安全性,編譯后,所有代碼都會轉(zhuǎn)換為字節(jié)碼,人類無法讀取。它使得開發(fā)無病毒、防篡改的系統(tǒng)/應(yīng)用程序成為可能。動態(tài)的,具有適應(yīng)變化環(huán)境的能力,可以支持動態(tài)內(nèi)存分配,從而減少內(nèi)存浪費,提高應(yīng)用程序性能。分布式,Java 提供了有助于創(chuàng)建分布式應(yīng)用程序的功能。使用遠程方法調(diào)用(RMI),程序可以通過網(wǎng)絡(luò)調(diào)用另一個程序的方法并獲取輸出。您可以通過從Internet 上的任何計算機調(diào)用方法來訪問文件。這是一項革命性的功能,對于當今的互聯(lián)網(wǎng)來說非常重要。健壯性,Java具有強大的內(nèi)存管理功能,可以在編譯和運行時檢查代碼,這有助于消除錯誤。高性能,Java最黑暗的技術(shù)就是字節(jié)碼編程。編譯成Java代碼的字節(jié)碼可以很容易地轉(zhuǎn)換成本地機器代碼。使用JIT 編譯器實現(xiàn)高性能。解釋型,Java被編譯成字節(jié)碼,由Java運行環(huán)境解釋。多線程,Java 支持多個執(zhí)行線程(也稱為輕量級進程),包括一組同步原語。這使得使用線程進行編程變得更加容易,并且Java通過監(jiān)視器模型實現(xiàn)了線程安全。描述值傳遞和引用傳遞之間的區(qū)別。如果你想真正了解,可以參考這篇文章:https://www.zhihu.com/question/31203609
簡單來說,就是
值傳遞是指在調(diào)用函數(shù)時將實際參數(shù)復(fù)制到函數(shù)中。這樣,如果函數(shù)修改了傳遞的形參,則不會影響實參。
引用傳遞是指在調(diào)用函數(shù)時將對象的地址直接傳遞給函數(shù)。如果修改形參,實參的值就會受到影響。
==和equals 有什么區(qū)別?==是Java中的運算符。它有兩種比較方法。
對于基本數(shù)據(jù)類型,==判斷兩邊的值是否相等public class DoubleCompareAndEquals { Person person1=new Person(24,'boy');人person2=new Person(24,'女孩');整數(shù)c=10;私有無效doubleCompare(){ int a=10;整數(shù)b=10; System.out.println(a==b); System.out.println(a==c); System.out.println(person1.getId()==person2.getId());對于引用類型,==判斷兩邊的引用是否相等,即兩個對象是否指向同一個內(nèi)存區(qū)域。 private void equals(){ System.out.println(person1.getName().equals(person2.getName()));}equals是Java中所有對象的父類,即Object類定義的方法。它只能比較對象,并且指示兩個引用的值是否相等。所以記住,并不是說==比較引用是否相等,equals比較值,這個需要區(qū)分。
equals用于對象之間的比較,具有以下特點
自反性:對于任何非空引用x,x.equals(x) 應(yīng)該返回true。對稱性:對于任何非空引用x 和y,如果x.equals(y) 為true,則y.equals(x) 也為true。傳遞性:對于任何非空參考值,都有三個值:x、y和z。如果x.equals(y) 返回true 并且y.equals(z) 返回true,則x.equals(z) 也應(yīng)該返回true。一致性:對于任何非空引用x 和y,如果x.equals(y) 相等,則它們必須始終相等。非空性:對于任何非空引用的值x,x.equals(null) 必須返回false。 String中的equals是如何重寫的? String在Java中表示字符串。 String 類很特殊。整個類都被final修飾了。也就是說String不能被任何類繼承。對String string 的任何修改所有方法都會創(chuàng)建一個新字符串。
equals方法是Object類定義的方法。 Object是所有類的父類,當然也包括String。字符串重寫equals 方法。我們來看看它是如何重寫的。
首先判斷兩個待比較字符串的引用是否相等。如果引用相等,則直接返回true。如果不相等,則繼續(xù)下面的判斷,然后判斷比較的對象是否是String的實例。如果不是,直接返回false。如果是,則比較兩個字符串的長度,看看它們是否相等。如果長度不想等待,則無需比較;如果長度相同,則將比較字符串中的每個字符是否相等。一旦有一個字符不相等,則直接返回false。下面是它的流程圖
這是另一個提醒。您可能想知道什么時候
if (this==anObject) { return true;}這個判斷語句怎么會返回true呢?因為都是字符串,字符串比較不都是堆空間嗎?乍一看,似乎永遠不會消失,但你忘記了String.intern() 方法。它所代表的概念有不同的JDK版本。不同的區(qū)別
在JDK1.7及以后版本中,調(diào)用intern方法是判斷運行時常量池中是否存在指定的字符串。如果沒有,則將該字符串添加到常量池中,并返回常量池中的對象。
驗證流程如下
私有無效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) 無論如何這都會返回true,因為s1 字符串在創(chuàng)建時已經(jīng)存在于常量池中。那么第二條語句返回false,因為s1返回常量池中的對象,s2返回堆中的對象。第三條語句s3.intern.equals(s1)返回true,因為雖然s3對象在堆中創(chuàng)建了一個對象,但是s3中的'aaa'返回的是常量池中的對象。為什么重寫equals方法必須重寫hashcode方法。 equals方法和hashCode都是Object中定義的方法,它們經(jīng)常被一起重寫。
equals方法是用來比較對象大小是否相等的方法,hashcode方法是用來確定每個對象的哈希值的方法。如果只重寫equals方法而不重寫hashcode方法,很可能兩個不同的對象會有相同的hashcode,造成沖突。例如
字符串str1='調(diào)用'; String str2='重位置';兩者的hashcode相等,但equals可能不相等。
我們看一下hashCode的官方定義
把它們加起來:
如果Java運行時對同一個對象調(diào)用hashCode方法,那么無論調(diào)用多少次,都應(yīng)該返回相同的hashCode。但是,在不同的Java程序中,hashCode方法返回的值可能不一致。如果兩個對象的equals 相等,那么hashCode 一定相同。如果兩個對象的equals不相等,那么hashCode也可能相同,所以需要重寫hashCode方法,因為你不知道hashCode的底層結(jié)構(gòu)(反正我也不知道,有很多牛都會教),所以需要重寫hashCode方法,為不同的對象生成不同的hashCode值,這樣可以提高不同對象的訪問速度。 HashCode通常是通過將地址轉(zhuǎn)換為整數(shù)來實現(xiàn)的。 String s1=new String('abc') 在內(nèi)存中創(chuàng)建一兩個對象。 String s1 聲明了一個String 類型的s1 變量,它不是一個對象。使用new關(guān)鍵字會在堆中創(chuàng)建一個對象,另一個對象是abc,會在常量池中創(chuàng)建,所以一共創(chuàng)建了兩個對象;如果常量池中已經(jīng)存在abc,則將創(chuàng)建一個對象。
詳細內(nèi)容請閱讀作者的另一篇文章:String、StringBuffer、StringBuilde獨特詳解
為什么String 是不可變的? jdk源碼中String是如何定義的?為什么要這樣設(shè)計呢?首先,我們來了解一下什么是不可變對象。不可變對象意味著一旦創(chuàng)建,其內(nèi)部狀態(tài)就無法修改。這是什么意思?換句話說,不可變對象需要遵守以下原則:
不可變對象的內(nèi)部屬性都是最終的。不可變對象的內(nèi)部屬性都是私有的。不可變對象不能提供任何可以修改內(nèi)部狀態(tài)的方法,setter 方法也不能。不可變對象不能被繼承和擴展。而不是問String 為什么它是不可變的。更好講一下如何將String設(shè)計成不可變的。
String類是一個獨立于Java基本數(shù)據(jù)類型而存在的對象。您可以將String 視為字符串的集合。 String被設(shè)計成final的,這意味著一旦創(chuàng)建了String對象,它的值就不能被修改,任何修改String值的方法都是重新創(chuàng)建一個字符串。 String對象創(chuàng)建后,會存在于運行時常量池中。運行時常量池是方法區(qū)的一部分。 JDK1.7之后被移至堆中。
不可變對象并不是真正不可變的。它們的內(nèi)部屬性和值可以通過反射來修改,但一般我們不這樣做。
static關(guān)鍵字有什么用?談?wù)勀愕睦斫狻?static是Java中一個非常重要的關(guān)鍵字。 static所代表的概念就是靜態(tài)的。 Java中主要使用static
修改變量。用static修飾的變量稱為靜態(tài)變量,也稱為類變量。類變量屬于類。對于不同的類,靜態(tài)變量只有一份副本。 static修飾的變量位于方法區(qū); static修飾的變量可以直接通過類名和變量名來訪問,而不必實例化類然后使用。修飾的方法,靜態(tài)修飾的方法稱為靜態(tài)方法。靜態(tài)方法可以通過類名和方法名直接使用。非靜態(tài)屬性和方法不能在靜態(tài)方法內(nèi)使用。 static可以修改代碼塊,主要分為兩種:一種是直接在類中定義,使用static{},稱為靜態(tài)代碼塊。另一種是在類中定義一個靜態(tài)內(nèi)部類,使用static class xxx來定義。 static 可用于通過import static xxx 靜態(tài)導(dǎo)入包。一般不推薦這種方法。 static 可以與單例模式一起使用,通過雙重檢查鎖實現(xiàn)線程安全的單例模式。詳細信息請參考這篇文章。靜態(tài)文章還能阻礙我嗎?
Final關(guān)鍵字的用途是什么?談?wù)勀愕睦斫狻?Final是Java中的一個關(guān)鍵字。意思是不可改變的。在Java中,final主要用來
修飾的類,被final修飾的類不能被繼承。這意味著你不能使用extends來繼承被final修飾的類。修改變量。由final修飾的變量不能被重寫。不被重寫有兩種含義。對于基本數(shù)據(jù)類型,被final修飾的變量的值是不能改變的。由final修飾的對象的引用不能被改變。但對象內(nèi)部的屬性是可以修改的。 Final修飾的變量在一定程度上具有不可變的作用,因此可以用來保護只讀數(shù)據(jù),尤其是在并發(fā)編程中,因為很明顯final變量不能再被賦值,這有助于減少額外的同步。高架。修改后的方法、final修飾的方法不能被覆蓋。 Final修飾符和Java程序性能優(yōu)化沒有必然聯(lián)系。抽象類和接口有什么區(qū)別?抽象類和接口是Java中的關(guān)鍵字。抽象類和接口都允許定義方法,而不需要具體的方法實現(xiàn)。抽象類和接口都允許被繼承,并且在JDK和框架的源代碼中廣泛使用它們來實現(xiàn)多態(tài)性和不同的設(shè)計模式。
區(qū)別在于
不同的抽象層次:類、抽象類、接口實際上是三種不同的抽象層次。抽象級別的順序是接口、抽象類和類。接口中只允許定義方法,不允許方法的實現(xiàn)。方法的定義和實現(xiàn)可以在抽象類中進行;但類中只允許實現(xiàn)方法。我所說的方法的定義在方法中是不允許的。 {}后面使用的關(guān)鍵字不同:類用class表示;抽象類用抽象類來表示;接口由接口表示:接口中定義的變量只能是公共靜態(tài)常量,抽象類中的變量是普通變量。重寫和重載的區(qū)別在Java中,重寫和重載是同一個方法的不同表達。讓我們簡單區(qū)分一下重寫和重載。
孩子和父母之間的關(guān)系是不同的。重寫是針對子類和父類的不同表達,而重載則是同一個類的不同表達。概念不同。子類重寫父類的方法一般用@override表示。被重寫方法的方法聲明、參數(shù)類型和順序必須與父類完全一致;重載是針對同一個類中的概念,它要求重載的方法必須滿足以下任何一個要求:方法參數(shù)的順序、參數(shù)的數(shù)量和參數(shù)的類型可以保持不同。字節(jié)的取值范圍是多少?如何計算byte的取值范圍?它在-128到127之間,總共256位。一個字節(jié)類型在計算機中占用一個字節(jié),即8位,所以最大為2^8=1111 1111。
Java 使用二進制補碼來表示二進制數(shù)。補碼的最高位是符號位。最高位為0 表示正數(shù),最高位1 表示負數(shù)。正數(shù)的補碼是它本身。由于最高位是符號位,所以正數(shù)代表0111 1111,即127。負數(shù)最大為1111 1111,涉及兩個0,一個+0,一個-0。 +0被歸類為正數(shù),即0,-0被歸類為負數(shù),即-128,所以字節(jié)的范圍是-128-127。
HashMap 和HashTable 的異同
HashMap和HashTable都是基于哈希表實現(xiàn)的,里面的每個元素都是一個鍵值對。 HashMap 和HashTable 都實現(xiàn)了Map、Cloneable 和Serialized 接口。
不同之處
父類不同:HashMap繼承AbstractMap類,而HashTable繼承Dictionary類??罩挡煌篐ashMap允許空的key和value值,而HashTable不允許空的key和value值。 HashMap 會將Null 鍵視為普通鍵。不允許重復(fù)空鍵。線程安全:HashMap不是線程安全的。如果多個外部操作同時修改HashMap的數(shù)據(jù)結(jié)構(gòu),比如添加、刪除等,就必須進行同步操作。僅修改key或value并不是改變數(shù)據(jù)結(jié)構(gòu)的操作。您可以選擇構(gòu)造一個線程安全的Map,例如Collections.synchronizedMap或ConcurrentHashMap。而HashTable本身就是一個線程安全的容器。性能:雖然HashMap和HashTable都是基于單鏈表,但HashMap對于put或get操作可以達到恒定時間的性能;而HashTable的put和get操作都是synchronized鎖,所以效率很差。初始容量不同:HashTable初始長度為11,后續(xù)每次擴容容量變?yōu)橹暗?n+1(n為之前的長度),而HashMap初始長度為16,后續(xù)每次擴容容量變?yōu)橹暗?倍原始長度。創(chuàng)建時,如果給定了一個容量的初始值,HashTable會直接使用你給定的大小,而HashMap會將其擴展為2的冪。 HashMap和HashSet的區(qū)別HashSet繼承自AbstractSet接口,實現(xiàn)了Set、Cloneable和java.io.Serialized 接口。 HashSet不允許集合中有重復(fù)的值。 HashSet的底層實際上是HashMap,對HashSet的所有操作實際上都是對HashMap的操作。所以HashSet不保證集合的順序,也不是線程安全的容器。
在HashMap的底層結(jié)構(gòu)JDK1.7中,HashMap采用位桶+鏈表的實現(xiàn)方式,即用鏈表來處理沖突,將哈希值相同的鏈表存儲在一個數(shù)組中。但當桶中元素較多時,即哈希值相等的元素較多時,按鍵值順序查找的效率較低。
因此,與JDK 1.7相比,JDK 1.8在底層結(jié)構(gòu)上做了一些改變。當每個桶中的元素數(shù)量大于8時,將轉(zhuǎn)換為紅黑樹,以優(yōu)化查詢效率。
這幾天我一直在思考為什么HashMap的長度是2的冪。當我和群里的朋友討論日常問題時,我問他們?yōu)槭裁磍ength%hash==(n - 1) hash。他們相等的前提是長度取2的次方。然后我回答說,長度不能取2的次方嗎?其實我沒明白其中的因果關(guān)系,因為HashMap的長度是2的冪,所以用余數(shù)來確定桶中的下標。如果length的長度不是2的冪,朋友們可以舉個例子試試。
比如長度為9時,3(9-1)=0、2(9-1)=0,都在0上,發(fā)生碰撞;
這會增加HashMap碰撞的概率。
HashMap多線程操作導(dǎo)致死循環(huán)問題。 HashMap 不是線程安全的容器。高并發(fā)場景下,應(yīng)該使用ConcurrentHashMap。在多線程場景下使用HashMap會出現(xiàn)死循環(huán)問題(基于JDK1.7)。問題所在的位置是rehash地方,即
do { 條目下一個=e.next; //-- 假設(shè)線程一執(zhí)行到這里就被安排掛起int i=indexFor(e.hash, newCapacity); e.next=newTable[i]; newTable[i]=e; e=下一個;} while (e !=null);這是JDK1.7的rehash代碼片段,在并發(fā)場景下會形成循環(huán)。
JDK1.8也會導(dǎo)致死循環(huán)問題。
HashMap的線程安全實現(xiàn)有哪些?因為HashMap不是線程安全的容器,所以并發(fā)場景下建議使用ConcurrentHashMap,或者使用線程安全的HashMap或者Collections包下的線程安全容器,例如
Collections.synchronizedMap(new HashMap());還可以使用HashTable,它也是一個基于鍵值存儲的線程安全容器。 HashMap和HashTable經(jīng)常被比較,因為HashTable的數(shù)據(jù)結(jié)構(gòu)與HashMap相同。
上面最高效的是ConcurrentHashMap。
下面說一下HashMap put的過程。首先使用哈希函數(shù)計算key,然后執(zhí)行真正的插入方法。
Final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node[] tab;節(jié)點p;整數(shù)n,我; //如果表為null或者沒有為表分配內(nèi)存,則調(diào)整一次大小if ((tab=table)==null || (n=tab.length)==0) n=(tab=resize()) 。長度; //如果指定hash值的節(jié)點為空,則直接插入。這個(n - 1) Hash 是表中真正的哈希if ((p=tab[i=(n - 1) hash])==null) tab[i]=newNode(hash, key, value, null) ; //如果不為空else { 節(jié)點e; K k; //計算表中真實的hash值,與要插入的key.hash進行比較if (p.hash==hash ((k=p.key)==key || (key !=null key.equals (k)))) e=p; //如果不同,且當前節(jié)點已經(jīng)在TreeNode上else if (p instanceof TreeNode) //使用紅黑樹存儲方法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é)點后如果節(jié)點數(shù)達到閾值,則進入treeifyBin()再次判斷if (binCount=TREEIFY_THRESHOLD - 1) //-1 for 1st treeifyBin(tab, hash);休息; } //如果找到hash和key相同的節(jié)點,則直接退出循環(huán)if (e.hash==hash ((k=e.key)==key || (key !=null key.equals(k) ))) 休息; //更新p 使其指向下一個節(jié)點p=e; } } //映射包含舊值,返回舊值if (e !=null) { //key 的現(xiàn)有映射V oldValue=e.value; if (!onlyIfAbsent || oldValue==null ) e.value=value; afterNodeAccess(e);返回舊值; } } //地圖調(diào)整次數(shù)+ 1 ++modCount; //鍵值對數(shù)量達到閾值,需要擴容if (++size Threshold) resize() ; afterNodeInsertion(逐出); return null;} HashMap put方法的核心是putval方法。其插入過程如下
首先會判斷HashMap是否是新建的。如果是,則先調(diào)整大小,然后判斷要插入的元素是否已經(jīng)存在于HashMap中(說明發(fā)生了碰撞)。如果不存在,則直接生成新的k-。
v 節(jié)點存放,再判斷是否需要擴容。如果要插入的元素已經(jīng)存在的話,說明發(fā)生了沖突,這就會轉(zhuǎn)換成鏈表或者紅黑樹來解決沖突,首先判斷鏈表中的 hash,key 是否相等,如果相等的話,就用新值替換舊值,如果節(jié)點是屬于 TreeNode 類型,會直接在紅黑樹中進行處理,如果 hash ,key 不相等也不屬于 TreeNode 類型,會直接轉(zhuǎn)換為鏈表處理,進行鏈表遍歷,如果鏈表的 next 節(jié)點是 null,判斷是否轉(zhuǎn)換為紅黑樹,如果不轉(zhuǎn)換的話,在遍歷過程中找到 key 完全相等的節(jié)點,則用新節(jié)點替換老節(jié)點ConcurrentHashMap 底層實現(xiàn)ConcurrentHashMap 是線程安全的 Map,它也是高并發(fā)場景下的首選數(shù)據(jù)結(jié)構(gòu),ConcurrentHashMap 底層是使用分段鎖來實現(xiàn)的。 Integer 緩存池Integer 緩存池也就是 IntegerCache ,它是 Integer 的靜態(tài)內(nèi)部類。 它的默認值用于緩存 -128 - 127 之間的數(shù)字,如果有 -128 - 127 之間的數(shù)字的話,使用 new Integer 不用創(chuàng)建對象,會直接從緩存池中取,此操作會減少堆中對象的分配,有利于提高程序的運行效率。 例如創(chuàng)建一個 Integer a = 24,其實是調(diào)用 Integer 的 valueOf ,可以通過反編譯得出這個結(jié)論 然后我們看一下 valueOf 方法 如果在指定緩存池范圍內(nèi)的話,會直接返回緩存的值而不用創(chuàng)建新的 Integer 對象。 緩存的大小可以使用 XX:AutoBoxCacheMax 來指定,在 VM 初始化時,java.lang.Integer.IntegerCache.high 屬性會設(shè)置和保存在 sun.misc.VM 的私有系統(tǒng)屬性中。 UTF-8 和 Unicode 的關(guān)系由于每個國家都有自己獨有的字符編碼,所以Unicode 的發(fā)展旨在創(chuàng)建一個新的標準,用來映射當今使用的大多數(shù)語言中的字符,這些字符有一些不是必要的,但是對于創(chuàng)建文本來說卻是不可或缺的。Unicode 統(tǒng)一了所有字符的編碼,是一個 Character Set,也就是字符集,字符集只是給所有的字符一個唯一編號,但是卻沒有規(guī)定如何存儲,不同的字符其存儲空間不一樣,有的需要一個字節(jié)就能存儲,有的則需要2、3、4個字節(jié)。 UTF-8 只是眾多能夠?qū)ξ谋咀址M行解碼的一種方式,它是一種變長的方式。UTF-8 代表 8 位一組表示 Unicode 字符的格式,使用 1 - 4 個字節(jié)來表示字符。 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 通過開頭的標志位位數(shù)實現(xiàn)了變長。對于單字節(jié)字符,只占用一個字節(jié),實現(xiàn)了向下兼容 ASCII,并且能和 UTF-32 一樣,包含 Unicode 中的所有字符,又能有效減少存儲傳輸過程中占用的空間。 項目為 UTF-8 環(huán)境,char c = '中',是否合法可以,因為 Unicode 編碼采用 2 個字節(jié)的編碼,UTF-8 是 Unicode 的一種實現(xiàn),它使用可變長度的字符集進行編碼,char c = '中' 是兩個字節(jié),所以能夠存儲。合法。 Arrays.asList 獲得的 List 應(yīng)該注意什么Arrays.asList 是 Array 中的一個靜態(tài)方法,它能夠?qū)崿F(xiàn)把數(shù)組轉(zhuǎn)換成為 List 序列,需要注意下面幾點 Arrays.asList 轉(zhuǎn)換完成后的 List 不能再進行結(jié)構(gòu)化的修改,什么是結(jié)構(gòu)化的修改?就是不能再進行任何 List 元素的增加或者減少的操作。public static void main(String[] args) { Integer[] integer = new Integer[] { 1, 2, 3, 4 }; List integetList = Arrays.asList(integer); integetList.add(5);}結(jié)果會直接拋出 Exception in thread "main" java.lang.UnsupportedOperationException我們看一下源碼就能發(fā)現(xiàn)問題 // 這是 java.util.Arrays 的內(nèi)部類,而不是 java.util.ArrayList private static class ArrayList extends AbstractList implements RandomAccess, java.io.Serializable繼承 AbstractList 中對 add、remove、set 方法是直接拋異常的,也就是說如果繼承的子類沒有去重寫這些方法,那么子類的實例去調(diào)用這些方法是會直接拋異常的。 下面是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 方法也拋出了一場,但是由于 內(nèi)部類 ArrayList 重寫了 set 方法,所以支持其可以對元素進行修改。 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 是集合類的父類,它是一個頂級接口,大部分抽象類比如說 AbstractList、AbstractSet 都繼承了 Collection 類,Collection 類只定義一節(jié)標準方法比如說 add、remove、set、equals 等,具體的方法由抽象類或者實現(xiàn)類去實現(xiàn)。 Collections 是集合類的工具類,Collections 提供了一些工具類的基本使用 sort 方法,對當前集合進行排序, 實現(xiàn) Comparable 接口的類,只能使用一種排序方案,這種方案叫做自然比較比如實現(xiàn)線程安全的容器 Collections.synchronizedList、 Collections.synchronizedMap 等reverse 反轉(zhuǎn),使用 reverse 方法可以根據(jù)元素的自然順序 對指定列表按降序進行排序。fill,使用指定元素替換指定列表中的所有元素。有很多用法,讀者可以翻閱 Collections 的源碼查看,Collections 不能進行實例化,所以 Collections 中的方法都是由 Collections.方法 直接調(diào)用。 你知道 fail-fast 和 fail-safe 嗎fail-fast 是 Java 中的一種快速失敗機制,java.util 包下所有的集合都是快速失敗的,快速失敗會拋出 ConcurrentModificationException 異常,fail-fast 你可以把它理解為一種快速檢測機制,它只能用來檢測錯誤,不會對錯誤進行恢復(fù),fail-fast 不一定只在多線程環(huán)境下存在,ArrayList 也會拋出這個異常,主要原因是由于 modCount 不等于 expectedModCount。 fail-safe 是 Java 中的一種 安全失敗 機制,它表示的是在遍歷時不是直接在原集合上進行訪問,而是先復(fù)制原有集合內(nèi)容,在拷貝的集合上進行遍歷。 由于迭代時是對原集合的拷貝進行遍歷,所以在遍歷過程中對原集合所作的修改并不能被迭代器檢測到,所以不會觸發(fā) ConcurrentModificationException。java.util.concurrent 包下的容器都是安全失敗的,可以在多線程條件下使用,并發(fā)修改。 ArrayList、LinkedList 和 Vector 的區(qū)別這也是一道老生常談的問題了 ArrayList、LinkedList、Vector 都是位于 java.util 包下的工具類,它們都實現(xiàn)了 List 接口。 ArrayList 的底層是動態(tài)數(shù)組,它是基于數(shù)組的特性而演變出來的,所以ArrayList 遍歷訪問非??欤窃鰟h比較慢,因為會涉及到數(shù)組的拷貝。ArrayList 是一個非線程安全的容器,在并發(fā)場景下會造成問題,如果想使用線程安全的容器的話,推薦使用 Collections.synchronizedList;ArrayList 在擴容時會增加 50% 的容量。LinkedList 的底層是雙向鏈表,所以 LinkedList 的增加和刪除非??欤恍璋言貏h除,把各自的指針指向新的元素即可。但是 LinkedList 遍歷比較慢,因為只有每次訪問一個元素才能知道下一個元素的值。LinkedList 也是一個非線程安全的容器,推薦使用 Collections.synchronizedListVector 向量是最早出現(xiàn)的集合容器,Vector 是一個線程安全的容器,它的每個方法都粗暴的加上了 synchronized 鎖,所以它的增刪、遍歷效率都很低。Vector 在擴容時,它的容量會增加一倍。Exception 和 Error 有什么區(qū)別Exception 泛指的是 異常,Exception 主要分為兩種異常,一種是編譯期出現(xiàn)的異常,稱為 checkedException ,一種是程序運行期間出現(xiàn)的異常,稱為 uncheckedException,常見的 checkedException 有 IOException,uncheckedException 統(tǒng)稱為 RuntimeException,常見的 RuntimeException 主要有NullPointerException、 IllegalArgumentException、ArrayIndexOutofBoundException等,Exception 可以被捕獲。 Error 是指程序運行過程中出現(xiàn)的錯誤,通常情況下會造成程序的崩潰,Error 通常是不可恢復(fù)的,Error 不能被捕獲。 詳細可以參考這篇文章 看完這篇 Exception 和 Error ,和面試官扯皮就沒問題了 String、StringBuilder 和 StringBuffer 有什么區(qū)別String 特指的是 Java 中的字符串,String 類位于 java.lang 包下,String 類是由 final 修飾的,String 字符串一旦創(chuàng)建就不能被修改,任何對 String 進行修改的操作都相當于重新創(chuàng)建了一個字符串。String 字符串的底層使用 StringBuilder 來實現(xiàn)的 StringBuilder 位于 java.util 包下,StringBuilder 是一非線程安全的容器,StringBuilder 的 append 方法常用于字符串拼接,它的拼接效率要比 String 中 + 號的拼接效率高。StringBuilder 一般不用于并發(fā)環(huán)境 StringBuffer 位于 java.util 包下,StringBuffer 是一個線程安全的容器,多線程場景下一般使用 StringBuffer 用作字符串的拼接 StringBuilder 和 StringBuffer 都是繼承于AbstractStringBuilder 類,AbstractStringBuilder 類實現(xiàn)了 StringBuffer 和 StringBuilder 的常規(guī)操作。 動態(tài)代理是基于什么原理代理一般分為靜態(tài)代理和 動態(tài)代理,它們都是代理模式的一種應(yīng)用,靜態(tài)代理指的是在程序運行前已經(jīng)編譯好,程序知道由誰來執(zhí)行代理方法。 而動態(tài)代理只有在程序運行期間才能確定,相比于靜態(tài)代理, 動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進行統(tǒng)一的處理,而不用修改每個代理類中的方法。可以說動態(tài)代理是基于 反射 實現(xiàn)的。通過反射我們可以直接操作類或者對象,比如獲取類的定義,獲取聲明的屬性和方法,調(diào)用方法,在運行時可以修改類的定義。 動態(tài)代理是一種在運行時構(gòu)建代理、動態(tài)處理方法調(diào)用的機制。動態(tài)代理的實現(xiàn)方式有很多,Java 提供的代理被稱為 JDK 動態(tài)代理,JDK 動態(tài)代理是基于類的繼承。 int 和 Integer 的區(qū)別int 和 Integer 區(qū)別可就太多了 int 是 Java 中的基本數(shù)據(jù)類型,int 代表的是 整型,一個 int 占 4 字節(jié),也就是 32 位,int 的初始值是默認值是 0 ,int 在 Java 內(nèi)存模型中被分配在棧中,int 沒有方法。Integer 是 Java 中的基本數(shù)據(jù)類型的包裝類,Integer 是一個對象,Integer 可以進行方法調(diào)用,Integer 的默認值是 null,Integer 在 Java 內(nèi)存模型中被分配在堆中。int 和 Integer 在計算時可以進行相互轉(zhuǎn)換,int -> Integer 的過程稱為 裝箱,Integer -> int 的過程稱為 拆箱,Integer 還有 IntegerCache ,會自動緩存 -128 - 127 中的值Java 提供了哪些 I/O 方式Java I/O 方式有很多種,傳統(tǒng)的 I/O 也稱為 BIO,主要流有如下幾種 Java I/O 包的實現(xiàn)比較簡單,但是容易出現(xiàn)性能瓶頸,傳統(tǒng)的 I/O 是基于同步阻塞的。 JDK 1.4 之后提供了 NIO,也就是位于 java.nio 包下,提供了基于 channel、Selector、Buffer的抽象,可以構(gòu)建多路復(fù)用、同步非阻塞 I/O 程序。 JDK 1.7 之后對 NIO 進行了進一步改進,引入了 異步非阻塞 的方式,也被稱為 AIO(Asynchronous IO)??梢杂蒙钪械睦觼碚f明:項目經(jīng)理交給手下員工去改一個 bug,那么項目經(jīng)理不會一直等待員工解決 bug,他肯定在員工解決 bug 的期間給其他手下分配 bug 或者做其他事情,員工解決完 bug 之后再告訴項目經(jīng)理 bug 解決完了。 談?wù)勀阒赖脑O(shè)計模式一張思維導(dǎo)圖鎮(zhèn)場 比如全局唯一性可以用 單例模式。 可以使用 策略模式 優(yōu)化過多的 if...else... 制定標準用 模版模式 接手其他人的鍋,但不想改原來的類用 適配器模式 使用 組合 而不是繼承 使用 裝飾器可以制作加糖、加奶酪的咖啡 代理 可以用于任何中間商...... Comparator 和 Comparable 有什么不同Comparable 更像是自然排序Comparator 更像是定制排序同時存在時采用 Comparator(定制排序)的規(guī)則進行比較。 對于一些普通的數(shù)據(jù)類型(比如 String, Integer, Double…),它們默認實現(xiàn)了Comparable 接口,實現(xiàn)了 compareTo 方法,我們可以直接使用。 而對于一些自定義類,它們可能在不同情況下需要實現(xiàn)不同的比較策略,我們可以新創(chuàng)建 Comparator 接口,然后使用特定的 Comparator 實現(xiàn)進行比較。 Object 類中一般都有哪些方法Object 類是所有對象的父類,它里面包含一些所有對象都能夠使用的方法 hashCode():用于計算對象的哈希碼equals():用于對象之間比較值是否相等toString(): 用于把對象轉(zhuǎn)換成為字符串clone(): 用于對象之間的拷貝wait(): 用于實現(xiàn)對象之間的等待notify(): 用于通知對象釋放資源notifyAll(): 用于通知所有對象釋放資源finalize(): 用于告知垃圾回收器進行垃圾回收getClass(): 用于獲得對象類Java 泛型和類型擦除關(guān)于 Java 泛型和擦除看著一篇就夠了。 反射的基本原理,反射創(chuàng)建類實例的三種方式是什么反射機制就是使 Java 程序在運行時具有自省(introspect) 的能力,通過反射我們可以直接操作類和對象,比如獲取某個類的定義,獲取類的屬性和方法,構(gòu)造方法等。 創(chuàng)建類實例的三種方式是 對象實例.getClass();通過 Class.forName() 創(chuàng)建對象實例.newInstance() 方法創(chuàng)建強引用、若引用、虛引用和幻象引用的區(qū)別我們說的不同的引用類型其實都是邏輯上的,而對于虛擬機來說,主要體現(xiàn)的是對象的不同的可達性(reachable) 狀態(tài)和對垃圾收集(garbage collector)的影響。 可以通過下面的流程來對對象的生命周期做一個總結(jié) 對象被創(chuàng)建并初始化,對象在運行時被使用,然后離開對象的作用域,對象會變成不可達并會被垃圾收集器回收。圖中用紅色標明的區(qū)域表示對象處于強可達階段。 JDK1.2 介紹了 java.lang.ref 包,對象的生命周期有四個階段:強可達(Strongly Reachable)、軟可達(Soft Reachable)、弱可達(Weak Reachable)、 幻象可達(Phantom Reachable)。 如果只討論符合垃圾回收條件的對象,那么只有三種:軟可達、弱可達和幻象可達。 軟可達:軟可達就是我們只能通過軟引用才能訪問的狀態(tài),軟可達的對象是由 SoftReference 引用的對象,并且沒有強引用的對象。軟引用是用來描述一些還有用但是非必須的對象。垃圾收集器會盡可能長時間的保留軟引用的對象,但是會在發(fā)生 OutOfMemoryError 之前,回收軟引用的對象。如果回收完軟引用的對象,內(nèi)存還是不夠分配的話,就會直接拋出 OutOfMemoryError。弱可達:弱可達的對象是 WeakReference 引用的對象。垃圾收集器可以隨時收集弱引用的對象,不會嘗試保留軟引用的對象?;孟罂蛇_:幻象可達是由 PhantomReference 引用的對象,幻象可達就是沒有強、軟、弱引用進行關(guān)聯(lián),并且已經(jīng)被 finalize 過了,只有幻象引用指向這個對象的時候。除此之外,還有強可達和不可達的兩種可達性判斷條件 強可達:就是一個對象剛被創(chuàng)建、初始化、使用中的對象都是處于強可達的狀態(tài)不可達(unreachable):處于不可達的對象就意味著對象可以被清除了。下面是一個不同可達性狀態(tài)的轉(zhuǎn)換圖 判斷可達性條件,也是 JVM 垃圾收集器決定如何處理對象的一部分考慮因素。 所有的對象可達性引用都是 java.lang.ref.Reference 的子類,它里面有一個get() 方法,返回引用對象。 如果已通過程序或垃圾收集器清除了此引用對象,則此方法返回 null 。也就是說,除了幻象引用外,軟引用和弱引用都是可以得到對象的。而且這些對象可以人為拯救,變?yōu)閺娨茫绨?this 關(guān)鍵字賦值給對象,只要重新和引用鏈上的任意一個對象建立關(guān)聯(lián)即可。 final、finally 和 finalize() 的區(qū)別這三者可以說是沒有任何關(guān)聯(lián)之處,我們上面談到了,final 可以用來修飾類、變量和方法,可以參考上面 final 的那道面試題。 finally 是一個關(guān)鍵字,它經(jīng)常和 try 塊一起使用,用于異常處理。使用 try...finally 的代碼塊種,finally 部分的代碼一定會被執(zhí)行,所以我們經(jīng)常在 finally 方法中用于資源的關(guān)閉操作。 JDK1.7 中,推薦使用 try-with-resources 優(yōu)雅的關(guān)閉資源,它直接使用 try(){} 進行資源的關(guān)閉即可,就不用寫 finally 關(guān)鍵字了。 finalize 是 Object 對象中的一個方法,用于對象的回收方法,這個方法我們一般不推薦使用,finalize 是和垃圾回收關(guān)聯(lián)在一起的,在 Java 9 中,將 finalize 標記為了 deprecated, 如果沒有特別原因,不要實現(xiàn) finalize 方法,也不要指望他來進行垃圾回收。 內(nèi)部類有哪些分類,分別解釋一下在 Java 中,可以將一個類的定義放在另外一個類的定義內(nèi)部,這就是內(nèi)部類。內(nèi)部類本身就是類的一個屬性,與其他屬性定義方式一致。 內(nèi)部類的分類一般主要有四種 成員內(nèi)部類局部內(nèi)部類匿名內(nèi)部類靜態(tài)內(nèi)部類靜態(tài)內(nèi)部類就是定義在類內(nèi)部的靜態(tài)類,靜態(tài)內(nèi)部類可以訪問外部類所有的靜態(tài)變量,而不可訪問外部類的非靜態(tài)變量; 成員內(nèi)部類 就是定義在類內(nèi)部,成員位置上的非靜態(tài)類,就是成員內(nèi)部類。成員內(nèi)部類可以訪問外部類所有的變量和方法,包括靜態(tài)和非靜態(tài),私有和公有。 定義在方法中的內(nèi)部類,就是局部內(nèi)部類。定義在實例方法中的局部類可以訪問外部類的所有變量和方法,定義在靜態(tài)方法中的局部類只能訪問外部類的靜態(tài)變量和方法。 匿名內(nèi)部類 就是沒有名字的內(nèi)部類,除了沒有名字,匿名內(nèi)部類還有以下特點: 匿名內(nèi)部類必須繼承一個抽象類或者實現(xiàn)一個接口匿名內(nèi)部類不能定義任何靜態(tài)成員和靜態(tài)方法。當所在的方法的形參需要被匿名內(nèi)部類使用時,必須聲明為 final。匿名內(nèi)部類不能是抽象的,它必須要實現(xiàn)繼承的類或者實現(xiàn)的接口的所有抽象方法。說出幾種常用的異常NullPointerException: 空指針異常NoSuchMethodException:找不到方法IllegalArgumentException:不合法的參數(shù)異常IndexOutOfBoundException: 數(shù)組下標越界異常IOException:由于文件未找到、未打開或者I/O操作不能進行而引起異常ClassNotFoundException :找不到文件所拋出的異常NumberFormatException: 字符的UTF代碼數(shù)據(jù)格式有錯引起異常;InterruptedException: 線程中斷拋出的異常靜態(tài)綁定和動態(tài)綁定的區(qū)別一個Java 程序要經(jīng)過編寫、編譯、運行三個步驟,其中編寫代碼不在我們討論的范圍之內(nèi),那么我們的重點自然就放在了編譯 和 運行這兩個階段,由于編譯和運行階段過程相當繁瑣,下面就我的理解來進行解釋: Java 程序從源文件創(chuàng)建到程序運行要經(jīng)過兩大步驟: 1、編譯時期是由編譯器將源文件編譯成字節(jié)碼的過程 2、字節(jié)碼文件由Java虛擬機解釋執(zhí)行 綁定綁定就是一個方法的調(diào)用與調(diào)用這個方法的類連接在一起的過程被稱為綁定。 綁定主要分為兩種: 靜態(tài)綁定 和 動態(tài)綁定 綁定的其他叫法 靜態(tài)綁定 == 前期綁定 == 編譯時綁定 動態(tài)綁定 == 后期綁定 == 運行時綁定 為了方便區(qū)分: 下面統(tǒng)一稱呼為靜態(tài)綁定和動態(tài)綁定 靜態(tài)綁定在程序運行前,也就是編譯時期 JVM 就能夠確定方法由誰調(diào)用,這種機制稱為靜態(tài)綁定 識別靜態(tài)綁定的三個關(guān)鍵字以及各自的理解 如果一個方法由 private、static、final 任意一個關(guān)鍵字所修飾,那么這個方法是前期綁定的 構(gòu)造方法也是前期綁定 private:private 關(guān)鍵字是私有的意思,如果被 private 修飾的方法是無法由本類之外的其他類所調(diào)用的,也就是本類所特有的方法,所以也就由編譯器識別此方法是屬于哪個類的 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類獨有的,所以Animal類無法訪問(動物本來就不能說話)// p.canTalk(); }}final:final 修飾的方法不能被重寫,但是可以由子類進行調(diào)用,如果將方法聲明為 final 可以有效的關(guān)閉動態(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類無法調(diào)用// String eatingFruit(String name){// super.eatingFruit(name);// } String eatingApple(String name){ return super.eatingFruit(name); }}static: static 修飾的方法比較特殊,不用通過 new 出某個類來調(diào)用,由類名.變量名直接調(diào)用該方法,這個就很關(guān)鍵了,new 很關(guān)鍵,也可以認為是開啟多態(tài)的導(dǎo)火索,而由類名.變量名直接調(diào)用的話,此時的類名是確定的,并不會產(chǎn)生多態(tài),如下代碼: public class SuperClass { public static void sayHello(){ System.out.println("由 superClass 說你好"); }}public class SubClass extends SuperClass{ public static void sayHello(){ System.out.println("由 SubClass 說你好"); } public static void main(String[] args) { SuperClass.sayHello(); SubClass.sayHello(); }}SubClass 繼承 SuperClass 后,在 是無法重寫 sayHello 方法的,也就是說 sayHello() 方法是對子類隱藏的,但是你可以編寫自己的 sayHello() 方法,也就是子類 SubClass 的sayHello() 方法,由此可見,方法由 static 關(guān)鍵詞所修飾,也是編譯時綁定 動態(tài)綁定在運行時根據(jù)具體對象的類型進行綁定 除了由 private、final、static 所修飾的方法和構(gòu)造方法外,JVM 在運行期間決定方法由哪個對象調(diào)用的過程稱為動態(tài)綁定 如果把編譯、運行看成一條時間線的話,在運行前必須要進行程序的編譯過程,那么在編譯期進行的綁定是前期綁定,在程序運行了,發(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é)果得出的是兒子喜歡喝牛奶。那么上面的綁定方式是什么呢? 上面的綁定方式稱之為動態(tài)綁定,因為在你編寫 Father son = new Son() 的時候,編譯器并不知道 son 對象真正引用的是誰,在程序運行時期才知道,這個 son 是一個 Father 類的對象,但是卻指向了 Son 的引用,這種概念稱之為多態(tài),那么我們就能夠整理出來多態(tài)的三個原則: 繼承重寫父類引用指向子類對象也就是說,在 Father son = new Son() ,觸發(fā)了動態(tài)綁定機制。 動態(tài)綁定的過程 虛擬機提取對象的實際類型的方法表;虛擬機搜索方法簽名;調(diào)用方法。動態(tài)綁定和靜態(tài)綁定的特點靜態(tài)綁定 靜態(tài)綁定在編譯時期觸發(fā),那么它的主要特點是 1、編譯期觸發(fā),能夠提早知道代碼錯誤 2、提高程序運行效率 動態(tài)綁定 1、使用動態(tài)綁定的前提條件能夠提高代碼的可用性,使代碼更加靈活。 2、多態(tài)是設(shè)計模式的基礎(chǔ),能夠降低耦合性。