class Clz<T> {
private T data;
public Clz(T data) {
this.data = data;
}
}
泛型類是以類(Class)為基礎再加上泛型類型的符號就可以使用,之後這個類就可以使用泛型指定的類型
public class GenericClass1<T> {
private T data;
private GenericClass1() {
}
public GenericClass1(T data) {
this(); // 呼叫無參構造函數
this.data = data;
}
public T GetData() {
return data;
}
}
// 兩個泛型變量 T K...
public class GenericClass2<T, K> {
private T t;
private K k;
public GenericClass2() {
}
public void setDataT(T t, K k) {
this.t = t;
this.k = k;
}
public T getTData() {
return t;
}
public K getKData() {
return k;
}
}
abstract class MyClz<K> {
public abstract int compareTo(K k);
}
class NormalClass<T, K> extends MyClz<K> {
private T data;
public NormalClass(T data) {
this.data = data;
}
@Override
public int compareTo(K k) {
return 0;
}
}
Generic Interface:泛型界面
● 泛型界面基本上跟 Class 相同,不過泛型界面是使用界面(interface)作為基礎,再加上泛型類型的符號,之後就可以使用泛型界面
// 泛型界面
public interface GenericInterface<T> {
public void setData(T t);
public T getData();
}
// 實現 1 未指定類型
public class NormalClass<T> implements GenericInterface<T> {
public T data;
public void setData(T t) {
this.data = t;
}
public T getData() {
return data;
}
}
// 實現 2 指定類型
public class NormalClass2 implements GenericInterface<String> {
public String data;
public void setData(String t) {
this.data = t;
}
public String getData() {
return data;
}
}
public static void main(String[] args) {
NormalClass<String> n = new NormalClass<>();
n.setData("Hello World");
n.getData();
NormalClass2 n2 = new NormalClass2();
n2.setData("Hello World");
n2.getData();
}
public static void main(String[] args) {
NormalClass n1 = new NormalClass();
String s1 = n1.<String>GetGeneric("Hello", "World", "123");
System.out.println(s1);
int i1 = n1.<Integer>GetGeneric(111, 222, 333);
System.out.println(i1);
// 原本定義為 String 型態的變數 T (內部就是 String 型態)
NormalClass2<String> n2 = new NormalClass2<>();
// 泛型方法可分開定義,自己定義方法內部使用的 T 為 Double 類型
double i2 = n2.<Double>GetGeneric(0.0, 0.1, 0.2, 0.3, 0.4, 0.5);
System.out.println(i2);
}
// 1. 一般類別使用 Generic Method
public class NormalClass {
// <T> T,<T> 為返回的型態 T 為返回的值
public <T> T GetGeneric(T ...a) {
return a[a.length / 2];
}
}
// 2. 混和 Generic Class interface、method
public class NormalClass2<T> implements GenericInterface<T> {
public T data;
@Override
public void setData(T t) {
this.data = t;
}
public <T> T GetGeneric(T ...a) {
// 內部的類型 T != 外部的類型 T
return a[a.length / 2];
}
}
限定類型 Qulified Type
有時會對類型變量進型約束:例如約束型態 T 必須要實現 Comparable 界面
如果不進行約束將無法確保該型態是否可執行此界面的函數!
// 未用現定符號,「無法」確定是否都有實現 compareTo 方法,這時就需要限定類型 Qulified Type
public static <T> T min(T a, T b) {
return (a.compareTo(b) > 0) ? a : b;
}
Constraint (約束)、limitation (限制)
● 基礎限定方法 <T exetnds A>,其中 extends 左右都允許多個泛型符號,像是… <T, k extends A & B>
注意:限定類型中 只能有一類 (一個繼承),而且必須是第一個 !
● 可限定多個類型,使用 & 連接多個限制條件 <T extends A & b>,當然如上面所說,第一個 A 是「類別」,之後的 b 則是「界面」,也就是同樣遵守著 Java 語言的單一繼承的特性!
// 1. 使用限定符號
public static <T extends Comparable> T minNumber(T a, T b) {
return (a.compareTo(b) > 0) ? a : b;
}
// 2. 多個限定
public static <T extends Comparable & Serializable> T maxNumber(T a, T b) {
return (a.compareTo(b) > 0) ? b : a;
}
// 3. 類限定類 Activity為類型,Serializable為界面
public static <T extends Activity & Serializable> T recordActivity(T a) {
Log.d("TEG", a.toString);
return a;
}
● 想看更多的範例,可參考 Java 源碼中實現的 List and ArrayList 類,這些類型都有使用泛型約束…
public class Extends {
public static void main(String[] args) {
test();
}
private static void test() {
Shop<?> shop = new Shop<>();
// 無法確定確切的類
//shop.set(new Banana());
//shop.set(new Fruit());
//shop.set(new Food());
shop.set(null); // null 可以
// 無法確定取出的類
//Food d = shop.get();
//Fruit f = shop.get();
//Banana b1 = shop.get();
Banana b2 = (Banana) shop.get();// 強制轉型,可能有問題 warning
Object o = shop.get(); // 所有類的父類 Object 一定是
}
}
class Shop<T> {
private T t;
void set(T t) {
this.t = t;
}
T get() {
return t;
}
}
class Food {
}
class Meat extends Food {
}
class Beef extends Meat {
}
class Fruit extends Food {
}
class Banana extends Fruit {
}
● 若泛型使用了 通配符 ?(單單只有通配符),但是作為「引數」,代表 全部類型都可以接受
public class WildcardExample {
// 定義一個方法,接受通配符類型的 List 作為引數
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
public static void main(String[] args) {
// 創建一個 List<String>
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// 創建一個 List<Integer>
List<Integer> intList = new ArrayList<>();
intList.add(1);
intList.add(2);
// 調用 printList 方法,傳入不同類型的 List
System.out.println("Printing stringList:");
printList(stringList);
System.out.println("Printing intList:");
printList(intList);
}
}
通配符 & extends
● 通配符與 extends 配合後的功能是:安全的 取得 (get) 數據;作為引數,基礎使用 <? extends X>,表示類型的 上限到 X 類,包含 X 類以及其衍生子類
public static void main(String[] args) {
funcPrint_2(shop1); // ok
"1: " funcPrint_2(shop2); // ok
//Shop<T> 是泛型
Shop<? extends Fruit> shop3 = new Shop<>();
"2: " Fruit f1 = shop3.getType();
"3: " Apple a1 = (Apple) shop3.getType();
Apple apple = new Apple();
"4: " shop3.setType(); // Fail !!
}
static void funcPrint_1(Shop<Fruit> p) {
System.out.println("Func1: " + p.getType().type());
}
static void funcPrint_2(Shop<? extends Fruit> p) {
System.out.println("Func2: " + p.getType().type()); // 安全取得
p.set(new Fruit()); // Fail !!
}
A. 使用 <? extends Fruit> 拓展了傳入泛型類別的寬度,拓展範圍是 Fruit 跟它的子類 (include Fruit)
B. 可安全的取得類,因為返回的一定是 Fruit 類 (上限到 Fruit)
C. 返回的是 Fruit 類,它的 子類要強制轉型
D. 只知道設定的是 apple,但卻不能具體的知道 Shop 內的類型變數 T 要設定哪個類 (可能是 Fruit、Apple、HonFuShi、Orang)
主要用來安全的訪問(取得)數據,就像是 Kotlin 的協變 (out)
● 範例二:強調通配符 extends 是限定泛型,extends 可安全的取出
public class Extends {
public static void main(String[] args) {
testExtends();
}
private static void testExtends() {
Shop<? extends Fruit> shop = new Shop<>(); // extends 用於安全取值
//shop.set(new Banana()); // 無法確定確切的類,有可能是 Fruit、Banana or ...
//shop.set(new Fruit());
//shop.set(new Food());
shop.set(null); // null 可以
Food d = shop.get();
Fruit f = shop.get(); // 只能肯定一定是 Fruit
//Banana b1 = shop.get(); // 只能取出是 Fruit 的基類
Banana b2 = (Banana) shop.get();
Object o = shop.get();
}
}
class Shop<T> {
private T t;
void set(T t) {
this.t = t;
}
T get() {
return t;
}
}
class Food {
}
class Meat extends Food {
}
class Beef extends Meat {
}
class Fruit extends Food {
}
class Banana extends Fruit {
}
通配符 & super
● 通配符與 super 配合的功能是:安全的 設定 數據;作為引數,基礎使用 <? super X>,表示類型的 下限是 X 類,包含 X 類 以及其父類(超類)
限定符不能使用 super,像是如果使用<T super Shop> 語法錯誤
public static void main(String[] args) {
Shop<Food> shop1_1 = new Shop<>();
Shop<Fruit> shop2_1 = new Shop<>();
Shop<Apple> shop3_1 = new Shop<>();
Shop<Orange> shop3_2 = new Shop<>();
Shop<HonFuShi> shop4_1 = new Shop<>();
funcSuperPrint(shop1_1); // ok (基類
funcSuperPrint(shop2_1); // ok (包含限定界線
"1: "
funcSuperPrint(shop3_1); // fail
funcSuperPrint(shop3_2); // fail
funcSuperPrint(shop4_1); // fail
Shop<? super Fruit> test = new Shop<>();
"2: "
test.setType(new Food()); // fail
test.setType(new Fruit()); // ok
test.setType(new Apple()); // ok
"3: " Object o = test.getType();
}
public static void funcSuperPrint(Shop<? super Fruit> p) {
System.out.print("Super Func: " + p.getType());
// fail 返回的是 Object 類
"4: "System.out.println("Super Func: " + p.getType().type());
}
A. 使用 <? super Fruit> 拓展了傳入泛型類別的寬度,拓展範圍是 Fruit 跟它的父類(include Fruit),所以不符合入參拓展規定
B. 編譯器不知道set 它的具體類型,但可保證 Fruit and 其 子類 可安全轉型成 Fruit,set 無法得知設定的超類是否能轉型成 Fruit
C. 返回的類別是 Object,無法得知回傳的具體類別(有可能是 Food、Fruit),但一定是 Object 的子類
D. 返回 Object 類別,除非強轉否則無法使用指定的子類 Function
主要用來安全的設定 (set) 數據,只可寫入 X 的子類,就像是 Kotlin 的逆變 (in)
● 範例二:集合的安全設定 (set) 就可以使用到 通配符 + super
public class Extends {
public static void main(String[] args) {
}
private static void testSuper() {
// 安全設定 Fruit 基類數據
Shop<? super Fruit> shop = new Shop<>();
// 能確定一定是 Fruit 的衍生子類,要嘛 Fruit 要嘛 Fruit 的子類
shop.set(new Banana());
shop.set(new Fruit());
//shop.set(new Food()); // Err 無法確定跟 Fruit 的關係
shop.set(null); // null 可以
// 無法確定取出的類
//Food d = shop.get();
//Fruit f = shop.get();
//Banana b1 = shop.get();
Banana b2 = (Banana) shop.get();
// 只能確定取出的一定是 Fruit 的基類,而 Object 則一定 Ok
Object o = shop.get();
}
}
class Shop<T> {
private T t;
void set(T t) {
this.t = t;
}
T get() {
return t;
}
}
class Food {
}
class Meat extends Food {
}
class Beef extends Meat {
}
class Fruit extends Food {
}
class Banana extends Fruit {
}
● 為何安全設定數據,但無法安全取得數據 ?
Get 時無法確定是哪一個類,主要原因是因為泛型限制丟失,只能用 Object 存放
super & extends 關係 & 使用
● Shop<?> 非限定通配符,等同於 Shop<\? extends Object>
● Shop<? extends T>(上界)、Shop<? super T>(下屆) 兩者為 限定通配符
class Shop<T> {
private T t;
void set(T t) {
this.t = t;
}
T get() {
return t;
}
}
class Food {
}
class Meat extends Food {
}
class Beef extends Meat {
}
class Fruit extends Food {
}
class Banana extends Fruit {
}
配合上面的程式看下面限定 & 非限定通配符的關係圖
Java Collections 的通配符使用
● Java Collections 內大量地使用了泛型 super & extends,以 copy 為例
使用了 super 安全的設定 T 的衍生類數據,使用 extends 安全的取得了 T 的衍生類數據,以此做為 copy 基礎
public static <T> void copy(List<? super T> dest, // 安全 Set
List<? extends T> src) { // 安全 Get
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
...
} else {
// 取得 Iterator
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(
si.next() // 安全取得
); // 1. 安全設定
}
}
}
● 而為何要這樣使用 extends & super,可以看下面的例子
A. 類關係
class PC {
int id;
PC(int id){
this.id = id;
}
}
class CPU extends PC {
int id;
CPU(int id){
super(id);
this.id = id;
}
}
B. Collections 的 copy 函數使用
// 指定
public static void copy_1(List<CPU> p1, List<CPU> p2) {
Collections.copy(p1, p2);
}
// 限定類型
public static <T> void copy_2(List<T> p1, List<T> p2) {
Collections.copy(p1, p2);
}
// 限定 super (set)
public static <T> void copy_3(List<? super T> p1, List<T> p2) {
Collections.copy(p1, p2);
}
// 限定 super (set)、extends (get)
public static <T> void copy_4(List<? super T> p1, List<? extends T> p2) {
Collections.copy(p1, p2);
}
C. 測試
public class UseMixSuperExtend {
public static void main(String[] args) {
fixed();
System.out.println("");
gMethod();
System.out.println("");
Mix_1();
System.out.println("");
Mix_2();
}
// 固定 (指定) 泛型
public static void fixed() {
List<CPU> src = new ArrayList<>();
src.add(new CPU(1));
List<CPU> dest = new ArrayList<>();
dest.add(new CPU(2));
System.out.println("cpu-cpu before copy: " + dest.get(0).id);
copy_1(dest, src); // src 複製到 dest
System.out.println("cpu-cpu after copy: " + dest.get(0).id);
}
// 泛型方法
public static void gMethod() {
// 使用基類 PC
List<PC> src = new ArrayList<>();
src.add(new PC(1));
// 使用基類 PC
List<PC> dest = new ArrayList<>();
dest.add(new PC(2));
System.out.println("PC-PC before copy: " + dest.get(0).id);
UseMixSuperExtend.<PC>copy_2(dest, src); // 轉為 PC 泛型
System.out.println("PC-PC after copy: " + dest.get(0).id);
}
public static void Mix_1() {
// 指定衍生類 CPU
List<CPU> src = new ArrayList<>();
src.add(new CPU(1));
// 使用基類 PC
List<PC> dest = new ArrayList<>();
dest.add(new PC(2));
System.out.println("cpu-PC<CPU> before copy: " + dest.get(0).id);
// // 使用泛型方法 Err,因為第一個參數不符合
// UseMixSuperExtend.<PC>copy_2(dest, src);
// 使用通配符轉型為 CPU
UseMixSuperExtend.<CPU>copy_3(dest, // <? super CPU>
src);
System.out.println("cpu-PC<CPU> after copy: " + dest.get(0).id);
}
public static void Mix_2() {
// 指定衍生類 CPU
List<CPU> src = new ArrayList<>();
src.add(new CPU(1));
// 使用基類 PC
List<PC> dest = new ArrayList<>();
dest.add(new CPU(2));
System.out.println("cpu-PC<PC> before copy: " + dest.get(0).id);
// 使用通配符轉型為 PC Err,因為第二個參數是 CPU
//UseMixSuperExtend.<PC>copy_3(dest, src);
UseMixSuperExtend.<PC>copy_4(dest, // <? super PC>
src); // <? extends PC>
System.out.println("cpu-PC<PC> after copy: " + dest.get(0).id);
}
}
public class TestAnnotation {
public static void main(String[] args) {
List list = Arrays.asList(args);
//"1. "
ToDo.print(list);
}
}
class ToDo {
static void print (List<Integer> list) {
for(int i : list) {
System.out.println("value: " + i);
}
}
}