【Java基礎】Java基礎 面試高頻考點總結

總結的Java基礎部分高頻的知識(持續更新)

基礎

📌什麼是面向對象?有什麼特性?

面向對象就是把事物給對象化,包括其屬性和行爲。面向對象又三大特徵,分別是:封裝、繼承、多態。

  • 封裝: 就是指將對象的實現細節隱藏起來,通過公共的方法向外暴露除該對象的功能。比如我們通過將成員變量設置爲私有後通過提供的getter/setter方法來訪問。 使用封裝可以提高安全性,簡化操作,實現代碼的組件化。
  • 繼承: 繼承是一種提高代碼複用的重要手段,當一個子類繼承了父類時,就會擁有父類的成員和方法,同時子類還可以自己實現更多的功能。但繼承也是一種強耦合關係,父類改變子類也要相應的改變。
  • 多態: 多態就是同一類型對象在執行相同行爲的情況下會又不同表現形態。 實現多態就要首先實現繼承和重寫以及向上轉型。把不同的子類對象都當作父類來看,可以屏蔽不同子類對象之間的實現差異,可以降低類之間的耦合度,易於擴展。

📌面向對象和麪向過程的區別?

  • 面向過程是對於一個問題的解決過程的實現,重點強調的是功能的行爲和執行過程。而面向對象是站在某一個對象它可以實現的功能的角度,將不同對象可實現的功能組裝起來解決問題。
  • 面向過程採用的是自頂向下的設計方式,在設計初期就要考慮到每個模塊以及每個函數,所以它的系統的適應性和可擴展性較差。面向對象編程更加符合常規思維,可擴展性好,更加模塊化,所以也就擁有更低的耦合。

📌 Java和C++的區別?

  • 都是面向對象的編程語言,支持封裝、繼承和多態
  • 指針:Java不提供指針來直接訪問內存,程序更安全
  • 繼承:Java是單繼承,C++是多繼承,但Java可以實現多個接口
  • 內存:Java有自動內存管理機制,不需要程序員手動釋放內存

📌JDK、JRE、JVM分別是什麼?

  • JVM:Java虛擬機,是運行Java字節碼的虛擬機,JVM有對於不用系統和的特定實現,以保證可以使用相同的字節碼得出相同的結果。Java虛擬機不受平臺的約束,實現了“一次編譯,到處運行”
  • JDK : Java開發工具包,包含了Java虛擬機、Java程序設計語言、Java API類庫這三部分
  • JRE:Java運行時環境(Java Runtime Environment),包含了JavaSE API 子集以及Java虛擬機這兩部分

📌Java有哪些數據類型?

Java中有兩類數據類型,分別是:基本數據類型和引用數據類型

  • 基本數據類型:包括byte(1)、short(2)、int(4)、long(8)、double(8)、float(4)、char(2)、boolean(1)
  • 引用數據類型:包括類、接口、數組等

📌String類爲什麼設計爲不可變的?怎麼實現的?

  • 將String對象設置爲不可變有很大的好處。
    (1)首先,由於設計爲不可變,所以可以實現字符串常量池,就可以在運行時節約很多堆上的空間。
    (2)String設計爲不可變也大大提高了安全性,由於很多重要信息比如數據庫的用戶名密碼、網絡中的URL和主機名等都是使用String存儲,如果String可變,就會引起很多安全問題。
    (3)在多線程下時安全的,由於設計爲不可變,所以即使多個線程共享一個字符串,也不會造成多線程不同步的問題
    (4)因爲字符串時是不可變的,所以它創建的時候hashcode就被緩存了,不需要重新計算。這就使得字符串很適合作爲Map中的鍵
  • 在實現上,首先String被final關鍵字修飾,就不能被繼承以至於修改。其次String的底層是使用一個char[]實現的,而這個數組也使用了final修飾保證了其不可變性,另外對於String的所有方法都沒有將原有的String對象暴露出來,所以別人也就無從修改。至此就保證了String的不可變。

📌 字符串的拼接方式有哪些?它們有什麼區別?

