java 泛型
让我们进步的不是工作,不是加班,而是学习。勤奋会得到领导的好感,但只有真正的个人能力才会得到领导的青睐。
1 泛型在集合中的应用
- 在没有泛型之前把一个对象放入java集合中,集合就会忘记对象的类型,把所有的对象当成Object对象类型处理,当程序从集合中取出对象时,就需要进行强制类型转换,这种强制类型转换不仅使代码臃肿,而且容易引起ClassCastException异常。该异常在编译时不会被检查到,极容易导致程序停止运行。
- 添加泛型支持后的集合,完全可以记住集合中元素的类型,并可以在编译时检查集合中元素的类型,如果不满足集合元素的类型,编译器会提示错误,增加泛型集合可以让代码更简洁,程序更加健壮。
- java 7 泛型的“菱形”语法
List strList = new ArrayList<>();
从java7开始,JAVA运行在构造器后不需要带完整的泛型信息,只要给出一对尖括号(<>)即可。java可以推断尖括号里应该是什么泛型信息。
2 泛型接口,泛型类
2.1. 泛型在定义类,接口,方法时使用类型形参,这个类型形参在声明变量,调用方法,创建对象时动态指定(即传入实际类型的参数)
// 定义Apple类时使用了泛型声明
public class Apple<T>{
private T info;
public Apple(){}
public Apple(T info){
this.info = info;
}
public void setInfo(T info){
this.info = info;
}
public T getInfo(){
return this.info;
}
}
2.2. 泛型类派生子类
当定义一个类去继承父类,或者实现带泛型的接口时,应该为类,接口,方法的类型形参传入实际的类型,例如:
java// 必须指定一个类型
public class MyApple extends Apple<String>{ }
2.3. 不存在泛型类
泛型声明的类型可以在定义变量,创建对象时传入一个类型实参,从而可以动态地生成无数多个逻辑上的子类,但这种子类在物理上并不存在。
List<Integer> l1 = new ArrayList<>();
List<String> l2 = new ArrayList<>();
System.out.println(l1.getClass()== l2.getClass());
输出结果为true,因为不管泛型的实际类型参数是什么,对于Java来说,它们依然被当成同一个类处理。在内存中也只占用一块内存空间,因此在静态初始化块或者静态变量的声明或初始化中不允许使用类型形参。
由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类,例如:
java.util.Collection<String> cs = new Java.util.ArrayLis<>();
//下面代码编译时引起错误,instanceof运算不能使用泛型
if(cs instanceof java.util.ArrayList<String>){ .... }
2.4. 泛型设定类型形参的上限与泛型设定类型形参的下限
//泛型设定类型形参的上限,用于表示传给该类型要么是该类型,要么是该上限类型的子类。
public class Apple<T extends Number>{
T col;
public static void main(String [] args){
Apple<Integer> a1 = new Apple<>();
Apple<Double> a2 = new Apple<>();
//上面代码编译正常,但String不是Number的子类型,所以引起编译错误。
Apple<String> a3 = new Apple<>();
}
}
//泛型设定类型形参的下限,用于表示传给该类型要么是该类型,要么是该类型的父类。
public class Apple<T super Integer>{
T col;
public static void main(String [] args){
Apple<Integer> a1 = new Apple<>();
Apple<Number> a2 = new Apple<>();
//上面代码编译正常,但Char不是Integer的父类型,所以引起编译错误。
Apple<Char> a3 = new Apple<>();
}
}
3 类型通配符
3.1 通配符引入解决泛型导致的问题:
public void test(List<Object> c){
for(int i=0; i<c.size();i++){
System.out.println(c.get(i));
}
}
//调用该方法传入的实际参数值时,编译会出现错误
List<String> strList = new ArryList<>();
test(strList);
由于List类并不是List类的子类。
为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,List< ?>这个问号(?)被作为通配符,它的原生类型可以匹配任何类型。
public void test(List<?> c){
for(int i=0;i<c.size();i++){
System.out.println(c.get(i));
}
}
3.2 通配符应用场景是在方法的形参上使用,为了弥补泛型机制带来的参数传递问题,主要有三种通配符分类
无界通配:?
子类限定:? extends Object, Object类本身,或者Object的子类
父类限定:? super Integer Integer本类,或者Integer的父类
4 泛型方法
4.1 泛型方法的用法格式
修饰符<T,S>返回值类型 方法名(形参列表){
//方法体...
}
4.2 test()方法,将前面这个集合里的元素复制到后面这个集合中
public <T> void test(Collection <? extends T> from ,Collection<T> to){
for(T ele: from){
to.add(ele);
}
5 摩擦和转换
5.1 当把一个具体泛型信息的对象赋值给另一个没有泛型信息的变量时,所有在尖括号之间的类型信息都将被扔掉,比如一个List类型被转换为List,则该List对集合元素的类型检查变成了类型参数的上限(即Object)。
public class Apple <T extends Number> {
T size;
public Apple(){}
public Apple(T size ){
this.size = size;
}
public void setSize(T size){
this.size = size;
}
public T getSize(){
return this.size;
}
}
public class ErasureTest{
public static void main(String[] args){
Apple<Integer> a = new Apple<>(6);
// 把a的getSize()方法返回Integer对象
Integer as = a.getSize();
// 把a对象赋给Apple变量,丢失括号里的类型信息
Apple b = a;
// b只知道size的类型是number
Number size1 = b.getSize();
//编译错误
Integer size2 = b.getSize();
}
}
5.2 Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。在编译之后,List和List将变成List,Object和String类型信息对于JVM来说是不可见的。在编译阶段,编译器发现它们不一致,因此给出了一个编译错误。
public class ErasureTest {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
al.add("a");
al.add("b");
accept(al);
//编译不会通过,并提示如下错误:
//The method accept(ArrayList<Object>) in the type ErasureTest is not applicable for the arguments (ArrayList<String>)
}
public static void accept(ArrayList<Object> al) {
for (Object o : al)
System.out.println(o);
}
}
6 总结
- Java中的泛型是什么 ? 使用泛型的好处是什么?
- Java的泛型是如何工作的 ? 什么是类型擦除 ?
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。例如 List在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型 - 什么是泛型中的限定通配符和非限定通配符 ?
- List< ? extends T>和List < ? super T>之间有什么区别 ?
- 你可以把List传递给一个接受List参数的方法吗?
- Java中List< ?>和List之间的区别是什么?
- List和原始类型List之间的区别?
- Java中List和原始类型List之间的区别?