Java 基礎 知識點整理

1. 封裝 繼承 多態

封裝
封裝把一個對象的屬性私有化,同時提供一些可以被外界訪問的屬性的方法,如果屬性不想被外界訪問,我們大可不必提供方法給外界訪問。但是如果一個類沒有提供給外界訪問的方法,那麼這個類也沒有什麼意義了。

繼承
繼承是使用已存在的類的定義作爲基礎建立新類的技術,新類的定義可以增加新的數據或新的功能,也可以用父類的功能,但不能選擇性地繼承父類。通過使用繼承我們能夠非常方便地複用以前的代碼。

關於繼承如下 3 點請記住:

  1. 子類擁有父類對象所有的屬性和方法(包括私有屬性和私有方法),但是父類中的私有屬性和方法子類是無法訪問,只是擁有。
  2. 子類可以擁有自己屬性和方法,即子類可以對父類進行擴展。
  3. 子類可以用自己的方式實現父類的方法。

多態
所謂多態就是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編程時並不確定,而是在程序運行期間才確定,即一個引用變量到底會指向哪個類的實例對象,該引用變量發出的方法調用到底是哪個類中實現的方法,必須在由程序運行期間才能決定。

在 Java 中有兩種形式可以實現多態:繼承(多個子類對同一方法的重寫)和接口(實現接口並覆蓋接口中同一方法)。

2. 值傳遞

Java 程序設計語言總是採用按值調用。也就是說,方法得到的是所有參數值的一個拷貝,也就是說,方法不能修改傳遞給它的任何參數變量的內容。

  1. 一個方法不能修改一個基本數據類型的參數(即數值型或布爾型)。
  2. 一個方法可以改變一個對象參數的狀態。
  3. 一個方法不能讓對象參數引用一個新的對象。

3. 自動裝箱與拆箱

裝箱:將基本類型用它們對應的引用類型包裝起來;
拆箱:將包裝類型轉換爲基本數據類型;

4. 深拷貝與淺拷貝

淺拷貝:對基本數據類型進行值傳遞,對引用數據類型進行引用傳遞般的拷貝,此爲淺拷貝。
深拷貝:對基本數據類型進行值傳遞,對引用數據類型,創建一個新的對象,並複製其內容,此爲深拷貝。

淺拷貝讓類實現 Cloneable 接口,調用super.clone();
深拷貝

  1. 讓每個引用類型屬性內部都重寫clone() 方法,調用super.clone()
  2. 利用序列化,序列化產生的是兩個完全獨立的對象,所有無論嵌套多少個引用類型,序列化都是能實現深拷貝的。

5. 接口與抽象類

接口的方法默認是 public,所有方法在接口中不能有實現(Java 8 開始接口方法可以有默認實現),而抽象類可以有非抽象的方法。
接口中除了 static、final 變量,不能有其他變量,而抽象類中則不一定。
一個類可以實現多個接口,但只能實現一個抽象類。接口自己本身可以通過 extends 關鍵字擴展多個接口。
接口方法默認修飾符是 public,抽象方法可以有 public、protected 和 default 這些修飾符(抽象方法就是爲了被重寫所以不能使用 private 關鍵字修飾!)。

從設計層面來說,抽象是對類的抽象,是一種模板設計,而接口是對行爲的抽象,是一種行爲的規範。

總結一下 jdk7~jdk9 Java 中接口概念的變化:

  1. 在 jdk 7 或更早版本中,接口裏面只能有常量變量和抽象方法。這些接口方法必須由選擇實現接口的類實現。
  2. jdk8 的時候接口可以有默認方法和靜態方法功能。
  3. Jdk 9 在接口中引入了私有方法和私有靜態方法。