字符串的拼接方式有三種:

  • 使用+ 直接拼接,由於String是不可變的對象,所以不會被修改,每次使用+都會在堆中再創建一個新的對象,使用+ 是線程安全的方式,但效率較低
  • 使用StringBuilder 的append方法來拼接字符串,它的底層數組沒有使用final修飾,所以是可變的。另外它是線程不安全的,但拼接效率高
  • 使用StringBuffer 的append方法來拼接字符串,StringBuilder的所以公開方法都是使用synchronized關鍵字修飾的,所以它是線程安全的。

📌 final,finally和finalize區別是什麼?

它們各自有各自的用途:

  • final :是一個修飾類、方法、變量的關鍵字。使用final修飾的類是不可以被繼承的,使用final修飾的方法是不可以被重寫的,使用final修飾的變量引用不可以修改(引用對象的內容可以修改)
  • finally:是一個使用在異常機的try-catch代碼塊中的關鍵字。finally代碼塊中的代碼無論是否發生異常都會執行,通常用在釋放資源中
  • finalize:是Object類中的一個方法,每一個對象都有一個finalize方法,作用是在垃圾回收前調用一次,且只調用一次

📌 == 、equals 、hashCode三者區別和聯繫是什麼?

  • == 是一個操作符,如果比較的是基本數據類型,那麼就是比較兩個數值是否相同;如果是引用數據類型,比較的就是兩個變量是否是同一個對象
  • equals是Object類的一個方法,如果沒有進行重寫,那麼比較規則與==相同,如果重寫,那就按照重新寫的方法比較。一般情況下是將其重寫爲比較兩個對象的對應內容是否相同
  • hashCode也是Object類的一個方法,也是用於判斷兩個對象是否相等,對於每一個對象hashCode會返回一個其在內存中的地址轉換的一個int值。如果沒有進行重寫,那麼兩個不同對象的hashCode一定是不相同的。一般在重寫equals方法時就要重寫hashCode方法以達到對於兩個對象:
    (1)equals爲真,hashCode一定相同
    (2)equals爲假,hashCode 可能相同可能不同
    (3)hashCode相同,equals可能爲真可能爲假
    (4)hashCode不同,equals一定爲假

📌 方法重載和重寫有什麼區別?

  • 方法重載是在同一個類中的具有不同的參數列表的同名方法(無關返回值類型);而方法重寫是在父類和子類中,一個子類具有和父類相同的方法,包括返回值、參數類型
  • 重載的返回值類型和權限修飾符、異常拋出類型沒有要求;而重寫要求返回值類型要小於等於父類被重寫方法的類型,修飾符權限大於等於父類被重寫方法權限修飾符,拋出的異常類型小於等於父類被重寫方法拋出的異常類型
  • 在方法重載關係中,根據調用時的實參列表與形參列表來選擇方法體;而方法重寫調用方法體是根據引用的類型來決定

📌 抽象類和接口有什麼區別?

  • 接口只能定於public static final 修飾的變量;抽象類可以定義普通對象
  • 接口在JDK1.8前只能定義public abstract 修飾的方法,1.8可以定義默認方法和靜態方法;抽象類沒有限制
  • 接口中只能有方法的定義,不能有實現;抽象類可以有方法的實現。
  • 接口可以被多實現,抽象類只能被單繼承

📌 什麼時候使用抽象類?什麼時候使用接口?

接口被運用於實現比較常用的功能便於日後的維護或者添加刪除方法;而抽象類更傾向於充當公共類的角色,不適用於日後重新對裏面的代碼進行修改。如果知道某個類應該成爲基類,那麼第一選擇應該是讓它成爲一個接口,只有在必須要有方法定義和成員變量的時候,才應該選擇抽象類。

📌 static關鍵字的作用有哪些?

