[JAVA] 只知對象屬性,不知類屬性?就算類答應,static都不答應

由淺入深——Java 類、對象、static成員

對象

在面向對象的思想中,一切事物都可以認爲是對象——萬物皆對象,把對象定義成包含狀態和行爲的一個實體,存在於現實世界中並且可以與其他實體區分開來的。對象具有狀態和行爲;比如:想想你心儀的小姐姐,可以把這個小姐姐看作是一個對象,那麼該對象有兩方面的定義:狀態和行爲;狀態,如身高,年齡,三圍,頭髮(長髮或者短髮)等;行爲,如調戲你、跳舞,玩手機等。

通過多個相同類型的對象的狀態和行爲分析,可以把對象抽象成類(class);我們把具有相同特性(狀態)和行爲(功能)的對象的抽象定義類,對象的抽象是類,類實例化後便是對象,類的實例是對象,類其實就是對象的數據類型,但其和基本數據類型的差異在於類是程序員爲了解決某些問題而自定義的,基本數據類型是計算機中的數據存儲單元。

Java 對象

在Java中,對象的狀態,用成員變量來描述;對象的行爲,用方法來描述;故Java中類可以這樣定義,語法如下:

Java 類的語法

類定義示例代碼:

類定義示例代碼

定義Java 類時有一些必要的規範需要遵守:

  1. 類名一律使用英文或者國際通用的拼音符號,做到見名知義,如taobao,weixin,雖然是拼音,但卻是國際通用的,可以使用;
  2. 如果類使用了public修飾符,必須保證當前java文件名稱和當前類名相同,而且在一個java文件中,只能有一個public修飾的類(class);
  3. 類名首字母大寫,如果類名是多個單詞組成的,使用駝峯命名法,如: OperatingSystem(操作系統);

對象比較操作

先考慮下面的代碼:

對象比較示例代碼

上述示例代碼運行結果爲:

對象比較結果

爲什麼會出現這樣的結果呢?都是同樣的值,爲什麼會有不同的比較結果?那是因爲**==!=這兩個比較運算符在比較基本數據類型和對象對象類型時是由區別的:**

  • 對於基本數據類型來說,比較的是值,也就是變量存儲的數據內容;
  • 對於引用數據類型來說,比較的是對象的引用,也就是其在堆內存中的地址值,每次使用new關鍵字創建對象,都會在堆中新開闢一塊內存空間存儲新創建的對象, 並且會爲該內存空間生成一個唯一的地址,故內存空間不同,內存空間的地址值也就不同。

那麼哪些數據類型時基本數據類型,哪些是引用數據類型呢?

  • 基本數據類型:byte、short、char、int、long、float、double、boolean
  • 引用數據類型:除基本數據類型以外的所有數據類型都是引用數據類型,包括String和基本數據類型的封裝類型;

所以,如果要對對象的值做比較,就必須要是用對象的**equals()**方法了;這裏需要注意,**equals()方法並不適用於基本數據類型,對於基本數據類型的變量來說,使用== 和 !=**足夠了。下面用一個例子來實踐,代碼如下:

"equals()方法 案例"

上述案例輸出結果爲:

"equals()方法案例 運行結果"

由此可看出,使用對象的equals()方法是能正確比較對象的值的,因爲Integer已經自定義了equals方法了,下面是源碼:

"Integer equals方法源碼"

不難發現,Integer的equals()方法的底層是使用基本數據類型的值做==比較的。如果是我們自定義的類,而且沒有重新定義equals()方法呢,結果又會是怎樣的,一起來看看:

"沒有重新定義equals()方法的案例"

輸出結果爲:false。

因爲在Java中,有一個所有引用類型都直接或者間接繼承的父類,Object;因此,也可以說在java中,所有類都是Object的子類,那麼,如果我們沒重新實現**equals()**方法,會默認調用Object的equals()方法,Object的equals()方法比較的是對象的引用,所以結果輸出爲false。

所以想要使用自定義對象的equals方法比較對象的值,那麼就必須重新實現equals方法。

對象的打印操作

默認情況下,Java對象打印的效果是:類的名稱@十六進制的hashCode,比如:

"Java對象打印的案例"

輸出爲:com.strlite.admin.demo.Value@79b4d0f,com.strlite.admin.demo.Value是類的名稱,79b4d0f是一個十六進制的數,是對象在堆中的內存地址。

重寫toString() 方法

可以通過重寫toString() 方法來改變對象的打印效果:

"重寫toString方法的案例"

輸出爲:

  • i = 13

對象的生命週期

對象的開始:每次使用new關鍵字創建對象,就會在內存中開闢新的空間存儲對象信息,此時對象開始存在。

對象的結束:當堆中的對象,沒有被任何變量所引用,此時該對象就成了垃圾,等待垃圾回收器(GC)來回收;當對象被回收後,對象被銷燬,對象佔用的內存空間被釋放,對象的生命週期結束。

匿名對象

對象創建之後沒有將其賦給某一個變量。匿名對象只是在堆中開闢一塊新的內存空間,但是沒有把該空間地址賦給任何變量。因爲沒有變量引用指向,所以匿名對象僅僅只能使用一次,一般會把匿名對象作爲方法的參數傳遞。

  • new Integer(); // 創建的就是匿名對象

構造器

  • Integer i = new Integer();

在創建對象時使用的特殊方法,出現new 關鍵字之後的方法,稱之爲構造方法、構造器、構造函數(Constructor)

構造器的作用

  1. 用於創建對象,但是必須和 new 一起使用;比如:new Integer(13);
  2. 完成對象的初始化操作,可以創建帶參數的構造器,爲成員變量賦初始值;

