1. 泛型 (Generics)
Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制允許程序員在編譯時檢測到非法的類型。
泛型本質上是參數化類型,就是將類型由原來的具體的類型參數化,類似於方法中的變量參數,此時類型也定義成參數形式(可以稱之為類型形參),然后在使用/調用時傳入具體的類型(類型實參)。
在泛型使用過程中,操作的數據類型被指定為一個參數,這種參數類型可以用在類、接口和方法中,分別被稱為泛型類、泛型接口、泛型方法。
1)泛型方法
可以寫一個泛型方法,該方法在調用時可以接收不同類型的參數。根據傳遞給泛型方法的參數類型,編譯器適當地處理每一個方法調用。
下面是定義泛型方法的規則:
(1) 所有泛型方法聲明都有一個類型參數聲明部分(由尖括號分隔),該類型參數聲明部分在方法返回類型之前;
(2) 每一個類型參數聲明部分包含一個或多個類型參數,參數間用逗號隔開。一個泛型參數,也被稱為一個類型變量,是用於指定一個泛型類型名稱的標識符;
(3) 類型參數能被用來聲明返回值類型,並且能作為泛型方法得到的實際參數類型的占位符;
(4) 泛型方法體的聲明和其他方法一樣。注意類型參數只能代表引用型類型,不能是原始類型(像 int、double、char 等);
Java 中泛型標記符:
E - Element (在集合中使用,因為集合中存放的是元素)
T - Type(Java 類)
K - Key(鍵)
V - Value(值)
N - Number(數值類型)
?- 表示不確定的 java 類型
2) 泛型類
泛型類的聲明和非泛型類的聲明類似,除了在類名后面添加了類型參數聲明部分。
泛型類的類型參數聲明部分也包含一個或多個類型參數,參數間用逗號隔開。一個泛型參數,也被稱為一個類型變量,是用於指定一個泛型類型名稱的標識符。因為他們接受一個或多個參數,這些類被稱為參數化的類或參數化的類型。
3) 類型通配符
(1) 無界通配符 <?>
不確定或者不關心實際要操作的類型,可以使用無限制通配符(尖括號里一個問號,即 ),表示可以持有任何類型。
類型通配符一般是使用 ? 代替具體的類型參數。
例如 List<?> 在邏輯上是 List<String>,List<Integer> 等所有 List<具體類型實參> 的父類。
(2) 上界通配符 < ? extends E >
上界:用 extends 關鍵字聲明,表示參數化的類型可能是所指定的類型,或者是此類型的子類。
在類型參數中使用 extends 表示這個泛型中的參數必須是 E 或者 E 的子類,這樣有兩個好處:
a) 如果傳入的類型不是 E 或者 E 的子類,編譯不成功;
b) 泛型中可以使用 E 的方法,要不然還得強轉成 E 才能使用;
類型參數列表中如果有多個類型參數上限,用逗號分開。
(3) 下界通配符 < ? super E >
下界: 用 super 進行聲明,表示參數化的類型可能是所指定的類型,或者是此類型的父類型,直至 Object。
在類型參數中使用 super 表示這個泛型中的參數必須是 E 或者 E 的父類。
上界通配符主要用於讀數據,下界通配符主要用於寫數據。
(4) ?和 T 的區別
T 是一個確定的類型,通常用於泛型類和泛型方法的定義,?是一個不確定的類型,通常用於泛型方法的調用代碼和形參,不能用於定義類和泛型方法。兩者的區別:
a) 通過 T 來 確保 泛型參數的一致性;
b) 類型參數可以多重限定而通配符不行;
c) 通配符可以使用超類限定而類型參數不行;
實例:
1 public class App { 2 3 // 泛型方法 printArray 4 public static < E > void printArray( E[] inputArray ) { 5 6 for (E element : inputArray ) { 7 System.out.printf( "%s ", element ); 8 } 9 System.out.println(); 10 } 11 12 // 泛型方法, 上界通配符 < ? extends E > 13 public static <T extends Comparable<T>> T maximum(T x, T y, T z) { 14 T max = x; 15 if ( y.compareTo( max ) > 0 ) { 16 max = y; 17 } 18 if ( z.compareTo( max ) > 0 ) { 19 max = z; 20 } 21 return max; 22 } 23 24 public static void main( String[] args ) { 25 26 // Generics 27 28 // Generics - Integer, Double, Character 29 Integer[] intArray = { 1, 2, 3, 4, 5 }; 30 Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 }; 31 Character[] charArray = { 'C', 'h', 'a', 'r', 'a', 'c', 't', 'e', 'r' }; 32 33 System.out.println("Integer Array:"); 34 printArray(intArray); 35 System.out.println("Double Array:"); 36 printArray(doubleArray); 37 System.out.println("Character Array:"); 38 printArray(charArray); 39 40 // Generics 41 System.out.println(); 42 System.out.printf( "Max of %d, %d and %d is %d\n", 43 3, 4, 5, maximum( 3, 4, 5 ) ); 44 System.out.printf( "Maxm of %.1f,%.1f and %.1f is %.1f\n", 45 6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ) ); 46 System.out.printf( "Max of %s, %s and %s is %s\n","pear", 47 "apple", "orange", maximum( "pear", "apple", "orange" ) ); 48 49 // Generics class 50 System.out.println(); 51 Round<Integer> integerRound = new Round<Integer>(); 52 Round<String> stringRound = new Round<String>(); 53 54 integerRound.add(new Integer(30)); 55 stringRound.add(new String("Round Test")); 56 57 System.out.printf("Integer Value :%d\n", integerRound.get()); 58 System.out.printf("String Value :%s\n", stringRound.get()); 59 } 60 } 61 62 // 泛型類 63 class Round<T> { 64 65 private T t; 66 67 public void add(T t) { 68 this.t = t; 69 } 70 71 public T get() { 72 return t; 73 } 74 75 }
輸出:
Integer Array:
1 2 3 4 5
Double Array:
1.1 2.2 3.3 4.4
Character Array:
C h a r a c t e r
Max of 3, 4 and 5 is 5
Maxm of 6.6,8.8 and 7.7 is 8.8
Max of pear, apple and orange is pear
Integer Value :30
String Value :Round Test
2. 序列化 (Serialize)
Java 提供了一種對象序列化的機制,該機制中,一個對象可以被表示為一個字節序列,該字節序列包括該對象的數據、有關對象的類型的信息和存儲在對象中數據的類型。
整個過程都是 Java 虛擬機(JVM)獨立的,也就是說,在一個平台上序列化的對象可以在另一個完全不同的平台上反序列化該對象。
對象的序列化是將一個Java對象寫入IO流(或文件);與此對應的,反序列化則是從IO流(或文件)中恢復一個Java對象。
要將一個java對象序列化,那么對象的類需要是可序列化的。要讓類可序列化,那么這個類需要實現接口 Serializable 或 Externalizable。Externalizable 繼承了 Serializable,該接口中定義了兩個抽象方法:writeExternal() 與 readExternal()。
1) 實現 Serializable 的對象
創建一個 Employee 類,實現 Serializable 接口:
1 class Employee implements Serializable { 2 private String name = null; 3 transient private Integer age = null; 4 5 public Employee(String name, Integer age) { 6 this.name = name; 7 this.age = age; 8 } 9 10 @Override 11 public String toString() { 12 return "[" + name + ", " + age + "]"; 13 } 14 }
(1) 序列化
使用 ObjectOutputStream 類的 writeObject() 方法序列化對象,格式如下:
1 public class App { 2 public static void main( String[] args ) { 3 4 try { 5 File file = new File("employee.out"); 6 ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file)); 7 Employee employee = new Employee("Tester", 25); 8 objectOutputStream.writeObject(employee); 9 objectOutputStream.close(); 10 } catch (IOException) { 11 e.printStackTrace(); 12 } 13 } 14 }
(2) 反序列化
使用 ObjectInputStream 類的 readObject() 方法反序列化對象,格式如下:
1 public class App { 2 public static void main( String[] args ) { 3 4 try { 5 File file = new File("employee.out"); 6 ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file)); 7 Object newEmployee = objectInputStream.readObject(); // 沒有強制轉換到Person類型 8 objectInputStream.close(); 9 System.out.println(newEmployee); 10 } catch (IOException | ClassNotFoundException e) { 11 e.printStackTrace(); 12 } 13 } 14 }
輸出:
[Tester, null]
(3) transient 關鍵字
當某個字段被聲明為 transient 后,默認序列化機制就會忽略該字段。此處將 Person 類中的 age 字段聲明為 transient,age 字段未被序列化。
2) 實現 Externalizable 的對象
當使用 Externalizable 接口來進行序列化與反序列化的時候,需要開發人員重寫 writeExternal() 與 readExternal()。
創建一個 Employee2 類,實現 Externalizable 接口:
1 class Employee2 implements Externalizable { 2 private String name = null; 3 transient private Integer age = null; 4 5 public Employee2() { 6 7 } 8 9 public Employee2(String name, Integer age) { 10 this.name = name; 11 this.age = age; 12 } 13 14 @Override 15 public void writeExternal(ObjectOutput out) throws IOException { 16 out.writeObject(name); 17 out.writeInt(age); 18 } 19 20 @Override 21 public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { 22 name = (String) in.readObject(); 23 age = in.readInt(); 24 } 25 26 @Override 27 public String toString() { 28 return "[" + name + ", " + age + "]"; 29 } 30 }
(1) 序列化
使用 ObjectOutputStream 類的 writeObject() 方法序列化對象,格式如下:
1 public class App { 2 public static void main( String[] args ) { 3 4 try { 5 File file2 = new File("employee2.out"); 6 ObjectOutputStream objectOutputStream2 = new ObjectOutputStream(new FileOutputStream(file2)); 7 Employee2 employee2 = new Employee2("Tester2", 32); 8 objectOutputStream2.writeObject(employee2); 9 objectOutputStream2.close(); 10 } catch (IOException) { 11 e.printStackTrace(); 12 } 13 } 14 }
(2) 反序列化
使用 ObjectInputStream 類的 readObject() 方法反序列化對象,格式如下:
1 public class App { 2 public static void main( String[] args ) { 3 4 try { 5 6 File file2 = new File("employee2.out"); 7 ObjectInputStream objectInputStream2 = new ObjectInputStream(new FileInputStream(file2)); 8 Object newEmployee2 = objectInputStream2.readObject(); 9 objectInputStream2.close(); 10 System.out.println(newEmployee2); 11 12 } catch (IOException | ClassNotFoundException e) { 13 e.printStackTrace(); 14 } 15 16 } 17 }
輸出:
[Tester2, 32]