Java中常用的API——阿里架構師的兩萬字總結

本文轉載自:Java中常用的API——阿里架構師的兩萬字總結


目錄

  1. Java面向對象基本概念
  2. System
  3. String, StringBuffer
  4. 數值,字符,布爾對象與簡單類型的操作
  5. Class, ClassLoader
  6. Java IO系統
  7. Java集合類
  8. ResourceBundle, Properties
  9. Exceptions
  10. JDBC類庫
  11. 常用設計模式

一、Java面向對象基本概念

Java基本上是面向對象的程序設計語言, 除了一些簡單類型(primitive)的變量以外,一切都是對象, 程序是對象的組合, 每個對象都有自己的空間, 並且每個對象都有一種類型, 同一類所有對象都能接受相同的消息。 下面只對Java中對象的結構作簡單的說明:

  • 類(class): class是定義類的關鍵字, 類中包含類變量, 方法, 內部類, 內部接口等。由class可以生成類的實例, 即一個個對象。 如果一個類的成員被定義成static的,則這個成員不專屬於任何對象, 而是屬於這個類, 所有的對象共享這個成員。
  • 抽象類(abstract class): 抽象類不能直接生成一個實例, 抽象類中必需有方法是abstract的,抽象類的意思就是它實現了一部分的方法, 而定義爲abstract的方法則需要在它的字類中去實現。
  • 接口(interface): 接口可以理解爲純抽象的類, 它的每個方法都是未實現的, 它可以有成員變量, 但必須是static的。 一個類如果從這個接口繼承(implements)則它必須實現這個接口的所有方法。

繼承類用關鍵字:extends,繼承接口用關鍵字:implements。 一個類只能從一個類繼承下來, 但可以從多個接口繼承(類似於C++的多重繼承)。 字類可以覆蓋父類的方法(method), 但不能覆蓋父類的成員變量(field)。 如果父類的方法爲final或static的則不能被覆蓋。類的初始化順序是, 如果有父類, 則先初始化父類的field,然後執行父類的構造函數, 如果子類沒有顯式的去調父類的構造函數則缺省的會去調父類的無參數構造函數。 然後是子類的field與構造函數的初始化。

public interface SuperInterface {
	public staitc String SOME_FLAG = “1”;
	public void someMethod();
}
public Class SuperClass {
	{
		System.out.println(“init SuperClass field”);
	}
	public SuperClass() {
		System.out.println(“init SuperClass Constructor”);
	}
	public void runMethod() {
		System.out.println(“run SuperClass runMethod()”);
	}
}
public Class SubClass extends SuperClass implements SuperInterface {
	{
		System.out.println(“init SubClass field”);
	}
	public SubClass() {
		System.out.println(“init SubClass Constructor”);
	}
	public void someMethod() {
		System.out.println(“run SubClass someMethod()”);
	}
	public void runMethod() {
		System.out.println(“run SubClass runMethod()”);
	}
}

有以下test代碼:

public class Test {
	public void main(String[] args) {
		SubClass sub = new SubClass();
		sub. runMethod();
	}
}

則會輸出:

init SuperClass field
init SuperClass Constructor
init SubClass field
init SubClass Constructor
run SubClass runMethod()

以下章節所講述到的常用的Java API就是一些Java自帶的一些Class或Interface的用法。

二、System

System類位於package java.lang下面, 凡是此package下面的類我們可以直接引用無需先import進來, 因爲JVM缺省就load了這下面的所有class。

System包含了一些我們常用的方法與成員變量。 System不能被實例化, 所有的方法都可以直接引用。 主要作用大致有:

2.1 輸入輸出流

  • (PrintStream) System.out (標準終端輸出流),
  • (PrintStream) System.err(標準錯誤輸出流),
  • (InputStream) System.in(標準輸入流)。

我們還可以重定向這些流, 比如將所有的System.out的輸出全部重定向至一文件中去。

  • System.setOut(PrintStream) 標準輸出重定向
  • System.setErr(PrintStream) 標準錯誤輸出重定向
  • System.setIn(InputStream) 標準輸入重定向

2.2 取當前時間

System.currentTimeMillis() 所取到的時間是從1970/01/01以來1/1000秒計算的long型值。這個值可以轉換至Date或Timestamp值。 它一般還可以用來計算程序執行的時間。例:

long beginTime = System. currentTimeMillis();
…
…
System.out.println(“run time = ” + (System. currentTimeMillis() – beginTime));

2.3 數組拷貝

System.arraycopy(Object src, int src_position, Object dst, int dst_position, int length)
  • src: 源數組。
  • src_position: 源數組拷貝的起始位置。
  • dst: 目標數組
  • dst_position: 拷貝至目標數組的起始位置
  • length: 拷貝元素的長度

利用System.arraycopy進行數組的拷貝效率是最高的, 一般情況下我們自己很少直接用到這個方法,但在集合類的內部中都大量使用了這個方法。

例:

int[] array1 = {1, 2, 3, 4, 5};
int[] array2 = {4, 5, 6, 7, 8};
int array3 = new int[8];
System.arraycopy(array1, 0, array3, 0, 5);
System.arraycopy(array2, 2, array3, 5, 3);

此時array3 = {1, 2, 3, 4, 5, 6, 7, 8}

這比用for循環來進行賦值效率要高。

2.4 存取系統的Properties

System.getProperties():取得當前所有的Properties, Properties將在後面的集合一節進行詳細的論述。

  • System.setProperties(Properties props):設置系統的Properties。
  • System.getProperty(String key): 根據一個鍵值來取得一個Property。
  • System.setProperty(String key, String value): 設置系統的一個Property。

JVM起動的時候將會有一些缺省的Properties值, 例如:

  • java.version Java運行環境版本
  • java.home Java主目錄 installation directory
  • java.class.path Java 的class path
  • java.ext.dirs Java的擴展目錄路徑
  • file.separator 文件分隔符("/" on UNIX)
  • path.separator 路徑分隔符(":" on UNIX)
  • line.separator 行分隔符 ("\n" on UNIX)
  • user.name 用戶名
  • user.home 用戶主目錄
  • user.dir 用戶當前工作目錄

更詳細的信息請參照Java API。 另外在起動一個java程序的時候可以通過-D來設置系統的Property, 比如 java –Dejb.file=ejb_Test PrintTest 在PrintTest裏面就可以通過System.getProperty(“ejb.file”)來取得值ejb_Test。

2.5 其它

  • System. loadLibrary(String libname): 加載native的動態庫。 可以用C寫JNI的庫, 然後在java中通過native方法來調用。
  • System.setSecurityManager(SecurityManager s)
  • System.getSecurityManager(): 設置與取得系統的security class。

三、String, StringBuffer

3.1 基本用法

String可以說是我們最常用的一個類, 熟練掌握它的一些基本用法是很有用的。

String是由一組字符組成的字符串, 下標由0開始。 一旦有必要改變原來的內容, 每個String方法都有返回了一個新的String對象。

  • char charAt(int index) 返回指定位置的字符。
  • int compareTo(Object o)
  • int compareTo(String anotherString)

與另外一個對象進行比較。

  • int compareToIgnoreCase(String str) 與另一個String進行比較, 不區分大小寫
  • String concat(String str) 連接兩字符串, 可以直接用+, 因爲Java給String覆蓋了+
  • static String copyValueOf(char[] data)
  • static String copyValueOf(char[] data, int offset, int count)

