为什么需要泛型?
泛型是java SE1.5的新特性,泛型的本质是参数化类型,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。在JavaSE1.5之前没有泛型的情况下只能通过对类型 Object的引用来实现参数的任意化,其带来的缺点是要做显式强制类型转换,而这种转化编译器是不做检查的,容易把问题留在运行时,所以泛型的好处是在编译时检查类型安全,并且所有的强制类型转换都是自动和隐式的,提高了代码的重用率,避免在运行时出现ClassCastException.
泛型类型
泛型类型是被参数化的类或接口
泛型类的语法格式: class name<T1,T2,…Tn>{}
一般将泛型中的类名称为原型,而将<>指定的参数称为类型参数。
多个类型参数的泛型类: public class MyMap<K,V>{}
泛型类的类型嵌套:
Info<String> info = new Info("Hello");
MyMap<Integer,Info<String>>map = new MyMap<>(1,info);
System.out.println(map);
//MyMap{key=1,value=Info{value=hello}}
泛型接口:
泛型接口的语法形式:
Public interface Content<T>{
T text();
}
泛型方法:
泛型方法是引入其自己的类型参数方法。泛型方法可以是普通方法、静态方法以及构造方法。
泛型方法语法形式: public <T> T func(T obj){}
是否拥有泛型方法,与其所在的类是否是泛型没有关系。
泛型方法的语法包括一个类型参数列表,在尖括号内,它出现在方法的返回值之前,对于静态泛型方法,类型参数部分必须出现在方法的返回类型之前,类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际类型参数的占位符。
使用泛型方法的时候,通常不必指明类型参数,因为编译器会为我们找出具体的类型。这称为类型参数推断。类型推断只对赋值操作有效,其他时候并不起作用。如果将一个泛型方法调用的结果作为参数,传递给另一个方法,这时编译器并不会执行推断。编译器会认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。
public class GenericsMethodDemo01 {
public static <T> void printClass(T obj) {
System.out.println(obj.getClass().toString());
}
public static void main(String[] args) {
printClass("abc");
printClass(10);
}
}
// Output: // class java.lang.String // class java.lang.Integer
泛型方法中也可以使用可变参数列表:
public class GenericVarargsMethodDemo {
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<T>();
Collections.addAll(result, args);
return result;
}
public static void main(String[] args) {
List<String> ls = makeList("A");
System.out.println(ls);
ls = makeList("A", "B", "C");
System.out.println(ls);
}
}
// Output: // [A] // [A, B, C]
类型擦除:
java泛型是使用类型擦除来实现的,使用泛型时,任何具体的类型信息都被擦除了。
类型擦除引起的问题及解决方法
1、先检查,在编译,以及检查编译的对象和引用传递的问题
2、自动类型转换
3、类型擦除与多态的冲突和解决方法
4、泛型类型变量不能是基本数据类型
5、运行时类型查询
6、异常中使用泛型的问题
7、数组(这个不属于类型擦除引起的问题)
9、类型擦除后的冲突
10、泛型在静态方法和静态类中的问题
那么,类型擦除做了什么呢?
把泛型中的所有类型参数替换为Object,如果指定类型边界,则使用类型边界来替换,因此生成的字节码仅包含普通的类,接口和方法。
擦除出现的类型声明,即去掉<>的内容,比如T get()方法声明就变成了Object get();
List<String>就变成了List.如果必要,插入类型转换以保持类型安全。
生成桥接方法以保留扩展泛型类型中的多态性。类型擦除确保不为参数化类型创建新类,因此,泛型不会产生运行 时开销。
示例:
public class GenericsErasureTypeDemo {
public static void main(String[] args) {
List<Object> list1 = new ArrayList<Object>();
List<String> list2 = new ArrayList<String>();
System.out.println(list1.getClass());
System.out.println(list2.getClass());
}
}
// Output
// class java.util.ArrayList
// class java.util.ArrayList
说明:上面的例子中,虽然指定了不同的类型参数,但是list1和list2的类信息却是一样的。
这是因为:使用泛型时,任何具体的类型信息都能被擦除了,这意味着:ArrayList<Object>和ArrayList<String>在运行时,JVM将它们视为同一类型。
泛型和继承:
泛型不能用于显式地引用运行时类型的操作之中,例如:转型,instanceof操作和new表达式,因为所有关于参数的类型信息都丢失了。
正是由于泛型是基于类型擦除实现的,所以泛型类型无法向上转型。
Integer 继承了 Object;ArrayList 继承了 List;但是 List<Interger> 却并非继承了 List<Object>。
这是因为,泛型类并没有自己独有的 Class 类对象。比如:并不存在 List<Object>.class 或是 List<Interger>.class,Java 编译器会将二者都视为 List.class。
类型边界:
有时你可能希望限制可在参数化类型中用作类型参数的类型。类型边界可以对泛型的类型参数设置显示条件。
要声明有界类型参数,请列出类型参数的名称,然后是extends关键字,后跟其限制类或接口。
类型边界的语法形式如下:
<T extends XXX>
例如:
public class GenericsExtendsDemo01 {
static <T extends Comparable<T>> T max(T x, T y, T z) {
T max = x; // 假设x是初始最大值
if (y.compareTo(max) > 0) {
max = y; //y 更大
}
if (z.compareTo(max) > 0) {
max = z; // 现在 z 更大
}
return max; // 返回最大对象
}
public static void main(String[] args) {
System.out.println(max(3, 4, 5));
System.out.println(max(6.6, 8.8, 7.7));
System.out.println(max("pear", "apple", "orange"));
}
}
// Output:
// 5
// 8.8
// pear
上面的示例声明了一个泛型方法,类型参数 T extends Comparable<T> 表明传入方法中的类型必须实现了 Comparable 接口。
注意:extends 关键字后面的第一个类型参数可以是类或接口,其他类型参数只能是接口。
类型通配符:
类型通配符一般是使用?代替具体的类型参数,例如List<?>
上界通配符:
可以使用上界通配符来缩小类型参数的类型范围。
语法形式为:<? extends Number>
下界通配符:将未知类型限制为该类型的特定类型或超类类型
注:上界通配符和下界通配符不能同时使用。
语法形式为:<? super Number>
无界通配符:
无界通配符有两种应用场景:
可以使用Object类中提供的功能来实现的方法。
使用不依赖于类型参数的泛型类中的方法。
语法形式为:<?>
通配符和向上转型:
泛型不能向上转型,但是,我们可以通过通配符来向上转型。
public class GenericsWildcardDemo {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List<Number> numList = intList; // Error
List<? extends Integer> intList2 = new ArrayList<>();
List<? extends Number> numList2 = intList2; // OK
}
}
泛型的约束:
泛型类型的类型参数不能是值类型
不能创建类型参数的实例
不能声明类型为类型参数的静态成员
类型参数不能使用类型转换或instanceof
不能创建类型参数的数组
不能创建、catch或throw参数化类型对象
仅仅是泛型类相同,而类型参数不同的方法不能重载
使用泛型的建议:
消除类型检查告警
List优先于数组
优先考虑使用泛型来提高代码通用性
优先考虑泛型方法来限定泛型的范围
利用有限制通配符来提升API的灵活性
优先考虑类型安全的异构容器