Thinking in Java(1-1) 深入理解 Java 中的物件與參考
在 Java 的世界裡,一切都是物件(Object)。理解物件與參考(Reference)的運作方式,是掌握 Java 程式設計的基石。本文將帶你深入探討 Java 中的物件操作、記憶體分配、基本類型(Primitive Types)、包裝器(Wrapper Classes)、陣列(Arrays)以及作用域(Scope)的相關概念。
使用 Reference 操作 Object
一切皆物件 (Everything is an Object)
在 Java 中,所有事物都被視為物件。然而,聲明一個變數時,實際上建立的只是對物件的參考(reference),而非物件本身。例如:
String s;
這行程式碼僅僅是建立了一個 String
類型的參考 s
,但尚未指向任何實際的 String
物件。如果此時向 s
發送消息(例如調用方法),將會導致執行時錯誤(Runtime Error)。
安全的初始化方式
為了避免這類錯誤,建議在建立參考的同時進行初始化:
String s = "asdf";
這樣,s
不僅是一個參考,還指向了一個實際的 String
物件。
必須由你建立所有 Object
一旦建立了參考,你通常希望它與某個物件相關聯。這通常是通過使用 new
關鍵字來實現的。new
的意思是「給我一個新的物件」。例如:
String s = new String("asdf");
這行程式碼明確地在堆(Heap)中建立了一個新的 String
物件,並將其參考賦值給 s
。
物件的儲存位置
Java 程式在運行時,會將物件存放在記憶體的不同區域。了解這些區域有助於編寫高效的程式碼。
寄存器(Register)
寄存器是最靠近 CPU 的記憶體儲存區,存取速度最快。然而,寄存器的數量極為有限,且無法直接控制。C/C++ 語言允許開發者向編譯器建議寄存器的分配方式,但在 Java 中,這並不適用。
堆疊(Stack)
堆疊位於通用 RAM 中,透過堆疊指標(Stack Pointer)的上下移動來分配和釋放內存。堆疊的存取速度僅次於寄存器,且具有高效的記憶體管理方式。然而,Java 系統必須知道堆疊中所有項目的確切生命週期,這限制了程式的靈活性。堆疊中通常存放參考,但實際的物件並不會放在這裡。
堆(Heap)
堆也是位於 RAM 的通用內存池,用來存放所有 Java 物件。相比堆疊,堆具有更大的彈性,因為編譯器不需要知道數據的存活時間。使用 new
關鍵字可以自動在堆中分配內存。然而,這種彈性也帶來了代價:堆的分配和清理比堆疊更耗時。
常量儲存
常量通常存放在程式碼區塊(Code Segment)內部。由於程式碼區塊不會被修改,這種做法保護了常量的不變性。
非 RAM 儲存
非 RAM 儲存指的是在進程控制之外的資料,例如流(Stream)、檔案(File)等。
物件中的特例:基本類型(Primitive Types)
在程式設計中,經常使用一系列小而簡單的類型,我們將它們稱為基本類型(Primitive Types)。這些類型不需要使用 new
來建立物件,因為這樣做效率不高。Java 與 C++ 採用相同的方法,直接在堆疊中建立 非參考 的自動變數,直接儲存值,因而更加高效。
基本類型一覽
Java 的每種基本類型在不同硬體架構下的大小都是固定的。以下是 Java 中所有基本類型的詳細資訊:
Primitive | 大小 | 最小值 | 最大值 | 包裝器類型 |
---|---|---|---|---|
boolean | - | - | - | Boolean |
char | 16 bits | Unicode 0 | Unicode 2<sup>16</sup> - 1 | Character |
byte | 8 bits | -128 | +127 | Byte |
short | 16 bits | -2<sup>15</sup> | +2<sup>15</sup> - 1 | Short |
int | 32 bits | -2<sup>31</sup> | +2<sup>31</sup> - 1 | Integer |
long | 64 bits | -2<sup>63</sup> | +2<sup>63</sup> - 1 | Long |
float | 32 bits | IEEE 754 | IEEE 754 | Float |
double | 64 bits | IEEE 754 | IEEE 754 | Double |
void | - | - | - | Void |
- 所有類型都有正負號:Java 中沒有無符號(unsigned)類型。
- boolean 佔用的空間沒有明確指定,僅定義為能夠取字面值
true
或false
。
包裝器(Wrapper Classes)
每種基本類型都有對應的包裝器類型,用於在堆中建立一個非基本類型的物件。這對於需要物件形式的情境(如集合框架)非常有用。
char c = 'x';
Character ch = new Character(c);
Character ch = new Character('x');
// Java SE5 自動包裝功能會自動將 primitive 轉換為包裝器類型
Character ch = 'x';
// 反向轉換
char c = ch;
Java SE5 引入了自動包裝(Autoboxing)和自動拆箱(Unboxing),使得基本類型和包裝器類型之間的轉換更加便捷。
高精度數字
Java 提供了兩個高精度計算用的類別:
- BigInteger:支援任意長度的整數而不會丟失資料。
- BigDecimal:支援任意長度的浮點數,可以進行精確的貨幣計算。
這兩個類別雖然在功能上類似於包裝器類型,但沒有對應的基本類型。它們的方法需要通過函數調用來使用,這使得運算速度相對較慢,但提供了更高的精度。
Java 中的陣列(Array)
在 Java 中,陣列是一種特殊的物件,具有以下特性:
- 初始化保證:Java 會確保陣列在創建時被初始化。
- 範圍檢查:不能在範圍之外訪問陣列元素,否則會拋出
ArrayIndexOutOfBoundsException
。 - 引用的創建:當創建一個陣列時,實際上是創建了一個指向陣列的參考。
例如:
int[] numbers = new int[5];
這行程式碼創建了一個包含 5 個整數的陣列,並將其參考賦值給 numbers
。
Java 的作用域(Scope)
Java 與 C/C++ 的作用域差異
在 C/C++ 中,作用域是由大括號的位置決定的。例如:
{
int x = 12;
// Only x available
{
int q = 96;
// Both x and q available
}
// Only x available
// q is "out of scope"
}
然而,在 Java 中,內層作用域中不能重複定義外層作用域中的變數:
{
int x = 12;
{
int x = 96; // Illegal: 重複定義
}
}
這種設計避免了變數名衝突,提高了程式碼的可讀性和維護性。
物件的作用域
Java 中的物件不具備和基本類型一樣的生命週期。當使用 new
建立物件時,該物件可以存活於其作用域之外。例如:
{
String s = new String("a string");
// End of scope
}
// reference s 已經消失,但 String 物件仍存在於記憶體中
當參考 s
超出作用域後,s
指向的 String
物件仍會保留在記憶體中,直到垃圾回收器(Garbage Collector,GC)將其回收。Java 的垃圾回收機制能自動監視並回收不再被引用的物件,避免了程式人員需要手動管理記憶體,從而消除了因忘記釋放記憶體而導致的內存洩漏問題。
總結
理解 Java 中物件與參考的運作方式、記憶體的分配區域、基本類型與包裝器類型的區別、以及作用域的管理,對於編寫高效、穩定的 Java 程式至關重要。通過掌握這些核心概念,你將能更自信地在 Java 的世界中駕馭各種挑戰。
希望這篇文章能夠幫助你更深入地理解 Java 的基礎概念,並在你的程式設計旅程中提供有價值的參考。
Comments ()