# http://docs.oracle.com/javase/7/docs/api/
一. include 與 import 的差異 :
Java 程式頭常會用 import 指令匯入所需之類別或類別庫 (用 .*), 這似乎與 C/C++ 的 imclude 很像, 功能上都是匯入 library, 但其實兩者不同. C/C++ 的 include 會在編譯時把指定的函式全部匯入程式裡, 但 Java 的 import 並非將類別匯入程式中, 只是告訴編譯器程式中會用到的類別的路徑而已. 當使用 * 匯入函式庫時, 程式中用不到的類別並不會造成程式膨脹或執行效能下降, 因為 JVM 是在執行時期才會把實際使用到的類別內容匯入.
要注意的是, 位於相同目錄下的其他類別不需用 import 指令匯入, 直接使用即可, 因為編譯器會自動在目前目錄底下搜尋使用到的其他類別, 搜尋不到時才到 import 指令區去找該類別的套件位置.
二. 變數與資料型態 :
Java 是物件導向語言, 主要的資料型態當然是物件, 字串與陣列都是物件, 但為了增進執行效率也提供了 8 種原始資料型態, 這使得 Java 不能像 Small Talk 語言那樣稱為 100% 的物件導向語言. Java 的資料類型如下圖所示 :
Java 的資料型別可以說只有兩類, 一個是原始資料型態, 一是參考型態. 原始型態又可分為數值 與布林兩大類 (字元可以視為數值型態). 而參考類型則包括字串, 陣列, 與物件.
這裡首先要特別注意的是字元, 整數, 與實數的常數表示法 :
1. 字元與字串常數表示法 :
字串常數要用雙引號括起來, 字元常值要用單引號 :
char ch='K'; //以字面值表示字元
char ch='\101'; //以 8 進位表示字元
char ch='\u0041'; //以 unicode 表示字元
String str="string"; //字串要用雙引號
字元除了用字面值 (Literal) 'A' 表示以外, 也可以使用 8 進位或 16 進位 unicode 字元編碼表示, 因為 Java 的字元本身就是用 Unicode 儲存的. 用 16 位元 unicode 字元編碼表示時, 其值為 '\u0000' 至 '\uffff' (0 至 65535), 只要將字元宣告為 int 類型, 就可顯示其整數編碼值了 :
char ch1='A';
char ch2='台';
char ch3='\u0041';
char ch4='\101';
int i1=ch1; //自動轉型為 int
int i2=ch2; //自動轉型為 int
int i3=ch3; //自動轉型為 int
int i4=ch4; //自動轉型為 int
System.out.println(ch1); //輸出 A
System.out.println(ch2); //輸出 台
System.out.println(ch3); //輸出 A
System.out.println(ch4); //輸出 A
System.out.println(i1); //輸出 65
System.out.println(i2); //輸出 21488
System.out.println(i3); //輸出 65
System.out.println(i4); //輸出 65
2. 整數常數表示法 :
Java 的整數有 byte, short, int, 與 long 四種, 整數常數預設型態是 int, 如果數值較大, 可以在最後面加上 l 或 L 表示用長整數儲存, 例如 123L.
整數常數原本只有三種表示法, 即 8, 10, 16 進位表示法, Java 7 新增了 2 進位表示法. 除了十進位表示法以外, 其他三種表示法都需要前置字元, 八進位的前置字元為 0, 十六進位為 0x 或 0X, 二進位則為 0b 或 0B :
int i1=15;
int i2=017;
int i3=0xf;
int i4=0b1111; //二進制表示法 : Java 7 才有支援
int i5=9_000_210; //以底線代表千位數分段 (Java 7 才有支援)
System.out.println(i1); //輸出 15
System.out.println(i2); //輸出 15
System.out.println(i3); //輸出 15
System.out.println(i4); //輸出 15
System.out.println(i5); //輸出 9000210
注意, 這裡的 ox/0X 與 0b/0B 是 Java 兩個不分大小寫的地方之一. 其次, 不論是 2/8/10/16 哪一種進位表示法, 整數常數預設型態是 int (例如上面的例子最後面都沒有 l 或 L, 所以預設都以 int 存放), 但也可以在數值最後面加上 l 或 L 表示要用 long 型態儲存 (8 個 bytes), 例如 :
long i1=15L;
long i2=15l;
這是 Java 兩個不分大小寫的地方之二. 這樣就會出現一個問題, 當為一個變數賦值時, 就須考慮變數與常數值長度的匹配, 例如 :
int i1=15L;
常數 15 是用 long 儲存, 但變數 i1 卻宣告為 int, 這在編譯時就會出現 "error: possible loss of precision" 的錯誤, 表示這樣的賦值可能會造成數值失去精確度. 這必須使用強制轉型才能通過編譯 :
int i1=(int)15L;
強制轉型是告訴編譯器, 我們接受可能之精確度損失, 這樣就能通過編譯了.
另外, 在 Java 7 以後開始支援商用的千位分段符號, 但用底線取代逗號.
3. 實數常值表示法 :
Java 的實數有兩個 : float 與 double, 實數常數有浮點數與科學表示法兩種表示法, 在數值末尾可以加 f/F 或 d/D 指定儲存型態, 若未指定, 則預設型態為 double :
float f1=123.45f;
float f2=1.2345e4F;
double d1=123.45; //預設為 double
double d2=123.45d;
double d3=1.2345e4D;
上面的 d1 值末尾未指定型態, 故預設為 double, 變數 d1 也須宣告為 double, 若上例中的 d1 變數宣告為 float, 則編譯時將出現 "loss of precision" 錯誤, 必須宣告為 double, 或用 float 強制轉型才能通過編譯.
其次 Java 的實數還有位數限制, 須注意這會影響數值精確度, 小數位數部分, float 最多是 7 位數, double 最多 17 位數, 位數超過時會被截掉, float 在剪掉尾巴時會四捨五入, 但 double 則直接捨去, 如下所示 :
public class test {
public static void main(String[] args) {
float f1=0.123456789F;
float f2=0.1234567890e2F;
float f3=123456789F;
double d1=0.1234567890123456D;
double d2=0.1234567890123456789e2D;
double d3=1234567890123456789D;
System.out.println(f1); //輸出 0.12345679 (8 位數不含 0)
System.out.println(f2); //輸出 12.345679 (8 位數)
System.out.println(f3); //輸出 1.23456792E8 (9 位數)
System.out.println(d1); //輸出 0.1234567890123456 (17 位數不含 0)
System.out.println(d2); //輸出 12.345678901234567 (17 位數)
System.out.println(d3); //輸出 1.23456789012345677E18 (18 位數)
}
}
另外, 實數都會以科學表示法儲存, 因此上例中 f1 會先轉成 1.23456789E-1, 因為 float 只能儲存 7 位小數, 所以會四捨五入為 1.2345679E-1, 最後顯示為 0.12345679. 同樣地, f2 先轉成 1.234567890E1, 小數部分四捨五入成 7 位數 1.2345679E1, 最後轉回 12.345679 :
f2 : 0.1234567890E2 -> 1.234567890E1 -> 1.2345679E-1 -> 12.345679
f3 : 123456789 -> 1.23456789E8 -> 1.2345679E8 -> 1.23456792E8
f3 會先轉成 1.23456789E8, 但因為 float 只能儲存 7 位小數, 因此第 8 位的 9 會四捨五入變成 1.2345679E8, 但因為小數第 8 位會有誤差, 最後得到之值為 1.23456792E8. 而變數 d3 會先轉成 1.234567890123456789E18, 因為 double 只能存 17 位小數, 所以會直接刪掉最後面的 9 變成 1.23456789012345678E18, 同樣因為第 18 位小數的誤差, 最後得到 1.23456789012345677E18.
關於浮點數精確度問題, 參考 : java中的float和double的精度问题
4. 成員變數的預設值 :
類別的成員若為原始型態變數, 即使宣告時未初始化, JDK 編譯器仍會自動賦予預設值如下, 故可通過編譯 :
public class test {
static byte by;
static short s;
static int i;
static long l;
static float f;
static double d;
static char c;
static boolean bo;
public static void main(String[] args) {
System.out.println(by); //輸出 0
System.out.println(s); //輸出 0
System.out.println(i); //輸出 0
System.out.println(l); //輸出 0
System.out.println(f); //輸出 0.0
System.out.println(d); //輸出 0.0
System.out.println(c); //輸出
System.out.println(bo); //輸出 false
}
}
但區域變數就不會自動給予預設值了, 方法中的區域變數一定要初始化才能使用, 否則無法通過編譯, 出現 "variable xxx might not have been initialized" 錯誤.
至於 String 與陣列均屬於物件, 若類別的成員變數為物件, 宣告後未初始化, 其預設值為 null.
5. 原始資料類型的包裹類別 :
雖然原始資料型態運算較快速, 但只能做單純的運算, 對於較複雜之計算必須呼叫方法, 而只有類別才有方法, 因此 Java 在 java.lang 類別庫中為 8 種基本類型定義了包裹類別 (wrapper class), 可以將原始型態包裝成類別 :
由於八個原始型態中有六個為數值型態, 因此包裹類別繼承了 java.lang.Number 為父類別. 呼叫其建構子即可建立包裹物件, 可傳入對應之原始資料型態或數值字串 :
Byte b=new Byte((byte)127);
Byte b=new Byte("127");
注意, 因為整數常值預設為 int 型態, 因此必須強制轉型為 byte, 否則編譯時會出現 "no suitable constructor found for Byte(int)" 錯誤. 其他包裹類別也是如此. 包裹類別提供了幾個有用的方法.
首先是 xxxValue() 方法, 此方法可將包裹物件的值轉成指定之數值類原始型態值傳回, 這個方法不支援 char 與 boolean, 只支援下列六種原始數值型態 : byte, short, int, long, float, 與 double :
Double -> byte, short, int, long, float
Float -> byte, short, int, long, double
Long -> byte, short, int, float, double
Integer -> byte, short, long, float, double
Short -> byte, int, long, float, double
例如 :
Byte b=new Byte((byte)127); //必須強制轉型
Short s=new Short("32767");
Integer i=new Integer(12345); //不須強制轉型
Float f=new Float(1234.56789);
Double d=new Double("23.59123e10");
System.out.println(b.doubleValue()); //輸出 127.0
System.out.println(s.floatValue()); //輸出 32767.0
System.out.println(f.intValue()); //輸出 1234 (有誤差)
System.out.println(d.floatValue()); //輸出 2.35912298e11 (有誤差)
注意傳入原始型態常數時可能需要強制轉型, 而且浮點數轉型時可能會有誤差出現.
第二個常用方法是 parseXxx() 方法, 此方法會剖析傳入之字串, 傳回指定之原始資料型態值, 支援除了 char 以外的 7 種原始資料型態 :
byte parseByte(String str)
short parseShort(String str)
int parseInt(String str)
long parseLong(String str)
boolean parseBoolean(String str)
float parseFloat(String str)
double parseDouble(String str)
例如 :
byte b=Byte.parseByte("127");
int i=Integer.parseInt("123");
float f=Float.parseFloat("123.456789");
boolean b1=Boolean.parseBoolean("true");
boolean b2=Boolean.parseBoolean("True");
boolean b3=Boolean.parseBoolean("OK");
System.out.println(b); //輸出 127
System.out.println(i); //輸出 123
System.out.println(f); //輸出 123.456789
System.out.println(b1); //輸出 true
System.out.println(b2); //輸出 true
System.out.println(b3); //輸出 false
注意, 數值字串之值不可超過值域, 例如上例中的 byte 若傳入 "128", 雖可通過編譯, 但執行時會出現 "java.lang.NumberFormatException: Value out of range." 錯誤.
第三個常用方法是 equals() 與 compareTo(), 這跟 String 類別一樣, 用來比較兩個包裹物件之值. 兩者的差別只是傳回值不同, equals() 傳回 true/false, 而 compareTo() 則傳回數值, 相等傳回 0, 本物件較大傳回正數, 較小傳回負數, 例如 :
Integer i1=new Integer(127);
Integer i2=new Integer(126);
System.out.println(i1.equals(i2)); //輸出 false
System.out.println(i1.compareTo(i2)); //輸出 1
另外, Float 與 Double 提供了 isNaN() 與 isInfinite() 用來判斷是否為數值, 以及是否為無限大 (即除以 0 情況), 此為類別方法, 可以直接呼叫, 傳入計算式 :
System.out.println(Double.isInfinite(1.0/0.0)); //輸出 true (除以 0 為無限大)
System.out.println(Double.isNaN(1.0/0.0)); //輸出 false (無限大仍為數值)
System.out.println(Double.isNaN(0.0/0.0)); //輸出 true (0/0 為非數值)
也可以用包裹物件方法呼叫 :
Double d=new Double(1.0/0.0);
System.out.println(d.isInfinite()); //輸出 true
System.out.println(d.isNaN()); //輸出 false
但是 Float 只能使用物件方法呼叫, 不能呼叫類別方法 (雖然 API 中說可以) :
Float f=new Float(1.0/0.0);
System.out.println(f.isInfinite()); //輸出 true
System.out.println(f.isNaN()); //輸出 false
下列呼叫無法編譯成功, 出現 "no suitable method found for isInfinite(double)" 錯誤訊息 :
//System.out.println(Float.isInfinite(1.0/0.0));
//System.out.println(Float.isNaN(1.0/0.0));
除了 Character 外, 其他 7 個包裹類別都定義了兩個屬性 MIN_VALUE 與 MAX_VALUE 是用來紀錄各資料類型的值域 :
System.out.println(java.lang.Byte.MIN_VALUE); //輸出 -128
System.out.println(java.lang.Byte.MAX_VALUE); //輸出 127
System.out.println(java.lang.Short.MIN_VALUE); //輸出 -32768
System.out.println(java.lang.Short.MAX_VALUE); //輸出 32767
System.out.println(java.lang.Integer.MIN_VALUE); //輸出 -2147483648
System.out.println(java.lang.Integer.MAX_VALUE); //輸出 2147483647
System.out.println(java.lang.Long.MIN_VALUE); //輸出 -9223372036854775808
System.out.println(java.lang.Long.MAX_VALUE); //輸出 922337203685477580
System.out.println((int)java.lang.Character.MIN_VALUE); //輸出 0
System.out.println((int)java.lang.Character.MAX_VALUE); //輸出 65535
System.out.println(java.lang.Float.MIN_VALUE); //輸出 1.4E-45
System.out.println(java.lang.Float.MAX_VALUE); //輸出 3.4028235E38
System.out.println(java.lang.Double.MIN_VALUE); //輸出 4.9E-324
System.out.println(java.lang.Double.MAX_VALUE); //輸出 1.7976931348623157E308
注意, 因為字元是以 unicode 儲存, 因此要強制轉型為 int 才會轉成字元編碼, 否則會顯示空字串與問號.
6. 原始資料型態的轉型問題 :
當使用 "=" 運算子為變數賦值時, 等號兩邊的資料型態必須相同, 亦即, 等號左邊變數宣告的型態, 必須與等號右邊常數的型態相同, 否則可能出現 "type imcompatible" 或 "loss of precision" 的錯誤. 例如 :
byte b=true;
就會出現如下錯誤訊息 :
test.java:3: error: incompatible types
byte b=true;
^
required: byte
found: boolean
1 error
因為變數 b 是 byte 整數, 而 true 是布林值, 兩者不匹配. 再者, 例如 :
int i=20.1;
會出現如下編譯錯誤 :
test.java:3: error: possible loss of precision
int i=20.1;
^
required: int
found: double
1 error
因為變數 i 是整數, 而 20.1 預設是 double, 所以會失去精確度. 精確性錯誤可以透過強制轉型解決, 這是告訴編譯器, 我們知道並容許失去精確性, 這樣就能編譯成功了 :
int i=(int)20.1; //將 double 強制轉型為 int
但是型態不匹配問題卻不是強制轉型能解決的 (無解), 因為那是無法轉換的, 例如 :
byte b=(byte) true; //錯誤的強制轉換
這會出現下列錯誤訊息 :
test.java:3: error: inconvertible types
byte b=(byte)true;
^
required: byte
found: boolean
1 error
可見強制轉型也是有限制的, boolean 型態無法跟任何數值型態互轉, 只有數值 (整數, 實數, 字元) 型態之間才能互轉, 而且只有在精確度大變小 (narrowing) 時才需要強制轉型, 而精確度小變大 (widening) 時不需要強制轉型, 編譯器會進行自動轉型 (auto-casting).下列是大變小必須強制轉型的例子 :
byte b=(byte)65;
short s=(short)123;
int i=(int)123L;
long l=(long)1.23F;
float f=(float)1.23D;
char c1=(char)65L;
char c2=(char)s;
char c3=(char)b;
byte b1=(byte)c3;
System.out.println(b); //輸出 65
System.out.println(s); //輸出 123
System.out.println(i); //輸出 123
System.out.println(l); //輸出 1
System.out.println(f); //輸出 1.23
System.out.println(c1); //輸出 A
System.out.println(c2); //輸出 {
System.out.println(c3); //輸出 A
System.out.println(b1); //輸出 65
注意, 上例中的 float 轉 long, 雖然 long 是 8 bytes, 而 float 僅 4 bytes, 但是 float 精確度比 long 大, 因此一定要強制轉換才能通過編譯. 其次, char 佔 2 bytes (與 short 一樣), 雖然比 byte 大, 但是 char 沒有負數, 因此 short 與 byte 轉成 char 也是必須強制轉換, 事實上, 所有數值類型轉成 char 都必須強制轉換. 但是 char 常數轉成其他數值卻都會自動轉型 (因為 char 沒有負數) :
char c='A';
byte b='A'; //用 b=c 不行
short s='A'; //用 b=c 不行
int i=c;
long l=c;
float f=c;
double d=c
System.out.println(b); //輸出 65
System.out.println(s); //輸出 65
System.out.println(c); //輸出 A
System.out.println(i); //輸出 65
System.out.println(l); //輸出 65
System.out.println(f); //輸出 65.0
System.out.println(d); //輸出 65.0
可見常數字元皆會自動轉型為其他任何數值型態, 但轉成 byte 與 short 時不可使用字元變數, 必須強制轉換, 用字元常數才會自動轉換. 除了轉成 byte 與 short 外, char 轉成其他類型不論常數或變數均可.
以下是會自動轉型的兩種情況 :
- byte -> short -> int -> long -> float -> double
- int -> byte, short, char (僅在值域內時會自動轉型)
byte b=127; //int 轉 byte
short s=b; //byte 轉 short
int i=s; //short 轉 int
long l=i; //int 轉 long
float f=l; //long 轉 float
double d=f; //float 轉 double
System.out.println(b); //輸出 127
System.out.println(s); //輸出 127
System.out.println(i); //輸出 127
System.out.println(l); //輸出 127
System.out.println(f); //輸出 127.0
System.out.println(d); //輸出 127.0
上面第二項 int 轉成 byte, short, char 是精確度由大變小, 照理是不會自動轉型, 但因為 int 是整數常數之預設型態, 因此只要常數是在值域之內, 會自動轉型, 不需強制轉型, 例如 :
byte b1=127; //值域內自動轉型
byte b2=(byte)128; //值域外需強制轉型
short s1=32767; //值域內自動轉型
short s2=(short)32768; //值域外需強制轉型
System.out.println(b1); //輸出 127
System.out.println(b2); //輸出 -128
System.out.println(s1); //輸出 32767
System.out.println(s2); //輸出 -32768
但是實數就不是這樣了, 都必須強制轉型. 實數常數預設型態為 double, 即使其值在值域之內, 仍然不會自動轉型 :
float f1=(float)1234.5678; //必須強制轉型
float f2=(float)1.2345678e3; //必須強制轉型
System.out.println(f1); //輸出 1234.5678
System.out.println(f2); //輸出 1234.5678
7. 宣告適當資料型態 :
宣告原始型態變數時要選擇適當資料型態, 否則會浪費記憶體空間, 甚至影響計算結果, 導致不正確的輸出, 例如數值運算有精確度問題 :
int i=1/2;
float f=10/3;
double d=10.0/3;
System.out.println(i); //輸出 0
System.out.println(f); //輸出 3.0
System.out.println(d); //輸出 3.3333333333333335
可見兩個整數相除, 結果也是只取整數, 所以 1/2 得到 0, 而不是 0.5.
沒有留言 :
張貼留言