static作爲關鍵字可以修飾成員變量、成員方法、代碼塊、內部類,主要的就是實現與對象的解綁

  • 修飾成員變量: 被static修飾的成員變量屬於類,在內存中只有一份,所有對類的實例對象的該成員屬性都指向一個內存空間,所以就可以使用 類名.成員變量 直接訪問
  • 修飾成員方法:被static修飾的靜態方法直屬於類,不需要創建對象就可以直接調用。在static方法中不可使用this、super關鍵字,也不可以調用非靜態成員變量和方法
  • 修飾代碼塊:static修飾的代碼塊即靜態代碼塊,直屬於類,在JVM加載類時就執行,如果有多個就按順序執行,通常用來初始化靜態變量,而且只會被執行一次
  • 修飾內部類:static修飾內部類即靜態內部類,它可以不依賴外部類的實例對象而被實例化,不能訪問外部類的普通成員變量,只能訪問外部類中的靜態成員變量和靜態方法

📌 內部類有什麼作用?有哪些分類?

內部類可以有更好的封裝性,有更多的權限修飾符,封裝性可以得到更多的控制。內部類有四種不同的內部類:靜態內部類、成員內部類、局部內部類、匿名內部類

  • 靜態內部類:由static修飾,屬於外部類本身,只加載一次。不能訪問外部類的普通成員變量,只能訪問外部類中的靜態成員變量和靜態方法
  • 成員內部類:屬於外部類的每個對象,隨對象一起加載,不可以定義靜態成員和方法,可以訪問外部類的所有內容
  • 局部內部類:定義在方法、構造器、代碼塊、循環中。只能定義實例成員變量和實例方法,作用也是隻在局部代碼塊中
  • 匿名內部類:沒有名字的局部內部類,匿名內部類會立即創建一個匿名內部類的對象返回,對象類型相當於當前new的類的子類類型

📌 泛型和泛型擦除是什麼?

泛型就是參數化類型,在創建對象或調用方法時才明確參數的具體類型。
泛型擦除就是在編譯階段採用泛型加上的類型參數,會被編譯器在編譯時去掉,這個過程就被稱爲類型擦除,因此泛型主要用於編譯階段,在編譯後生成的Java字節代碼文件中不包含泛型中的類型信息。

📌泛型標記的規範有什麼?

  • E:值Element,在集合中使用,表示在集合中存放的元素。
  • T:指Type,表示Java類,包括基本的類以及自定義類。
  • K:指Key,表示鍵,例如Map集合中的Key。
  • V:指Value,表示值,例如Map集合中的Value。
  • N:指Number,表示數值類型。
  • ?:表示不確定的Java類型。

📌異常有哪些分類?出現的原因是什麼?

  • Throwable 是所有錯誤和異常的父類,Throwable分爲Error和Exception
  • Error 是指Java程序運行錯誤,出現Error通常是因爲系統的內部錯誤或資源耗盡導致的,Error不能在運行過程中被動態處理,如果程序運行中出現了Error,系統只能記錄錯誤原因並安全終止
  • Exception指Java程序運行異常,即運行中發生了不期望的情況,分爲RuntimeException和checkedException。其中RuntimeException指在Java虛擬機正常運行期間拋出的異常,可以被捕獲並處理,例如空指針異常,數組越界。
  • 其中RuntimeException和Error統稱爲非受查異常,checkedException爲受查異常需要我們進行處理

📌異常的處理方式有哪些?

異常通常由兩種處理方式:

  • 直接拋出異常給調用者,由調用者根據情況處理。可以使用throws作用在方法上,也可以throw作用在方法內
  • 也可以使用try/catch代碼塊進行異常捕獲處理,try代碼塊中的代碼發生異常會被catch代碼塊捕獲進行處理,還可以添加finally代碼塊,無論是否發生異常都會執行,一般用於釋放資源。

📌throw 和 throws 有什麼區別?

  • throw關鍵字是用來明確處理程序的異常
  • throws是用於在方法上表明該異常不能被處理

集合

📌說說常見的集合有哪些?

Map接口和Collection接口是所有集合框架的父接口:

  • Collection 接口的子接口包含:List、Set和Queue
  • Map接口的實現類有:HashMap、TreeMap、ConcurrentHashMap等
  • Set 是唯一的且無序的,它的實現類有:HashSet、TreeSet、LinkedHashSet(有序的HashSet)
  • List 是有序的,它的實現類主要有:ArrayList、LinkedList、Stack以及Vector

📌Collection和Collections有什麼區別?

  • Collection是一個集合接口,它包含List、Set、Queue等
  • Collections是一個類,它爲Collection對象提供了很多便捷的方法,比如sort()

