JavaEE基礎

Java中接口與抽象類的區別

抽象類用來捕捉子類的通用特性、不能被實例化,只能被用作子類的超類

接口是抽象方法的集合。

  1. 如果類實現了某個接口,就繼承了該接口的抽象方法
  2. 實現了這個接口,必須確保使用這些方法
  3. 接口本身只能是public

何時使用:

  • 想有默認實現,抽象類
  • 基本功能在不斷改變,使用抽象類。若使用接口,需要改變所有實現了該接口的類
  • 多重繼承,必須使用接口。子類不能夠繼承多個類,但可以實現多個接口

Java併發包

  1. ConcurrentHashMap
  2. CopyOnWriteArrayList
    1. 線程安全版本的ArrayList,每次增加的時候,需要新創建一個比原來容量+1大小的數組
    2. 拷貝原來的元素到新的數組中,同時將新插入的元素放在最末端。
    3. 然後切換引用;
    4. 迭代時生成快照數組;適合讀多寫少
  3. CopyOnWriteArraySet
    1. 基於CopyOnWriteArrayList實現;
    2. 不能插入重複數據,每次add的時候都要遍歷數據,性能略低於CopyOnWriteArrayList
  4. ArrayBlockingQueue 數組結構組成的有界阻塞隊列
  5. Atomic類,如AtomicInteger、AtomicBoolean i++變成原子操作, 底層是CAS,別的線程自旋等到該方法執行完成

HashMap簡介 非線程安全

JDK7-位桶+鏈表的方式

JDK8-位桶+鏈表/紅黑樹

鏈表的長度達到閥值 8 ,鏈表就將轉換成紅黑樹;小於6,轉鏈表

實現了Serializable接口,支持序列化,實現了Cloneable接口,能被克隆

 

HashMap底層維護一個數組,數組中的存儲Entry對象組成的鏈表

Map中的key,value則以Entry的形式存放在數組中,通過key的hashCode計算,

PUT

當插入新元素時,對於紅黑樹的判斷如下:

判斷table[i] 是否是紅黑樹,如果是紅黑樹,則直接在樹中插入鍵值對,否則轉向下面;

遍歷table[i],判斷鏈表長度是否大於8,大於8的話把鏈表轉換爲紅黑樹,在紅黑樹中執行插入操作,否則進行鏈表的插入操作;插入到前面;

遍歷中若發現key已經存在直接覆蓋value;

hash衝突(碰撞):HashMap解決hash衝突的方式是用鏈表; 先找到下標 i,KEY值找Entry對象

當發生hash衝突時,新值存放在數組中,舊值在新值的鏈表上,將存放在數組中的Entry設置爲新值的next

GET

對key進行null檢查。如果key是null,table[0]這個位置的元素將被返回。

key的hashcode()方法被調用,然後計算hash值。

indexFor(hash,table.length)用來計算要獲取的Entry對象在table數組中的精確的位置,

遍歷鏈表,調用equals()方法檢查key的相等性,如果equals()方法返回true,get方法返回Entry對象的value,否則,返回null。

紅黑樹

紅黑樹是一種近似平衡的二叉查找樹,它能夠確保任何一個節點的左右子樹的高度差不會超過二者中較低那個的一陪。具體來說,紅黑樹是滿足如下條件的二叉查找樹(binary search tree):

  1. 每個節點要麼是紅色,要麼是黑色。
  2. 根節點必須是黑色
  3. 紅色節點不能連續(也即是,紅色節點的孩子和父親都不能是紅色)。
  4. 對於每個節點,從該點至null(樹尾端)的任何路徑,都含有相同個數的黑色節點。

紅-黑樹會盡量保證樹總是平衡,對一個要插入的數據項,插入例程要檢查會不會破壞樹的特徵,如果破壞了,程序就會進行糾正,根據需要改變樹的結構

能以較快的時間O(logN)來搜索一棵樹,需要保證樹總是平衡的(或者至少大部分是平衡的),這就是說對樹中的每個節點在它左邊的後代數目和在它右邊的後代數目應該大致相等。

 

HashMap共有四個構造方法 構造方法中兩個很重要的參數:初始容量16和加載因子0.75

這兩個是影響HashMap性能的重要參數,初始容量表示哈希表的長度,初始是16

加載因子是哈希表在其容量自動增加之前可以達到多滿的一種尺度,當哈希表中的條目數超出了加載因子與當前容量的乘積時,則要對該哈希表進行 resize 操作(即擴容) 默認0.75

