本篇讲解泛型,虽然之前也一直使用泛型,但理解的不够透彻。今天详细归纳下泛型的表示和使用场景,同时将平时一些没注意的地方,使用泛型来转变。
概念篇:
有三种使用方式,分别为:泛型类、泛型接口、泛型方法
泛型类
1、定义方式:
public class Test<T>{
private T value;
public Test(){
}
public Test(T value){
this.value = value;
}
public void setValue(T value){
this.value = value;
}
public T getValue(){
return value;
}
}
说明:
① 泛型申明需要菱形运算符,在类中使用需要在类名后<>申明,<>可以有多个泛型,以","分割,多个的情况可以参考 java.util.Map。
② T可以修改为任意标识(大小写英文字母都可以),但一般都是用大写英文字母,常见如:T、E、K、V等
2、使用(以上方Test为例)
① 在泛型类中,当我们不使用到其中的泛型时,可以无视泛型,正常使用该类即可。
② 当我们使用到泛型时,变量申明可以带泛型也可以不带,可以理解成继承关系,使用父类型来申明(但注意实际中和继承关系是两码事,甚至需要注意两者的区别,区别在下文会提到)。如:
Test test1 = new Test<>("test");
Test<String> test2 = new Test<>("test");
③ 在使用到泛型时则需要带上<省略>或者<T>,不带的话编译器会有警告,如下方new对象时使用到了泛型:
Test test1 = new Test<>("test");
Test test2 = new Test<String>("test");
警告的原因在于确保我们在使用到泛型时,类型是正确的。如在变量申明时不带泛型,直接调用setValue("")则会有警告,编译器提示我们使用的类型是否正确。
泛型接口
1、定义:
在定义上同泛型类
public interface Test<T>{
T next();
}
2、特殊点:
1)接口被接口继承,同时需要继承泛型:
public interface Test2<T> extends Test<T>{
}
2)接口被实现(抽象类被实现也一样)。可以实现泛型,也可以不实现,不实现则以Object。如下为实现和不实现的代码:
public class Test3 implements Test<String>{
@Override
public String next(){
return null;
}
}
public class Test3 implements Test{
@Override
public Object next(){
return null;
}
}
泛型方法
1、定义
泛型方法,需要方法自己定义泛型,其生命周期是在于方法使用期间,定义格式是在方法名之前使用菱形运算符定义<>,如下:
publc <T> void getValue(T value){
}
2、注意:
① 注意泛型方法与泛型类下的普通方法的区分。单纯使用在class上定义的泛型的方法,不算泛型方法。当然泛型方法也就一个称号。
② 静态方法不能直接使用类上的泛型。可以这么理解:泛型的使用会伴随着对象,静态方法不能访问该类生成对象的属性。
通配符
1、定义:
通配符通常用来表示对已经实例化的对象,忽略其使用的泛型的一种统一概括。一般都是作为引用表示。格式:Class<?>
例如:
public void setList(List<?> lists){
}
2、过滤:
<?> 表示是所有情况,我们也可以将? 缩小点范围。例如: <? extends xxx> 泛型范围缩小为继承于xxx类的所有类。
3、注意:需要注意的是与泛型<T>及Object的区别
1)与泛型<T>的区别
与泛型<T>相同,都是用来表示所有泛型里面的某一种。但泛型<T>需要跟随申明,让编译器明白具体使用的类型。通配符不需要申明,一般都是用来作为引用,只能读取,不能修改。
官方点的话:在Java集合框架中,对于参数值是未知类型的容器类,只能读取其中元素,不能向其中添加元素, 因为,其类型是未知,所以编译器无法识别添加元素的类型和容器的类型是否兼容,唯一的例外是NULL
2)与Object的区别
尽管? 或者 T 的使用一般都是使用Object的衍生类。不同之处:
public void setList(List<Object> lists){
}
private void test(){
List<String> strList = new ArrayList<>();
List<?> list = new ArrayList<>();
// 错误1:setList(java.util.List<java.lang.Object>) in xxx cannot be applied to ( java.util.List<java.lang.String>)
setList(strList);
// 错误2:setList(java.util.List<java.lang.Object>) in xxx cannot be applied to ( java.util.List<capture<?>>)
setList(list);
}
可以看出通配符<?>,指定的范围比<Object>广,它还可以表示一种关系,如上方提到的继承关系下的所有类。
使用篇
使用泛型可以给我们的程序提高一定的扩展性。举例说明,下方几种情况我们可以修改为泛型的表示方式:
1)在不清楚引入的类型时,常常就简单了使用Object来定义解决,使用时通过instanceof来判断。这里我们就可以转换下思维,用上泛型。
2)规范化代码。在设计抽象类时,我们希望一个类能够被指定多种类型中的一种类型使用,在使用时又不用担心类型转换异常等问题。就像List,当定义List<String> 时,之后的该List只能使用String,借助编译器,不怕该List中还保存着Integer等类型。
3)在实际开发中,我们很多情况下想确保继承于一个自定义的抽象(可以理解为框架),而非仅Object,减少后续使用该类的开发者阅读源码的时间,也算也是规范化代码。如下方例子,这里指定为 extends RecyclerView.ViewHolder:
public abstract class FrameAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
}
暂时仅想到这三条,后续想到合适的地方我再补上。
本篇泛型博客,都是结合网上说法及自身理解所归纳的,不敢确保完全正确,希望大家看到有问题的地方能够及时指正,后续我也会不断改正。