📌ArrayList 和 Vector 有什麼區別?

首先它們兩個都實現了List接口,都是有序集合,他們倆的區別主要有:

  • 同步性:Vector是線程安全的,它的實現方法中使用了synchronized關鍵字,而ArrayList是線程不安全的,它的方法並沒有保證線程同步。
  • 數據增長:ArrayList和Vector 都有一個初始的容量大小,當插入元素的數量到達容量大小時就會進行擴容,ArrayList一次擴容爲原來的1.5倍,Vector一次擴容爲原來的2倍

📌ArrayList 和 LinkedList 有什麼區別?

  • 底層實現:ArrayList底層是採用數組實現的,內存是連續的,需要考慮容量;LinkedList底層是雙向鏈表實現的,內存不一定是連續的,不需要考慮容量
  • 效率:由於底層實現方式的不同,ArrayList的隨機訪問下標處元素效率更好,而LinkedList在插入和刪除數據時效率更好

📌Array和ArrayList有什麼區別?

  • Array 可以存儲基本類型和引用類型,ArrayList只能存儲引用數據類型
  • Array 的大小是固定不變的,ArrayList的大小是可以變化的
  • ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

📌List、Set、Map有什麼區別?

  • List是保證了有序,同時也是可以重複的有索引的集合,它繼承了Collection集合的全部功能,可使用迭代器遍歷,也可使用索引遍歷
  • Set 是一個無序的,保證不可重複的結合。Set的實現類中LinkedHashSet和TreeSet是有序的
  • Map是一個無序的,以鍵值對的形式存儲的集合

📌瞭解HashMap嗎?

  • HashMap是實現了Map接口的一個鍵值對形式存儲對象的一個集合,它採用散列方式根據元素的Key散列到不同位置存儲,它允許鍵爲null。
  • 在JDK1.8之前,HashMap的底層是由數組+鏈表實現的,在JDK1.8時Hash Map的底層變味了數組+鏈表/紅黑樹實現,加入紅黑樹結構提升了查詢效率。鏈表形式和數型結構都有對應的改變的閾值
  • HashMap的默認初始容量爲16,負載因子是0.75,默認擴容閾值是16*0.75,如果大於擴容閾值就會進行擴容再散列操作,每次擴容的大小是之前的2倍。HashMap的長度都是2的次冪。
  • 如果發生衝突,HashMap採用鏈地址將衝突的數據加入鏈表中,在JDK1.8以前採用的是頭插的方式,但這樣會導致發生死循環,所以在1.8時優化成了尾插的方式,解決了死循環的問題
  • 另外,Hash Map是線程不安全的,在多線程情況下我們可以使用Collections.synchronizedMap(Map)、Hashtable、ConcurrentHashMap

📌HashMap的put方法具體流程是怎麼樣的?

  • 首先再存放數據前將對象hashCode的高16位和低16位進行異或得到對象的hash值,再將hash值和Map的長度-1進行與運算得到要插入的下標index的值
  • 使用高16位和低16位的異或運算可以保證儘可能多的位數參與運算,可以使得鍵值儘可能分散降低hash衝突。使用hash值和Map長度-1進行與運算可以控制下標的範圍在數組長度以內,這也是爲什麼Map長度是2的冪次方。
  • 算出索引後,如果該下標處沒有元素,就直接插入。如果發生衝突,判斷如果是樹結構就調用紅黑樹的插入方式插入節點,如果是鏈表結構,遍歷鏈表,如果找到Key 相同的元素就覆蓋該節點的value值,如果沒發現相同的Key就遍歷到尾部插入該節點。
  • 插入後判斷如果超過轉化爲樹的閾值,那就把鏈表結構轉化爲樹型結構。如果元素數量達到擴容閾值就進行擴容