加載因子越大,對空間的利用更充分,但是查找效率會降低(鏈表長度會越來越長);

加載因子太小,那麼表中的數據將過於稀疏(很多空間還沒用,就開始擴容了),對空間造成嚴重浪費

加入鍵值對時,先判斷當前已用數組長度是否大於等於閥值(容量*加載因子),如果大於等於,則進行擴容,容量擴爲原容量2倍

散列法 h&(length-1)

Hashtable:key的hash值對length取模(即除法散列法),基本能保證元素在哈希表中散列的比較均勻,但除法運算,效率很低;

HashMap改進,&操作:通過h&(length-1)的方法來代替取模,同樣實現了均勻的散列,但效率要高很多,h = key的hash

h = 101100110,length = 1000,h%(length-1)就是保留h右邊的3位二進制=110 (也是爲什麼數組從0開始)

爲什麼哈希表的容量一定要是2的整數次冪

h&(length-1)就相當於對length取模,這樣便保證了散列的均勻,同時提升了效率

  • 2的整數次冪爲偶數,length-1爲奇數,最後一位是1,保證h&(length-1)的最後一位可能爲0或1,保證散列的均勻性
  • length爲奇數的話,length-1爲偶數,最後一位是0,h&(length-1)的最後一位爲0,浪費了近一半的空間

resize方法

新建一個HashMap的底層數組,而後調用transfer方法,將就HashMap的全部元素添加到新HashMap,

要重新計算元素在新的數組中的索引位置

containsKey方法和containsValue方法

前者直接可以通過key的哈希值將搜索範圍定位到對應的鏈表;後者要對哈希數組的每個鏈表進行搜索

concurrenthashmap 源碼 JDK1.8版 synchronizedMap

1.8拋棄Segment分段鎖機制,利用CAS+Synchronized來保證併發更新的安全,底層依然採用數組+鏈表+紅黑樹的存儲結構

table:默認爲null,初始化發生在第一次put操作,默認大小爲16的數組,存儲Node節點數據,擴容時大小總是2的冪次方。

nextTable:默認爲null,擴容時新生成的數組,其大小爲原數組的兩倍。

sizeCtl :默認爲0,用來控制table的初始化和擴容操作,具體應用在後續會體現出來

  • -1 代表table正在初始化(保證只有一個put觸發初始化)
  • -N 表示有N-1個線程正在進行擴容操作
  • 其餘情況:
  • 如果table未初始化,表示table需要初始化的大小
  • 如果table初始化完成,默認是table大小的0.75倍

Node:保存key,value及key的hash值的數據結構 class Node<K,V> implements Map.Entry<K,V> ;value和next都用volatile修飾,保證併發的可見性

ForwardingNode:特殊的Node節點,hash值爲-1,其中存儲nextTable的引用,table發生擴容的時候,ForwardingNode作爲一個佔位符放在table尾端,表示當前節點爲null

延遲初始化

延遲初始化:只會初始化sizeCtl值,並不會直接初始化table,而是延緩到第一次put操作

多線程下的初始化機制:執行第一次put操作的線程會CAS方法修改sizeCtl爲-1,且只有一個線程能夠修改成功,

put操作

假設table已經初始化完成,put操作採用CAS+synchronized實現併發插入或更新操作

  • hash計算,定位索引位置,index = hash &(lengh - 1)
  • 獲取table中對應索引的對象f,node類型:Unsafe.getObjectVolatile來獲取 tabAt[index],獲取指定內存的數據,保證每次拿到數據都是最新;因爲線程都有一個工作內存,裏面存儲着table的副本,雖然table是volatile修飾的,但不能保證線程每次都拿到table中的最新元素
  • 如果f爲null,說明table中該位置第一次插入元素,利用Unsafe.compareAndSwapObject(CAS)方法插入Node節點
    • CAS成功,說明Node節點已經插入,隨後檢查是否需要進行擴容
    • CAS失敗,說明有其它線程提前插入了節點,自旋重新嘗試插入節點
    • 如果f的hash值爲-1,說明當前f是ForwardingNode節點,意味有其它線程正在擴容,則一起進行擴容操作
  • f不爲null的其餘情況把新的Node節點按鏈表或紅黑樹的方式插入到合適的位置,這個過程採用同步內置鎖實現併發(Synchronized鎖住Node,減少了鎖粒度)
    • 在節點f上進行同步,節點插入之前,再次利用tabAt(tab, i) == f判斷,防止被其它線程修改
    • 如果f.hash >= 0,說明f是鏈表結構的頭結點,遍歷鏈表,如果找到對應的node節點,則修改value,否則在鏈表尾部加入節點
    • 如果f是TreeBin類型節點if(f instanceof TreeBin),說明f是紅黑樹根節點,則在樹結構上遍歷元素,更新或增加節點
    • 如果鏈表中節點數binCount >= TREEIFY_THRESHOLD(默認是8),則把鏈表轉化爲紅黑樹結構

 

