[Think in Java]第2章 一切都是對象 如果我們說另一種不同的語言,那麼我們就會發覺有一個有些不同的世界 -- Luduing Wittgerstein 儘管Java基於C++,但相比之下,Java是一種更純粹的面向對象程式設計語言. Java語言假設我們只進行面向對象的程式設計(OOP ...
[Think in Java]第2章 一切都是對象
如果我們說另一種不同的語言,那麼我們就會發覺有一個有些不同的世界
-- Luduing Wittgerstein
儘管Java基於C++,但相比之下,Java是一種更純粹的面向對象程式設計語言.
Java語言假設我們只進行面向對象的程式設計(OOP)。
2.1 用引用操縱對象
每種語言都有自己操縱記憶體中元素的方式,1. 直接操縱 2. 或者基於某種特殊語法的間接表示。
引用可以單獨存在,你擁有一個引用,並不一定需要有對象和它關聯。
String s;
這裡創建了一個引用,並不是對象。如果此時向s發送消息,那麼會報運行時錯誤,因為它實際上並沒有和任何對象關聯。更加安全的做法是:創建一個引用的同時便進行初始化
String good = “good”;
這種創建方式是java語言的一個特性:字元串可以用帶引號的文本初始化。更通用的方式是用new。
2.2 必須由你創建所有對象
一個引用被創建了,就希望它能與一個新的對象相關聯。通常用new操作符來實現這一目的,是以new關鍵字進行對象的初始化,new關鍵字的意思是:”給我一個新的對象”(上面的例子可以寫成)
String good = new String("good");
表示: "給我一個新的字元串",而且通過提供一個初始字元串,給出了怎樣產生這個String的信息!
2.2.1 對象存儲到什麼地方
素質三連問:程式運行時,對象是怎麼進行放置安排的?記憶體是怎麼樣分配的?你個垃圾會嗎?
-
寄存器 最快的存儲區
-
棧 位於通用RAM
-
堆 通用記憶體池
-
常量存儲
-
非RAM存儲
(更加詳細的內容,參考《深入理解JVM》)
2.2.2 特例:基本類型 (由父標題推出:這些對象是Java已經幫你創建好了的)
基本類型之所以要特殊對待,是因為new將對象存儲在“堆“里,故用new創建一個對象——特別是小的,簡單的變數,往往不是很有效。因此,不用new創建變數,針對基本變數創建一個並非是引用的”自動“變數,這個變數直接存儲”值“,並置於棧中,因此更加高效。
java的基本類型的大小是固定的,這也是可移植性的原因之一!
需要註意的是 boolean 沒有確定大小,只有字面值 true和false
//這裡要有一張基本類型的總結圖
基本類型有著對應的包裝類,使得可以在對堆中創建一個非基本對象,用來表示對應的基本類型。
char c = "x";
Character ch = new Character(c);
//或者
Character ch = new Character("x");
Java SE5 有自動裝箱和拆箱的功能。
Character ch = "x"; //基本類型轉換成包裝類型 ,裝箱
char c = ch; //包裝類型反向轉換基本類型 ,拆箱
//包裝類的後面再詳細介紹!
高精度數字 :以調用方法的形式進行運算,運算速度會比較慢;犧牲速度換取了精度!
-
BigInteger
支持任意精度的整數,不會丟失任何數據
-
BigDecimal
支持任何精度的定點數,例如貨幣匯率的計算!
2.2.3 Java中的數組
java中的數組需要被初始化,而且不能超出數據的範圍,這種範圍檢查需要以少量的記憶體開銷和運行時的下表檢查為代價的!但由此換來了安全性和效率的提高,小小的記憶體開銷可以說很值得了!
當創建一個數組對象時,就是創建了一個引用數組,並且引用都會自動被初始化為一個特定值,該值擁有自己的關鍵字null!一旦java看到了null,就知道這個引用還沒有指向某個對象,在使用任何引用時,都要為其指定一個對象! ( 關於數組更詳細的內容,見後面章節!)
2.3 永遠不要銷毀對象 (對應著JVM的垃圾回收機制)
2.3.1 作用域
作用域決定了在其內定義的變數名的可見性和生命周期!
註意代碼
//這是c++,較大的作用域的變數可以”隱藏”!
{
int x = 12;
{
int x = 24; //在java中,非法的,x在之前已經被使用了
}
}
2.3.2 對象的作用域
java對象不具備和基本類型的生命周期。當用new創建了一個java對象時,它可以存活於作用域之外。
{
String s = new String("a String");
}//end of scope
此時,引用s在作用域就是花括弧內,超過該區域無法再訪問這個對象,但是s指向的String對象仍繼續占據記憶體空間。
問題素質三連問:如何防止這些超過了作用域卻依舊存活在記憶體空間的對象撐爆你的記憶體,讓你的程式終止呢?
java有垃圾回收器(GC,garbige collector),用來監視用new創建的所有對象,那些不再被引用的對象的記憶體空間將會被釋放掉,以便給新對象使用!
吹一波java的垃圾回收機制:不需要關心對象的銷毀,消除了記憶體泄漏的顧慮一定程度上!
2.4 創建新的數據類型 :類
用class 關鍵字來聲明類
class ATypeClass { /*class body*/ }
用new來創建這個類型的對象
ATypeClass a = new ATypeClass();
2.4.1 欄位和方法
類中有兩種元素: 1.欄位(也叫數據類型)和方法(也叫成員函數)
欄位可以是任意類型的對象,可以通過其引用與其進行通信,也可以是基本類型的一種。
如果欄位是某個對象的引用,那必須初始化該引用(就是用new一下)
每個對象都有其欄位空間;普通欄位不能在對象間共用。
//定義一個類
class DataOnly{
int i;
double d;
boolean b;
}
//創建對象
DataOnly dataOnly = new DataOnly();
給欄位賦值
dataOnly.i = 47;
dataOnly.d = 40.0;
dataOnly.b = false;
給對象欄位賦值
myPlane.leftTank.capacity = 100;
基本成員預設值
如果類的某個成員是基本數據類型,即使沒有初始化(既沒有new一下)java也會自動給它一個預設值。
基本類型 | 預設值 |
---|---|
boolean | false |
chat | '\u0000'(null) |
byte | (byte)0 |
short | (short)0 |
int | 0 |
long | 0.L |
float | 0.0f |
double | 0.0d |
java會自動幫你初始化這些基本類型的成員變數,但是這種方法並不適用於局部變數(幾並非某個類的欄位)。如果某個方法中定義了:
int x;
那麼變數x得到的可能是任意值,而不會被自動初始化為0,java在編譯時會返回一個錯誤,告訴你這個局部變數沒有初始化
2.5 方法,參數和返回值
方法的基本組成:名稱,參數,返回值,方法體
ReturnType methodName( /* Argument list*/) {
/*Method body*/
}
返回類型:調用方法之後從方法返回的值
參數列表:要傳給方法的信息類型和名稱
方法簽名=方法名+參數列表 ,它唯一地標識出某個方法
方法是類的一部分,只能通過對象調用(當然也有直接類調用的,static方法就是針對類調用的,後詳解 見2.6.3)
方法調用方法:
objectName.methodName(arg1,arg2);
int x = a.f(); //向對象發送消息
返回值的類型必須要和x的類型相容!
2.5.1 參數列表
方法的參數列表指定要傳遞給方法什麼樣的信息,採用的都是對象的形式。在列表中指定對象的類型和名字,但這裡實際上傳遞的是對象的引用。
例子:接受String為參數,返回該字元串長度的兩倍
int storage(String s){ return s.length()*2; }
這裡s一旦傳遞給此方法,就可以把他當作其他對象一樣進行處理。如果不想返回任何值,
void nothing(){ /****/ }
2.6 構建一個Java程式
2.6.1 名字可見性
java使用包名來解決文件名字衝突的問題,創建名字空間來唯一確定一個類的位置
net.mindview.utility.fobiles
com.sean.Intruduce.class
2.6.2 運用其他構件(導入其他包內的類)
使用import關鍵字
import java.util.ArrayList; //導入指定某個類 import java.util.* //導入指定某個包下的所有類
2.6.3 static關鍵字
static 出現的兩種原因:
-
只想為某特定域分配單一存儲空間
-
即使沒創建對象,也能調用這個方法
在欄位前加上 static 就定義了一個靜態變數
class StaticTest{ static int i = 47; }
StaticTest st1 = new StaticTest(); StaticTest st2 = new StaticTest();
這裡的 st1.i和st2.i指向同一個存儲空間,具有相同的值48.//媽的,為什麼是48
static的引用方法
-
通過一個對象去定位它,st1.i;
-
通過類名直接引用,StaticTest.i;
對於方法而言,也是類似的。
class Person{ staic void hello( ... ); }
引用方法:
//通過對象來調用靜態方法 Person p = new Person(); p.hello(); //類名直接調用靜態方法 Person.hello();
2.7 第一個java程式
//HelloDate.java import java.util.*; public class HelloDate{ public static void main(String[] args){ System.out.print("Hello. It's:"); System.out.print(new Ddate()); } }
import關鍵字聲明要使用的包, java.lang是預設導入到每個java文件中的,所以他的類可以直接被使用.但是這裡java.lang裡面沒有Date類,所以得導入有Date類的包. 也可以具體的顯示導入哪個類
import java.util.Date
類的名字和文件名要相同.main
2.7.1編譯和運行
//編譯java文件 javac HelloDate.java //運行 java HelloDate
2.8註釋和嵌入式文檔
// 這是當行註釋 /* 這是多行註釋... 這是多行註釋... */ /** 這是文檔註釋,開頭多了一個*號. */
2.8.1 註釋文檔
使用javadoc 來提取註釋以及特殊的標簽,並把相鄰的類和方法都抽取出來,輸出一個HTML文件,形成一份說明文檔
2.8.1 語法
javadoc的兩種方式
-
嵌入HTML
-
文檔標簽
獨立文檔標簽是用@開頭的命令,並且是在行首, 行內的@ 需要用花括弧內,才能視為標簽
註釋文檔的三種類型
-
類 位於類定義之前
-
域 域定義之前 (感覺叫變數註釋比較容易理解)
-
方法 方法定義之前
例子
/**這是類註釋*/ public class Documents{ /**這是域註釋*/ int i; /**這是方法註釋*/ public void f(){.....} }
需要註意的是:javadoc只能對public和protected成員進行文檔註釋, private和包內訪問成員的註釋會被忽略掉 (不過可以使用 -private 進行標記,達到被輸出的效果)
2.8.3 嵌入HTML
可以對註釋文檔加上html標簽進行格式上的優化,因為javadoc輸出的就是HTML文件格式
例如:
/** *<pre> *System.out,print(new Date()); *</pre> * *here is the list: *<ol> *<li> item one *<li> item two *</ol> */
不需要自己加標題 (h1,hr)之類的標簽,javadoc自己會生成標題.不然會衝突
2.8.4 一些標簽的實例
-
@see 引用其他類
格式:
@see classname @see fuuly-qualified-classname @see fully-qualified-classname#method-name
-
{@link package.class#member label}
和@see類似, 用於行內,且用label作為超鏈接文本.
-
{@docRoot} 生成到根目錄的相對路徑
-
{@inheritDoc} 表示: 從當前這個類的最直接父類中繼承相關文檔到當前的文檔註釋中
-
@version 版本信息
格式: @version version-information
-
@author 作者信息
-
@since 最早的版本
-
@param 用於方法中
格式: @param parameter-name description
-
@return
格式: @return description ,用於描述返回值的含義
-
@throws 拋出異常
-
@deprecated
2.9 編碼風格
-
類名的首字母要大寫,如果幾個單片語成,把它們合在一起
class AllTheColorsOfTheRainbow{....}
-
欄位以及對象引用名稱等,第一個字母採用小寫
class AllTheColorsOfTheRainbow{ int anIntegerRepresentRainboow; void changeTheValue(int newValue){....} }