構造器的特點

  1. 構造器的名稱和當前所在類的名稱相同;
  2. 構造器是一個特殊的方法,其沒有定義返回類型,所有不必使用void作爲返回類型。 假設需要寫返回類型,也應該這樣寫:Integer Integer(); 但沒有這樣的必要;
  3. 在構造器中,不需要使用return語句,其實構造器是有返回值的,會默認返回當前創建對象的引用。

如果類中沒有構造器,編譯器會自動創建一個默認的無參構造器

"沒有構造器的案例"

我們將上述代碼經過編譯,得到字節碼文件,再將字節碼文件反編譯,反編譯的結果如下:

"默認構造器案例的反編譯效果"

通過反編譯後的結果,不難發現,即便我們沒有創建構造器,編譯器也會爲我們創建一個默認的,編譯器創建的默認構造器有以下的特點:

  • 符合構造器特點;
  • 無參數的;
  • 無方法體;
  • 如果類沒有使用public修飾, 則編譯器問起創建的構造器也沒有public修飾;使用了public修飾,則編譯器創建的構造器也使用public修飾;

"默認構造器"

如果類中沒有構造器,編譯器會自動創建一個默認的無參構造器。但是,如果我們顯式地定義了一個構造器,則編譯器不再創建默認構造器。案例如下所示:

"編譯器不再創建默認構造器"

通過上述對比,不難發現,當類中存在一個構造器時,編譯器便不會創建默認的構造器,而是使用我們定義的構造器,由此可得出:在一個類中,至少存在一個構造器

static 修飾符

假如每個人都有name和age兩個狀態,但是不同人的name和age是不一樣的;也就說name和age是屬於對象的。但是在生活中有些東西並不是單單屬於某一個對象的,而是屬於整個類的,比如:每個人都會老去、都會死。

所以,狀態和行爲的所屬也應該有對象和類之分。 有的狀態和行爲應該屬於對象,不同的對象,狀態和行爲可以不一樣;而有的狀態和行爲應該屬於類,不屬於對象。爲了區別與對象的狀態和行爲,引入static修飾符來修飾類的狀態和行爲。

static修飾符表示靜態的,可修飾字段、方法、內部類,其修飾的成員屬於類,static修飾的資源屬於類級別,區別於對象級別。static的真正作用是用來區別字段、方法、內部類、初始化代碼塊是屬於對象還是屬於類本身。

static修飾符的特點

  1. static修飾的成員(字段/方法),隨着所在類的加載而加載,當JVM把字節碼加載進JVM的時候,static修飾的成員已經在內存中存在了。
  2. 優先於對象的存在,對象是我們手動通過new關鍵字創建出來的,static成員是JVM創建的;
  3. satic修飾的成員被該類型的所有對象所共享,該類創建的任何對象都可以訪問該類的static成員;
  4. 直接使用類名訪問static成員因爲static修飾的成員直接屬於類,不屬於對象,所以可以直接使用類名訪問static成員.

下面我們通過一個案例來實踐static關鍵字的使用:

"static關鍵字的使用案例"

static修改的變量稱爲常量,會長時間存在於JVM內存中,所以JVM也會爲它分配一定的存儲空間,以下便是static常量在jvm 中的內存模型:

"static常量在jvm 中的內存模型"

JVM會將靜態變量存儲在方法區中,以便於及時調用;並保證其能夠長時間存儲於JVM中。

類成員和實例成員的訪問

類中的成員:字段,方法,內部類。

  • 類成員:使用static修飾的成員,直接屬於類,通過類名.static成員來訪問;
  • 實例成員:沒有使用static修飾的成員,實例成員只屬於對象, 通過對象來訪問非static字段和非static方法;

一般情況下,類成員只能訪問類成員,實例成員只能訪問實例成員;但深究發現,對象其實可以訪問類成員,但是底層依然使用類名訪問的。

static方法

static方法中,只能調用static成員;非static方法,可以訪問靜態成員,也可以訪問實例成員;

那什麼時候定義成static的字段和方法:

  • 如果這個一個狀態/行爲屬於整個事物(類),被所有對象所共享,就直接使用static修飾;
  • 在開發中,往往把工具方法使用static修飾,比如:數組中常用的java.util.Arrays中的方法;

如果不使用static修飾,則這些方法屬於該類的對象,我們得先創建對象才能調用方法,在開發中工具對象只需要一份即可,可能創建N個對象,此時可以考慮使用單例設計模式。

"成員生命週期"

類成員的使用

好處:對對象的共享數據進行單獨空間的存儲,節省空間,沒有必要每一個對象中都存儲一份,可以直接被類名調用。

弊端:生命週期過長。

局部變量初始化

局部變量定義後,必須顯式初始化後才能使用,因爲JVM不會爲局部變量執行初始化操作。這就意味着,定義局部變量後,JVM並未爲這個變量分配內存空間。直到程序爲這個變量賦值時,系統纔會爲局部變量分配內存,並將初始值保存到該內存中。

局部變量不屬於任何類或實例,因此它是保存在其所在方法的棧幀內存中。

  • 基本數據局部變量:基本數據類型變量的值會直接保存到該變量所對應的內存中。
  • 引用數據局部變量:變量內存中存的是堆中對象的地址,通過該地址引用到該變量實際指向的堆裏的對象。

棧幀內存中的變量隨方法或代碼塊的運行結束而銷燬,無須JVM回收。

一點小建議

  • 開發中應該儘量縮小變量的作用範圍,如此在內存中停留時間越短,性能也就更高。
  • 合理使用static修飾,一般只有定義工具方法的時候使用;
  • static方法需要訪問的變量,只有該變量確實屬於類,才使用static修飾字段;
  • 儘量使用局部變量;

完結。老夫雖不正經,但老夫一身的才華

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