get操作:

getObjectVolatile 保證可見性獲取索引的對象f=tabAt[index],遍歷key,找到相等的,cas來保證變量的原子性讀取

table擴容

元素數量達到容量閾值sizeCtl(長度*0.75),擴容分爲兩部分:

構建一個nextTable,大小爲table的兩倍

Unsafe.compareAndSwapInt修改sizeCtl值-1,保證只有一個線程初始化,擴容後的數組長度爲原來的兩倍,但是容量是原來的1.5

把table的數據複製到nextTable中:擴容操作支持併發插入,支持節點的併發複製,性能提升,但實現的複雜度上升

大體思想是遍歷、複製的過程

1.末尾放fwd,其他線程會幫助擴容

得到需要遍歷的次數 i,在tab的 i 位置,初始化一個forwardingNode實例fwd

2.構造反序列表,放入nextTable的 i 和 i+n 的位置上(舊數據放在數組後面,即新擴容的地方)

如果f是鏈表的頭節點,就構造一個反序鏈表,把他們分別放在nextTable的 i 和 i+n 的位置上,移動完成,採用Unsafe.putObjectVolatile方法給table原位置賦值fwd

3.遍歷過所有的節點,把table指向nextTable,更新sizeCtl爲新數組大小的0.75

 

synchronizedMap()

SynchronizedMap類是定義在Collections中的一個靜態內部類。實現了Map接口,通過synchronized關鍵字對map實現同步控制

區別及應用場景

1.ConcurrentHashMap的實現更加精細,在性能以及安全性方面更優

它對map中的所有桶加了鎖。只要要有一個線程訪問map,其他線程就無法進入map

同步操作精確控制到桶,其他線程,仍然可以對桶執行某些操作

例如:即使在遍歷map時,其他線程試圖對map進行數據修改,不會拋出ConcurrentModificationException

2.ConcurrentHashMap只能是HashMap,而Collections.synchronizedMap()可以接收任意Map實例,實現Map的同步,如TreeMap實現排序

Map<String, Object> map2 = Collections.synchronizedMap(new TreeMap<String, Object>());

Map<String, Object> map3 = new ConcurrentHashMap<String, Object>();

 

CAS底層實現原理 

CAS:Compare and Swap, 翻譯成比較並交換。 

CAS需要在:操作值的時候,檢查值有沒有發生變化,如果沒有發生變化則更新

JVM中的CAS操作正是利用了處理器提供的CMPXCHG指令實現的

通過鎖和循環CAS的方式來實現原子操作

 

java中創建子類實例時會創建父類實例嗎    

  • 創建一個子類對象,不會創建父類對象
  • 通過調用父類的init 方法,可以加載父類的方法
  • 先調用的是父類的構造器初始化屬性,再調用的子類的構造器

 

linkedHashMap,treemap(排序)

TreeMap會按照key值默認排序

LinkedHashMap是HashMap的一個子類,輸出的順序和輸入的相同

 

用java實現生產者消費者的三種方法 

 

簡述java Object類中的方法有哪些

Object類是Java中其他所有類的祖先,基類

Object類位於java.lang包中,沒有定義屬性,在編譯時會自動導入。一共有13個方法

clone方法

實現對象的淺複製,需實現了Cloneable接口;(可重寫實現字段深複製)

getClass方法 final方法,獲得運行時類(class對象)

finalize() :當垃圾回收器確定不存在對該對象的更多引用時,由對象的垃圾回收器調用此方法

public boolean equals(Object obj); 比較對象(內存地址)是否相同

public native int hashCode(); 返回一個整形數值,表示該對象的哈希碼值。

public String toString(); toString()方法返回該對象的字符串表示

notify(),notifyAll():喚醒在此對象監視器上等待的單個(所有)線程,

