[Java] 基礎知識

1. 一切都是對象


1.1 面向對象設計語言

Java是基於C++的,但相比之下,Java是更純粹的面向對象程序設計語言。C++和Java都是混合/雜合型語言,允許多種編輯風格,C++之所以成國一種雜合型語言主要是因爲它支持與C語言的向後兼容。Java語言假設我們只進行面向對象的程序設計,在Java中幾乎一切都是對象。

每種編程語言都有自己的操縱內存中元素的方式,因爲在Java中一切都被視爲對象,所以可採用單一固定的語法來操縱元素,即是對象的一個引用。擁有一個引用並不一定需要有一個對角與之關聯,例如創建一個String引用:

String s;

但這裏只是創建一個引用,並不是對象,如此此時向s發送一個消息,就會返回一個運行時錯誤。一種安全的做法是創建一個引用的同時便進行初始化,例如:

String s = "Hello World";

通常用new操作符來使創建的引用和新的對象相關聯,例如:

String s = new String("Hello World");


1.2 對象存儲

有五個不同的地方可以存儲數據。

1) 寄存器

這是最快的存儲區,因爲它位於處理器內部。但是寄存器的數量極其有限,所有寄存器根據需求進行分配,不能直接控制。

2) 堆棧

位於通用RAM中,但通過堆棧指針可以從處理器那裏獲得直接支持。這是一種快速有效的分配存儲的方法,僅次於寄存器。創建程序時,Java系統必須知道存儲在堆棧內所有項的確切生命週期,以便移動堆棧指針。

3) 堆

一種通用的內存池(也位於RAM區),用於存放所有的Java對象。堆不同於堆棧的好處是:編譯器不需要知道存儲的數據在堆裏存活多長時間,因此在堆裏分配存儲有很大的靈活性。只需要用new寫一行簡單的代碼,執行這行代碼時,會自動在堆裏進行存儲分配。

4) 常量存儲

常量值通常直接存放在程序代碼內部,因爲它們永遠不會被改變。有時在嵌入式系統中,常量本身會和其他部分隔離開,這種情況下可以選擇將其存放在ROM中。

5) 非RAM存儲

如果數據完全存活於程序之外,那麼它可以不受程序的任何控制,在程序沒有運行時也可以存在,比如流對象和持久化對象。


1.3 基本類型

在程序設計中經常用到一系列類型,需要特殊對待。之所以特殊對待,是因爲new將對象存儲在堆裏,所以用new創建一個對象(特別是小的、簡單的變量)往往不是很有效。因此對於這些類型,Java採取與C++相同的辦法,即不用new來創建變量,而是創建一個並非是引用的自動變量,這個變量直接存儲值,並置於堆棧中,因此更加高效。

Java要確定每種基本類型所佔存儲空間的大小,它們的大小並不隨機器硬件架構的變化而變化,這也是Java程序比其他大多數語言編寫的程序更具可移植性的原因之一。

基本類型
大小最小值最大值包裝器類型
booleanN/AN/AN/ABoolean
char16-bitUnicode 0Unicode 2^16-1Character
byte8 bits-128+127Byte
short16 bits-2^15+2^15-1Short
int32 bits-2^31+2^31-1Integer
long64 bits-2^63+2^63-1Long
float32 bitsIEEE754IEEE754Float
double64 bitsIEEE754IEEE754Double
voidN/AN/AN/AVoid

基本類型具有的包裝器類,使得可以在堆中創建一個非基本對象,用來表示對應的基本類型,例如:

char c = 'x';
Character ch = new Character('x');

Java提供了兩個用於高精度計算的類:BigInteger和BigDecimal,雖然它們大體上屬於包裝器類的範疇,但是都沒有對應的基本類型。不過,這兩個類包含的方法,提供的操作與對基本類型所能執行的操作相似。只不過必須以方法調用方式取代運算符方式來實現,所以運算速度會比較慢。BigInteger支持任意精度的整數,BigDecimal支持任何精度的定點數。

