泛型程序设计入门

泛型程序设计

泛型,字面意思就是广泛的类型,泛型程序设计意味着我们编写的代码可以对多种不同类型的对象重用,这将提高我们代码的复用性。
在Java引进泛型类之前,泛型程序设计是通过继承来实现的,来看下面的代码:

public static String func(String obj){
    System.out.println(obj);
    return obj;
}

我们在使用上面这个方法时,只能传递字符串类型的参数给它,但如果我们还想打印其他类型的实例该如何实现?
在Java还没有引入泛型之前,我们只能通过继承来实现:

public static Object func(Object obj){
    System.out.println(obj);
    return obj;
}

参数的类型被定义为Object类,它是所有类的父类,因此所有类都可以作为参数传递给func方法,而基础类型在传递时会进行自动装箱变成包装器类,所以也可以作为参数进行传递。
但这么做会存在两个问题:

  1. 当我们获取func方法的返回值时必须进行强制类型转换String str = (String) func("hello");
  2. 由于没有错误检查,任何类型的参数都可以传递给func方法,这么做并不安全。

泛型类与泛型方法

Java5中引入了泛型,它提供了一个更好的解决方案:类型参数。在定义类中的属性和方法参数时,我们使用类型参数来替代原来具体的类型,一般规定类型参数可以用E表示集合,K|V表示键值,T|U|S表示任意类型,类型参数可以用在类和方法中,称为泛型类和泛型方法。当我们需要使用它们时再指定具体的类型。

我们可以使用类型参数对前面的例子进行优化:

public static <T> T func(T obj){
    System.out.println(obj);
    return obj;
}

这种称为泛型方法,在使用它的时候必须指定类型参数的具体类型:String str = <String>func("hello);,这时方法定义中的类型参数可以全部看成String类型。一般情况下泛型方法可以通过你传递的参数的类型判断出你类型形参所表示的具体类型,所以菱形语法可以省略不写。

我们还可以定义泛型类:

class DemoClass<T> {
    private T obj;
    public T getObj() { return obj; }
    public void setObj(T obj) { this.obj = obj; }
}

使用泛型的好处:

  1. 编写的代码可以对多种不同类型的对象重用,这将提高我们代码的复用性。
  2. 在使用时指定参数类型,不必再进行强制类型转换,这将提高我们代码的可读性。
  3. 类型检查工作提前到编译时期,运行阶段不会因为类型错误而出现异常,这将提高我们代码的安全性。

类型变量的限定

我们可以给类型参数加上限制,看下面的代码:

public static <T extends A & B> T func(T obj){
    ...
}

在上面这段代码中,我们限制了T的类型,必须为A类型的子类且必须实现了B接口。T的限定类型可以拥有一个类和多个接口,使用 & 进行连接。如果有类作为限定,必须放在第一个限定。


4.4 类型擦除

虚拟机没有泛型类型对象,所有对象都属于普通类。泛型代码只存在于编译阶段,在进入JVM之前,所有类型参数都会替换为它们的限定类型(没有限定类型则替换为Object类型),会合成桥方法来保持多态,为保持类型安全性,必要时会插入强制类型转换。

有限定类型替换成限定类型:

class Num<T extends A & B> { private T obj; }
//类型擦除后
class Num { private A obj; }

没有限定类型替换为Object类型:

public static <U> U getMiddle(U... objs){}
//类型擦除后
public static Object getMiddle(Object... obj){}

合成桥方法来保持多态:

interface Info<T>{
    T info(T var);
}
public class InfoImpl implements Info<Integer>{
    public Integer info(Integer var){
        return var;
    }
}
//类型擦除后
interface Info{ Object info(Object var); }
public class InfoImpl implements Info{
    public Integer info(Integer var){
        return var;
    }
    //生成桥方法
    public Object info(Object var){
        return info((Integer)var);
    }
}

4.5 限制与局限性

  1. 不能用基本类型实例化参数类型

  2. 运行时类型查询只适用于原始类型

    if(a instanceof Pair<T>){} 	//错误,试图查询一个对象是否属于某个泛型类型
    Pair<T> p = (Pair<T>)a;      //警告,强制类型转换
    
  3. 可以声明但不能创建参数化类型的数组

    var table = new Pair<String>[10];	//错误
    
  4. 向参数可变的方法传递泛型类型实例

    @SafeVarargs	//方法2:Java7中,用@SafeVarargs注解addAll方法。
    public <T> void addAll(Collection<T> coll, T... ts){
        for(T t:ts) coll.add(t);
    }
    //方法1:为包含addAll调用的方法添加注解@SuppressWarnings("unchecked")
    @SuppressWarnings("unchecked")	
    public void test(){
        addAll(coll,ts);
    }
    
  5. 不能实例化类型变量

    obj = new T();	//错误
    
  6. 不能构造泛型数组

    T[] table = new T[10];	//错误
    
  7. 不能在静态字段或方法中引用类型变量

  8. 不能抛出或捕获泛型类实例

  9. 可以取消对检查型异常的检查,Java异常处理的一个基本原则是,必须为所有检查型异常提供一个处理器,利用泛型可以取消这个机制。

  10. 一个类或类型变量不能同时作为同一接口不同参数化的两个接口类型的子类

    class Employee implements Comparable<Employee>{}	
    class Manager extends Employee implements Comparable<Manager>{}
    

    错误:Manager类同时实现了Comparable接口的不同参数化


4.6 通配符类型

无论S与T什么关系,Pair<S>Pair<T>都没有关系。

如果 S extends T,那么Pair<S>Pair<T>是Pair<? extends T>的子类。

通配符的超类型限定,Pair<? super S>限制为S的所有超类型,Pair<T>Pair<Object>


posted @ 2022-07-22 10:59  独游空想家  阅读(41)  评论(0编辑  收藏  举报