泛型基础
泛型规范
T1,T2,T3...
泛型可以随便写吗,可以随便写,但我们追求规范。
-E — Element
,常用在java Collection
里,如:List<E>,Iterator<E>,Set<E>
- K,V — Key,Value
,代表Map
的键值对
- N — Number
,数字
- T — Type
,类型,如String, Integer
等等
泛型使用方式
泛型类
我们首先定义一个简单的Box
类:
public class Box {
private String object;
public void set(String object) { this.object = object; }
public String get() { return object; }
}
这是最常见的做法,这样做的一个坏处是Box
里面现在只能装入String
类型的元素,今后如果我们需要装入Integer
等其他类型的元素,还必须要另外重写一个Box
,代码得不到复用,使用泛型可以很好的解决这个问题。
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
这样我们的Box
类便可以得到复用,我们可以将T替换成任何我们想要的类型:
Box<Integer> integerBox = new Box<Integer>();
Box<Double> doubleBox = new Box<Double>();
Box<String> stringBox = new Box<String>();
- 多泛型变量定义
public class ObjClass<T,U>
泛型接口
在JDK1.5
之后,不仅可以声明泛型类,也可以声明泛型接口,与声明泛型类基本相似,在接口后面加上<T>
,如下所示:
[访问权限public]interface 接口名称<泛型标识T,也可以和类一样有多个泛型变量T,U>{}
在接口上定义泛型与在类中定义泛型是一样的,代码如下:
interface Information<T> {
public T getInfo() ;
}
泛型接口的两种实现方式
定义子类:在子类的定义上也声明泛型类型
interface Information<T> {
public T getMsg() ;
}
class InfoImpl<T> implements Information<T> {
private T msg;
public T getMsg() {
return msg;
}
public static void main(String arsg[]){
Information<String> info = null; // 声明接口对象
info = new InfoImpl<String>("李兴华") ; // 通过子类实例化对象
System.out.println("内容:" + info.getMsg()) ;
}
}
如果现在实现接口的子类不想使用泛型声明,则在实现接口的时候直接指定好其具体的操作类型即可:
interface Information<T> {
public T getMsg() ;
}
class InfoImpl implements Information<String> {
private String msg;
public String getMsg() {
return msg;
}
public static void main(String arsg[]){
Information info = null; // 声明接口对象
info = new InfoImpl("李兴华") ; // 通过子类实例化对象
System.out.println("内容:" + info.getMsg()) ;
}
}
泛型方法
static方法
- 对于
static
方法而言,无法访问泛型类的类型参数,因此,如果static
方法需要使用泛型,则必须使其成为泛型方法。 - 如果你定义了一个泛型(类、接口),那么
Java
规定,你不能在所有的静态方法、静态初块等所有静态内容中使用泛型的类型参数!!例如:
class A<T> {
public static void func(T t) { // 错误!在所有静态内容中不得使用泛型的类型参数
}
}
定义泛型方法
1 格式:修饰符 <类型参数列表> 返回类型 方法名(形参列表) { 方法体 }
- 例如:
public static <T, S> int func(List<T> list, Map<int, S> map) { ... }
// 就表示,在我func这个方法的作用域中,T和S是我的泛型类型参数
- 例如:
2 作用域
比如class A<T> { ... }
中T
的作用域就是整个A
,而public <T> func(...) { ... }
中T
的作用域就是方法func
;
class A<T> { // A已经是一个泛型类,其类型参数是T
public static <T> void func(T t) { // 再在其中定义一个泛型方法,该方法的类型参数也是T
}
}
当上述两个类型参数冲突时,在方法中,方法的T
会覆盖类的T
,即和普通变量的作用域一样,内部覆盖外部,外部的同名变量是不可见的!
- 3 泛型方法的类型参数也可以指定上限
- 类型上限必须在类型参数声明的地方定义上限!不能在方法参数中定义上限
- 例如:
- a.
<T extends Xxx> void func(List<T> list)
; // 正确 - b.
<T extends Xxx> void func(T t)
; // 必然正确 - c.
<T> void func(List<T extends Xxx> list)
; // 编译错误 - 规定了上限就只能在规定范围内指定类型实参,超出这个范围就会直接编译报错!
4 调用泛型方法
- 显式指定方法的类型参数,类型参数要写在尖括号中并放在方法名之前,例如:
obj.<String>func(...)
; 隐式地自动推断:那就是不指明泛型参数,让编译器根据传入的实参类型来自动推断类型参数是什么;
- a. 最简单的例如:
<T> void func(T t)
; 这样调用的话,obj.func("lala")
; // 那么就会根据”lala”的类型String
推断出类型参数T的类型是String
- b. 但是一定要避免歧义,例如:
<T> void func(T t1, T t2)
; 如果这样调用的话,obj.func("lala", 15)
; 虽然编译不会报错,但是仍然会有很大隐患,T
到底应该是String
还是Integer
存在歧义 - c. 但是有些歧义Java是会直接当成编译错误的,即所有和泛型参数有关的歧义,例如:
<T> void func(List<T> l1, List<T> l2)
; 如果这样调用的话,obj.func(new List<String>(), new List<Integer>())
; 这里当然会有歧义,编译器无法知道T到底应该是String
还是Integer
,但是这种歧义会直接报错的!!编译都无法通过;
即泛型要比普通类型的检查要严很多,很多在普通类型上行得通的擦边球,在泛型上都无法通过!
- a. 最简单的例如:
- 显式指定方法的类型参数,类型参数要写在尖括号中并放在方法名之前,例如:
5 完整例子
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public void setKey(K key) { this.key = key; }
public void setValue(V value) { this.value = value; }
public K getKey() { return key; }
public V getValue() { return value; }
}
我们可以像下面这样去调用泛型方法:
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
或者在Java1.7/1.8
利用type inference
,让Java自动推导出相应的类型参数:
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
通配符VS泛型方法
<?>
无限制通配符
<? extends E> extends
关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类;
<? super E> super
关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类
’PECS总结
+ Producer Extends
- 如果你需要一个只读List
,用它来produce T
,那么使用? extends T
。
+ Consumer Super
- 如果你需要一个只写List
,用它来consume T
,那么使用? super T
。
+ 如果需要同时读取以及写入,那么我们就不能使用通配符了。
详细解释见 《Java泛型 通配符》