方法調用後,線程不會立即釋放所持有的鎖,直到其所在同步代碼塊中的代碼執行完畢,再釋放鎖

wait():調用此方法所在的當前線程等待,直到在其他線程上調用對象的notify(),notifyAll()方法

wait(long timeout):線程等待,直到notify() notifyAll() 方法,或超過指定的時間量

wait(long timeout, int nanos)

線程等待,notify() notifyAll() 方法,或其他某個線程中斷當前線程,或超過時間量

簡述java中內部類    

java中的內部類有四種:

1.靜態內部類:作爲類的靜態成員,存在於某個類的內部。

2.成員內部類:作爲類的成員,存在於某個類的內部。

  成員內部類可以調用外部類的所有成員,但只有在創建了外部類的對象後,才能調用外部的成員

3.局部內部類:存在於某個方法的內部。

  局部內部類只能在方法內部中使用,一旦方法執行完畢,局部內部類就會從內存中刪除。

  必須注意:如果局部內部類中要使用他所在方法中的局部變量,那麼就需要將這個局部變量定義爲final的。

4.匿名內部類:存在於某個類的內部,但是無類名的類。

 

java中容器(集合)類

Java 的集合分爲兩種接口:Collection, Map

Collection : 是最基本的集合接口。只允許每個位置上放一個對象,List、Set和Queue接口的父接口

Map:(k,v)形式,sortedMap接口的父接口

Set集合不允許包含相同的元素

HashSet類: 是Set接口的一個子類,無序集合,採用散列存儲,所以沒有順序

TreeSet類: TreeSet是SortedSet接口的唯一實現類,有序集合,TreeSet可以確保集合元素處於排序狀態

List接口 List是有序,能夠控制每個元素插入的位置

ArrayList類: 動態數組的數據結構,適合改查

LinkedList類:基於鏈表的數據結構,適合增刪

Stack 類: Stack繼承自Vector,實現一個後進先出的堆棧

Map提供key到value的映射,一個Map中不能包含相同的key,每個key只能映射一個value,每個鍵最多隻能映射到一個值

Hashtable類: 同步的,線程安全,效率低。添加數據使用put(key, value),取出數據使用get(key)

ConcurrentHashMap 代替 Hashtable. 同步的,線程安全

HashMap類:非同步的,線程不安全,效率上比hashTable要高

LinkedHashMap是HashMap的子類,維護了插入的先後順序,適合LRU算法做緩存(最近最少使用)

WeakHashMap是改進的HashMap,它對key實行“弱引用”,如果一個key不再被外部所引用,那麼該key可以被GC回收

TreeMap實現了SortedMap接口,默認根據key值進行升序排序

Arraylist與LinkedList區別

ArrayList實現了基於動態數組的數據結構,而LinkedList是基於鏈表的數據結構;

  1. 對於隨機訪問get和set,ArrayList要優於LinkedList(O(1)時間複雜度隨機訪問),因爲LinkedList要移動指針(複雜度是O(n)); 
  2. 對於新增和刪除操作add和remove,LinedList比較佔優勢,(不需要重新計算大小或者更新索引)因爲ArrayList要移動數據;
  3. LinkedList更佔內存,因爲LinkedList爲每一個節點存儲了兩個引用,一個指向前一個元素,一個指向下一個元素。

由於LinkedList可以實現棧、隊列以及雙端隊列等數據結構,所以當特定需要時候,使用LinkedList

CopyOnWriteArrayList 適合讀多寫少的併發場景。比如白名單,黑名單

Copy-On-Write簡稱COW,是一種用於程序設計中的優化策略。其基本思路是,從一開始大家都在共享同一個內容,當某個人想要修改這個內容的時候,纔會真正把內容Copy出去形成一個新的內容然後再改,這是一種延時懶惰策略。從JDK1.5開始Java併發包裏提供了兩個使用CopyOnWrite機制實現的併發容器,它們是CopyOnWriteArrayList和CopyOnWriteArraySet

通俗的理解是當我們往一個容器添加元素的時候,不直接往當前容器添加,而是先將當前容器進行Copy,複製出一個新的容器,然後新的容器裏添加元素,添加完元素之後,再將原容器的引用指向新的容器。這樣做的好處是我們可以對CopyOnWrite容器進行併發的讀,而不需要加鎖,因爲當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想

讀的時候不需要加鎖,如果讀的時候有多個線程正在向ArrayList添加數據,讀還是會讀到舊的數據,因爲寫的時候不會鎖住舊的ArrayList。

