当前位置 博文首页 > 双木l之林:【一天一个基础系列】- java之泛型篇
说起各种高级语言,不得不谈泛型,当我们在使用java集合的时候,会发现集合有个缺点:把一个对象“丢进”集合之后,集合就会“忘记”这个对象的数据类型,当再次取出该对象时,改对象的编译类型就变成了Object
类型
A
对象的集合,但不小心把B
对象放进去,会引发异常Object
,因此去取集合元素后通常还需要进行强制类型装换,这个过程不仅增加了编程的复杂度,还可能引发CLassCastException
异常为解决以上问题,便引入“泛型”
java 5以后,java引入了“参数化类型”的概念,允许程序在创建集合时指定集合元素的类型
java 7之前,如果使用带泛型的接口、类定义变量,那么调用构造器创建对象时构造器的后面也必须带泛型
//java 7之前
List<String> list = new ArrayList<String>();//后面的<String>是必须带上的
//java 7之后,"菱形"语法
List<String> list = new ArrayList<>();
注:java 9允许在使用匿名内部类时使用菱形语法
概念定义:允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方式动态地指定
我们来看一下定义泛型接口、类
/**
* 定义泛型接口,实质:允许在定义接口、类时什么类型形参,
* 类型形参在整个接口、类体内可当成类型使用,几乎所有可
* 使用普通类型的地方都可以使用这种类型形参
*/
public interface List<T> {
void add(T x);
}
/**
* 定义
*
*
*/
@Data
public class Clazz<T> {
private T a;
public Clazz(T a){
this.a = a;
}
}
//使用Clazz
pulic void method(){
Clazz<String> clazz = new Clazz<>("");
}
从泛型类派生子类
//定义类Son类继承Parent类
public class Son extends Parenet<T>{
}
//使用Parent类时为T形参传入String类型
public class Son extends Parent<String>{
}
//使用Parent类时,没有为T形参传入实际的类型参数
public class Son extends Parent{
}
像这种使用Parent类时省略泛型的形式被称为原始类型(raw type)
如果从Parent类派生子类,则在Parent类中所有使用T类型的地方都将被替换成String类型
并不存在泛型类
List<String>
与List<Integer>
创建出来的是同样class文件,它们在运行时总有同样的类,故在静态方法、静态初始化块或者静态变量的生命和初始化中不允许使用泛型形参public class R<T>{
//错误,不能在静态变量声明中使用泛型形参
static T info;
//错误,不能再静态方法声明中使用泛型形参
public void foo(T p){
}
}
类型通配符
List<?>
(意思是元素类型未知的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型List<?>·这种形式时,即表明这个List集合可以是任何泛型List的父类。但还有一种特殊的情形,程序不希望这个
List<?>`是任何泛型List的父类,只希望它代表某一类泛型List的父类//定义上限为Parent类,表示泛型形参必须是Parent子类
List<? extends Parent>
A<Foo>
就相当于A<? extends Bar>
的子类,可以将A<Foo>
赋值给A<? extends Bar>
类型的变量,这种型变方式被称为协变<? super类型>
的方式来指定,通配符下限的作用与通配符上限的作用恰好相反//定义下限为Parent类
List<? super Parent>
A<? super Foo>
变量时,程序可以将A<Bar>
、A<Object>
赋值给A<? super Foo>
类型的变量,这种型变方式被称为逆变对于逆变的泛型而言,它只能调用泛型类型作为参数的方法;而不能调用泛型类型作为返回值类型的方法。口诀是:逆变只进不出
泛型方法
修饰符<T,S>返回值类型 方法名(形参列表){
//TODO
}
//使用类型通配符
public interface Collection<E>{
void add(Collection<?> p);
void delete(Collection<? extends E> p)
}
//使用泛型方法
public interface Collection<E>{
<T> void add(Collection<T> p);
<T extends E> void delete(Collection<T> p)
}
public class Collections{
public static <T> void copy(List<T> dest,List<? extends T> src){}
}
“菱形”语法与泛型构造器
class Foo{
public <T> Foo(T t){
}
}
public void method(){
//泛型构造器中T类型为String
new Foo("");
//也可以这么定义,显示指定T类型为String
new<String> Foo("");
//泛型构造器中T类型为Integer
new Foo(10);
}
泛型方法与方法重载
<T> void copy(Collection<T> des,Collection<? extends T> src){};
<T> T copy(Collection<? super T> des,Collection<T> src){};
public void method(List<String> list){}
public void method(List<Integer> list){}
List<Integer>
和List<String>
编译之后都被擦除了, 变成了同一种的裸类型List
,类型擦除导致这两个方法的特征签名变得一模一样(下面会提到类型擦除)类型推断
泛型擦除和转换
List<String>
对应的裸类型就是List
)
List<String>
类型会被转换成List,则该List对集合元素的类型检查变成了泛型参数的上限(Object),那么在使用,比如插入的时候,又会出现从Object到String的强制转型代码List<int>
这种是不支持的,那么一旦把泛型信息擦除后,遇到原生类型时把装箱、 拆箱也自动做了,这也成为Java泛型慢的重要原因泛型与数组
List<String>[]
形式的数组,但不能创建ArrayList<String>[10]
这样的数组对象总结:Java的泛型在使用期间需要更加注意泛型擦除的情况,总体而言,其写法也并不优雅。也希望未来的泛型会支持基本类型。