對象
在面向對象的思想中,一切事物都可以認為是對象——萬物皆對象,把對象定義成包含狀態和行為的一個實體,存在於現實世界中並且可以與其他實體區分開來的。對象具有狀態和行為;比如:想想你心儀的小姐姐,可以把這個小姐姐看作是一個對象,那麼該對象有兩方面的定義:狀態和行為;狀態,如身高,年齡,三圍,頭髮(長髮或者短髮)等;行為,如調戲你、跳舞,玩手機等。
類
通過多個相同類型的對象的狀態和行為分析,可以把對象抽象成類(class);我們把具有相同特性(狀態)和行為(功能)的對象的抽象定義類,對象的抽象是類,類實例化後便是對象,類的實例是對象,類其實就是對象的數據類型,但其和基本數據類型的差異在於類是程序員為了解決某些問題而自定義的,基本數據類型是計算機中的數據存儲單元。
Java 對象
在Java中,對象的狀態,用成員變量來描述;對象的行為,用方法來描述;故Java中類可以這樣定義:
[修飾符] class 類名
{
成員變量(字段/Field)
... ...
方法
... ...
}
類定義示例代碼:
// 類名
public class Student {
/** 狀態 **/
// id
int id;
// 姓名
String name;
// 成績
int score;
/** 行為 **/
// 通過學生id獲取姓名
public void getNameById(int id) {
System.out.println("學生:" + name);
}
}
定義類須注意:
- 類名一律使用英文或者國際通用的拼音符號,做到見名知義,如taobao,weixin,雖然是拼音,但卻是國際通用的,可以使用;
- 如果類使用了public修飾符,必須保證當前java文件名稱和當前類名相同,而且在一個java文件中,只能有一個public修飾的類(class);
- 類名首字母大寫,如果類名是多個單詞組成的,使用駝峰命名法,如: OperatingSystem(操作系統);
對象比較操作
先考慮下面的代碼:
public static void main(String[] args) {
int i = 13;
int j = 13;
System.out.println(i == j);
Integer k = new Integer(13);
Integer l = new Integer(13);
System.out.println(k == l);
}
以上代碼運行結果為:
true
false
為什麼會出現這樣的結果呢?
那是因為== 和 != 這兩個比較運算符 :
- 對於基本數據類型來說,比較的是值,也就是變量存儲的數據內容;
- 對於引用數據類型來說,比較的是對象的引用,也就是其在堆內存中的地址值,每次使用new關鍵字創建對象,都會在堆中新開闢一塊內存空間存儲新創建的對象, 並且會為該內存空間生成一個唯一的地址,故內存空間不同,內存空間的地址值也就不同。
基本數據類型:byte、short、char、int、long、float、double,boolean;
引用數據類型:除基本數據類型以外的所有數據類型都是引用數據類型,包括String和基本數據類型的封裝類型;
如果,要對對象的值做比較,就必須要是用對象的equals()方法了;這裡需要注意,equals()方法並不適用於基本數據類型,對於基本數據類型的變量來說,使用 == 和 != 足夠了。
考慮下面的代碼:
public static void main(String[] args) {
Integer k = new Integer(13);
Integer l = new Integer(13);
System.out.println(k == l);
System.out.println(k.equals(l));
}
輸出結果為:
false
true
由此可看出,使用對象的equals()方法是能正確比較對象的值的,因為Integer已經自定義了equals方法了,下面是源碼:
/**
* Compares this object to the specified object. The result is
* {@code true} if and only if the argument is not
* {@code null} and is an {@code Integer} object that
* contains the same {@code int} value as this object.
*
* @param obj the object to compare with.
* @return {@code true} if the objects are the same;
* {@code false} otherwise.
*/
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
不難發現,Integer的equals()方法的底層是使用基本數據類型的值做==比較的。
如果是我們自定義的類,而且沒有重新定義equals()方法呢,結果又會是怎樣的,一起來看看:
public class MyClass {
public static void main(String[] args) {
Value value1 = new Value();
Value value2 = new Value();
value1.i = value2.i = 13;
System.out.println(value1.equals(value2));
}
}
class Value {
int i;
}
輸出結果為:false。
因為在Java中,有一個所有引用類型都直接或者間接繼承的父類,Object;因此,也可以說在java中,所有類都是Object的子類,那麼,如果我們沒重新實現equals()方法,會默認調用Object的equals()方法,Object的equals()方法比較的是對象的引用,所以結果輸出為false。
所以想要使用自定義對象的equals方法比較對象的值,那麼就必須重新實現equals方法。
對象的打印操作
默認情況下,Java對象打印的效果是:類的名稱@十六進制的hashCode,比如:
public class MyClass {
public static void main(String[] args) {
Value value = new Value();
System.out.println(value);
}
}
class Value {
int i;
}
輸出為:com.strlite.admin.demo.Value@79b4d0f
可以通過重寫toString() 方法來改變對象的打印效果:
public class MyClass {
public static void main(String[] args) {
Value value = new Value();
value.i = 13;
System.out.println(value);
}
class Value {
int i;
@Override
public String toString() {
return "i = " + i;
}
}
輸出為:
i = 13
對象的生命週期
對象的開始:每次使用new關鍵字創建對象,就會在內存中開闢新的空間存儲對象信息,此時對象開始存在。
對象的結束:當堆中的對象,沒有被任何變量所引用,此時該對象就成了垃圾,等待垃圾回收器(GC)來回收;當對象被回收後,對象被銷燬,對象佔用的內存空間被釋放,對象的生命週期結束。
匿名對象
對象創建之後沒有將其賦給某一個變量。匿名對象只是在堆中開闢一塊新的內存空間,但是沒有把該空間地址賦給任何變量。因為沒有變量引用指向,所以匿名對象僅僅只能使用一次,一般會把匿名對象作為方法的參數傳遞。
new Integer(); // 匿名對象
構造器
Integer i = new Integer();
在創建對象時使用的特殊方法,稱之為構造方法、構造器、構造函數(Constructor)。
構造器的作用:
- 用於創建對象,但是必須和 new 一起使用;比如:new Integer(13);
- 完成對象的初始化操作,可以創建帶參數的構造器,為成員變量賦初始值;
構造器的特點:
- 構造器的名稱和當前所在類的名稱相同;
- 構造器是一個特殊的方法,其沒有定義返回類型,所有不必使用void作為返回類型。 假設需要寫返回類型,也應該這樣寫:Integer Integer(); 但沒有這樣的必要;
- 在構造器中,不需要使用return語句,其實構造器是有返回值的,會默認返回當前創建對象的引用。
如果類中沒有構造器,編譯器會自動創建一個默認的無參構造器。
public class ConstructorDemo
{
public static void main(String[] args) {
}
}
反編譯後的結果:
public class ConstructorDemo
{
public ConstructorDemo()
{
}
public static void main(String args[])
{
}
}
編譯器創建的默認構造器的特點:
- 符合構造器特點;
- 無參數的;
- 無方法體;
- 如果類沒有使用public修飾, 則編譯器問起創建的構造器也沒有public修飾;使用了public修飾,則編譯器創建的構造器也使用public修飾.
如果類中沒有構造器,編譯器會自動創建一個默認的無參構造器。但是,如果我們顯示定義了一個構造器,則編譯器不再創建默認構造器。
public class ConstructorDemo
{
// 自定義的構造器
public ConstructorDemo(int i) {
}
public static void main(String[] args) {
}
}
反編譯的結果:
public class ConstructorDemo
{
public ConstructorDemo(int i)
{
}
public static void main(String args[])
{
}
}
通過上述對比,不難發現,在一個類中,至少存在一個構造器。
static 修飾符
假如每個人都有name和age兩個狀態,但是不同人的name和age是不一樣的;也就說name和age是屬於對象的。但是在生活中有些東西並不是單單屬於某一個對象的,而是屬於整個類的,比如:每個人都會老去、都會死。
所以,狀態和行為的所屬也應該有對象和類之分。 有的狀態和行為應該屬於對象,不同的對象,狀態和行為可以不一樣;而有的狀態和行為應該屬於類,不屬於對象。為了區別與對象的狀態和行為,引入static修飾符來修飾類的狀態和行為。
static修飾符表示靜態的,可修飾字段、方法、內部類,其修飾的成員屬於類,static修飾的資源屬於類級別,區別於對象級別。
static的真正作用是用來區別字段、方法、內部類、初始化代碼塊是屬於對象還是屬於類本身。
static修飾符的特點:
- static修飾的成員(字段/方法),隨著所在類的加載而加載,當JVM把字節碼加載進JVM的時候,static修飾的成員已經在內存中存在了。
- 優先於對象的存在,對象是我們手動通過new關鍵字創建出來的,static成員是JVM創建的;
- satic修飾的成員被該類型的所有對象所共享,該類創建的任何對象都可以訪問該類的static成員;
- 直接使用類名訪問static成員因為static修飾的成員直接屬於類,不屬於對象,所以可以直接使用類名訪問static成員.
public class ConstructorDemo
{
// 自定義的構造器
public ConstructorDemo(int i) {
}
public static void main(String[] args) {
int count = StaticDemo.count;
System.out.println(count);
}
}
class StaticDemo
{
// 靜態成員
static int count = 0;
}
上述示例代碼在jvm 中是這樣的:
類成員和實例成員的訪問
類中的成員:字段,方法,內部類。
- 類成員:使用static修飾的成員,直接屬於類,通過類名.static成員來訪問;
- 實例成員:沒有使用static修飾的成員,實例成員只屬於對象, 通過對象來訪問非static字段和非static方法;
一般情況下,類成員只能訪問類成員,實例成員只能訪問實例成員;但深究發現,對象其實可以訪問類成員,但是底層依然使用類名訪問的。
static方法
在static方法中,只能調用static成員;非static方法,可以訪問靜態成員,也可以訪問實例成員;
那什麼時候定義成static的字段和方法:
- 如果這個一個狀態/行為屬於整個事物(類),被所有對象所共享,就直接使用static修飾;
- 在開發中,往往把工具方法使用static修飾,比如:數組中常用的java.util.Arrays中的方法;
如果不使用static修飾,則這些方法屬於該類的對象,我們得先創建對象才能調用方法,在開發中工具對象只需要一份即可,可能創建N個對象,此時可以考慮使用單例設計模式。
類成員的使用
好處:對對象的共享數據進行單獨空間的存儲,節省空間,沒有必要每一個對象中都存儲一份,可以直接被類名調用。
弊端:生命週期過長。
局部變量初始化
局部變量定義後,必須顯式初始化後才能使用,因為JVM不會為局部變量執行初始化操作。這就意味著,定義局部變量後,JVM並未為這個變量分配內存空間。直到程序為這個變量賦值時,系統才會為局部變量分配內存,並將初始值保存到該內存中。
局部變量不屬於任何類或實例,因此它是保存在其所在方法的棧幀內存中。
- 基本數據局部變量:基本數據類型變量的值會直接保存到該變量所對應的內存中。
- 引用數據局部變量:變量內存中存的是堆中對象的地址,通過該地址引用到該變量實際指向的堆裡的對象。
棧幀內存中的變量隨方法或代碼塊的運行結束而銷燬,無須JVM回收。
一點小建議
- 開發中應該儘量縮小變量的作用範圍,如此在內存中停留時間越短,性能也就更高。
- 合理使用static修飾,一般只有定義工具方法的時候使用;
- static方法需要訪問的變量,只有該變量確實屬於類,才使用static修飾字段;
- 儘量使用局部變量;
完結。
閱讀更多 老夫不正經 的文章