適合讀多寫少的併發場景。比如白名單,黑名單

CopyOnWrite的缺點

內存佔用問題 寫操作的時候,內存裏會同時駐紮兩個對象的內存

數據一致性問題 CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性

 

簡述java內存模型的happen before原則

happen-before原則是判斷數據是否存在競爭、線程是否安全的主要依據,保證了多線程環境下的可見性

在JMM(內存模型)中,如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在happens-before關係

happens-before原則定義如下:

  1. 如果A操作happens-before另一個操作B,那麼A操作的執行結果將對B操作可見,且A操作的執行順序排在B操作之前
  2. 如果兩個操作不存在happens-before規則,那麼這兩個操作就沒有順序的保障

hashmap保存自定義類 需要重寫自定義類的方法  hashCode()和equals()

需要重寫hashCode()和equals()方法纔可以實現自定義鍵在HashMap中的查找

JDK1.8的新特性

接口改善 接口裏已經完全可以定義靜態方法了

函數式接口 聲明

如果一個接口定義個唯一一個抽象方法,那麼這個接口就成爲函數式接口。比如,java.lang.Runnable就是一個函數式接口,因爲它只頂一個一個抽象方法

Lambdas:一個函數式接口非常有價值的屬性就是他們能夠用lambdas來實例化

java.util.stream:新的java.util.stream包提供了“支持在流上的函數式風格的值操作”

對編譯器判定泛型能力的努力改進

改進併發API

ConcurrentHashMap(v8)

hashmap

簡述java中的深拷貝與淺拷貝

區別:

如果是引用類型,淺拷貝只複製引用,不復制值;值類型則都是複製值

淺拷貝:stu = (Student)super.clone();

    1. 創建一個新對象,然後將當前對象的非靜態字段(變量)複製該新對象
    2. 如果字段是值類型的,那麼對該字段執行復制;
    3. 如果該字段是引用類型的話,則複製引用但不復制引用的對象。原始對象及其副本引用同一個對象

深拷貝:stu.addr = (Address)addr.clone();

  1. 一直copy對象所有的內部元素, 最後只剩下原始的類型(int)以及“不可變對象(String)”
  2. 將對象序列化再讀出來也是深拷貝

例子:

stu = (Student)super.clone(); //淺copy

stu.addr = (Address)addr.clone(); // 深copy:將(屬性變量字段)單獨clone一次

 

java開發過程中遇到過哪些exception

算術異常類:ArithmeticExecption

空指針異常類:NullPointerException

類型強制轉換異常:ClassCastException

數組負下標異常:NegativeArrayException

數組下標越界異常:ArrayIndexOutOfBoundsException

違背安全原則異常:SecturityException

文件已結束異常:EOFException

文件未找到異常:FileNotFoundException

字符串轉換爲數字異常:NumberFormatException

操作數據庫異常:SQLException

輸入輸出異常:IOException

方法未找到異常:NoSuchMethodException

散列表的衝突處理

散列表的衝突處理主要分爲閉散列法(開放定址法)和開散列法

閉散列法(開放定址法)

  1. 衝突的元素沒有開闢額外的存儲空間,還是在原先hash表的空間範圍之內
  2. 當插入元素髮生了散列衝突,就逐個查找下一個空的散列地址供插入,直到查找失敗

(1). 線性探測法:將散列表看作是一個循環向量,若初始地址是f(key)=d,則依照順序d、d+1、d+2…的順序取查找,即f(key)=(f(key)+1)mod N;

(2). 二次探測法:基本思路和線性探測法一致,只是搜索的步長和方向更加的多樣,會交替以兩個方向,步長爲搜索次數的平方來查找;

(3). 雙重散列法:通常雙重散列法是開放地址中最好的方法,其通過提供hash()和rehash()兩個函數,前者產生衝突的時候,定製化後者rehash()重新尋址,其機制比前面兩種固定格式的要靈活的多;

開放定址法一般用於衝突極少的情況,同時因爲沒有用到指針,所以對於數據的傳輸是友好的。

開散列法

  1. 一般通過將衝突的元素組織在鏈表中,採用鏈表遍歷的方式查找。
  2. 解決方法直觀,實現起來簡單,尤其在刪除元素的時候此處只是簡單的鏈表操作
  3. 開散列法可以存儲超過散列表容量個數的元素