將data數組轉換至String

  • boolean endsWith(String suffix) 測試此String是否以suffix結尾。
  • boolean startsWith(String prefix) 測試此String是否以prefix開頭。
  • boolean equals(Object anObject)
  • boolean equalsIgnoreCase(String anotherString)

比較兩字符串的值。 不相等則返回false

  • byte[] getBytes() 根據缺省的字符編碼將String轉換成字節數組。
  • byte[] getBytes(String enc) 根據指定的編碼將String轉換萬字節數組。
  • void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 拷貝字符至一數組中
  • int indexOf(int ch) 從字串的起始位置查找字符ch第一次出現的位置
  • int indexOf(int ch, int fromIndex) 從指定的fromIndex位置向後查找第一次出現ch的位置,
  • int indexOf(String str)
  • int indexOf(String str, int fromIndex)

如果不存在ch或str都返回-1

  • int lastIndexOf(int ch) 從字串的最終位置往前查找第一次出現ch的位置
  • int lastIndexOf(int ch, int fromIndex) 從指定的位置往前查找第一次出現ch的位置,
  • int lastIndexOf(String str)
  • int lastIndexOf(String str, int fromIndex)

如果不存在則返回-1

  • int length() 該字符串的字符長度(一個全角的漢字長度爲1)
  • String replace(char oldChar, char newChar) 將字符oldChar全部替換爲newChar, 返回一個新的字符串。
  • String substring(int beginIndex) 返回從beginIndex開始的字符串子集
  • String substring(int beginIndex, int endIndex) 返回從beginIndex至endIndex結束的字符串的子集。 其中endIndex – beginIndex等於子集的字符串長度
  • char[] toCharArray() 返回該字符串的內部字符數組
  • String toLowerCase() 轉換至小寫字母的字符串
  • String toLowerCase(Locale locale)
  • String toUpperCase() 轉換至大寫字母的字符串
  • String toUpperCase(Locale locale)
  • String toString() 覆蓋了Object的toString方法, 返回本身。
  • String trim() 將字符串兩邊的半角空白字符去掉, 如果需要去掉全角的空白字符得要自己寫。
  • static String valueOf(primitive p) 將其它的簡單類型的值轉換爲一個String

StingBuffer是一個可變的字符串,它可以被更改。同時StringBuffer是Thread safe的, 你可以放心的使用, 常用的方法如下:

  • StringBuffer append(param) 在StringBuffer對象之後追加param(可以爲所有的簡單類型和Object) 返回追加後的StringBuffer, 與原來的對象是同一份。
  • char charAt(int index) 返回指定位置index的字符。
  • StringBuffer delete(int start, int end) 刪除指定區域start~end的字符。
  • StringBuffer deleteCharAt(int index) 刪除指定位置index的字符。
  • void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin) 同String的getChars方法
  • StringBuffer insert(int offset, boolean b) 在指定位置offset插入param(爲所有的簡單類型與Object)
  • int length() 同String的length()
  • StringBuffer replace(int start, int end, String str) 將指定區域start~end的字符串替換爲str
  • StringBuffer reverse() 反轉字符的順序
  • void setCharAt(int index, char ch) 設置字符ch至index位置。
  • String substring(int start)
  • String substring(int start, int end) 同String的subString
  • String toString() 返回一個String

大家可能已經注意到很多方法都返回了一個StringBuffer對象, 但返回的這個對象與String的方法返回的String不一樣, 返回的StringBuffer對象與被操作的StringBuffer對象是同一份, 而String的方法返回的String則肯定是重新生成的一個String。

3.2 性能對比

因爲String被設計成一種安全的字符串, 避免了C/C++中的尷尬。因此在內部操作的時候會頻繁的進行對象的交換, 因此它的效率不如StringBuffer。 如果需要頻繁的進行字符串的增刪操作的話最好用StringBuffer。 比如拼SQL文, 寫共函。 另: 編繹器對String的+操作進行了一定的優化。

x = "a" + 4 + "c"

會被編繹成

x = new StringBuffer().append("a").append(4).append("c").toString()

但:

x = “a”;
x = x + 4;
x = x + “c”;

則不會被優化。 可以看出如果在一個表達式裏面進行String的多次+操作會被優化, 而多個表達式的+操作不會被優化。

3.3 技巧

①. 在Servlet2.3與JSP1.1以前畫面post到後臺的數據是通過ISO88591格式進行編碼的, 則當遇到全角日文字的時候, 在後臺request得到的數據可能就是亂碼, 這個時候就得自己進行編碼轉換, 通過String.getBytes(String enc)方法得到一個字節流, 然後通過String(byte[] bytes, String enc)這個構造函數得到一個用新的編碼生成的字符串. 例如將ISO88591的字符串轉換成Shift_JIS的字符串, 方法如下:

public static String convertString(String str) {
	if (str == null) {
		return null;
	}
	try {
		byte[] buf = str.getBytes("ISO8859_1");
		return new String(buf, "Shift_JIS");
	}
	catch (Exception ex) {
		ex.printStackTrace();
		return null;
	}
}

不過在最新的Servlet2.3與Jsp1.2中可以通過request.setCharacterEncoding來進行設置取值的碼制, 不需要自己再做轉換。

②. 因爲Java在計算String的長度是以字符爲單位的, 因此一個全角與半角的字符長度是一樣的, 但是DB中往往是根據字節來計算長度的, 因此我們在做Check的時候得要判斷String的字節長, 可以用以下的方法:

public static String length(String str) {
	if (str == null) {
		return 0;
	}
	return str.getBytes().length;
}

四、數值,字符,布爾對象與簡單類型的操作

簡單的對照表如下:

與C等其它語言不同的是數值的範圍不隨平臺的改變而改變, 這就保證了平臺之間的統一性,提高了可移植性。

Number: Number本身是個抽象類, 不能直接使用, 所有直接從Number繼承下來的子類都有以下幾種方法:

  • byte byteValue() 返回字節值
  • double doubleValue() 返回double值
  • float floatValue() 返回float值
  • int intValue() 返回float值
  • long longValue() 返回long值
  • short shortValue() 返回short值

在需要通過Object來取得簡單數據類型的值的時候就得用到以上的方法, 不過我不推薦不同類型之間的取值, 比如Long型的Object不要直接去調用intValue(),精度可能會丟失。

如果想通過String來得到一個數值類型的簡單類型值, 一般在每個Number的類裏面都有一個parseXXX(String)的靜態方法, 如下:

  • byte Byte.parseByte(String s)
  • double Double.parseDouble(String s)
  • float Float.parseFloat(String s)
  • int Integer.parseInt(String s)
  • long Long.parseLong(String s)
  • short Short.parseShort(String s)

如果想直接從String得到一個Number型的Object,則每個Number類裏面都有valueOf(String s) 這個靜態方法。如:

  • Byte Byte.valueOf(String s)
  • Double Double.valueOf(String s)
  • Float Float.valueOf(String s)
  • Integer Integer.valueOf(String s)
  • Long Long.valueOf(String s)
  • Short Short.valueOf(String s)

一般的在構造一個Number的時候都可以通過一個String來完成, 比如:

Long longObject = new Long(“1234567890”);

等價於

Long longObject = Long.valueOf(“1234567890”);