Java的主要目標之一是安全性,所以在C和C++中使用數組(也就是內存塊)的危險在Java裏不會再出現,Java確保數組會被初始化,而且不能在它的範圍之外被訪問。這種範圍檢查是以每個數組上少量的內存開銷及運行時的下標檢查爲代價的。

當創建一個數組對象時,實際上就是創建一個引用數組,並且每個引用都會自動被初始化爲一個特定值,該值擁有自己的關鍵字null。還可以創建用來存放基本數據類型的數組,同樣編譯器也能確保這種數組的初始化,因爲它會將這種數組所佔的內存全部置零。


1.4 變量生命週期

大多數語言都有作用域的概念,作用域決定了在其內定義的變量名的可見性和生命週期。在C、C++和Java中,作用域由花括號的位置決定,例如:

{
  int x = 10;
  {
    int y = 20;
  }
}

在作用域裏定義的變量只可用於作用域結束之前。Java是自由格式的語言,所以空格、製表符、換行都不會影響程序的執行結果,縮排格式會使Java代碼更易閱讀。Java中不能這樣寫:

{
  int x = 10;
  {
    int y = 20;
  }
}

編譯器將會報告變量x已經定義過,所以在C和C++裏將一個較大作用域的變量隱藏的做法在Java裏不不允許的。

Java對象不具備和基本類型一樣的生命週期,當用new創建一個Java對象時,它可以存活於作用域之外,例如:

{
  String s = new String("Hello World");
}

引用s在作用域終點就消失了,然而s指向的String對象仍繼續佔據內存空間,我們無法在這個作用域之後訪問這個對象,因爲對它唯一的引用已超出了作用域的範圍。

Java有一個垃圾回收器,用來監視new創建的所有對象,並辨別不會再被引用的對象,隨後,釋放這些對象的內存空間,以便供其他新的對象使用,這樣就消除了例如內存泄漏的問題。


1.5 類

class關鍵字用來表示創建一個新的類,例如:

class MyClass {
}

這就引入了一種新的類型,儘管類主體沒有實質的內容,還不能用它做太多事情,然而,已經可以用new來創建這種類型的對象,例如:

MyClass c = new MyClass();

一旦定義了一個類,就可以在類中設置兩種類型的元素:字段和方法。字段可以是任何類型的對象,可以通過其引用與其進行通信,也可以是基本類型中的一種。如果字段是對某個對象的引用,那麼必須初始化該引用,以便使其與一個實際的對象相關聯。

每個對象都有用來存儲其字段的空間,普通字段不能在對象間共享。可以給字段賦值,但首先必須引用一個對象的成員,具體的實現爲,在對象引用的名稱之後緊接着一個句點,然後接對象內部的成員名稱,例如:

class MyClass {
  int i;
}
MyClass c = new MyClass();
c.i = 100;

若類的某個成員是基本數據類型,即使沒有初始化,Java也會確保它獲得一個默認值。然而這種初始化方法並不適用於局部變量,例如在某個方法中定義一個變量,那麼變量值可能是任意值(與C和C++一樣)。

許多程序設計語言用函數來描術命名子程序,而在Java裏常用方法來表示。Java的方法決定了一個對象能夠接收什麼樣的消息,方法的基本組成部分包括:名稱、參數、返回值和方法體,例如:

int mymethod(int argu) {
}

返回類型描述的是在調用方法之後從方法返回的值。參數列表給出了要傳給方法的信息的類型和名稱。方法名和參數列表唯一標識一個方法。Java中的方法只能作爲類的一部分來創建,方法只有通過對象才能被調用,且這個對象必須能執行這個方法調用,例如:

c.mymethod(100);

這種調用方法的行爲通常被稱爲發送消息給對象。在上例中,消息是mymethod,對象是c。方法的參數列表指定要傳給方法什麼樣的信息,這些信息採用的都是對象形式,因此在參數列表中必須指定每個所傳遞對象的類型及名字。像Java中傳遞對象的場合一樣,這裏傳遞的實際上也是引用,並且引用類型必須正確。