📌Hash Map的resize方法具體是怎樣的?

  • 當HashMap中以及使用的容量達到總容量*負載因子就會開始擴容,擴容時會將數組容量增加至之前的兩倍,如果新的容量小於默認值16,就設置爲16,還會將閾值設置爲之前的倆倍。如果大於最大的整型,就設置爲最大值並返回。
  • 然後對每一個節點在新的hashMap中再散列,遍歷數組中的每一個節點,爲null就跳過。否則節遍歷每一個節點再次計算hash值和新的下標放入。如果是樹型結構再散列後紅黑樹太小就退化爲鏈表形式。

📌瞭解HashTable嗎?

Hashtable是與HashMap一樣的以鍵值對存儲元素的集合,它不允許鍵和值爲null。基本實現以及內部方法都與HashMap相同,但它每一個方法都添加了synchronized關鍵字,所以HashTable是線程安全的一個結合。

📌HashMap和HashTable的區別?

  • 線程安全性不同: HashMap不是線程安全的,Hashtable是線程安全的。
  • 爲null的要求不同:HashMap 中null 可以作爲Key,而Hashtable中不可以。
  • 實現方式不同:Hashtable 繼承了 Dictionary類,而 HashMap 繼承的是 AbstractMap 類。
  • 初始化容量不同:HashMap 的初始容量爲:16,Hashtable 初始容量爲:11,兩者的負載因子默認都是:0.75。
  • 底層實現不同:HashMap底層採用 數組+鏈表/紅黑樹 來實現的,HashTable採用 數組+鏈表實現。
  • 擴容機制不同:HashMap 擴容規則爲當前容量2倍,Hashtable 擴容規則爲當前容量2倍 + 1。
  • 迭代器不同:HashMap 中的 Iterator 迭代器是 fail-fast 的,而 Hashtable 是fail—safe 的

📌瞭解ConcurrentHashMap嗎?

  • ConcurrentHashMap 結合了 HashMap 和 HashTable 二者的優勢。不僅是線程安全的,而且減少了鎖的力度提升了整體的性能。
  • 在JDK1.7中,ConcurrentHashMap採用Segment分段鎖 + HashEntry數組的方式進行實現,是基於減小鎖粒度的思想來保證線程安全的。一個Segment就是一個鎖,裏面包含多個 HashEntry數組,Segment的數量就是鎖的併發度,默認是16,當修改 HashEntry數組中的元素時,必須獲取對應的Segment鎖。
  • 在JDK1.8中,ConcurrentHashMap對其進行優化,將原本的數組+鏈表的形式改爲數組+鏈表/紅黑樹的形式,提升了查詢效率。同時啓用分段鎖機制而是用CAS機制和Synchronized關鍵字保證線程安全,因爲Synchronized關鍵字經過優化後,性能已經得到了很大的改善
  • 不管是JDK1.7還是1.8,讀操作都沒有加鎖,而是使用volatile關鍵字修飾每個節點的Value值和next值,達到多線程之間的可見性
  • 同時使用了fail-safe迭代器,創建迭代器後可對元素進行更新

📌HashSet是如何去重的?

  • 對於基本類型的包裝類,可以按照值進行比較
  • 對於引用數據類型,會首先比較HashCode()返回值是都相同,如果不同再使用equals()進行比較,如果不同才進行插入

📌迭代器是什麼?

迭代器是實現了Iterator接口的一個用於遍歷集合元素的指針。主要有三個方法:

  • hasNext() : 判斷集合中是都還有元素
  • next() : 獲取集合的下一個元素
  • remove() : 刪除集合中迭代器上一個返回的元素

📌什麼是fail-fast?什麼是fail-safe呢?

  • fail-fast是java集合的一種錯誤檢測機制,不允許除了迭代器本身的操作外的其它操作對集合結構進行遍歷時修改(增刪元素),如果修改就會拋出異常。它是根據modCount 變量來實現的,只要對集合進行增加刪除操作,modCount就會+1,而每一遍歷時都會檢測modCount的值,如果發現改變,就排除異常。java.util包下的集合類都是快速失敗的,不能在多線程下發生併發修改。
  • fail-safe 是就是安全的修改集合結構的操作,使用了fail-safe機制後,會拷貝一份新的集合數據來進行遍歷修改,不會拋出異常。但它的缺點是會增加內存開銷,以及不能確保遍歷是最新內容。java.util.concurrent包下的容器都是安全失敗
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章