6. 成員變量與局部變量

  1. 從語法形式上看:成員變量是屬於類的,而局部變量是在方法中定義的變量或是方法的參數;成員變量可以被 public,private,static 等修飾符所修飾,而局部變量不能被訪問控制修飾符及 static 所修飾;但是,成員變量和局部變量都能被 final 所修飾。
  2. 從變量在內存中的存儲方式來看:如果成員變量是使用static修飾的,那麼這個成員變量是屬於類的,如果沒有使用static修飾,這個成員變量是屬於實例的。對象存於堆內存,如果局部變量類型爲基本數據類型,那麼存儲在棧內存,如果爲引用數據類型,那存放的是指向堆內存對象的引用或者是指向常量池中的地址。
  3. 從變量在內存中的生存時間上看:成員變量是對象的一部分,它隨着對象的創建而存在,而局部變量隨着方法的調用而自動消失。
  4. 成員變量如果沒有被賦初值:則會自動以類型的默認值而賦值(一種情況例外:被 final 修飾的成員變量也必須顯式地賦值),而局部變量則不會自動賦值。

7. final

final 關鍵字主要用在三個地方:變量、方法、類。

  1. 對於一個 final 變量,如果是基本數據類型的變量,則其數值一旦在初始化之後便不能更改;如果是引用類型的變量,則在對其初始化之後便不能再讓其指向另一個對象。
  2. 當用 final 修飾一個類時,表明這個類不能被繼承。final 類中的所有成員方法都會被隱式地指定爲 final 方法。
  3. 使用 final 方法的原因有兩個。第一個原因是把方法鎖定,以防任何繼承類修改它的含義;第二個原因是效率。在早期的 Java 實現版本中,會將 final 方法轉爲內嵌調用。但是如果方法過於龐大,可能看不到內嵌調用帶來的任何性能提升(現在的 Java 版本已經不需要使用 final 方法進行這些優化了)。類中所有的 private 方法都隱式地指定爲 final。

8. == 與 equals()

== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象(基本數據類型==比較的是值,引用數據類型==比較的是內存地址)。
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況:

情況 1:類沒有覆蓋 equals() 方法。則通過 equals() 比較該類的兩個對象時,等價於通過“==”比較這兩個對象。
情況 2:類覆蓋了 equals() 方法。一般,我們都覆蓋 equals() 方法來比較兩個對象的內容是否相等;若它們的內容相等,則返回 true (即,認爲這兩個對象相等)。

9. hashcode

爲什麼要有 hashCode ?
我們先以“HashSet 如何檢查重複”爲例子來說明爲什麼要有 hashCode: 當你把對象加入 HashSet 時,HashSet 會先計算對象的 hashcode 值來判斷對象加入的位置,同時也會與該位置其他已經加入的對象的 hashcode 值作比較,如果沒有相符的 hashcode,HashSet 會假設對象沒有重複出現。但是如果發現有相同 hashcode 值的對象,這時會調用 equals()方法來檢查 hashcode 相等的對象是否真的相同。如果兩者相同,HashSet 就不會讓其加入操作成功。如果不同的話,就會重新散列到其他位置。(摘自我的 Java 啓蒙書《Head first java》第二版)。這樣我們就大大減少了 equals 的次數,相應就大大提高了執行速度。

通過我們可以看出:hashCode() 的作用就是獲取哈希碼,也稱爲散列碼;它實際上是返回一個 int 整數。這個哈希碼的作用是確定該對象在哈希表中的索引位置。hashCode()在散列表中才有用,在其它情況下沒用。在散列表中 hashCode() 的作用是獲取對象的散列碼,進而確定該對象在散列表中的位置。

hashCode()與 equals()的相關規定:

  1. 如果兩個對象相等,則 hashcode 一定也是相同的
  2. 兩個對象相等,對兩個對象分別調用 equals 方法都返回 true
  3. 兩個對象有相同的 hashcode 值,它們也不一定是相等的

因此,equals 方法被覆蓋過,則 hashCode 方法也必須被覆蓋。hashCode() 的默認行爲是對堆上的對象產生獨特值。如果沒有重寫 hashCode(),則該 class 的兩個對象無論如何都不會相等(即使這兩個對象指向相同的數據)

10. 重載與重寫

重載
發生在同一個類中,方法名必須相同,參數類型不同、個數不同、順序不同,方法返回值和訪問修飾符可以不同。