可以定義方法返回任意的類型,如果不想返回任何值,可以指示方法返回void。若返回類型是void,return關鍵字的作用只是用來退出方法。因此,沒有必要到方法結束時才離開,可在任何地方返回。但如果返回類型不是void,那麼無論在何處返回,編譯器將強制返回一個正確類型的返回值。


2. 構建Java程序


2.1 基本問題

Java採用了一種全新的方法來避免相同名字產生的衝突問題。爲了給一個類庫生成不會與其他名字混淆的名字,Java設計者希望程序員反過來使用自己的Internet域名,這樣可以保證它們是獨一元二的。例如net.test.utility.foibles。

如果想在程序中使用預先定義好的類,可以使用關鍵字import來準確告訴編譯器想要的類是什麼。import指示編譯器導入一個包,也就是一個類庫。大多時候,我們使用與編譯器附在一起的Java標準類庫裏的構件,就不必寫一長串的反轉域名,例如:

import java.util.ArrayList;

這些代碼告訴編譯器,需要使用Java的ArrayList類。但是util包含了許多類,有時想使用其中幾個,又不想逐一聲明,可以使用通配符"*"來達到目的,例如:

import java.util.*

通常來說,創建類時只是在描述類的對象的外觀與行爲,除非用new創建類的對象,否則實際上並未獲得任何對象。執行new來創建對象時,數據存儲空間才被分配,其方法才供外界調用。有時只想爲某特定域分配單一存儲空間,而不考慮創建多少對象,或者希望某個方法不與包含它的類的任何對象關聯在一起,可以通過static關鍵字來實現。

當聲明一個事物是static時,意味着這個域或方法不會與包含它的類的任何對象實例關聯在一起。所以,即使從未創建某個類的任何對象,也可以調用其static方法或訪問其static域。只須將static關鍵字放在定義之前,就可以將字段或方法設定爲static,例如:

class MyClass {
  static int i = 10;
}

現在,即使創建了兩個MyClass對象,MyClass.i也只有一份存儲空間,這兩個對象共享同一個i。引用static變量有兩種方法,可以通過一個對象去定位它,如c.i,也可以通過其類名直接引用,例如:

MyClass.i++;

類似邏輯也應用於靜態方法,即可以像其他方法一樣,通過一個對象來引用某個靜態方法,也可以通過類名加以引用,定義靜態方法的方式也與定義靜態變量的方式相似,例如:

class MyClass {
  static void method() {
  }
}
MyClass.method();


2.2 第一個程序

我們將編寫一個完整的程序,此程序先是打印一個字符串,然後是打印當前日期,這裏用到了Java標準庫裏的Date類:

import java.util.*;
public class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello World.");
    System.out.println(new Date());
  }
}

在每個程序的開頭,必須聲明import語句,以便引入在文件代碼中需要用到的額外類。之所以說是額外,是因爲有一個特定類會自動被導入到每一個Java文件中:java.lang。查找Java的JDK文檔,java.lang裏沒有Date類,所有必須導入另外一個類庫才能使用它,而java.lang的system類則包含了靜態的out對象,直接使用即可。

要編譯、運行這個程序,首先必須要有一個Java開發環境,可以使用Sun免費提供的JDK開發環境。安裝好JDK後,還需要設定好路徑信息,以確保計算機能找到javac和java這兩個文件。


2.3 註釋和文檔

Java裏有兩種註釋風格。一種是C語言風絡的註釋,以"/*"開始,隨後是註釋內容,最後以"*/"結束,例如:

/* This is a comment */

第二種風格以一個"//"開頭,直到句末,例如:

// This is a comment

代碼文檔撰寫的最大問題,就是如果文檔與代碼是分離的,那麼在每次修改代碼時,都需要修改相應的文檔。解決的方法是將代碼同文檔鏈接起來,達到這個目的最簡單的方法是將所有東西都放在同一個文件內,此外還必須使用一種特殊的註釋語法來標記文檔,還需要一個工具用於提取那些註釋。

