likes
comments
collection
share

java泛型,不再迷糊了

作者站长头像
站长
· 阅读数 13

如果没有泛型会怎么样?这里使用一个已经存在的泛型类举例

示例代码

public class GenericTest {
    @Test
    public void comeHere(){
        List list = new ArrayList();
        list.add("赵四");
        list.add(40);
        //循环打印获取的元素
        list.forEach((x) -> System.out.println((String)x));
    }
}

运行结果:java.lang.ClassCastException

对于任何泛型接口或泛型类而言,如果在声明时或创建实例时没有为它指定任何一个类型,则类型默认为Object。其实在增加泛型类型之前,ArrayList就是只维护了Object引用的数组。虽然这样我们可以往集合中存放任意类型的数据,但是在获取数据的时候就免不了要进行类型转换,这增加了代码量,并且存在类型转换错误的风险。

当引入泛型后一切则不同了:

public void comeHere(){
    //使用<Type>的方式可以为接口或类指定具体的参数类型
    List<String> list = new ArrayList<String>();
    list.add("赵四");
    //指定具体的类型后,这面这行代码无法通过编译,因为指定的类型为String,并非Integer
    list.add(40);
}

可以发现,当指定泛型类型后很自然的就解决了类型转换问题,并且编译器会自动帮我们检查传入的参数类型是否为指定的具体类型。这里有必要看一下ArrayList类的部分代码。

ArrayList:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    boolean add(E e);
}

ArrayList类的后面跟了一个<E><E>定义了ArrayList是一个泛型类,那E是什么呢?其实可以这么理解它:E是一个类型占位符,它是什么取决于你传的类型是什么,如果传的String,那么这个类中所有的E都会被转换为String;如果传的是Double那么所有的E则都为Double。当然也可以用其他字母表示泛型类型,比如T、K、V等。

自定义一个泛型类:

class Batman<T,E>{
    T name;
    E age;
    public Batman(T name,E age){
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Batman{" +
                "name=" + name +
                ", age=" + age +
                '}';
    }
}

在这个自定义泛型类中指定了两个泛型类型,现在编写一个测试类去创建泛型类的对象并输出它

@Test
public void comeHere(){
    Batman<String,Integer> batman = new Batman<>("布鲁斯韦恩",20);
    System.out.println(batman);
}

运行结果:Batman{name=布鲁斯韦恩, age=20}

注意,声明变量接收Batman对象的这行代码中,只为变量指定了具体类型,因为编译器可以根据变量中指定的类型推断出具体类型,所以new Batman<String,Integer>(...)中的类型可以省略。在这行代码中传入了类型String和Integer,这表示泛型类中的T全都被替换为String,而E则全部替换为Integer。如果往构造器中传入了不符合具体类型的,编译器就会报错。

泛型方法

public <K> void battleSuperman(K superMan){
    System.out.println(name +"大战"+ superMan);
}

如果在方法的访问修饰符后指定了一个泛型类型,那么这个方法就是泛型方法。在这个方法中,实际传入了一个SuperMan类型的参数,所以K被替换为SuperMan

public class SuperMan {
    private String name;

    public SuperMan(String name){
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

使用单元测试工具运行程序

@Test
public void comeHere(){
    Batman<String,Integer> batman = new Batman<>("布鲁斯韦恩",20);
    SuperMan superMan = new SuperMan("克拉克");
    batman.battleSuperman(superMan);
}

运行结果:布鲁斯韦恩大战克拉克

这里有两个问题需要注意:

  1. 泛型方法可以在泛型类中定义,也可以在普通类中定义
  2. 只有在访问修饰符后面指定泛型类型的方法才是泛型方法,例如public void battleSuperman(K superMan){}只是一个使用了泛型类型参数的方法,并不是泛型方法。

类型限定

就目前来看,泛型类型可以被重新指定为任意引用类型。假设现在有一个需求,要求只能将泛型类型指定为一定范围内的类型该如何实现?

示例代码:

class Batman<T,E extends Number>{
    public <K extends SuperMan> void battleSuperman(K superMan){
        System.out.println(name +"大战"+ superMan);
    }
}

上述代码示例中,E extends Number表示只能为E指定Number类型或Number类型的子类型,如果传入其他类型编译器会报错。<K extends SuperMan>同理,只能为K指定为SuperMan类型或SuperMan类型的子类型。接口、类都可以作为类型限定

通配符类型

通配符类型是为泛型类型服务的,用符号?表示,通配符类型大体可以分为三类:无限定通配符、子类型限定通配符、超类型限定通配符,其中子类型限定通配符决定了泛型类型的上限,超类型限定通配符决定了泛型的下限,无限定通配符则表示任意泛型类型。

无限定通配符:

    @Test
    public void comeHere(){
        //可以指定任意类型的泛型类型
        Batman<A, Date> batman1 = new Batman<>(new A(),new Date());
        batMan1(batman1);
        //第二个泛型类型中,只能指定B或B的子类类型
        Batman<Integer,C> batman2 = new Batman<>(1,new C());
        batMan2(batman2);
        //第二个泛型类型中,只能指定C或C的父类类型
        Batman<String,A> batman3 = new Batman<>("bruce",new A()); 
        batMan3(batman3);
    }
    //无限定通配符
    public void batMan1(Batman<?,?> batman){}
    
    //子类型限定通配符
    public void batMan2(Batman<?,? extends B> batman){}
    
    //超类型限定通配符
    public void batMan3(Batman<?,? super B> batman){}
}

示例中,创建了三个方法,方法的参数都是泛型类型BatMan第一个方法中指定两个泛型为?,表示可以可以传入引用任意类型的泛型类实例。第二个方法中指定第一个泛型为?第二个为? extends B,表示第一个泛型类引用可以是任意类型,第二个泛型类引用必须为BB的子类类型。最后第三个方法中指定第一个泛型为?第二个为? super B,表示第一个泛型类引用可以是任意类型,第二个泛型类引用必须为BB的父类类型。