重寫
重寫發生在運行期,是子類對父類的允許訪問的方法的實現過程進行重新編寫。

  1. 返回值類型、方法名、參數列表必須相同,拋出的異常範圍小於等於父類,訪問修飾符範圍大於等於父類。
  2. 如果父類方法訪問修飾符爲 private/final/static 則子類就不能重寫該方法,但是被 static 修飾的方法能夠被再次聲明。
  3. 構造方法無法被重寫

在這裏插入圖片描述

11. 內部類

位於類的內部,有靜態內部類和非靜態內部類
非靜態內部類有成員內部類,方法內部類,匿名內部類。

靜態內部類
可以定義靜態或非靜態成員。從技術上來講,是兩個類。
非靜態內部類保存着指向外圍類的引用,靜態內部類沒有。

成員內部類
成員內部類在類的(非構造器,方法,塊)中
可訪問類的所有成員屬性和方法。
不能有staic變量和方法,需要先創建類才能再創建成員內部類。

局部內部類
成員內部類在類的構造器,方法,塊中
只能方法類中的final成員。

匿名內部類
沒有名稱,直接用new一個類實例。
常用在回調下定義。

12. 異常

在這裏插入圖片描述
在 Java 中,所有的異常都有一個共同的祖先 java.lang 包中的 Throwable 類。
Throwable: 有兩個重要的子類:Exception(異常) 和 Error(錯誤) ,二者都是 Java 異常處理的重要子類,各自都包含大量子類。

Error(錯誤):是程序無法處理的錯誤,表示運行應用程序中較嚴重問題。大多數錯誤與代碼編寫者執行的操作無關,而表示代碼運行時 JVM(Java 虛擬機)出現的問題。例如,Java 虛擬機運行錯誤(Virtual MachineError),當 JVM 不再有繼續執行操作所需的內存資源時,將出現 OutOfMemoryError。這些異常發生時,Java 虛擬機(JVM)一般會選擇線程終止。
這些錯誤表示故障發生於虛擬機自身、或者發生在虛擬機試圖執行應用時,如 Java 虛擬機運行錯誤(Virtual MachineError)、類定義錯誤(NoClassDefFoundError)等。這些錯誤是不可查的,因爲它們在應用程序的控制和處理能力之 外,而且絕大多數是程序運行時不允許出現的狀況。對於設計合理的應用程序來說,即使確實發生了錯誤,本質上也不應該試圖去處理它所引起的異常狀況。在 Java 中,錯誤通過 Error 的子類描述。

Exception(異常):是程序本身可以處理的異常。Exception 類有一個重要的子類 RuntimeException。RuntimeException 異常由 Java 虛擬機拋出。NullPointerException(要訪問的變量沒有引用任何對象時,拋出該異常)、ArithmeticException(算術運算異常,一個整數除以 0 時,拋出該異常)和 ArrayIndexOutOfBoundsException (下標越界異常)。

注意:異常和錯誤的區別:異常能被程序本身處理,錯誤是無法處理。

13. 內存溢出與內存泄漏