(4). 鏈地址法:相同散列值的記錄放到同一個鏈表中,他們在同一個Bucket中;

(5). 公共溢出法:將所有的衝突都放到一個公共的溢出表中去,適用於衝突情況很小的時候。

 

FInal

關於final的重要知識點

  1. final關鍵字可以用於成員變量、本地變量、方法以及類
  2. final成員變量必須初始化或者在構造器中初始化,否則就會報編譯錯誤。
  3. 你不能夠對final變量再次賦值,final變量就是常量,常量名大寫:

final變量,即常量

變量聲明爲final的都叫作final變量;final變量經常和static關鍵字一起使用,作爲常量

final方法 -- 不能重寫、快

方法前面加上final關鍵字,代表方法不能被子類的方法重寫。

final方法比非final方法要快,因爲在編譯的時候已經靜態綁定了,稱爲靜態綁定(static binding)。不需要在運行時再動態綁定

final類 -- 不能繼承

final類通常功能是完整的,不能被繼承。如:String, Interger

final關鍵字的好處

  1. final變量可以安全的在多線程環境下進行共享,不需要額外的同步開銷
  2. JVM會對final方法、變量、類進行優化,緩存final變量,提高了性能

String

String類是一個final類型的字符數組, 不允許繼承,改變 private final char value[]

可序列化,可比較,實現了Serializable, Comparable, CharSequence接口

equals( )方法被重寫,|| 運算 先判斷兩個String對象是否相等,如果不等再判斷兩個String對象的字面值是否相等;一個爲真即可

String對象的三種比較方式:

== 內存地址比較:new 出現新地址 直接對比兩個引用所指向的內存地址,精確簡潔直接明瞭。

equals 字符串值比較:比較兩個引用所指對象字面值是否相等;已對object的equals進行改寫

hashCode字符串數值化比較:都不保證

 

String:適用於少量的字符串操作的情況

StringBuilder:適用於單線程 速度快

StringBuffer:適用多線程 線程安全 很多方法可以帶有synchronized關鍵字

 

String對象本身放在堆裏面,已被虛擬機加載的類

String常量放在方法區的常量池 String s1 = "abc";String s2 = "abc";Java底層會優先在常量池中查找是否存在"abc",並引用

基本類型的變量數據和對象的引用(實例)都是放在棧裏面

Object類的equals方法的本質其實是和“==”一樣的,都是對比兩個引用指向的內存值;沒什麼用,需要改寫

 

static

主要用途:可以在沒有創建對象的前提下,通過類本身來調用static方法、變量

  1. static方法一般稱作靜態方法
    1. static方法不能調用非靜態方法,反過來可以
  2. static變量
    1. 靜態變量被所有的對象(實例)所共享,內存中只有一個副本
    2. 不允許修飾局部變量
  3. static靜態代碼塊優化程序性能
    1. 可以置於類中的任何地方,可以有多個static塊
    2. 只會在類加載時,按照static塊的順序執行,且只執行一次

 

JAVA值傳遞和引用傳遞:

值傳遞:基本型變量,傳遞變量副本,改變副本不影響原變量. 

引用傳遞【對象、數組】:對象型變量,傳遞對象地址副本,會改變對象

JAVA內存模型

所有變量都存儲在主內存,每條線程有各自的工作內存

工作內存保存了被線程用到的變量主內存中的拷貝,對變量的所有操作必須在工作內存中進行

不同線程之間無法直接互相訪問工作內存

 

面向對象

繼承:一個類如果繼承現有的類,則這個類將擁有被繼承類的所有非私有特性(屬性和操作)。這裏指的繼承包含:類的繼承和接口的實現。

封裝:使類具有獨立性和隔離性;保證類的高內聚。只暴露必須的屬性和操作給類外部或者子類。

多態:同一消息可以根據發送對象的不同而採用不同的行爲;

多態的三個要素:繼承、重寫、父類引用指向子類對象

方式一:重寫、重載:

重寫(Override):重寫發生在子類,子類重寫父類相同名稱的方法

重載(Overload):類中定義了一個以上相同名稱,但參數不同的方法

方式二:接口

方式三:抽象類和抽象方法

好處:

  1. 可替換性(substitutability):對已存在代碼具有可替換性
  2. 可擴充性(extensibility):對代碼具有可擴充性
  3. 接口性(interface-ability):超類可向子類提供了一個共同接口
  4. 靈活性(flexibility):靈活多樣的操作
  5. 簡化性(simplicity):簡化對代碼編寫和修改過程

 