javadoc便是用於提取註釋的工具,它是JDK安裝的一部分。它採用了Java編譯器的某些技術,查找程序內的特殊註釋標籤。它不僅解析由這些標籤標記的信息,也將毗鄰註釋的類名或方法名抽取出來。如此就可以用較少的工作量生成較好的程序文檔。全面的javadoc描述可從java.sun.com下載。


3. 操作符


3.1 操作符和優先級

Java是建立在C++基礎上的,所以C和C++程序員應該非常熟悉Java的大多數操作符。當然,Java也做了一些改進與簡化。

操作符接受一個或多個參數,並生成一個新值。參數的形式與普通的方法調用不同,但結果是相同的。操作符用於操作數,生成一個新值,有些操作符可能會改變操作數自身的值。幾乎所有的操作符都只能操作基本類型,例外的操作符是"="、"=="和"!=",這些操作符能操作所有的對象,除此以外,String類支持"+"和"+="。

當一個表達式中存在多個操作符時,操作符的優先級決定了各部分的計算順序。Java對計算順序做了特別的規定。其中最簡單的規則就是先乘除後加減。其他優先級規則因爲容易被忘記,所以應該用括號明確規定計算順序。


3.2 賦值

賦值使用操作符"="。它的意思是最右邊的值複製給左邊的值。右值可以是任何常數、變量或者表達式,但左值必須是一個明確的已命名的變量,例如:

a = 1;

基本類型存儲了實際的數值,而並非指向一個對象的引用,所以在爲其賦值的時候,是直接將一個地方的內容複製到了另一個地方。但是在爲對象賦值時,真正操作的是對對象的引用,所以如果將一個對象賦值給另一個對象,實際是將引用從一個地方賦值到另一個地方,例如:

MyClass c1 = new MyClass();
MyClass c2 = new MyClass();
c2 = c1;

由於賦值操作的是一個對象的引用,所以修改c2的同時也會改變c1,這種特殊的現象稱作別名現象,是Java操作對象的一種基本方式。


3.3 算術操作符

Java的基本算術操作符包括加號"+"、減號"-"、除號"/"、乘號"*以及取模操作符"%"。整數除法會直接去掉結果的小數位。Java也使用簡化符號同時進行運算與賦值操作,這用操作符後緊跟一個等號來表示,它對於Java中的所有操作符都適用,例如:

x += 1;

一元減號"-"和一元加號"+"與二元減號和加號都使用相同的符號。根據表達式的書寫形式,編譯器會自動判斷出使用的是哪一種,例如:

x = -y;

和C類似,Java提供了大量的快捷運算,遞增和遞減是其中兩種。遞減操作符是"--",意爲減少一個單位,遞增操作符是"++",意爲增加一個單位。這兩個操作符各有兩種使用方式,通常稱爲“前綴式”和“後綴式”。前綴遞增表示"++"操作符位於變量或表達式的前面,而後綴遞增表示"++"操作符位於變量或表達式的後面,前綴遞減和後綴遞減的情況類似。對於前綴操作符,會先執行運算,再生成值,而對於後綴操作符,會先生成值,再執行運算,例如:

int i = 1;
i++;
--i;


3.4 關係運算符

關係操作符生成的是一個boolean結果,它們計算的是操作數的值之間的關係。如果關係是真實的,關係表達式會生成true,如果關係不真實,則生成false。關係操作符包括小於"<"、大於">"、小於或等於"<="、大於或等於">="、等於"=="以及不等於"!="。等於和不等於適用於所有基本類型,而其他比較符不適用於boolean類型,例如:

Integer n1 = new Integer(10);
Integer n2 = new Integer(10);
System.out.print(n1 == n2);