內存溢出(out of memory),是指程序在申請內存時,沒有足夠的內存空間供其使用,出現out of memory;
內存泄露(memory leak),是指程序在申請內存後,無法釋放已申請的內存空間,無用對象(不再使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而造成的內存空間的浪費稱爲內存泄露。一次內存泄露危害可以忽略,但內存泄露堆積後果很嚴重,無論多少內存,遲早會被佔光。

內存泄漏最終會導致內存溢出。

內存溢出原因

  1. 內存中加載的數據量過於龐大,如一次從數據庫取出過多數據;
  2. 集合類中有對對象的引用,使用完後未清空,使得JVM不能回收;
  3. 代碼中存在死循環或循環產生過多重複的對象實體;
  4. 使用的第三方軟件中的BUG;
  5. 啓動參數內存值設定的過小

內存泄漏原因

  1. 靜態集合類引起內存泄漏:像HashMap、Vector等的使用最容易出現內存泄露,這些靜態變量的生命週期和應用程序一致,他們所引用的所有的對象Object也不能被釋放,因爲他們也將一直被Vector等引用着。
  2. 當集合裏面的對象屬性被修改後,再調用remove()方法時不起作用。
  3. 監聽器:在釋放對象的時候卻沒有去刪除這些監聽器,增加了內存泄漏的機會。
  4. 各種連接:比如數據庫連接(dataSourse.getConnection()),網絡連接(socket)和io連接,除非其顯式的調用了其close()方法將其連接關閉,否則是不會自動被GC 回收的。
  5. 內部類和外部模塊的引用:內部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導致一系列的後繼類對象沒有釋放。此外程序員還要小心外部模塊不經意的引用,例如程序員A 負責A 模塊,調用了B 模塊的一個方法如: public void registerMsg(Object b); 這種調用就要非常小心了,傳入了一個對象,很可能模塊B就保持了對該對象的引用,這時候就需要注意模塊B 是否提供相應的操作去除引用。
  6. 單例模式:不正確使用單例模式是引起內存泄漏的一個常見問題,單例對象在初始化後將在JVM的整個生命週期中存在(以靜態變量的方式),如果單例對象持有外部的引用,那麼這個對象將不能被JVM正常回收,導致內存泄漏。

14. IO

Java 中 IO 流分爲幾種?

  1. 按照流的流向分,可以分爲輸入流和輸出流;
  2. 按照操作單元劃分,可以劃分爲字節流和字符流;
  3. 按照流的角色劃分爲節點流和處理流。

Java Io 流共涉及 40 多個類,這些類看上去很雜亂,但實際上很有規則,而且彼此之間存在非常緊密的聯繫, Java I0 流的 40 多個類都是從如下 4 個抽象類基類中派生出來的。

InputStream/Reader: 所有的輸入流的基類,前者是字節輸入流,後者是字符輸入流。
OutputStream/Writer: 所有輸出流的基類,前者是字節輸出流,後者是字符輸出流。

按操作方式分類結構圖:
在這裏插入圖片描述
按操作對象分類結構圖:
在這裏插入圖片描述

既然有了字節流,爲什麼還要有字符流?
問題本質想問:不管是文件讀寫還是網絡發送接收,信息的最小存儲單元都是字節,那爲什麼 I/O 流操作要分爲字節流操作和字符流操作呢?
回答:字符流是由 Java 虛擬機將字節轉換得到的,問題就出在這個過程還算是非常耗時,並且,如果我們不知道編碼類型就很容易出現亂碼問題。所以, I/O 流就乾脆提供了一個直接操作字符的接口,方便我們平時對字符進行流操作。如果音頻文件、圖片等媒體文件用字節流比較好,如果涉及到字符的話使用字符流比較好。

字符流方便我們操作,字節流用於音頻文件和圖片等媒體文件比較好。

BIO,NIO,AIO 有什麼區別?

  1. BIO (Blocking I/O): 同步阻塞 I/O 模式,數據的讀取寫入必須阻塞在一個線程內等待其完成
  2. NIO (Non-blocking/New I/O): NIO 是一種同步非阻塞的 I/O 模型,在 Java 1.4 中引入了 NIO 框架,對應 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解爲 Non-blocking,不單純是 New。它支持面向緩衝的,基於通道的 I/O 操作方法。 NIO 提供了與傳統 BIO 模型中的 Socket 和 ServerSocket 相對應的 SocketChannel 和 ServerSocketChannel 兩種不同的套接字通道實現,兩種通道都支持阻塞和非阻塞兩種模式。阻塞模式使用就像傳統中的支持一樣,比較簡單,但是性能和可靠性都不好;非阻塞模式正好與之相反。對於低負載、低併發的應用程序,可以使用同步阻塞 I/O 來提升開發速率和更好的維護性;對於高負載、高併發的(網絡)應用,應使用 NIO 的非阻塞模式來開發
  3. AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引入了 NIO 的改進版 NIO 2,它是異步非阻塞的 IO 模型。異步 IO 是基於事件和回調機制實現的,也就是應用操作之後會直接返回,不會堵塞在那裏,當後臺處理完成,操作系統會通知相應的線程進行後續的操作。AIO 是異步 IO 的縮寫,雖然 NIO 在網絡操作中,提供了非阻塞的方法,但是 NIO 的 IO 行爲還是同步的。對於 NIO 來說,我們的業務線程是在 IO 操作準備好時,得到通知,接着就由這個線程自行進行 IO 操作,IO 操作本身是同步的。查閱網上相關資料,我發現就目前來說 AIO 的應用還不是很廣泛,Netty 之前也嘗試使用過 AIO,不過又放棄了。

14.1 NIO

三個核心部分組成:

  1. Buffer 緩衝區,緩存數據
  2. Channel 管道,運輸Buffer中的數據
  3. Selector 選擇器,中能夠檢測一到多個NIO通道,並能夠知曉通道是否爲諸如讀寫事件做好準備的組件。這樣,一個單獨的線程可以管理多個channel,從而管理多個網絡連接。

Buffer 緩衝區
容量Capacity:緩衝區能夠容納的數據元素的最大數量。容量在緩衝區創建時被設定,並且永遠不能被改變。(不能被改變的原因也很簡單,底層是數組嘛)
上界Limit:緩衝區裏的數據的總數,代表了當前緩衝區中一共有多少數據。
位置Position:下一個要被讀或寫的元素的位置。Position會自動由相應的 get( )和 put( )函數更新。標記Mark一個備忘位置。用於記錄上一次讀寫的位置。

寫模式:capacity和limit值相等,指向最大數量。通過改變position的值,記錄寫入位置。
讀模式:limit指向position的位置,position指向頭部,讀取buffer中的值。
在這裏插入圖片描述

直接與非直接緩衝區
非直接緩衝區是需要經過一個:copy的階段的(從內核空間copy到用戶空間)
直接緩衝區不需要經過copy階段,也可以理解成—>內存映射文件,

分散讀取和聚集寫入
分散讀取(scatter):將一個通道中的數據分散讀取到多個緩衝區中
聚集寫入(gather):將多個緩衝區中的數據集中寫入到一個通道中

Channel 通道
只負責傳輸數據、不直接操作數據的。操作數據都是通過Buffer緩衝區來進行操作!

UNIX網絡編程對I/O模型

  1. 阻塞I/O模型:在進程(用戶)空間中調用recvfrom,其系統調用直到數據包到達且被複制到應用進程的緩衝區中或者發生錯誤時才返回,在此期間一直等待。
  2. 非阻塞I/O:recvfrom從應用層到內核的時候,如果沒有數據就直接返回一個EWOULDBLOCK錯誤,一般都對非阻塞I/O模型進行輪詢檢查這個狀態,看內核是不是有數據到來。
  3. I/O多路複用:利用文件描述符(file descriptor)來實現的。調用select/poll/epoll/pselect其中一個函數,傳入多個文件描述符,如果有一個文件描述符就緒,則返回,否則阻塞直到超時。

網絡中使用NIO往往是I/O模型的多路複用模型!

NIO時的要點:

  1. 將Socket通道註冊到Selector中,監聽感興趣的事件
  2. 當感興趣的時間就緒時,則會進去我們處理的方法進行處理
  3. 每處理完一次就緒事件,刪除該選擇鍵(因爲我們已經處理完了)

15. SPI

SPI是Service Provider Interface 的簡稱,即服務提供者接口的意思。
說白了就是一種擴展機制,我們在相應配置文件中定義好某個接口的實現類,然後再根據這個接口去這個配置文件中加載這個實例類並實例化。
JDBC驅動加載案例:利用Java的SPI機制,我們可以根據不同的數據庫廠商來引入不同的JDBC驅動包;

16. JDK8變化

  1. jvm內存模型變化。去掉方法區改爲元空間,方法區中運行時常量池改爲放在堆中。
  2. hashmap 數組+鏈表 -》 數據+鏈表/紅黑樹
  3. hashmap 頭插改尾插
  4. concurrenthashmap segment+hashentry -> 數據+鏈表/紅黑樹 synchronized +cas
  5. 接口可以有默認方法和靜態方法功能

參考:
Java 面試突擊 Java基礎
應屆生/社招面試最愛問的幾道Java基礎問題
Java hashCode() 和 equals()的若干問題解答
java內存泄漏與內存溢出
如何學習Java的NIO?
經常聽到SPI機制,那什麼是SPI呢?

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