public class Factory<T>{
private T first;
private T second;
//getter setter
}
public class Factory<T,E>{
private T first;
private E[] array;
}
(1) 如果定义了一个泛型(类、接口),那么你就不能在静态方法、静态初块等静态内容中使用泛型的类型参数,如下方式就不合法:
public class A<T> {
public static void func(T t){
//报错,编译不通过
}
}
如下方式为合法:
public class A<T> {
public void func(T t){
//do something
}
}
(2) 在静态内容(静态方法)中使用泛型,形式如下
public static <T> void func(T t){...}
(3) 类型参数的作用域
class A 中T的作用于就是整个A
public 中T的作用于就是方法func,包括返回类型,输入参数,方法内容块。
作用域覆盖:内部覆盖外部
class A<T>{
public static <T> void func(T t){}
}
这里的func是在A中定义的一个泛型方法,虽然类型参数表示也记为T,但在该方法中覆盖了A类的T,可以效仿同名局部变量覆盖全局变量来理解。
一般为了避免不必要的错误,建议不重名。
public static <T extends Comparable> T min(T[] a)
也可以有多个限定
public static <T extends Comparable & Serializable> T min(T[] a)
(1) 显式指定方法的类型参数,类型参数写在尖括号中并放在调用的方法名之前。
obj.<String>func(...);
这样就显式地制定了泛型方法的类型参数为String,则所有方法内出现类型参数T的地方都被编译器通过类型擦除,替换成String类型。
(2) 隐式地自动推断,不指明泛型参数,编译器根据传入的实参类型自动推断类型参数。例如这是一个泛型方法:
<T> void func(T t){...}
如下是隐式调用它的方式
obj.func("name");
编译器根据"name"的类型String腿短出类型参数T的类型是String
(1)无限定
Pair<?>
(2)对父类有限定
Pair<? extends Father>
(3)对子类有限定
Pair<? super Son>
例如,Pair super Father>有getter setter方法,将如下:
void setFirst(? super Father)
? super Father getFirst()
(1) 所有能用类型通配符(?)解决的问题都能用泛型方法解决,并且泛型方法可以解决得更好。
类型通配符:
void func(List<? extends A> list);
用泛型方法达到一样效果:
<T extends A> void func(List<T> list);
(2) 两种方法都可以达到相同的效果,“?”可以代表范围内任意类型,而T也可以传入范围内的任意类型实参,并且泛型方法更进一步,“?”泛型对象是只读的,而泛型方法里的泛型对象是可修改的,即List中的list是可以修改的。
(3)使用场景
一般只读只用“?”,要修改就用泛型方法。例如要对list进行修改;
public <T> void func(List<T> list, T t){
list.add(t);
}
多个参数、返回值之间存在类型依赖关系就该使用泛型方法。如果被依赖的类型如果是不确定的“?”,那么其他元素就无法依赖它。例如:
<T> void func(List<? extends T) list, T t);
第一个参数依赖第二个参数的类型
如果没有依赖关系,就可以将泛型类型规约成“?”
<T,E extends T> void func(List<T> l1,List<E> l2);
这里E只在形参中出现了一次,没有其他任何地方依赖它,那么就可以把E规约成“?”,规约结果是:
<T> void func(List<T> l1, list<? extends T> l2);
无论何时定义一个泛型类型,都会自动提供一个相应的原始类型。擦除类型变量,并替换为限定类型(无限定的变量用 Object 类型)
当有多限定的时候,将采取就近原则擦除替换
public class Interval<T extends Comparable & Serializable>{...}
编译器将 T 都更改为Comparable类型。
如果交换次序,变成以下形式。
public class Interval<T extends Serializable & Comparable>{...}
编译器将T都更改为Serializable类型。但编译器将会在必要的时候向Comparable插入强制类型转换。所以为了提高效率,应该将 标签接口 (没有方法的接口)放在边界列表的末尾。
假设下面一个场景,我们有一个泛型接口Operator,接口中有一个process(T t)方法,其作用是对输入参数T进行逻辑处理。示例代码如下
public interface Operator<T>{
void process(T t);
}
在实际业务场景中,我们会有不同的实现方式,进行业务逻辑处理,那么我们现在创建一个具体的实现方式,并重写process(T t)方法。如下:
public class CustomOperator implements Operator<String>{
@Override
public void process(String s){
//do something
}
}
我们从编译的角度分析一下。Operator接口编译之后,编译器将无限定的泛型参数变成 java.lang.Object。伪代码看来是这样的:
public interface Operator{
void process(Object obj);
}
如果编译器没有生成桥方法,那么在编译层面是不能通过的。接口 Operator 中的process方法进过编译后,参数类型为 java.lang.Object 类型,而实现类 CustomOperator 中的 process 方法的参数是 java.lang.String 类型,两者方法参数不一致,而 CustomOperator 中并没有重写接口中的 process 方法,因此编译无法通过。
为了解决这种问题,编译器将会自动生成一个桥接方法 void process(Object obj)方法,使得编译可以通过,同时在该桥接方法中,调用 CustomOperator 中的参数为 java.lang.String 类型的 process 方法。编译后的 CustomOperator 中会有两个 process 方法,一个是擦除泛型类型生成的 process 方法,一个是编译器生成的桥方法 process 方法。大概如下:
public class CustomOperator implements Operator{
//这是擦除泛型生成的方法
public void process(String s){
//do something
}
//这是编译器生成的桥方法,签名与Operator中定义的一致,不会有编译错误
public void process(Object obj){
process((String)obj);//调用擦除泛型类型生成的方法,达成桥接效果,使得编译正常通过。
}
}
如何判断哪个方法是编译器生成的桥方法?
Method类中提供了 Method#isBridge() 方法。通过反射,我们可以获取到CustomOperator类中的两个process方法,在调用 Method#isBridge()方法,即可锁定需要的方法。
桥方法还在方法重写中有表现
JDK5之后,重写方法的返回类型,可以与父类方法返回类型相同,也可以不同,但必须是父类方法返回类型的子类。考虑如下示例:
public class Father{
public Objecet test(String s){
return s;
}
}
public class Child extends Father{
@Override
public String test(String s){
return s;
}
}
在 Child 子类中,我们重写了 test() 方法,但是返回值的类型,我们将 java.lang.Object 改变为它的子类 java.lang.String ,两个方法参数不同,为了使编译通过,编译器会使用类型转换,自动生成一个桥方法。大概如下:
public class Child extends Father{
public String test(String s){
return s;
}
public Object test(String s){
return test(s);
}
}
所以,在继承的背景下,在子类方法重写父类方法,返回类型不一致的情况下,编译器也为我们生成了桥方法。
由于类型擦除之后,只含有Object类型的域,而Object并不能存储基本类型例如double类型的值。为了处理这种问题,我们通常使用包装类,或者使用独立的类来处理。例如:
Pair<Double> p;//可行
Pair<double> p;//报错
由于编译时的类型擦除,在运行时,判断类型只能查询到原始类型,例如
Pair<String> stringPair = ...;
Pair<Student> studentPair = ...;
if(stringPair.getClass() == studentPair.getClass())//总是返回true
结果总是返回true,因为两次调用getClass返回的都是Pair.class
不能实例化参数化类型的数组,例如:
Pair<String>[] table = new Pair<String>[10];//报错,不允许这样创建参数化类型数组
如果这样的创建数组方式被允许的话,经过类型擦除,将会是以下情况:
Pair[] table = new Pair[10];
认为元素类型是Pair,如果想要在其中加入其他类型的元素,例如:
table[0] = "hello";
这本身就是不合法的,只能是Pair类型的元素,而不能是上述string类型的元素。
要通过泛型来加入其他类型的元素,虽然由于类型擦除,可以避开数组元素的检查,但是仍然会有类型错误。
table[0] = new Pair<Double>();
所以java里不允许这样创建参数化类型的数组,因为这样不安全。如果想要手机参数化类型的对象,只有一种安全有效的方法:使用集合:
ArrayList<Pair<String>>
当然除了上述创建数组方式,但Java中可以向参数可变的方法传递一个泛型类型的实例:
public static <T> void func(T... ts){
...
}
事实上ts是一个数组,考虑以下调用:
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
func(pair1,pair2);
Java虚拟机必须建立一个Pair
如下是不允许的:
public static <T> T func(){
T t = new T();//不被允许
T[] tList = new T[2];//不被允许
}