瘋狂Java程式設計師的基本素養學習筆記

chengp919發表於2017-08-07

學習一門技術,就要深入,投身一門職業,就要專注。作為一名Java程式設計師,不僅要知道怎麼去用這些技術、框架,而且要知道這些技術、框架是怎麼執行的、怎麼實現的。知其然而知其所以然。學習不能浮躁,循序漸進。


一、陣列及其記憶體管理

棧記憶體、堆記憶體、方法區

問題:

Java中,宣告並建立陣列的過程中,記憶體是如何分配的?

Java陣列的初始化方式有哪幾種?

基本型別陣列和引用型別陣列,在初始化時的記憶體分配機制的區別?

 

陣列,複合結構,Java語言的陣列變數是引用型別的變數。Java語言是典型的靜態語言,Java陣列也是靜態的,陣列初始化之後,所佔的記憶體空間、長度是不可變的,必須經過初始化後才能使用。

  • 初始化方式

靜態初始化,顯示指定每個陣列元素的初始值,由系統決定陣列長度

動態初始化,只指定陣列長度,系統為陣列元素分配初始值

注意:靜態初始化和動態初始化不能同時使用,即不能同時指定陣列的長度和為每個陣列元素分配初始值。

Java陣列變數是引用型別的變數,陣列變數並不是陣列本身,它指向堆記憶體中的陣列物件(相當於指標)

 

Java是強型別語言,JavaScript是弱型別語言。

Java程式中的引用變數並不需要經過所謂的初始化操作,需要進行初始化的是引用變數所引用的物件

  • 基本型別的陣列,陣列元素的值直接儲存在對應的陣列元素中

int [] a;

方法棧中定義一個a陣列變數,引用型別的變數,並未指向任何有效的記憶體,沒有真正指向實際的陣列物件,因此還不能使用陣列物件。

a = new int[]{2,1,3};

靜態初始化,系統決定長度,a指向堆記憶體空間。

注意:所有區域性變數都放在棧記憶體中儲存,即存在各自的方法棧記憶體中。引用型別的變數所引用的物件總是儲存在堆記憶體中。堆記憶體中的物件通常不允許直接訪問。main方法宣告的變數都屬於區域性變數,故都儲存在main方法棧區中。

Runtime異常:NullPointerException(空指標異常),當通過引用變數來訪問例項屬性,或呼叫非靜態方法時,如果該引用變數還未引用一個有效的物件,程式就會引發NullPointerException執行時異常。

  • 引用型別陣列,陣列元素裡儲存的還是引用,它指向另一塊記憶體,該記憶體儲存了該引用變數所引用的物件

注意:Java語言避免訪問堆記憶體中的資料可以保護程式更加健壯,如果程式直接訪問並修改堆記憶體中的資料,可能會破壞記憶體中的資料完整性,導致程式Crash。

Java允許將多維陣列當成一維陣列處理。通過陣列的length屬性獲取陣列的長度前提是把N維陣列當成陣列元素N-1維陣列的一維陣列


二、物件及其記憶體管理

問題:

為什麼要建立這麼多的例項?

物件建立對系統的開銷

Java物件的記憶體分配機制


可以參考:

http://imtianx.cn/2016/11/19/java%20%E7%9F%A5%E8%AF%86%E4%B9%8B%20%E5%AF%B9%E8%B1%A1%E5%8F%8A%E5%85%B6%E5%86%85%E5%AD%98%E7%AE%A1%E7%90%86/


Java記憶體管理

記憶體分配:特指建立Java物件時JVM為該物件在堆記憶體中所分配的記憶體空間

記憶體回收:當Java物件失去引用,變成垃圾時,JVM的垃圾回收機制自動清理物件,並回收該物件所佔用的記憶體


注意:Java程式依然會有記憶體洩漏


JVM的垃圾回收機制由一條後臺執行緒完成,非常消耗效能。建立沒用的物件,壞處:

不斷分配記憶體使得系統中可用的記憶體減少

大量已分配記憶體的回收使得垃圾回收的負擔加重

都會降低程式的執行效能

  • Java程式的變數

成員變數(類體內定義的變數):非靜態變數/例項變數,靜態變數/類變數

區域性變數:形參、方法內的區域性變數、程式碼塊內的區域性變數,區域性變數的作用時間很短,儲存在棧記憶體中


注意:

static就是一個標誌,static只能修飾類裡的成員,不能修飾外部類,不能修飾區域性變數、區域性內部類,static的作用是將例項成員變為類成員

 

