1. 面向對象三大特性相關
1.1 三大特性
- 封裝:對抽象的事物抽象化成一個對象,并對其對象的屬性私有化,同時提供一些能被外界訪問屬性的方法;
- 繼承:子類擴展新的數據域或功能,并復用父類的屬性與功能,單繼承,多實現;
- 多態:通過繼承(多個?類對同??法的重寫)、也可以通過接?(實現接?并覆蓋接?);
1.2 Java與C++區別
- c++支持多繼承,并且有指針的概念,由程序員自己管理內存;
- Java是單繼承,可以用接口實現多繼承,Java不提供指針來直接訪問內存,程序內存更加安全,并且Java有JVM?動內存管理機制,不需要程序員?動釋放??內存。
1.3 多態實現原理
多態的底層實現是動態綁定,即在運行時才把方法調用與方法實現關聯起來。
靜態綁定與動態綁定:
- 一種是在編譯期確定,被稱為靜態分派,比如方法的重載;
- 一種是在運行時確定,被稱為動態分派,比如方法的覆蓋(重寫)和接口的實現。
多態的實現: 虛擬機棧中會存放當前方法調用的棧幀(局部變量表、操作棧、動態連接、返回地址)。多態的實現過程,就是方法調用動態分派的過程,如果子類覆蓋了父類的方法,則在多態調用中,動態綁定過程會首先確定實際類型是子類,從而先搜索到子類中的方法。這個過程便是方法覆蓋的本質。
1.4 static和final關鍵字
-
static:可以修飾屬性、方法
- 修飾屬性: 類級別屬性,所有對象共享一份,隨著類的加載而加載(只加載一次),先于對象的創建;可以使用類名直接調用。
- 修飾方法: 隨著類的加載而加載;可以使用類名直接調用;靜態方法中,只能調用靜態的成員,不可用this;
-
final: 主要?在三個地?:變量、?法、類。
- 修飾變量: 如果是基本數據類型的變量,則其數值一旦在初始化之后便不能更改;如果是引?類型的變量,則在對其初始化之后便不能再讓其指向另?個對象。
- 修飾方法: 把?法鎖定,以防任何繼承類修改它的含義(重寫);類中所有的private?法都隱式地指定為final。
- 修飾類: final修飾類時,表明這個類不能被繼承。final類中的所有成員?法都會被隱式地指定為final?法。
注:想要讓一個類不能被繼承的兩種方式:
- 使用final關鍵字修飾;
- 私有化構造器,但對內部類無效;
1.5 抽象類和接口
抽象類: 包含抽象方法的類,即使用abstract修飾的類;抽象類只能被繼承,所以不能使用final修飾,抽象類不能被實例化;
接口: 接口是一個抽象類型,是抽象方法的集合,接口支持多繼承,接口中定義的方法,默認是public abstract修飾的抽象方法;
相同點:
-
都不能被實例化;
-
都可以定義抽象方法,子類/實現類必須覆寫這些抽象方法;
不同點:
抽象類 | 接口 | |
---|---|---|
構造方法 | 有 | 沒有 |
普通方法 | 可以包含 | 只能是public abstract修飾抽象方法(Java8之后可以) |
成員變量 | 可以定義各種類型 | 只能是public static final修飾的靜態常量 |
繼承 | 單繼承 | 多繼承 |
使用場景:
抽象類:既想約束子類具有共同的行為(但不在乎其如何實現),又想擁有缺省的方法,又能擁有實例變量;
接口:約束多個實現類具有統一的行為,但是不在乎每個實現類如何具體實現;實現類中各個功能之間可能沒有任何聯系;
1.6 反射原理以及使用場景
Java反射: 是指在運行狀態中,對于任意一個類都能夠知道這個類所有的屬性和方法;并且都能夠調用它的任意一個方法;
反射原理: 反射首先是能夠獲取到Java中的反射類的字節碼,然后將字節碼中的方法,變量,構造函數等映射成相應的Method、Filed、Constructor等類。
// 如何得到Class的實例?
YourClassName.class //本質是一份字節碼
使用場景:
- 開發通用框架: 反射最重要的用途就是開發各種通用框架。很多框架(比如 Spring)都是配置化的(比如通過XML文件配置JavaBean、Filter等),為了保證框架的通用性,需要根據配置文件運行時動態加載不同的對象或類,調用不同的方法。
- 動態代理: 在切面編程(AOP)中,需要攔截特定的方法,通常,會選擇動態代理方式。這時,就需要反射技術來實現了。
JDK:spring默認動態代理,需要實現接口;
CGLIB:通過asm框架序列化字節流,可配置,性能差; - 自定義注解: 注解本身僅僅是起到標記作用,它需要利用反射機制,根據注解標記去調用注解解釋器,執行行為。
2. 數據結構
2.1 ArrayList
- 底層基于數組實現,支持對元素進行快速隨機訪問,適合隨機查找和遍歷,不適合插入和刪除(因為從ArrayList的中間位置插入或者刪除元素時,需要對數組進行復制和移動,代價比較高)。
- 默認初始大小為10,當數組容量不夠時,會觸發擴容機制(擴大到當前的1.5倍),需要將原來數組的數據復制到新的數組中。
2.2 LinkedList:
- 底層基于雙向鏈表實現,適合數據的動態插入和刪除;
- 內部提供了List接口中沒有定義的方法,用于操作表頭和表尾元素,可以當作堆棧、隊列和雙向隊列使用。(JDK官方推薦使用基于linkedList的Deque進行堆棧操作)。
ArrayList與LinkedList區別:
都是線程不安全的,ArrayList適用于查找的場景,LinkedList適用于增加、刪除多的場景。
怎么實現線程安全?
可以使用Collections.synchronizedList(List list)函數返回一個線程安全的ArrayList集合(比如:CopyOnWriteArrayList)。
2.3 List快速遍歷和安全失敗
- 遍歷元素的3種方式(以刪除元素為例)
// 1. 普通for循環遍歷
for(int i=0; i < list.size(); i++) { if(list.get(i) == 5) list.remove(i);
}// 2. 迭代遍歷
Iterator<Integer> it = list.iterator();
while(it.hasNext()) { Integer value = it.next(); if(value == 5) { list.remove(value); }
}// 3. foreach遍歷
for(Integer i:list) { if(i==3) list.remove(i);
}
- 遍歷過程中的失敗處理方式
- fail—fast(快速失敗): 當異常產生時,直接拋出異常,程序終止。fail-fast主要是體現在當我們在遍歷集合元素的時候,經常會使用迭代器,但在迭代器遍歷元素的過程中,如果集合的結構(modCount)被改變的話,就會拋出異常ConcurrentModificationException,防止繼續遍歷。這就是所謂的快速失敗機制。
- fail—safe(安全失敗): 采用安全失敗機制的集合容器,在遍歷時不是直接在集合內容上訪問的,而是先復制原有集合內容,在拷貝的集合上進行遍歷。由于在遍歷過程中對原集合所作的修改并不能被迭代器檢測到,所以不會觸發ConcurrentModificationException。java.util.concurrent包下的容器都是安全失敗,可以在多線程下并發使用,并發修改。
缺點:基于拷貝內容的優點是避免了ConcurrentModificationException,但同樣地,迭代器并不能訪問到修改后的內容,即:迭代器遍歷的是開始遍歷那一刻拿到的集合拷貝,在遍歷期間原集合發生的修改迭代器是不知道的。
2.4 HashMap
可以看這篇文章,基本涵蓋所有常見面試考點
- 底層數據結構:
- JDK1.7 : 數組和鏈表 結合在?起使?,也就是鏈表散列。如果相同的話,直接覆蓋,不相同就通過拉鏈法解決沖突。擴容翻轉時順序不一致使用頭插法會產生死循環,導致cpu100%
- JDK1.8 :采用了數組+鏈表+紅黑樹;當鏈表?度?于閾值(默認為 8-泊松分布),數組的?度大于 64時,鏈表將轉化為紅?樹,以減少搜索時間。(解決了tomcat臭名昭著的url參數dos攻擊問題)
- 擴容情況:默認的負載因子是0.75,如果數組中已經存儲的元素個數大于數組長度的75%,將會引發擴容操作。
- put操作流程
- 哈希函數:通過hash函數(優質因子31循環累加)先拿到key的hashcode,是一個32位的值,然后讓hashcode的高16位和低16位進行異或操作。該函數也稱為擾動函數,做到盡可能降低hash碰撞,通過尾插法進行插入。
- 容量為什么始終都是2^N: 先做對數組的?度取模運算,得到的余數才能?來要存放的位置也就是對應的數組下標。這個數組下標的計算?法是“ (n - 1) & hash ”。(n代表數組?度)。方便數組的擴容和增刪改時的取模。
2.5 ConcurrentHashMap
可以通過ConcurrentHashMap和Hashtable來實現線程安全;Hashtable 是原始API類,通過synchronize同步修飾,效率低下;ConcurrentHashMap通過分段鎖實現,效率較比Hashtable要好。
ConcurrentHashMap的底層實現:
- JDK1.7的ConcurrentHashMap 底層采? 分段的數組+鏈表 實現;采用 分段鎖(Sagment) 對整個桶數組進?了分割分段(Segment默認16個),每?把鎖只鎖容器其中?部分數據,多線程訪問容器?不同數據段的數據,就不會存在鎖競爭,提?并發訪問率
- JDK1.8的 ConcurrentHashMap采?的數據結構跟HashMap1.8的結構?樣,數組+鏈表/紅?樹;摒棄了Segment的概念,?是直接? Node 數組+鏈表+紅?樹的數據結構來實現,通過并發控制synchronized和CAS來操作保證線程的安全。
2.6 序列化和反序列化
序列化的意思就是將對象的狀態轉化成字節流,以后可以通過這些值再生成相同狀態的對象。對象序列化是對象持久化的一種實現方法,它是將對象的屬性和方法轉化為一種序列化的形式用于存儲和傳輸。反序列化就是根據這些保存的信息重建對象的過程。
序列化: 將java對象轉化為字節序列的過程。
反序列化: 將字節序列轉化為java對象的過程。
優點:
- 實現了數據的持久化,通過序列化可以把數據永久地保存到硬盤上(通常存放在文件里)Redis的RDB
- 利用序列化實現遠程通信,即在網絡上傳送對象的字節序列。Google的protoBuf。
反序列化失敗的場景: 序列化ID:serialVersionUID不一致的時候,導致反序列化失敗。
2.7 String
String使用數組存儲內容,數組使用final修飾,因此String定義的字符串的值也是不可變的。
StringBuffer對方法加了同步鎖,線程安全,效率略低于StringBuilder。
3. Java異常體系
Throwable是Java語言中所有錯誤或異常的超類。下一層分為Error 和Exception。
3.1 Error
是指java運行時系統的內部錯誤和資源耗盡錯誤。應用程序不會拋出該類對象。如果出現了這樣的錯誤,除了告知用戶,剩下的就是盡力使程序安全的終止。
3.2 Exception
包含:RuntimeException、CheckedException。
-
編程錯誤
- 語法錯誤(也稱編譯錯誤)是在編譯過程中出現的錯誤,由編譯器檢查發現語法錯誤。
- 邏輯錯誤指程序的執行結果與預期不符,可以通過調試定位并發現錯誤的原因。
- 運行錯誤是引起程序非正常終端的錯誤,需要通過異常處理的方式處理運行錯誤。
-
**RuntimeException:**運行時異常,程序應該從邏輯角度盡可能避免這類異常的發生。如NullPointerException、ClassCastException ;
-
**CheckedException:**受檢異常,程序使用trycatch進行捕捉處理;如IOException、SQLException、NotFoundException;
4. 其他
4.1 構造方法
- 可以被重載,只有當類中沒有顯性聲明任何構造方法時,才會有默認構造方法。
- 沒有返回值,構造方法的作用是創建新對象。
4.2 初始化塊
- 靜態初始化塊的優先級最高,會最先執行,在非靜態初始化塊之前執行。
- 靜態初始化塊會在類第一次被加載時最先執行,因此在main方法之前。
4.3 This
- 代表當前對象的引用。當前對象指的是調用類中的屬性或方法的對象。
- 不可以在靜態方法中使用。靜態方法不依賴于類的具體對象的引用。
4.4 重寫和重載的區別
- 重載:指在同一個類中定義多個方法,這些方法名稱相同,簽名不同。
- 重寫:指在子類中的方法的名稱和簽名都和父類相同,使用override注解。
4.5 Object類方法**
- toString默認是個指針,一般需要重寫;
- equals比較對象是否相同,默認和==功能一致;
- hashCode散列碼,equals則hashCode相同,所以重寫equals必須重寫hashCode;
- finalize用于垃圾回收之前做的遺囑,默認空,子類需重寫;
- clone深拷貝,類需實現cloneable的接口;
- getClass反射獲取對象元數據,包括類名、方法;
- notify、wait用于線程通知和喚醒;