對java的認識
java是一門開源的面向對象的編程語言,具有面向對象的封裝、繼承、多態(tài)的特點。
封裝:將類的某些信息隱藏起來,只提供特定的方法來訪問或修改這些隱藏信息,從而防止直接操作類中的某些屬性。是通過訪問權限修飾符來實現封裝的,public——protected——default——private訪問權限依次減小,封裝多使用private關鍵詞修飾屬性或者方法。封裝可以提高代碼的安全性、將內部實現封裝起來提高代碼的復用。
繼承:通過extends實現子類繼承父類,子類如果繼承了父類就同時擁有父類公共的特征和行為(屬性和方法),私有的屬性也可以繼承但是不能這直接訪問,需要使用super.get/setXXX()方法(如果子類沒有重寫父類的get/set方法也可以直接使用this.getXXX方法,因為創(chuàng)建子類對象時,會先創(chuàng)建父類,父類的屬性和方法與子類特有的屬性和方法組成了子類的空間)。繼承時不能繼承父類的構造器;一個類只能繼承一個類,一個類可以被多個類繼承,允許多級繼承;Object是所有類的父類(超類);子類不能繼承父類中的靜態(tài)變量,但是可以訪問(共享不等于繼承)。繼承可以實現代碼的提高復用性。
多態(tài):從表現形式上看,多態(tài)就是調用同一個方法有不同的處理方式和處理結果,多態(tài)實現的條件有:①存在繼承關系,②子類重寫父類中的方法,③存在父類的引用指向子類的實例。例如在java中有接口時我們會使用到多態(tài), 多態(tài)使得代碼更加靈活,在程序執(zhí)行時根據具體的實例對象調用方法。
java與C和C++ 相比去除了難以理解的指針,并且實現了自動垃圾回收,可以讓程序員專注于業(yè)務代碼的編寫。
java還做到了跨平臺性。java虛擬機jvm是實現跨平臺的關鍵,編寫好的源代碼是.java文件,經過編譯后形成字節(jié)碼.class文件,.class文件并不能直接被執(zhí)行,需要jvm將字節(jié)碼轉換為機器能讀懂的機器碼并執(zhí)行指令,所以只需要在不同的平臺上安裝不讓的jvm就可以實現”一次編譯,到處使用“。(具體的jvm加載過程屬于jvm中類加載器內容)
對JDK、JRE、JVM的認識
JDK:是整個java的核心,包含了java運行環(huán)境JRE、java工具和java基礎類。
JRE:是運行java程序所必須的環(huán)境集合,包含jvm以及java核心類庫
JVM:是整個java實現跨平臺的核心部分。
java中的數據類型
java屬于強類型語言,其中數據類型可以分為基本數據類型和引用數據類型。
8種基本數據類型:整數型、字符型、浮點型、布爾型
整數型:
byte:1個字節(jié);short:2個字節(jié);int:4個字節(jié);long:8個字節(jié)。
字符型:
char:2個字節(jié)
浮點型:
float:4個字節(jié);double:8個字節(jié)。
布爾型(boolean):只包含兩個值true/false(不能用0/1表示)
應用類型:類、數組等
隱式類型轉換和顯示類型轉化
隱式也稱為自動類型轉換:從存儲范圍小的到存儲范圍大的類型。
byte——》short(char)——》int——》long——》float——》double
顯示也稱為強制類型轉換:從存儲范圍大的到存儲范圍小的類型。但是存在精度損失的情況。
基本數據類型的包裝類
java為8中基本數據類型提供了包裝類,并提供了一系列的操作。
為什么要為基本類型提供包裝類呢?
- 作為與基本類型對應的類 類型存在,方便涉及到對象的操作。
例如在集合中類型參數只能傳入引用類型;在mybatis中對于從數據庫中查詢到的某些字段值為null時如果該字段在java代碼中定義的是基本數據類型會封裝為對應數據類型的默認值(以int為例,就會封裝為0,0和null是不同的可能會導致錯誤),而引用數據類型默認值就是null。 - 提供一些屬性如最大值、最小值,以及一些方法。
基本數據類型與包裝類有什么不同?
包裝類是以對象的形式,可以對其進行對象的一些操作,而八種基本數據類型不是對象。
使用方式不同,包裝類使用new關鍵字創(chuàng)建對象,基本數據類型是通過關鍵字聲明。
初始值不同,基本數據類型的初始值由具體數據類型定,包裝類的初始值為null。
存儲的位置不同,包裝類的實例是存儲在堆中,基本數據類型存儲在棧中。
包裝類的自動封箱與自動拆箱
裝箱就是將基本數據類型封裝為包裝類,拆箱恰好是反過來將包裝類型轉為基本數據類型。
自動裝箱和拆箱就是不用寫顯示的代碼,內部直接進行封箱和拆箱。
//自動封箱
Integer a = 4;
//自動拆箱
Integer integer = new Integer(4);
int b=integer;
System.out.println(b==a);
//true
在封箱的時候底層默認調用的是valueOf()靜態(tài)方法,在拆箱的時候底層默認調用的是intValue()方法。
值得注意的是在調用valueOf()的時候存在一個問題:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
當值在-128127之間時會取出Integer中的私有類IntegerCache里面的cache數組里的數據(目的是:為了避免頻繁的創(chuàng)建和銷毀對象而影響系統(tǒng)性能,它也實現了對象的共享,節(jié)省內存空間。)。所以在-128127之間的值是同一個值,而區(qū)間之外的值是通過new關鍵字創(chuàng)建出來的新的對象使用==判斷時會出現相同的數字結果為false。
switch-case中支持的數據類型有哪些?支持String類型嗎?怎么判斷支持還是不支持?
switch支持的數據類型有:char、byte、short、int以及他們的包裝類、enum和String。是支持String類型的。
//[switch使用tableswitch 和lookupswitch 指令來實現,Java虛擬機的 tableswitch 和 lookupswitch 指令只能作用在int類型的數據]
其實switch中的表達式只支持int類型,char、byte、short都可以向上轉型為int類型,包裝類型可以自動拆箱;enum枚舉類型中有一個private final int ordinal;
實例變量,表示序數,定位枚舉常量的位置;支持String是因為在String中有hashCode方法返回的也是一個int類型的數值。
重寫和重載
重載:
首先得要知道重載是解決什么問題的:在一個類中的某些功能的名稱相同但是具體實現有不同,當然我們可以給它取不同的名字,但是這樣取名就已經絞盡腦汁了,而且不方便調用,不建議采用,由此就產生了方法的重載。就例如System.out.println();我們可能打印的是八種基本數據類型,也有可能打印的是引用類型,但是我們使用打印是都只書寫一個println()。
構成重載的規(guī)則:
同一個類中,
方法名相同,
參數列表不同(類型、順序、個數),
總結:兩同三不同,
注意:只有返回值和訪問權限修飾符不同是不能構成重載的。參數列表順序不同指的是:不同類型參數的順序。
重寫:
重寫是為了解決什么問題:重寫是為了實現能有與父類不同的表現形式,就是為了實現多態(tài)。
我們經常重寫的方法就是equals和toString方法,我們重寫的目的就是為了讓子類有不同于父類的表現形式,equals和toString也是如此。
重寫的規(guī)則:
子類繼承父類或者是實現接口,
方法名稱與父類相同,參數列表與父類相同,
返回值類型是父類中方法返回值的類型或者其返回值類型的子類,
拋出的異常不能比父類的異常更大,
訪問權限能比父類中方法的小
總結:兩同兩小一大。
接口和抽象類的異同
相同點:接口和抽象類都不能被實例化;實現接口的類和繼承抽象類的子類只有全部實現了其中抽象方法的子類才能被實例化。
(為什么不能被實例化:接口沒有構造方法,所以不能實例化,抽象類有構造方法,但是不是用來實例化的,是用來初始化的。)
不同點:
①抽象類是由abstract關鍵字修飾的。接口是interface關鍵字修飾的。
②繼承抽象類的關鍵字是extends。實現接口的關鍵字是implement,一個類只能繼承一個類,可以實現多個接口。
③抽象類可以有普通方法和抽象方法。接口只能有抽象方法。
④抽象類有構造方法。接口沒有構造方法。
⑤抽象類的成員變量默認是default修飾,子類可以進行重新定義和重賦值;抽象方法不能使用private,state,synchronize,native修飾。接口的成員變量默認使用public static final 修飾,必須賦初始值,并且賦初始值之后不能修改。成員方法默認都是public abstract修飾的。
JDK8之后接口的新特性
接口中增加了靜態(tài)方法和用default修飾的默認方法??梢酝ㄟ^“接口名.方法名”進行調用。
Object中有哪些方法
native int hashCode():用來計算哈希值的;
boolean equals():比較對象是否相等,一般的類都會進行重寫,使其比較對象內容是否相等。
native Object clone():克??;
String toString():將對象轉為字符串形式;
native void notify():喚醒一個wait線程;
native void notifyAll():喚醒所有wait線程;
wait():使得線程處于等待狀態(tài)。
equals 和==
equals是Object類中的方法,==是比較運算符。二者都可以用于比較是否相等。
== :當比較的是基本數據類型時,比較的是對應的值是否相等,如果比較的是引用類型時,比較的是對象的地址是否相同,就是比較兩個是不是同一個對象。
equals:不能用于兩個基本類型比較,如果引用類型的類中沒有對equals方法進行重寫則它相當于==符號,比較地址,只有正確重寫后才會比較兩個對象的內容是否相等。
instanceof的作用
instanceof可以理解為一個雙目運算符,它的作用就是查看左邊的對象是不是其右邊類的一個實例,用法如
String str = new String("你好");
System.out.println(str instanceof String);
注意:編譯器會判斷左邊的對象能否裝換為右邊類的實例,如果不能,直接報錯,如果不能確定,則交由運行時處理。
String常量池
專門用來存儲字符串常量,可以提高內存的使用率,避免開辟多塊空間存儲相同的字符串。
當通過字面量創(chuàng)建的String 變量,如:
String str = "ert";
會在字符串常量池中查找一遍看有沒有“ert”,如果有就會返回該對象的引用地址賦值給str,沒有的話就會在字符串常量池中創(chuàng)建一個對象,然后返回地址。
當通過new關鍵字創(chuàng)建的String對象時,如:
String s = new String("aa")
無論字符串常量池中有沒有"aa"都會在堆中開辟空間存儲"aa"。這種創(chuàng)建對象的方式會保證字符串常量池和堆中都有"aa",并且返回的是堆中的對象地址。
對于jvm底層,String str = new String(“123”)創(chuàng)建對象流程是什么?
在常量池中查找是否存在"123"這個字符串;若有,則返回對應的引用實例;若無,則創(chuàng)建對應的實例對象;
在堆中new一個String類型的"123"字符串對象;
將對象地址復制給str,然后創(chuàng)建一個應用。
注意:
若常量池里沒有"123"字符串,則創(chuàng)建了2個對象;若有該字符串,則創(chuàng)建了一個對象及對應的引用。
String、StringBuffer和StringBuilder
String、StringBuffer和StringBulider底層都維護的是字符數組。
String:底層的char數組被final修飾,一創(chuàng)建不可更改,也就是長度不可變,每次在字符需要修改時都會創(chuàng)建一個新的String對象并返回。
String不可變的好處:
可以緩存hash值。因為string的hash值經常被使用,其不可變性也使得其hash值不可變,所以其hash值只需要計算一次。
常量池的優(yōu)化。String對象創(chuàng)建后,會在字符串常量池中進行緩存,如果下次創(chuàng)建同樣的對象時,會直接返回緩存的引用。
String a = "Hello";
String b = "你好";
String c = a + "," + b;
//String c = (new StringBuilder()).append(a).append(",").append(b).toString();
當String對象使用“+”拼接字符時,java提供了一個語法糖,其底層原理是:通過查看反編譯以后的代碼,我們可以發(fā)現,原來字符串常量在拼接過程中,是將String轉成了StringBuilder后,使用其append方法進行處理的。也就是說 +和StringBuilder的append等價。
語法糖:語法糖(Syntactic sugar),也譯為糖衣語法,是由英國計算機科學家彼得·蘭丁發(fā)明的一個術語,指計算機語言中添加的某種語法,這種語法對語言的功能沒有影響,但是更方便程序員使用。語法糖讓程序更加簡潔,有更高的可讀性。
String常用的方法:
判斷功能:
equals()判斷兩個字符串是否相等
equalsIgnoreCase():忽略大小寫,比較內容是否相等
contains():判斷是否包含指定字符串
isEmpty()判斷是不是為空
startWith():判斷字符串是不是以某個字符開頭
endWith():判斷字符串是不是以某個字符結尾。
獲取功能:
int length():返回一個int值,表示字符串的長度;
char charAt(int index):返回一個char類型的值,索引index位置處的值;
int indexOf(String str):返回一個數字,表示str在該字符串中首次出現的位置;
int lastIndexOf(String str):返回一個數,表示從后往前找首次str出現的位置;
int indexOf(String str,int fromIndex):返回一個數字,表示從該字符串的fromIndex位置開始向后檢索該字符串中str首次出現的位置;
String substring(int start):返回一個字符串,表示從start位置輸出原字符串;
String substring(int start,int end):返回一個字符串,表示從start位置開始到end位置輸出原字符串;
轉換功能:
byte[] getBytes():轉換成字節(jié)數
char[] toCharArray():將字符串轉換成一個新的字符數組
static String valueOf(char[] chs):將一個字符數組轉換成一個字符串(形參基本類型)
String toLowerCase():將字符串中的字母全都轉換為小寫
String toUpperCase():將字符串中的字母全都轉換為大寫
String concat(String str):將str鏈接在原字符串后
Stirng[] split(“分割符”):返回的是一個字符串數組,按照 分割符 將原來的字符串拆分為數組
替換功能:
String replace(char old,char new):如果存在old字符就將它替換為new字符;
String replace(String old,String new):如果存在old字符串就將它替換為new字符串;
replaceAll(String regex, String replacement):是一種模式匹配,替換某一類;
replaceFirst(String regex, String replacement):替換第一次出現的位置。
StringBuffer:底層維護的char數組沒有被final修飾,并且是長度可變的。
StringBuffer stringBuffer = new StringBuffer();//默認創(chuàng)建容量為16的char數組
StringBuffer stringBuffer = new StringBuffer(12);//創(chuàng)建容量為12的char數組
StringBuffer stringBuffer = new StringBuffer("aaa");//創(chuàng)建容量為"aaa"長度+16的char數組
在添加字符或者字符串的時候:首先現有容量是否足夠容納判斷添加后的字符串,如果不夠先將容量擴充到原容量的2倍+2,如果還是不夠直接擴容到添加元素后的長度。最大長度不能超過Integer的最大值Integer.MAX_VALUE;
為什么要+2?
StringBuffer和StringBuilder都繼承了 AbstractStringBuilder抽象類但是StringBuffer每個方法使用了synchronized所以是線程安全的。
switch是否能作用在byte上,是否能作用在long上,是否能作用在String上
switch可以作用在char、byte、short、int以及他們所對應的包裝類型,switch不能作用long、double、float、boolean以及他們的包裝類型。jdk1.7之后可以作用于String類型。
關鍵字
static關鍵字
主要的用途就是方便在沒有創(chuàng)建對象的時候調用方法和屬性。
static變量:
static修飾變量稱為靜態(tài)變量,也稱為類變量,可以通過類名+變量名調用。在內存中只有一份,類加載的時候被初始化,所有的對象共享這一份。而非靜態(tài)變量內存中可以有多個,并且每個對象之間互不干擾。
static方法:
靜態(tài)方法,在靜態(tài)方法中不能訪問非靜態(tài)成員變量和非靜態(tài)成員方法,因為非靜態(tài)方法和變量只能有對象進行調用,而靜態(tài)方法和變量是可以直接通過類名調用的,但是可以在非靜態(tài)成員方法中調用靜態(tài)成員方法和變量的。
靜態(tài)代碼塊:
靜態(tài)代碼塊的主要作用是優(yōu)化程序性能,因為他只能在類加載的時候加載一次,很多時候會將一些只需要執(zhí)行一次初始化的操作都放在static代碼塊中進行執(zhí)行。如果程序中有多個static塊,在類的初始化被加載的時候,會按照static塊的順序類執(zhí)行每個static代碼塊。
初始化的順序:
靜態(tài)變量和靜態(tài)語句塊優(yōu)先于實例變量和普通語句塊,靜態(tài)變量和靜態(tài)代碼塊的初始化順序取決于他們的位置,如果在繼承關系中,初始化的順序為:父類的靜態(tài)變量/代碼塊——子類的靜態(tài)變量/代碼塊——父類普通代碼塊——父類構造方法——子類變量/代碼塊——子類構造方法
final關鍵字
final關鍵字可以修飾類、變量、方法。
修飾的類不能被繼承,
修飾的方法不能被重寫,
修飾的變量,如果是基本數據變量,值不能改變;如果是引用數據類型,變量不能再指向其他引用對象,但是變量本事的值是可以改變的。
this關鍵字
- this關鍵字可以用來引用當前類的實例變量。主要用于形參和成員變量重名是進行區(qū)分
- 可以用于調用當前類的方法
- this()可以用來調用當前類的構造方法,但是必須放在構造方法的第一行
super關鍵字
- super關鍵字可以用來調用父類的構造方法,必須要放在子類構造方法的第一行
- 可以用來指向父類的實例變量
- 可以直接調用父類的方法
this和super的區(qū)別
-
相同點:
- 兩個都必須寫在構造器的第一行;
- 都指向的是對象,不能用于static環(huán)境中。
-
不同點:
- this()主要對重載構造器的調用,super()主要是對父類構造器的調用;
- this主要
equals和hashcode
在集合中set集合是無序且不可重復,如何實現不可重復呢?就是在使用add()方法添加元素時先判斷是否已經存在該元素了,我們可以通過equals()方法判斷,但是當集合中有大量的元素時使用equals()方法太慢了,于是就推出了hashcode()方法,hashcode()方法可以為每個對象計算出一個哈希值,然后查看集合中是否存在這個哈希值,如果不存在則可以直接存儲,如果存在則需要調用equals()方法比較具體內容,是否相同。hashcode計算哈希值會出現”哈希碰撞“現象,就是兩個不同的值計算出來的hash值是一樣的,如“通話”和“重地”。
equals和hashcode都可以用來比較兩個對象是否相等,但是hashcode效率高,不安全;equals效率低,安全。
對集合的認識?
集合和數組是十分類似的,都是存儲一組特定數據類型的數據的容器。但是數組有個缺點是大小定義后不能更改,而且在定義時就必須明確規(guī)定數組大小。但是集合在創(chuàng)建時不強制確定大小,并且是可以擴容的。
在java中集合可以分為單列集合和雙列集合(map)
單列集合:list、set并且兩個都繼承了Collection接口
Collection接口的方法有:
isEmpty():判斷是否為空
add():添加元素
clear():刪除集合中所有的元素
contains(Object o):判斷集合中是否包含指定對象元素
iterator():返回一個Iterator迭代器對象,可以遍歷集合中的元素
size():返回集合中元素的數目
remover(Object o):從集合中刪除一個指定對象
toArray():將集合轉為數組,返回一個Object[]
list和set集合的區(qū)別:
list按照元素添加的順序存儲元素,而set無序存儲;
list支持重復元素,而set不支持重復元素。
List集合有兩個常用的兩個實現類:ArrayList和LinkedList
ArrayList:是以索引結構實現的單列,底層維護的是一個Object[],可以實現動態(tài)擴容,使用無參構造創(chuàng)建對象時,在創(chuàng)建的時候不會給底層數組定義長度,在第一次添加元素時判斷添加元素的長度和默認長度10,選擇值大的作為底層數組的容積,如果使用的是有參構造指定初始容積創(chuàng)建對象時,直接確定容積。在添加元素時,首先會判斷添加這個元素后大小,當前容量是否足夠,如果足夠直接添加;如果不夠,先將容量擴容到原來容量的1.5倍,在判斷新容量是否還小于需求容量,如果小于直接以需求容量當做新容量,最后將原來集合中的元素復制到新數組中去。ArrayList沒有自動縮容機制。無論是remove方法還是clear方法,它們都不會改變現有數組elementData的長度。但是它們都會把相應位置的元素設置為null,以便垃圾收集器回收掉不使用的元素,節(jié)省內存。ArrayList的縮容,需要我們自己手動去調用trimToSize()方法,達到縮容的目的。
ArrayList集合由于是索引結構,它的查詢、遍歷快,但是刪除元素慢。
LinkedList:是以鏈表結構實現的單列集合,一個節(jié)點指向下一個節(jié)點,不存在擴容問題,LinkedList集合的優(yōu)點是,增刪效率高,但是查詢、遍歷的時候效率比較低。
Set集合兩個常用的實現類HashSet和TreeSet
HashSet類按照哈希算法來存取集合中的對象,存取速度比較快,底層使用的是map中的key,不安添加元素順序存儲,集合中可以存放一個null值,添加一個元素時,底層會計算出該對象的hashcode值,用hashcode值和底層數組的長度來計算出該對象需要存儲的位置,如果該位置上沒有元素則直接存,如果有元素證明發(fā)生了哈希碰撞,此時調用equals方法來判斷具體內容是否相等,如果相等,就不添加,如果不等就鏈接到上一個節(jié)點的后面。
TreeSet類實現了SortedSet接口,能夠對集合中的對象進行排序,不允許添加null元素。TreeSet底層數據結構是二叉樹。
雙列集合:Map
Map集合存儲的每一個元素都是由鍵值對組成,并且集合中的每個key必須唯一,每個元素并不是按照添加順序進行存儲的。
Map集合遍歷的方式有兩種:①取出Map集合的key集合,然后由key獲得value;②將Map集合的鍵值對當做一個Enrty對象存儲在set集合中然后在遍歷;③利用Lambda表達式forEach()。
Map中的方法有:
size():集合大小
isEmpty():判斷是否為空
containsKey(Object key):判斷是否存在指定key
containsValue(Object value):判斷是否包含指定value
put():添加元素
remove(Object key):移除指定key的元素
putAll():添加整個集合
clear():清空集合
keySet():返回一個封裝key值的Set集合
values():返回個封裝value的Collection集合
Set<Map.Entry<K, V>> entrySet();
Map的實現類:HashMap、HashTable和ConcurrentHashMap
HashMap是Map的非同步的實現(一個對象可以同時被多個線程訪問),是線程不安全的,允許存在null鍵和null值,但是只能有一個null鍵,可以有多個null值。JDK8之后,底層數據結構是數組+鏈表/紅黑樹(當單鏈表達到閾值8時就會轉化為紅黑樹)
以自定義的類作為key值的類型時,自定義類需要重寫equals和hashCode方法,否則會直接調用Object類里的方法。
put添加元素時(首次添加時會創(chuàng)建哈希數組默認長度是16 ,數組只是用來定位元素在哈希表中的位置),根據添加元素的哈希值通過哈希函數計算得到一個小于哈希數組長度的值,將需要添加的元素以鏈表的形式添加到函數求得的值對應的哈希數組位置中,當哈希數組的某一個位置重復時,將連接到上一次鏈表的后面,當一個鏈表的長度達到8時,會將鏈表轉成紅黑樹,以方便檢索,當哈希數組數據存儲達到當前數組長度的0.75(稱為負載因子)時就會擴容到原來的2倍.
HashMap的主要流程:
1.判斷table數組是否被初始化,如果沒有就會進行初始化擴容。
2.利用添加元素的key的hash值與數組長度-1進行&運算,得到一個小于數組長度的值,就是對應添加元素應該添加的數組位置。
3.獲取到這個位置上的頭結點,判斷當前節(jié)點是否為空,為空的話直接添加元素。
4.不為空時,判斷當前節(jié)點的hash值是否與添加元素key的hash值相等,如果相等,判斷當前節(jié)點的key是否與添加元素key是同一個對象,如果是,直接用添加元素value替換當前節(jié)點的value,如果不是,調用equals方法判斷當前節(jié)點的key與添加元素的key是否內容相等,如果相等,也直接用添加元素value替換當前節(jié)點的value。如果前面的條件都不滿足進行下一步。
5.判斷當前節(jié)點是不是樹的頭結點,如果是,直接在樹種尋找位置添加。
6.如果不是樹的頭結點,那么必定是鏈表。接下來死循環(huán)遍歷當前鏈表,如果鏈表上存在某個節(jié)點的key與添加元素的key相同,就直接替換value,如果不存在,就直接在鏈表的末尾添加元素,添加成功后判斷當前鏈表長度是否≥7,如果是就將鏈表轉換為紅黑樹,判斷當前HashMap對象中的元素達到當前數組的0.75就會進行resize進行擴容,擴容到原來的2倍。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//執(zhí)行流程前的初始化定義
//定義桶數組
Node<K,V>[] tab = table;
//定義當前key對應的節(jié)點
Node<K,V> p;
//初始化數組長度
int n = tab.length;
//開始執(zhí)行
//如果桶不存在,初始化擴容
if (tab == null || n == 0){
//擴容并重新賦值數組長度
n = (tab = resize()).length;
}
//將數組長度減一與hash值做&運算,得到0到數組長度減一之間的數字【如數組長度為16,那么得到的i是0~15之間的數字】
//i就是對應的數組桶下標
int i= (n - 1) & hash;
//通過得到數組桶坐標的鏈表頭節(jié)點
p = tab[i];
//如果頭結點為空,創(chuàng)建新節(jié)點
if (p == null){
tab[i] = newNode(hash, key, value, null);
} else {
//如果不為空,那么確定節(jié)點已經位于鏈表上了
Node<K,V> e; K k;
//和鏈表中的頭節(jié)點比較,如果相同,那么就取得了相同key的節(jié)點
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))){
e = p;
} else if (p instanceof TreeNode){
//如果是樹節(jié)點,那么找到頭部節(jié)點為樹節(jié)點,那么將對應的樹中的節(jié)點進行賦值
//如果返回了e = null,表示在樹上新創(chuàng)建了一個節(jié)點 - 詳情見hashMap - putTreeVal詳解
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
} else {
//匹配鏈表上的節(jié)點,如果能匹配上,就返回對應節(jié)點,
//如果匹配不上,那么創(chuàng)建一個新的節(jié)點,并且驗證是否需要樹化
//樹化的條件:
//condition1: 大于等于閾值-1即7;
//condition2: 數組長度大于MIN_TREEIFY_CAPACITY即64
for (int binCount = 0; ; ++binCount) {
//如果下一個節(jié)點為空(尾節(jié)點),那么創(chuàng)建節(jié)點,并且判斷是否需要樹化
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//有匹配的節(jié)點,那么返回
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))){
break;
}
p = e;
}
}
//如果在鏈表中有值,那么將值替換
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
//hashMap未實現,linkedHashMap因為帶序問題會實現
afterNodeAccess(e);
return oldValue;
}
}
//修改次數加一
++modCount;
//如果添加元素之后的大小超過了閾值,需要擴容
if (++size > threshold)
resize();
//hashMap沒有實現,linkedHashMap實現了,因為是帶序的
afterNodeInsertion(evict);
return null;
}
HashTable是Map的同步實現(synchronized實現,一個對象只能被一個線程訪問),線程安全,但是并發(fā)效率極其低下,不允許key和value的值為null。底層數據結構是數組+鏈表
HashTable初始默認容積是11,擴容時并不是2倍,而是2倍+1,HashTable現在已經還少使用,單線程多使用HashMap多線程使用ConcurrentHashMap。
ConcurrentHashMap同樣是Map的同步實現,雖然HashTable已經實現了同步,但是其效率太低,與HashTable不同的是它的鎖的粒度更小,底層使用了與HashMap相同的數組+鏈表/紅黑樹的結構,線程安全使用synchronized+CAS。在大數據量,高并發(fā)情況下ConcurrentHashMap更加實用。
Lambda 表達式
lambda表達式是JDK8的新特性,在JDK8之前我們想要吧某個功能傳遞給某個方法,我們只能寫匿名內部類。如為集合進行排序的時候,我們會為Comparator類創(chuàng)建一個匿名內部類對象,重寫其中的compare方法。
//創(chuàng)建一個匿名內部類
alist.sort(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.id-o2.id;
}
});
為了簡化匿名內部類,JDK8之后提供了簡單的寫法,使用lambda表達式。
list.sort((o1,o2)->{
return o1.compareTo(o2);
});
當花括號里的語句只有一條時,可以將花括號省略。
需要注意的是Lambda表達式中只能是函數式接口(接口中只有一個抽象方法需要實現)
Lambda表達式的標準書寫形式:(參數列表)->{方法體}。
Lambda表達式可以簡化書寫:
①小括號里參數列表的參數類型可以不寫,
②當參數只有一個時小括號可以不寫,
③當方法體只有一條語句時,花括號,return,分號都可以不寫。
對java中的異常的認識
異常狹義理解就是在程序運行時,出現不正常的情況,導致jvm被迫停止運行,異常指的并不是語法錯誤,語法出錯編譯時期就會報錯,是不會產生字節(jié)碼文件,根本不運行;java等一些面向對象語言中的異常本身就是一個類,產生異常就是創(chuàng)建一個異常對象然后拋出一個異常對象。
為什么要設計異常?引入異常后我們就可以將可能會有異常的代碼與正常的代碼分離出來,單獨處理,這樣使得代碼更加整潔;其次,在出現特殊情況的時候還可以拋出一個檢查異常,讓調度者處理。
Throwable是所有Error(錯誤)和Exception(異常)的父類。
Error:(又稱錯誤) java虛擬機無法解決的問題.一般不編寫代碼處理.
Exception:(程序異常,狹義的異常)因編程錯誤或者是偶然的外在因素導致的一般性問題,可以編寫針對性代碼進行解決處理的
異常有運行時異常和非運行時期異常,
RuntimeException此類異常,Java 編譯器不會檢查它,屬于不受檢查的異常。此類異常會由JVM自動拋出并自動捕獲(就算我們沒寫異常捕獲語句運行時也會拋出錯誤!?。祟惍惓5某霈F絕大多數情況是代碼本身有問題,應該從邏輯上去解決并改進代碼。
非運行時異常:Exception中除 RuntimeException 及其子類之外的異常。此類異常, Java 編譯器會檢查它。如果程序中出現此類異常,從程序語法角度講是必須進行處理得異常。例如:ClassNotFoundException(沒有找到指定的類異常),IOException(IO流異常),要么通過throws 進行聲明拋出,要么通過try-catch進行捕獲處理,否則不能通過編譯。
異常處理分為拋出異常(throw)、捕獲異常(try-catch-finally)、聲明異常(throws)
五個關鍵字:
throw:拋出異常,在方法內部拋出一個Throwable 類型的異常。任何Java代碼都可以通過throw語句拋出異常。
throws:在方法上聲明異常交由調度者處理,
try-catch-finally有三種組合:try-catch 、try-finally、try-catch-finally。try必須要有,catch可以有0至多個,finally可以有一個或者沒有。
try用與捕獲異常,catch用于處理異常,finally無論異常是否發(fā)生都會執(zhí)行的代碼塊文章來源:http://www.zghlxwxcb.cn/news/detail-421302.html
try {
// 可能會發(fā)生異常的程序代碼
} catch (異常類型A e){
// 捕獲并處置try拋出的異常類型A
} finally {
// 無論是否發(fā)生異常,都將執(zhí)行的語句塊
}
注意:try-finally只是捕獲到了異常,并沒有處理文章來源地址http://www.zghlxwxcb.cn/news/detail-421302.html
到了這里,關于java基礎知識點復習①的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!