Java中定義成員變數時要注意前向引用同一個JVM中,每個類對應一個Class物件,但每個類可以建立多個Java物件


類也是物件,所有類都是Class的例項。每個類初始化完成之後,系統都會為該類建立一個對應的Class例項,程式可以通過反射獲取對應的Class例項。

Person.class;

Class.forName(“Person”);

類的例項也可以訪問類變數,這樣設計似乎有些不合理。類的物件訪問類變數本質也是轉換為類訪問類變數

  • 例項變數的初始化時機(非靜態變數)

每次建立Java物件都需要為例項變數分配記憶體空間,對例項變數執行初始化

1、定義例項變數時指定初始值

2、非靜態初始化塊中對例項變數指定初始值

3、構造器中對例項變數指定初始值

(第1/2方式比第3方式更早執行,1/2方式執行順序與原始碼中的排列順序相同)

分析:

double w = 2;

double w;建立Java物件時系統根據該語句為物件分配記憶體

w = 2;這條語句會在Java類的構造器中執行

  • 類變數的初始化時機

每個JVM對一個Java類只初始化一次,為該類的類變數分配記憶體空間,指定預設值。

1、定義類變數時指定初始值

2、靜態初始化塊中對類變數指定初始值

(1/2方式的執行順序與排列順序相同)

 

建立Java物件時,程式總會依次呼叫每個父類的非靜態初始化塊、構造器執行初始化然後才呼叫本類的非靜態初始化塊、構造器執行初始化

 

注意:

super呼叫用於顯示呼叫父類構造器,this呼叫用於顯示呼叫本類中另一個過載的構造器。super和this呼叫都只能在構造器中使用,都必須作為構造器的第一行程式碼

只能使用其中之一,而且只能最多隻能呼叫一次

  • 子類與父類中的例項變數、方法的區別

一個Java物件可以擁有多個同名的例項變數,子類定義的成員變數並不能完全覆蓋父類中成員變數

當變數的編譯時型別和執行時型別不同時,通過該變數訪問引用的物件的例項變數時,該例項變數的值由宣告該變數的型別決定

  • 父子例項的記憶體控制

Java繼承中對成員變數和方法的處理是不同的。 

當通過變數呼叫方法時,方法的行為總是表現出它們實際型別的行為,訪問它們的所知物件的例項變數時,例項變數的值總是表現出變數所引用型別的行為。(方法可以被重寫,變數不存在重寫

當子類使用public訪問控制修飾符,父類不使用public修飾符,才可以通過javap看到編譯器將父類的public方法直接轉移到子類中

子類物件的儲存記憶體中不存在父類物件,只存在一個子類物件,只是儲存了它所有父類所定義的全部例項變數

Java程式中允許某個方法通過return this;返回撥用該方法的Java物件,但不能直接使用return super;甚至不允許直接將super當成一個引用變數使用。super關鍵字本身並沒有引用任何物件

1、子類方法不能直接使用return super;允許某個方法通過return this;返回撥用該方法的Java物件

2、甚至不允許直接將super當成一個引用變數使用。Super == a;引起編譯錯誤

子類中,一般用super.作為限定來修飾例項變數、方法

 

訪問父類的靜態變數/類變數,方式

1、使用父類的類名(推薦,程式碼可讀性)

2、使用super.限定來訪問

  • final修飾符

1、final修飾的變數,賦值後,不能重新賦值

final例項變數:

必須顯示指定初始值

三種賦初始值的方式,都會抽到構造器中賦初始值(本質一樣)


final類變數:

必須顯示指定初始值

只能在定義時、靜態初始化塊中指定初始值

 

注意:

初始值在編譯時就被確定下來,系統不會在靜態初始化塊中對該類變數賦初始值

 

final修飾符定義“巨集變數”,定義時就賦初始值,編譯器會把程式中所有用到該變數的地方直接替換成值

對於final例項變數,只有在定義該變數時指定初始值才會有“巨集變數”的效果

對於final類變數,只有在定義時指定初始值才有“巨集變數”的效果

2、final修飾的方法,不能重寫

如果程式需要在匿名內部類中使用區域性變數,必須使用final修飾符修飾

在任何內部類中訪問的區域性變數都應該使用final修飾

此處內部類為區域性內部類,區域性內部類才可以訪問區域性變數,普通靜態內部類、非靜態內部類不可能訪問方法體內的區域性變數

內部類可能產生隱式的“閉包”,閉包將使得區域性變數脫離其所在的方法繼續存在

內部類會擴大區域性變數作用域的例項


3、final修飾的類,不能派生子類

相關文章