因爲每個Number的子類都實現了Object的toString()方法, 所以, 如果想得到一個String型的數值, 直接調用XXX.toString()就可以了。 如果想得到一個簡單類型的String, 方法很多總結如下:

  • 首先生成對應的Number Object類型, 然後調用toString()
  • 調用Number子類 .toString(type t) 其中t就是簡單類型的數據。
  • 調用String.valueOf(type t) (推薦使用這種方法)

大家可以看出, 往往一種結果可以用多種方法實現, 總的原則就是深度最少優先。比如由一個String得到一個簡單類型的值可以有以下兩種方法:

Integer.parseInt(“12345”);

(new Integer(s)).intValue(“12345”);

當然應該使用第一種方法。

Character: Character對應着char類型, Character類裏面有很多靜態的方法來對char進行判斷操作, 詳細的操作請參照JDK API。 Java對字符的判斷操作基本都是以Unicode進行的, 比如Character.isDigit(char ch)這個方法, 不光半角的0-9符合要求, 全角的日文0-9也是符合要求的。

Boolean: Boolean對應着boolean類型, boolean只有true和false兩個值, 不能與其它數值類型互換, 可以通過字符串”true”以及”false”來得到Object的Boolean, 也可以通過簡單類型的boolean得到Boolean, 常用方法如下:

  • Boolean(boolean value) 通過簡單類型的boolean構造Boolean
  • Boolean(String s) 通過String(“true”, “false”)構造Boolean
  • boolean booleanValue() 由Object的Boolean得到簡單類型的boolean值
  • boolean equals(Object obj) 覆蓋了Object的.equals方法, Object值比較
  • static Boolean valueOf(String s) 功能與構造函數Boolean(String s)一樣

五、Class, ClassLoader

Java是一種介於解釋與編繹之間的語言, Java代碼首先編繹成字節碼, 在運行的時候再翻譯成機器碼。 這樣在運行的時候我們就可以通過Java提供的反射方法(reflect)來得到一個Object的Class的額外信息, 靈活性很大,可以簡化很多操作。

Class: 任何一個Object都能通過getClass()這個方法得到它在運行期間的Class。 得到這個Class之後可做的事情就多了, 比如動態得到它的構造函數, 成員變量, 方法等等。 還可以再生成一份新的實例, 下面只給出幾個我們常用的方法, 更詳細的用法參照Java API

  • Class Class.forName(String className) throws ClassNotFoundException: 這是個靜態方法, 通過一個Class的全稱來得到這個Class。
  • String getName() 取得這個Class的全稱, 包括package名。
  • Object newInstance() 得到一個實例, 調用缺省的構造函數。

例如我們有一個類: com.some.util.MyClass 如果得到它的一個實例呢? 可能有以下兩種方法:

MyClass myClass = new MyClass(), 直接通過操作符new生成;

或者:

MyClass myClass =  (MyClass) Class.forName(“com.some.util.MyClass”).newInstance();

也許有人就會懷疑第二種方法實際意義, 能夠直接new出來幹嘛繞彎。 但實際上它的用處卻很大, 舉個例子: 用過struts的人都知道, 在action-config.xml當中定義了一系列的formBean與actionBean, 當然每個form與action都具有同類型, 這樣在一個request過來的時候我可以動態的生成一個form與action的實例進行具體的操作, 但在編碼的時候我並不知道具體是何種的form與action, 我只調用它們父類的方法。 你如果要用第一種方法的話, 你得在編碼的時候通過一個標誌來判斷每一次request需要具體生成的form與action, 代碼的靈活性大大降低。 總的來說在面向接口的編程當中經常使用這種方法, 比如不同數據庫廠家的JDBC Driver都是從標準的JDBC接口繼承下去的, 我們在寫程序的時候用不着管最終是何種的Driver, 只有在運行的時候確定。 還有XML的Parser也是, 我們使用的只是標準的接口, 最後到底是誰來實現它的, 我們用不着去管。

ClassLoader: ClassLoader是一個抽象類,一般的系統有一個缺省的ClassLoader用來裝載Class, 用ClassLoader.getSystemClassLoader()可以得到。不過有時候爲了安全或有其它的特殊需要我們可以自定義自己的ClassLoader來進行loader一些我們需要的Class, 比如有的產品它用了自己的ClassLoader可以指定Class只從它指定的特定的JAR文件裏面來loader,如果你想通過覆蓋ClassPath方法來想讓它用你的Class是行不通的。 有興趣的可以參照Java API 的更詳細的用法說

六、Java IO系統

6.1 JDK1.0輸入流

InputStream
ByteArrayInputStream
FileInputStream
FilterInputStream
ObjectInputStream
PipedInputStream
SequenceInputStream
BufferedInputStream
DataInputStream
PushbackInputStream
LineNumberInputStream

6.2 JDK1.1輸入流

Reader
BufferedReader
FilterReader
PipedReader
StringReader
InputStreamReader
CharArrayReader
FileReader
PushbackReader
LineNumberReader

6.3 JDK1.0輸出流

OutputStream
BufferedOutputStream
DataOutputStream
PrintStream
ByteArrayOutputStream
FileOutputStream
FilterOutputStream
ObjectOutputStream
PipedOutputStream

6.4 JDK1.1輸出流

Writer
BufferedWriter
FilterWriter
PipedWriter
StringWriter
FileWriter
PrintWriter
OutputStreamWriter
CharArrayWriter

如果你剛剛接觸Java的IO部分, 你可能會感覺無從入手, 確實Java提供了過多的類,反而讓人感到很亂。

可將Java庫的IO類分爲輸入與輸出兩個部分, 在1.0版本中提供了兩個抽象基類, 所有輸入的類都從InputStream繼承, 所有輸出的類都從OutputStream繼承, 1.1提供了兩個新的基類, 負責輸入的Reader與輸出的Writer, 但它們並不是用來替換原來老的InputStream與OutputStream, 它們主要是讓Java能更好的支持國際化的需求。 原來老的IO流層只支持8位字節流, 不能很好地控制16位Unicode字符。 Java內含的char是16位的Unicode, 所以添加了Reader和Writer層次以提供對所有IO操作中的Unicode的支持。 除此之外新庫也對速度進行了優化, 可比舊庫更快地運行。

6.5 InputStream的類型

  1. 字節數組
  2. String對象
  3. 文件
  4. 管道, 可以從另外一個輸出流得到一個輸入流
  5. 一系列的其他流, 可以將這些流統一收集到單獨的一個流內。
  6. 其他起源(如socket流等)

還有一個是File類, Java中一個目錄也是一個文件,可以用file.isFile()和file.isDirectory()來進行判斷是文件還是目錄。 File 對象可能作爲參數轉換爲文件流進行操作。 具體操作參照Java IO API。

6.6 常用方法

/**
 * 拼文件名, 在windows()跟unix(/)上的文件分割符是不一樣的
 * 可以通過File類的靜態成員變量separator取得
 */