簡述分派

重載和重寫的原理:確定執行哪個方法的過程就是方法分派

程序設計中,函數命名成名字相同,需要根據方法的參數、引用的類型等信息來確定到底應該執行哪個方法。

靜態分派是在編譯期完成的,方法重載相關,在編譯期確定,引用的類型、參數不同 (方法重載)

動態分派發生在執行階段,方法重寫相關,虛擬機根據執行方法的實際類型來判斷執行哪個目標方法(子類重寫父類)

Human man = new Man() ; man = new Woman() -》 woman say hello

 

靜態方法可以通過類名直接訪問,子類會繼承父類的靜態方法,但是不會複寫父類方法

如果子類聲明瞭和父類一樣的方法,子類方法隱藏,不是複寫

 

equals、==、 hashcode 區別

默認的“equals()”方法,等價於“==”方法,Object.java中,通過判斷兩個對象的地址是否相等。

我們通常會重寫equals()方法:若兩個對象的內容相等,則equals()方法返回true;否則,返回fasle。  

 

hashCode() 定義在JDK的Object.java中,hashCode() 在散列表中才有用

1)、如果兩個對象相等,那麼它們的hashCode()值一定相同。

           這裏的相等是指,通過equals()比較兩個對象時返回true。

2)、如果兩個對象hashCode()相等,它們並不一定相等。

           因爲在散列表中,hashCode()相等,即兩個鍵值對的哈希值相等,仍舊存在哈希衝突

 

 

java中的泛型擦除

Java中的泛型基本上都是在編譯器這個層次來實現的。

在生成的Java字節碼中是不包含泛型中的類型信息

類型擦除:使用泛型的時候加上的類型參數,在編譯器在編譯的時候去掉,成爲原生類型

public class TestF { public static void main(String[] args) { Class a1 = new ArrayList<Integer>().getClass(); Class a2 = new ArrayList<String>().getClass(); System.out.println(a1 == a2); } }

運行時會輸出 true,運行時會輸出 true

由於擦除機制,泛型不能用於顯示地引用運行時類型的操作之中,例如轉型、new表達式和instanceof操作。

若在泛型內部必須使用類型操作時,可以在運行時採用反射的方法將正在運行的類信息添加到泛型內部,這種方法稱爲補償

 

java是如何解決這個問題的呢?java編譯器是通過先檢查代碼中泛型的類型,然後再進行類型擦除

 

反射

動態獲取的信息以及動態調用對象的方法的功能,在運行狀態中,能知道類的所有屬性和方法,並調用;

 

jdk動態代理 vs cglib動態代理 Spring AOP底層實現;

代理模式:爲對象提供代理以控制對某個對象的訪問。預處理消息、過濾消息、轉發消息等

區別:

cglib動態代理:無需實現接口,針對類來實現代理,對指定目標 產生一個子類 通過方法攔截技術攔截所有父類方法的調用。

JDK動態代理:只能用於實現了接口的類

JDK動態代理:根據被代理的接口動態生成代理類的class文件,並加載運行 ;被代理對象每調用一次方法,則調用一次代理

public void helloTest1() {

// 調用代理對象的create方法,代理HelloService接口;因爲肯定是實現該接口的對象,所以直接傳接口 ;create名字自己定,可以是getProxy

HelloService helloService = rpcProxy.create(HelloService.class);

// 調用代理的方法,執行invoke

String result = helloService.hello("World");

System.out.println("服務端返回結果:"+ result);

}

  1. 代理類中:
    1. 通過反射獲取(UUID,類名,方法名,參數類型,參數),封裝成request對象
public <T> T create(Class<?> interfaceClass) {

    //返回代理對象
    return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),

    new Class<?>[] { interfaceClass }, new InvocationHandler() {

    public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {

        //正常執行方法
        //return method.invoke(T, args)
    
        //增強代碼 數據庫事務等
        RpcRequest request = new RpcRequest();
        request.setRequestId(UUID.randomUUID().toString());
        request.setClassName(method.getDeclaringClass().getName());
        request.setMethodName(method.getName());
        request.setParameterTypes(method.getParameterTypes());
        request.setParameters(args);

    });

}

 

cglib動態代理 spring core包已經引入

針對類來實現代理,對指定目標 產生一個子類 通過方法攔截技術攔截所有父類方法的調用。 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章