這個語句的輸出的結果是false,儘管對象的內容相同,然而對象的引用卻是不同的,而"=="和"!="比較的就是對象的引用。如果想比較兩個對象的實際內容是否相同,必須使用所有對象都適用的特殊方法equals(),但這個方法不適用於基本類型,基本類型直接使用"=="和"!="。


3.5 邏輯操作符

邏輯操作符與"&&"、或"||"、非"!"能根據參數的邏輯關係,生成一個布爾值,例如:

int i = 1;
System.out.println(i < 10 && i > 0);
System.out.println(i < 8 || i > 4);

邏輯操作只可就用於布爾值,不可將一個非布爾值當作布爾值在邏輯表達式中使用。如果在應該使用Strin值的地方使用了布爾值,布爾值會自動轉換成適當的文本形式。

當使用邏輯操作符時,會遇到一種短路現象,即一旦能夠明確無誤地確定整個表達式的值,就不再計算表達式餘下部部,因此,整個邏輯表達式靠後的部分有可能不會被運算,例如:

boolean b1 = false && true;
boolean b2 = true || false;


3.6 直接常量

一般而言,如果在程序裏使用了直接常量,編譯器可以準確知道要生成什麼樣的類型,但有時候卻是模棱兩可的,需要用與直接常量相關的某些字符來額外增加一些信息。直接常量後面的後綴字符標誌了它的類型。若爲大寫或小寫的字母L代表long,大寫或小寫的字母F代表float,大寫或小寫的字母D,代表double,例如:

long i = 100L;
float j = 1F;
double k = 100D;

十六進制數適用於所有整數數據類型,以前綴0x以及後續的0-9或字母的a-f來表示。八進數數由前綴0以及後續的0-7的數字來表示,例如:

int i1 = 0x2f;
int i2 = 0177;

在Java的指數計數中,使用e代表10的冪次,所以在Java中看到像1.23e-45f這樣的表達式時,含義爲1.23乘以10的負45次方,如果編譯器能夠正確識別類型,就不必在數值後附加字符。


3.7 位操作符

按位操作符用來操作整數基本數據類型中的二進制位,按位操作符會對兩個參數中對應的位執行布爾代數運算,並最終生成一個結果。如果兩個輸入位都是1,則按位與"&"生成一個輸出位1,否則生成一個輸出位0。如果兩個輸入位裏只要有一個是1,則按位或"|"生成一個輸出位1,只有在兩個輸入位都是0的情況下,它纔會生成一個輸出位0。如果輸入位的某一個是1,但不全都是1,那麼按位異或"^"生成一個輸出位1。按位非"~"屬於一元操作符,生成與輸入位相反的值。按位操作符可與等號"="聯合使用,比如"&="、"|="、"^="。

移位操作符操作的運算對象也是二進制位。左移位操作符"<<"按照操作符右側指定的位數將操作符左邊的操作數向左移動,在低位補0。有符號右移位操作符">>"按照操作符右側指定的位數將操作符左邊的操作數向右移動。有符號操作符使用符號擴展,若符號爲正,則在高位插入0,若符號爲負,則在高位插入1。Java中增加了一種無符號右移位操作符">>>",無論正負,都在高位插入0。移位可與等號"="組合使用,例如"<<="、">>="或">>>="。

如果對char、byte或short類型的數值進行移位處理,那麼在移位之前,它們會被轉換爲int類型,並且得到的結果也是一個int類型,只有數值右端的低5位纔有用。若對一個long類型的數值進行處理,最後得到的結果也是long,此時只會用到數值右端的低6位。


3.8 三元操作符

三元操作符也稱爲條件操作符,它有三個操作數,其表達式採取下述形式:

boolean-exp ? value0 : value1;

如果boolean-exp表達式的結果爲true,就計算value0,如果結果爲false,就計算value1,它的結果就成爲了操作符最終產生的值。


3.9 字符串操作符

"+"和"+="操作符在Java中用以連接不同的字符串,如果表達式以一個字符串起頭,那麼後續所有操作數都必須是字符串型,例如:

int x = 0, y = 1, z = 2;
String s = "Hello ";
s += "World : ";
s = s + x + y + z