public static String concatFileName(String dir, String fileName) {
	String fullFileName = "";
	if (dir.endsWith(File.separator)) {
		fullFileName = dir + fileName;
	} else {
		fullFileName = dir + File.separator + fileName;
	}
	return fullFileName;
}
/**
 * 從控制檯讀取輸入的數據, System.in (InputStream) 先轉換至InputStreamReader再用
 * BufferedReader進行讀取.
 *
 */
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String str = "";
while (str != null) {
	str = in.readLine();
	// process(str); }
	/**
 * 從文件中按行進行讀取數據處理
 */
	BufferedReader in = new BufferedReader(new FileReader("infilename"));
	String str;
	while ((str = in.readLine()) != null) {
		// process(str); }
		in.close();
		/**
 * 寫數據至一個新的文件中去.
 */
		BufferedWriter out = new BufferedWriter(new FileWriter("outfilename"));
		out.write("a String");
		out.close();
		/**
 * 追加新的數據到一個文件中去, 如果原文件不存在
 * 則新建這個文件.
 */
		BufferedWriter out = new BufferedWriter(new FileWriter("filename", true));
		out.write("aString");
		out.close();
		/**
 * 將一個可序列化的Java的object以流方式寫到一個文件中去中.
 */
		ObjectOutput out = new ObjectOutputStream(new FileOutputStream("filename.ser"));
		out.writeObject(object);
		out.close();
		/**
 * 從文件中恢復序列化過的Java Object
 */
		ObjectInputStream in = new ObjectInputStream(new FileInputStream("filename.ser"));
		Object object = (Object) in.readObject();
		in.close();
		/**
 * 以指定的編碼方式從文件中讀取數據
 */
		BufferedReader in = new BufferedReader(new InputStreamReader(
							 new FileInputStream("infilename"), "UTF8"));
		String str = in.readLine();
		/**
 * 以指定的編碼方式寫數據到文件中
 */
		Writer out = new BufferedWriter(new OutputStreamWriter(
									new FileOutputStream("outfilename"), "UTF8"));
		out.write("a String");
		out.close();

由上面的例子可以看出, 流之間可以互相轉換, 如果想對流進行字符操作最好將之轉換成BufferedReader與BufferedWriter這樣可以提高讀寫的效率。

需要注意的是一般來說流的大小是得不到的, 雖然一般的InputStream都有一個available()的方法可以返回這個流可以讀到的字節數, 不過這個方法有時候不會很準確, 比如在讀取網絡傳輸的流的時候, 取到的長度並不一定是真實有效的長度。

大家在寫程序的時候可能已經注意到一些類都繼承了java.io.Serializable這個接口, 其實繼承了這個接口之後這個類的本身並不做任何事情, 只是這個類可以被序列化並通過流來進行傳輸, 並可被還原成原Object。 這個流可以通過網絡傳輸(EJB 中進行傳遞參數和返回值的Data Class), 也可以保存到文件中去(ObjectOutputStream)。

七、Java集合類

在寫程序的時候並不是每次只使用一個對象, 更多的是對一組對象進行操作, 就需要知道如何組合這些對象, 還有在編碼的時候我們有時並不知道到底有多少對象,它們需要進行動態的分配存放。

Java的集合類只能容納對象句柄, 對於簡單類型的數據存放, 只能通過數據來存放, 數組可以存放簡單類型的數據也能存放對象。

Java提供了四種類型的集合類: Vector(矢量), BitSet(位集), Stack(堆棧), Hashtable(散列表)。

  1. 矢量: 一組有序的元素, 可以通過index進行訪問。
  2. 位集: 其實就是由二進制位構成的Vector, 用來保存大量”開-關”信息, 它所佔的空間比較小, 但是效率不是很高, 如果想高效率訪問, 還不如用固定長度的數組。
  3. 堆棧: 先入後出(LIFO)集合, java.util.Stack類其實就是從Vector繼承下來的, 實現了pop, push方法。
  4. 散列表: 由一組組“鍵–值”組成, 這裏的鍵必須是Object類型。 通過Object的hashCode進行高效率的訪問。

對於這些集合之間的關聯關係見下圖, 其中標色的部分爲我們常用的類。

由上圖可以看出, 基本接口有兩個:

Collection: 所有的矢量集合類都從它繼承下去的, 但並不直接從它繼承下去的。 List與Set這兩個接口直接繼承了Collection, 他們的區別是List裏面可以保存相同的對象句柄, 而Set裏面的值是不重複的。 我們經常用的Vector與ArrayList就是從List繼承下去的, 而HashSet是從Set繼承的。

Map:散列表的接口, Hashtable與HashMap繼承了這個接口。
下面給出常用集合類的常用方法。

/**
 * Vector 與 ArrayList的操作幾乎是一樣的
 * 常用的追加元素用add(), 刪除元素用remove()
 * 取元素用get(), 遍歷它可以循環用get()取. 或者
 * 先得到一個Iterator, 然後通過遍歷Iterator的方法
 * 遍歷Vector或ArrayList
 */
// 生成一個空的Vector
Vector vector = new Vector();
// 在最後追加一個元素。
vector.add("one");
vector.add("two");
// 在指定的地方設置一個值
vector.set(0, "new one");
// 移走一個元素或移走指定位置的元素
vector.remove(0);
// 用for循環遍歷這個Vector
for (int i = 0; i < vector.size(); i++) {
	String element = (String) vector.get(i);
}
// 用枚舉器(Enumeration)遍歷它(只有Vector有,ArrayList沒有)
Enumeration enu = vector.elements();
while (enu.hasMoreElements()) {
	enu.nextElement();
}
// 用反覆器(Iterator)遍歷它
Iterator it = vector.iterator();
while (it.hasNext()) {
	it.next();
}
/**
 * Hashtable與HashMap的操作, 追加元素用put(不是add)
 * 刪除元素用remove, 遍歷可以用Iterator 既可以遍歷
 * 它的key, 也可以是value
 */
// 生成一個空的Hashtable或HashMap
Hashtable hashtable = new Hashtable();
// 追加一個元素
hashtable.put("one", "one object value");
// 刪除一個元素
hashtable.remove("one");
// 用Iterator遍歷
Iterator keyIt = hashtable.keySet().iterator();
while (keyIt.hasNext()) {
	Object keyName = keyIt.next();
	String value = (String) hashtable.get(keyName);
}
Iterator valueIt = hashtable.values().iterator();
while (valueIt.hasNext()) {
	valueIt.next();
}
// 用Enumeration遍歷, 只有Hashtable有, HashMap沒有.
Enumeration enu = hashtable.elements();
while (enu.hasMoreElements()) {
	enu.nextElement();
}

說明: Enumeration是老集合庫中的接口, 而Iterator是新集合(1.2)中出現的, 而Vector與Hashtable也都是老集合中的類, 所以只有Vector與Hashtable可以用Enumeration。

Vector與ArrayList對比

雖然在使用的時候好象這兩個類沒什麼區別, 它們都是從List繼承下來的, 擁有相同的方法, 但它們的內部還是有些不同的,

  • 首先Vector在內部的一些方法作了線程同步(synchronized)。 同步的代價就是降低了執行效率, 但提高了安全性。而ArrayList則是線程不同步的, 可以多線程併發讀寫它。
  • 內部數據增長率。 所有的這些矢量集合在內部都是用Object的數組進行存儲和操作的。 所以也就明白了爲什麼它可以接受任何類型的Object, 但取出來的時候需要進行類型再造。 Vector與ArrayList具有自動伸縮的功能, 我們不用管它size多大, 我們都可以在它的後面追加元素。 Vector與ArrayList內部的數組增長率是不一樣的, 當內部的數組不能容納更多元素的時候, Vector會自動增長到原兩倍大小, ArrayList會變爲原一倍半大小, 而不是我們所想象的一個元素一個元素的增長。

Hashtable與HashMap對比

Hashtable與HashMap都是從Map繼承下來的, 方法幾乎都一樣, 它們內部有兩個不同點:

  • 與Vector和ArrayList一樣, 它們在線程同步是不同的, Hashtable在內部做了線程同步, 而HashMap是線程不同步的。
  • HashMap的鍵與值都可以爲null, 而Hashtable不可以, 如果你試圖將一個null值放到Hashtable裏面去, 會拋一個NullPointException的。

性能對比

拋開不常用的集合不講, 每種集合都應該有一個我們常用的集合類, 而在不同的場合下應該使用效率最高的一個。 一般來說我推薦儘量使用新的集合類, 除非不得已, 比如說需要用用了老集合類寫的產品的程序。 也就是說盡量使用ArrayList與HashMap, 而少使用Vector與Hashtable。

  • 在單線程中使用ArrayList與HashMap, 而在多線程中如果需要進行線程同步可以使用Vector與Hashtable, 但也可以用synchronized對ArrayList與HashMap進行同步, 不過同步後的ArrayList與HashMap是比Vector與Hashtable慢的。 不過我認爲需要進行線程同步的地方並不多。 如果一個變量定義在方法內部同時只可能有一個線程對之進行操作, 就不必要進行同步, 如果定義在類的內部並且不是靜態的, 屬於實例變量, 而這個類並沒有被多線程使用也就不必要同步。
  • 一般自己寫的程序很少會自己去另開線程的, 但在Web開發的時候, 如果用了Servlet, 則每個request都是一個線程, 也就是說每個Servlet都是在多線程環境下運行的, 如果Servlet中使用了全局靜態的成員變量就得小心點兒, 如果需要同步就得在方法上加上synchronized修飾符, 如果允許多個線程操作它, 並且你知道不會有什麼衝突問題就可以大膽的使用ArrayList與HashMap。 另外如果在多線程中有線程在對ArrayList或HashMap進行修改(結構上的修改), 而有一個線程在用Iterator進行讀取操作, 這個時候就有可能會拋ConcurrentModificationException, 因爲用Iterator的時候, 不允許原List的結構改變。但可以用get方法來取。

常用技巧

①. 採用面向接口的編程技巧, 比如現在需要寫一個共通函數,對矢量集合類諸如Vector,ArrayList,HashSet等等進行操作, 但我並不知道最終用戶會具體傳給我什麼類型的類, 這個時候我們可以使用Collection接口, 從而使代碼具有很大的靈活性。 代碼示例如下:

/**
 * 將list裏面的所有元素用sep連接起來,
 * list可以爲Vector, ArrayList, HashSet等。
 */
public static String join(String sep, Collection list) {
	StringBuffer sb = new StringBuffer();
	Iterator iterator = list.iterator();
	while (iterator.hasNext()) {
		sb.append(iterator.next());
		if (iterator.hasNext()) {
			sb.append(sep);
		}
	}
	return sb.toString();
}

②. 利用Set進行Unique, 比如有一組對象(其中有對象是重複的), 但我們只對不同的對象感興趣, 這個時候可以使用HashSet這個集合類, 然後可以通過覆蓋Object的equals方法來選擇自定義判斷相等的rule。 缺省的是地址判斷。 例:

class DataClass {
	private String code = null;
	private String name = null;
	public void setCode(String code) {
		this.code = code;
	}
	public String getCode() {
		return this.code;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getName() {
		return this.name;
	}
	public Boolean equals(DataClass otherData) {
		if (otherData != null) {
			if (this.getCode() != null&& this.getCode().equals(otherData.getCode()) {
				return true;
			}
		}
		return false;
	}
}
DataClass data1 = new DataClass();
DataClass data2 = new DataClass();
data1.setCode("1");
data2.setCode("1");
HashSet singleSet = new HashSet();
singleSet.add(data1);
singleSet.add(data2);

結果singleSet裏面只有data1, 因爲data2.equals(data1), 所以data2並沒有加進去。

③. 靈活的設計集合的存儲方式, 以獲得較高效的處理。 集合裏面可以再嵌套集合, 例:在ArrayList裏面存放HashMap, HashMap裏面再嵌套HashMap。

八、ResourceBundle, Properties

ResourceBundle:開發一個項目, 配置文件是少不了的, 一些需要根據環境進行修改的參數, 都有得放到配置文件中去, 在Java中一般是通過一個properties文件來實現的, 這個文件以properties結尾。 內部結構是二維的, 以key=value的形式存在。 如下:

options.column.name.case=1
options.column.bean.serializable=1
options.column.bean.defaultconstructor=1
options.column.method.setter=1
options.general.user.version=1.0
database.connection[0]=csc/csc@localhost_oci8
database.connection[1]=cscweb/cscweb@localhost_thin

ResourceBundle用來解析這樣的文件, 它的功能是可以根據你的Locale來進行解析配置文件, 如果一個產品需要進行多語言支持, 比如在不同語種的系統上, 會顯示根據它的語言顯示相應的界面語言, 就可以定義多份的properties文件, 每個文件的key是一樣的, 只是value不一樣, 然後在application起動的時候, 可以判別本機的Locale來解析相應的properties文件。 Properties文件裏面的數據得要是Unicode。 在jdk下面可以用native2ascii這個命令進行轉換。 例: native2ascii Message.txt Message.properties 會生成一個Unicode的文件。

Properties: Properties這個類其實就是從Hashtable繼承下來的, 也就是說它是一個散列表, 區別在於它的key與value都是String型的, 另外也加了幾個常用的方法:

  • String getProperty(String key) 取得一個property
  • String getProperty(String key, String defaultValue) 取property, 如果不存在則返回defaultValue。
  • void list(PrintStream out) 向out輸出所有的properties
  • void list(PrintWriter out)
  • Enumeration propertyNames() 將所有的property key名以Enumeration形式返回。
  • Object setProperty(String key, String value) 設置一個property。

ResourceBundle與Properties一般結合起來使用。 它們的用法很簡單, 由ResourceBundle解析出來的key與value然後放至到一個靜態的Properties成員變量裏面去, 然後就可以通過訪問Properties的方法進行讀取Property。 下面給個簡單的例子:

public class PropertyManager implements Serializable {
	/** 定義一個靜態的Properties變量 */
	private static Properties properties = new Properties();
	/**
 * 通過一個類似於類名的參數進行Property文件的初期化
 * 比如現在有一個文件叫Message.properties, 它存放在
 * ejb/util下面並且, 這個目錄在運行的classpath下面
 * 則in就爲ejb.util.Message
 *
 */
	public static void init(String in) throws MissingResourceException {
		ResourceBundle bundle = ResourceBundle.getBundle(in);
		Enumeration enum = bundle.getKeys();
		Object key = null;
		Object value = null;
		while (enum.hasMoreElements()) {
			key = enum.nextElement();
			value = bundle.getString(key.toString());
			properties.put(key, value);
		}
	}
	/**
 * 取得一個Property值
 */
	public static String getProperty(String key) {
		return properties.get(key);
	}
	/**
 * 設置一個Property值
 */
	public static void setProperty(String key, String value) {
		properties.put(key, value);
	}
}

不過現在的Java產品中,越來越傾向於用XML替換Properties文件來進行配置。 XML配置具有層次結構清楚的優點。

九、Exceptions

Throwable
Exception
Error
RuntimeException
ClassNotFoundException
IOException
SQLException
IndexOutOfBoundsException
ClassCastException
NullPointerException
OutOfMemoryError
StackOverflowError
NoClassDefFoundError

Java採用違例(Exception)處理機制來進行錯誤處理。 違例機制的一個好處就是能夠簡化錯誤控制代碼, 我們再也不用檢查一個特定的錯誤, 然後在程序的多處地方對其進行控制。 此外, 也不需要在方法調用的時候檢查錯誤(因爲保證有人能夠捕獲這裏的錯誤)。 我們只需要在一個地方處理問題:”違例控制模塊”或者”違例控制器”。 這樣可有效減少代碼量, 並將那些用於描述具體操作的代碼與專門糾正錯誤的代碼分隔開。

一個完整的違例例子:

public void throwTest() throws MyException {
	try {
		...
	}
	catch (SQLException se) {
		cat.error("", se);
		throw new MyException(se.getMessage());
	}
	catch (Exception e) {
		cat.error("", e);
	}
	finally {
		...
	}
}

如果一段代碼有可能會拋出違例可以用try {} catch {}來處理。 被catch到的違例可以再拋出, 也可以轉換爲其它類型的Exception拋出。 finally塊裏面的代碼總會被執行到的, 不管前面是否已經throw或return了。

Throwable是所有違例的基類, 它有兩種常規類型。 其中, Error代表編繹期和系統錯誤, 我們一般不必特意捕獲它們。 Exception是可以從任何標準Java庫的類方法中擲出的基本類型。

看上面的圖, 如果是Error的子類或是RuntimeException的子類這種違例有一定的特殊性, 可以說我們可以當它們不存在, 當這種違例拋出的時候, 我們可以不catch它, 也可以不在方法上throws它。 RuntimeException一般代表的是一個編程錯誤, 是完全可以避免的。

性能注意點: 因爲使用了Exception之後是要影響一些效率的, 所以Exception不能濫用。一般的不要用Exception來控制業務流程, 其次不要循環體內使用。

技巧:我們可以從Exception或直接從Throwable繼承寫我們自己的Exception, 然後根據業務需要拋不同種類的Exception。

十、JDBC類庫

有了 JDBC,向各種關係數據庫發送 SQL 語句就是一件很容易的事。換言之,有了 JDBC API,就不必爲訪問 Sybase 數據庫專門寫一個程序,爲訪問 Oracle 數據庫又專門寫一個程序,爲訪問 Informix 數據庫又寫另一個程序,等等。您只需用 JDBC API 寫一個程序就夠了,它可向相應數據庫發送 SQL 語句。而且,使用 Java 編程語言編寫的應用程序,就無須去憂慮要爲不同的平臺編寫不同的應用程序。將 Java 和 JDBC 結合起來將使程序員只須寫一遍程序就可讓它在任何平臺上運行。

下面爲常用的處理流程:

DriverManager
Connection
Statement
PreparedStatement
CallableStatement
ResultSet

簡單地說,JDBC 可做三件事:

  1. 與數據庫建立連接
  2. 發送 SQL 語句
  3. 處理結果

下列代碼段給出了以上三步的基本示例:

Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
Connection conn = DriverManager.getConnection (
"jdbc:oracle:thin:@eai-sol:1521:eai_db", "csc2", "csc2");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT CONTACTID FROM CONTACTINFO");
while (rs.next()) {long contactID = rs.getLong("CONTACTID");}

下面對常用的幾個類和接口做些簡單的說明。

DriverManager

DriverManager類是 JDBC 的管理層,作用於用戶和驅動程序之間。它跟蹤可用的驅動程序,並在數據庫和相應驅動程序之間建立連接。另外,DriverManager 類也處理諸如驅動程序登錄時間限制及登錄和跟蹤消息的顯示等事務。對於簡單的應用程序,一般程序員需要在此類中直接使用的唯一方法是 DriverManager.getConnection。正如名稱所示,該方法將建立與數據庫的連接。

Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
Connection conn = DriverManager.getConnection (
"jdbc:oracle:thin:@eai-sol:1521:eai_db", "csc2", "csc2");

其中第一句話的作用是在當前的環境中load一個DB Driver, 有人可能覺得奇怪, 這句話執行完之後, 後面怎麼知道去用這個Driver呢? 其實DriverManager可以從load的classes裏面找到註冊過的driver,然後使用它所找到的第一個可以成功連接到給定 URL 的驅動程序。 第二句話的三個參數分別是URL, User, Password。Driver不一樣, URL可能也不一樣。

Statement

Statement 對象用於將 SQL 語句發送到數據庫中。實際上有三種 Statement 對象,它們都爲在給定連接上執行 SQL 語句的包容器:Statement、PreparedStatement(它從 Statement 承而來)和 CallableStatement(它從 PreparedStatement 繼承而來)。它們都專用於發送定類型的 SQL 語句: Statement 對象用於執行不帶參數的簡單 SQL 語句;PreparedStatement 對象用於執行帶或不帶 IN 參數的預編譯 SQL 語句;CallableStatement 對象用於執行對數據庫已存儲過程的調用。

Statement 接口提供了執行語句和獲取結果的基本方法。PreparedStatement 接口添加了處理 IN 參數的方法;而 CallableStatement 添加了處理 OUT 參數的方法。

①. 創建 Statement 對象

建立了到特定數據庫的連接之後,就可用該連接發送 SQL 語句。Statement 對象用 Connection 的方法 createStatement 創建,如下列代碼段中所示:

Statement stmt = conn.createStatement();

爲了執行 Statement 對象,被髮送到數據庫的 SQL 語句將被作爲參數提供給 Statement 的方法:

ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM table1");

②. 使用 Statement 對象執行語句

Statement 接口提供了三種執行 SQL 語句的方法:executeQuery、executeUpdate 和execute。使用哪一個方法由 SQL 語句所產生的內容決定。

方法 executeQuery 用於產生單個結果集的語句,例如 SELECT 語句。

方法 executeUpdate 用於執行 INSERT、UPDATE 或 DELETE 語句以及 SQL DDL(數據定義語言)語句,例如 CREATE TABLE 和 DROP TABLE。INSERT、UPDATE 或 DELETE 語句的效果是修改表中零行或多行中的一列或多列。executeUpdate 的返回值是一個整數,指示受影響的行數(即更新計數)。對於 CREATE TABLE 或 DROP TABLE 等不操作行的語句,executeUpdate 的返回值總爲零。

方法 execute 用於執行返回多個結果集、多個更新計數或二者組合的語句。

③. 語句完成

當連接處於自動提交模式時,其中所執行的語句在完成時將自動提交或還原。語句在已執行且所有結果返回時,即認爲已完成。對於返回一個結果集的 executeQuery 方法,在檢索完 ResultSet 對象的所有行時該語句完成。對於方法 executeUpdate,當它執行時語句即完成。但在少數調用方法 execute 的情況中,在檢索所有結果集或它生成的更新計數之後語句才完成。

④. 關閉 Statement 對象

Statement 對象將由 Java 垃圾收集程序自動關閉。而作爲一種好的編程風格,應在不需要 Statement 對象時顯式地關閉它們。這將立即釋放 DBMS 資源,有助於避免潛在的內存問題。 關閉Statement用 stmt.close() 方法。

ResultSet

ResultSet 包含符合 SQL 語句中條件的所有行,並且它通過一套 get 方法(這些 get 方法可以訪問當前行中的不同列)提供了對這些行中數據的訪問。ResultSet.next 方法用於移動到 ResultSet 中的下一行,使下一行成爲當前行。

結果集一般是一個表,其中有查詢所返回的列標題及相應的值。例如,如果查詢爲 SELECT a, b, c FROM Table1,則結果集將具有如下形式:

a        b         c
-------- --------- --------
12345    Cupertino CA
83472    Redmond   WA
83492    Boston    MA

下面的代碼段是執行 SQL 語句的示例。該 SQL 語句將返回行集合,其中列 1 爲 int,列 2 爲 String,而列 3 則爲日期型:

Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table1");
while (rs.next()) {
	int i = rs.getint("a");
	String s = rs.getString("b");
	Timestamp t = rs.getTimestamp("c");
}

①. 行和光標

ResultSet 維護指向其當前數據行的光標。每調用一次 next 方法,光標向下移動一行。最初它位於第一行之前,因此第一次調用 next 將把光標置於第一行上,使它成爲當前行。隨着每次調用 next 導致光標向下移動一行,按照從上至下的次序獲取 ResultSet 行。在 ResultSet 對象或其父輩 Statement 對象關閉之前,光標一直保持有效。

②. 列

方法 getXXX 提供了獲取當前行中某列值的途徑。在每一行內,可按任何次序獲取列值。但爲了保證可移植性,應該從左至右獲取列值,並且一次性地讀取列值。列名或列號可用於標識要從中獲取數據的列。例如,如果 ResultSet 對象 rs 的第二列名爲“title”,並將值存儲爲字符串,則下列任一代碼將獲取存儲在該列中的值:

String s = rs.getString("title");
String s = rs.getString(2);

注意列是從左至右編號的,並且從列 1 開始。同時,用作 getXXX 方法的輸入的列名不區分大小寫。 爲了代碼的可維護性與可讀性, 應該禁止用index的方法來取值, 要用讀列名的方法, 如上面的第一行取值方法。

③. 數據類型和轉換

對於 getXXX 方法,JDBC 驅動程序試圖將基本數據轉換成指定 Java 類型,然後返回適合的 Java 值。例如,如果 getXXX 方法爲 getString,而基本數據庫中數據類型爲 VARCHAR,則 JDBC 驅動程序將把 VARCHAR 轉換成 Java String。getString 的返回值將爲 Java String 對象。

④. NULL 結果值

要確定給定結果值是否是 JDBC NULL,必須先讀取該列,然後使用 ResultSet.wasNull 方法檢查該次讀取是否返回 JDBC NULL。

PreparedStatement

該 PreparedStatement 接口繼承 Statement,並與之在兩方面有所不同:

PreparedStatement 實例包含已編譯的 SQL 語句。這就是使語句“準備好”。 包含於 PreparedStatement 對象中的 SQL 語句可具有一個或多個 IN 參數。IN 參數的值在 SQL 語句創建時未被指定。相反的,該語句爲每個 IN 參數保留一個問號(“?”)作爲佔位符。每個問號的值必須在該語句執行之前,通過適當的 setXXX 方法來提供。

由於 PreparedStatement 對象已預編譯過,所以其執行速度要快於 Statement 對象。因此,多次執行的 SQL 語句經常創建爲 PreparedStatement 對象,以提高效率。

作爲 Statement 的子類,PreparedStatement 繼承了 Statement 的所有功能。另外它還添加了一整套方法,用於設置發送給數據庫以取代 IN 參數佔位符的值。同時,三種方法 execute、 executeQuery 和 executeUpdate 已被更改以使之不再需要參數。

①. 創建 PreparedStatement 對象

以下的代碼段(其中 conn 是 Connection 對象)創建包含帶兩個 IN 參數佔位符的 SQL 語句的 PreparedStatement 對象:

PreparedStatement pstmt=conn.prepareStatement("UPDATE table1 SET a = ? WHERE b = ?");

pstmt 對象包含語句 " UPDATE table1 SET a = ? WHERE b = ?",它已發送給 DBMS,併爲執行作好了準備。

②. 傳遞 IN 參數

在執行 PreparedStatement 對象之前,必須設置每個 ? 參數的值。這可通過調用 setXXX 方法來完成,其中 XXX 是與該參數相應的類型。例如,如果參數具有 Java 類型 long,則使用的方法就是 setLong。setXXX 方法的第一個參數是要設置的參數的序數位置,第二個參數是設置給該參數的值。例如,以下代碼將第一個參數設爲 123456789,第二個參數設爲 100000000:

pstmt.setLong(1, 123456789);
pstmt.setLong(2, 100000000);
CallableStatement:

CallableStatement 對象爲所有的 DBMS 提供了一種以標準形式調用已儲存過程(也就是SP)的方法。已儲存過程儲存在數據庫中。對已儲存過程的調用是 CallableStatement 對象所含的內容。有兩種形式:一種形式帶結果參數,另一種形式不帶結果參數。結果參數是一種輸出 (OUT) 參數,是已儲存過程的返回值。兩種形式都可帶有數量可變的輸入(IN 參數)、輸出(OUT 參數)或輸入和輸出(INOUT 參數)的參數。問號將用作參數的佔位符。

在 JDBC 中調用已儲存過程的語法如下所示。注意,方括號表示其間的內容是可選項;方括號本身並不是語法的組成部份。

{call 過程名[(?, ?, ...)]}

返回結果參數的過程的語法爲:

{? = call 過程名[(?, ?, ...)]}

不帶參數的已儲存過程的語法類似:

{call 過程名}

通常,創建 CallableStatement 對象的人應當知道所用的 DBMS 是支持已儲存過程的,並且知道這些過程都是些什麼。然而,如果需要檢查,多種 DatabaseMetaData 方法都可以提供這樣的信息。例如,如果 DBMS 支持已儲存過程的調用,則 supportsStoredProcedures 方法將返回 true,而 getProcedures 方法將返回對已儲存過程的描述。

CallableStatement 繼承 Statement 的方法(它們用於處理一般的 SQL 語句),還繼承了 PreparedStatement 的方法(它們用於處理 IN 參數)。CallableStatement 中定義的所有方法都用於處理 OUT 參數或 INOUT 參數的輸出部分:註冊 OUT 參數的 JDBC 類型(一般 SQL 類型)、從這些參數中檢索結果,或者檢查所返回的值是否爲 JDBC NULL。

③. 創建 CallableStatement 對象

CallableStatement 對象是用 Connection 方法 prepareCall 創建的。下例創建 CallableStatement 的實例,其中含有對已儲存過程 Csc_ GetCustomId調用。該過程有兩個變量,但不含結果參數:

CallableStatement cstmt = con.prepareCall("{call CSC_GetCustomId (?, ?, ?)}");

其中 ? 佔位符爲 IN、 OUT 還是 INOUT 參數,取決於已儲存過程 Csc_ GetCustomId。

④. IN 和 OUT 參數

將IN 參數傳給 CallableStatement 對象是通過 setXXX 方法完成的。該方法繼承自 PreparedStatement。所傳入參數的類型決定了所用的 setXXX 方法(例如,用 setFloat 來傳入 float 值等)。

如果已儲存過程返回 OUT 參數,則在執行 CallableStatement 對象以前必須先註冊每個 OUT 參數的 JDBC 類型(這是必需的,因爲某些 DBMS 要求 JDBC 類型)。註冊 JDBC 類型是用 registerOutParameter 方法來完成的。語句執行完後,CallableStatement 的 getXXX 方法將取回參數值。正確的 getXXX 方法是爲各參數所註冊的 JDBC 類型所對應的 Java 類型也就是說, registerOutParameter 使用的是 JDBC 類型(因此它與數據庫返回的 JDBC 類型匹配),而 getXXX 將之轉換爲 Java 類型。下面給出CSC中的一個例子:

String sqlSp = "{call CSC_GetCustomId(?, ?, ?)}";
cstmt = conn.prepareCall(sqlSp.toString());
cstmt.registerOutParameter(1, Types.NUMERIC);
cstmt.registerOutParameter(2, Types.NUMERIC);
cstmt.registerOutParameter(3, Types.VARcHAR);
cstmt.execute();
long customerID = cstmt.getlong(1);
long lRet = cstmt.getlong(2);
String sErr = cstmt.getString(3);

⑤. INOUT 參數

既支持輸入又接受輸出的參數(INOUT 參數)除了調用 registerOutParameter 方法外,還要求調用適當的 setXXX 方法(該方法是從 PreparedStatement 繼承來的)。setXXX 方法將參數值設置爲輸入參數,而 registerOutParameter 方法將它的 JDBC 類型註冊爲輸出參數。setXXX 方法提供一個 Java 值,而驅動程序先把這個值轉換爲 JDBC 值,然後將它送到數據庫中。這種 IN 值的 JDBC 類型和提供給 registerOutParameter 方法的 JDBC 類型應該相同。然後,要檢索輸出值,就要用對應的 getXXX 方法。例如,Java 類型爲 byte 的參數應該使用方法 setByte 來賦輸入值。應該給 registerOutParameter 提供類型爲 TINYINT 的 JDBC 類型,同時應使用 getByte 來檢索輸出值。下例假設有一個已儲存過程 reviseTotal,其唯一參數是 INOUT 參數。方法 setByte 把此參數設爲 25,驅動程序將把它作爲 JDBC TINYINT 類型送到數據庫中。接着,registerOutParameter 將該參數註冊爲 JDBC TINYINT。執行完該已儲存過程後,將返回一個新的 JDBC TINYINT 值。方法 getByte 將把這個新值作爲 Java byte 類型檢索。

CallableStatement cstmt = con.prepareCall("{call reviseTotal(?)}");
cstmt.setbyte(1, 25);
cstmt.registerOutParameter(1, java.sql.Types.TINYiNT);
cstmt.executeUpdate();
byte x = cstmt.getbyte(1);

十一、常用設計模式

10.1 Singleton模式

Singleton模式主要作用是保證在Java應用程序中,一個Class只有一個實例存在。一般有兩種方法:

①. 定義一個類,它的構造函數爲private的,所有方法爲static的。其他類對它的引用全部是通過類名直接引用。例如:

private SingleClass() {}
public static String getMethod1() {}
public static ArrayList getMethod2() {}

②. 定義一個類,它的構造函數爲private的,它有一個static的private的該類變量,通過一個public的getInstance方法獲取對它的引用,繼而調用其中的方法。例如:

private staitc SingleClass _instance = null;
private SingleClass() {
}
public static SingleClass getInstance() {
	if (_instance == null) {
		_instance = new SingleClass();
	}
	return _instance;
}
public String getMethod1() {
}
public ArrayList getMethod2() {
}

10.2 Prototype模式

Prototype模式用於創建對象,尤其是當創建對象需要許多時間和資源時。在Java中,Prototype模式的實現是通過方法clone(),該方法定義在Java的根對象Object中, 因此,Java中的其他對象只要覆蓋它就行了。通過clone(),我們可以從一個對象獲得更多的對象,

並請可以按照我們的需要修改他們的屬性。

public class Prototype implements Cloneable {
	private String Name;
	public rototype(String Name) {
		this.Name = Name;
	}
	public void setName(String Name) {
		this.Name = Name;
	}
	public String getName() {
		return Name;
	}
	public Object clone() {
		try{
			return super.clone();
		}
		catch(CloneNotSupportedException cnse){
			cnse.printStackTrace();
			return null;
		}
	}
}
Prototype p = new Prototype("My First Name");
Prototype p1 = p.clone();
p.setName("My Second Name");
Prototype p2 = p.clone();

10.3 Factory模式和Abstract Factory模式

①. Factory模式

利用給Factory對象傳遞不同的參數,以返回具有相同基類或實現了同一接口的對象。

②. Abstract Factory模式

先利用Factory模式返回Factory對象,在通過Factory對象返回不同的對象!

下面給出Sun XML Parser中的例子:

// 1. Abstract Factory模式
SAXParserFactory spf = SAXParserFactory.newInstance();
String validation = System.getProperty ("javax.xml.parsers.validation", "false");
if (validation.equalsIgnoreCase("true")) {
	spf.setValidating (true);
}
// 2. Factory模式
SAXParser sp = spf.newSAXParser();
parser = sp.getParser();
parser.setDocumentHandler(this);
parser.parse (uri);
  1. SAXParserFactory中的靜態方法newInstance()根據系統屬性javax.xml.parsers.SAXParserFactory不同的值生成不同的SAXParserFactory對象spf。然後SAXParserFactory對象又利用方法newSAXParser()生成SAXParser對象。

注意:

SAXParserFactory 的定義爲:

public abstract class SAXParserFactory extends java.lang.Object

SAXParserFactoryImpl 的定義爲:

public class SAXParserFactoryImpl extends javax.xml.parsers.SAXParserFactory
public static SAXParserFactory newInstance() {
	String factoryImplName = null;
	try {
		factoryImplName =System.getProperty("javax.xml.parsers.SAXParserFactory",
								"com.sun.xml.parser.SAXParserFactoryImpl");
	}
	catch (SecurityException se) {
		factoryImplName = "com.sun.xml.parser.SAXParserFactoryImpl";
	}
	SAXParserFactory factoryImpl;
	try {
		Class clazz = Class.forName(factoryImplName);
		factoryImpl = (SAXParserFactory) clazz.newInstance();
	}
	catch (ClassNotFoundException cnfe) {
		throw new FactoryConfigurationError(cnfe);
	}
	catch (IllegalAccessException iae) {
		throw new FactoryConfigurationError(iae);
	}
	catch (InstantiationException ie) {
		throw new FactoryConfigurationError(ie);
	}
	return factoryImpl;
}
  1. newSAXParser() 方法在SAXParserFactory定義爲抽象方法,

SAXParserFactoryImpl繼承了SAXParserFactory,它實現了方法newSAXParser():

public SAXParser newSAXParser()
throws SAXException, ParserConfigurationException {
	SAXParserImpl saxParserImpl = new SAXParserImpl(this);
	return saxParserImpl;
}

注意:

SAXParserImpl的定義爲:

public class SAXParserImpl extends javax.xml.parsers.SAXParser

SAXParserImpl的構造函數定義爲:

public SAXParserImpl(SAXParserFactory spf)
throws SAXException, ParserConfigurationException {
	super();
	this.spf = spf;
	if (spf.isValidating ()) {
		parser = new ValidatingParser();
		validating = true;
	} else {
		parser = new Parser();
	}
	if (spf.isNamespaceAware ()) {
		namespaceAware = true;
		throw new ParserConfigurationException(
		"Namespace not supported by SAXParser");
	}
}

本文轉載自:Java中常用的API——阿里架構師的兩萬字總結

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