Java編譯器會將x、y、z轉換成它們的字符串形式,然後連接這些字符串。


4. 控制流程


4.1 if-else

if-else語句是控制程序流程的最基本的形式,可以按以下兩種形式使用:

if (Boolean-expression)
  statement
if (Boolean-expression)
  statement
else
  statement

布爾表達式必須產生一個布爾結果,statement代表用分號結尾的簡單語句或複合語句。


4.2 while

while、do-while用來控制循環,語句會重複執行,直到起控制作用的布爾表達式得到的結果爲止,while循環的格式如下:

while (Boolean-expression)
  statement

在循環剛開始時,會計算一次布爾表達式的值,而在語句的下一次迭×××始前會再計算一次。

do-while的格式如下:

do
  statement
while (Boolean-expression)

while和do-while唯一的區別是do-while中的語句至少會執行一次,即便表達式第一次就被計算爲false。


4.3 for

for循環是最常命名用的迭代形式,第一次迭代之前要進行初始化,隨後,它會進行條件測試,而且在每一次迭代結束時,進行某種形式的步進,for循環的格式如下:

for (initialization; Boolean-expression; step)
  statement

初始化表達式,布爾表達式,或者步進運算,都可以爲空。每次迭代前會測試布爾表達式,若獲得的結果是false,就會執行for語句後面的代碼行,每次循環結束,會執行一次步進。


4.4 foreach

Java SE5引入了一種新的更加簡潔的for語法用於數組和容器,即foreach語法,表示不必創建int變量去對由訪問項構成的序列進行計算,foreach將自動產生每一項,例如:

Random rand = new Random(100);
for (int i = 0; i < 10; i++)
  f[i] = rand.nextFloat();
for (float x : f)
  system.out.print(x);

語句定義了一個float類型的變量x,繼而將每一個f元素賦值給x。任何返回一個數組的方法都可以使用foreach,例如:

for (char c : "Hello Word!".toCharArray())
  System.out.print(c + " ");


4.5 return

在Java中有多個關鍵詞表示無條件分支,它們只是表示這個分支無需任何測試即可發生,包括return、break、continue和類似goto跳轉到標號語句的方式。

return關鍵詞有兩方面用途:一方面指定一個方法返回什麼值,另一方面它會導致當前方法退出,並返回那個值,例如:

static int test(int value1, int value2) {
  if (value1 > value2)
    return +1;
  else if (value1 < value2)
    return -1;
  else
    return 0;
}

在任何迭代語句的主體部分,都可用break和continue控制循環的流程,其中break用於強行退出循環,而continue則停止執行當前的迭代,退回循環起始處,開始下一次迭代,例如:

for (int i = 0; i < 100; i++) {
  if (i % 10 == 0)
    continue;
  if (i % 50 == 0)
    break;
  System.out.print(i + " ");
}

在Java中,goto仍是保留字,但在語言中並未使用它。然而Java也能完成一些類似於跳轉的操作:標籤。break和continue通常只中斷當前循環,但若隨同標籤一起使用,它們就會中斷循環,直到標籤所在的地方。需要留意的是,標籤唯一起作用的地方剛好是在迭代語句之前,例如:

label1:
for (int i = 0; i < 100; i++)
  for (int j = 0; j < 100; j++) {
    if (i == 50)
      break label1;
    if (j == 50)
      continue label1;
  }


4.6 switch

switch語句根據整數表達式的值,可以從一系列代碼中選出一段去執行,它的格式如下:

switch (integral-selector) {
  case integral-value1: statement; break;
  case integral-value2: statement; break;
  case integral-value3: statement; break;
  defalut: statement
}

Integral-selector是一個能夠產生整數值的表達式,switch能將這個表達式的結果與每個ingegral-value比較,若發現相符的,就執行對應的語句,若沒有發現相符的,就執行default語句。break是可選的,若省略break,會繼續執行後面的case語句,直到遇到break爲止。



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