Java深入泛型

泛型:允许在定义类,接口,方法时使用类型形参,这个类型形参将在声明变量,创建对象,调用方法时动态指定(类型实参),Java5为集合框架的全部类和接口增加了泛型支持,可以在声明集合变量,创建集合对象时传入类型实参,就是我们经常看到的List和ArrayList

(1)定义泛型接口,类

public interface List<E> extends Collection<E> {

    Iterator iterator();
    boolean add(E var1);
}

public interface Set<E> extends Collection<E> {

    Iterator iterator();
    boolean add(E var1);
}

public interface Map<K, V> {

    V put(K var1, V var2);
    Set keySet();
    Collection values();
    Set> entrySet();
    void putAll(Map var1);
}

泛型实质:除了在定义接口,类时声明类型形参,类型形参在整个接口,类中可当做类型使用。

可以为任何类增加泛型支持,请看下面实例。

// 定义Apple类时使用了泛型声明
public class Apple
{
    // 使用T类型形参定义实例变量
    private T info;
    public Apple(){}
    // 下面方法中使用T类型形参来定义构造器
    public Apple(T info)
    {
        this.info = info;
    }
    public void setInfo(T info)
    {
        this.info = info;
    }
    public T getInfo()
    {
        return this.info;
    }
    public static void main(String[] args)
    {
        // 由于传给T形参的是String,所以构造器参数只能是String
        Apple a1 = new Apple<>("苹果");
        System.out.println(a1.getInfo());
        // 由于传给T形参的是Double,所以构造器参数只能是Double或double
        Apple a2 = new Apple<>(5.67);
        System.out.println(a2.getInfo());
    }
}

(2)泛型派生子类

当创建了带泛型声明的接口和父类时,可以为该接口创建实现类或者为该父类创建子类,但当使用这些父类或接口时不能再包含类型形参。

//Apple类不能跟类型形参
public class A extends Apple<T>

//这种是可以的
public interface List<E> extends Collection<E>
public abstract class TestClass<E> implements List<E>

在定义类,接口,方法时可以声明类型形参,在使用类,接口,方法时应该为类型形参传入实际类型,如下:

public class A extends Apple<String>

//如下示例
public class A1 extends Apple<String>
{
    // 正确重写了父类的方法,返回值
    // 与父类Apple的返回值完全相同
    public String getInfo()
    {
        return "子类" + super.getInfo();
    }
    /*
    下面方法是错误的,重写父类方法时返回值类型不一致
    public Object getInfo()
    {
        return "子类";
    }
    */
}

(3)并不存在真正的泛型

List<String> l1 = new ArrayList<String>();
List<Double> l2 = new ArrayList<Double>();
System.out.println(l1.getClass() == l2.getClass());

//结果为true,系统不会生成真正的泛型类

注:静态方法,静态初始化块,静态变量的声明和初始化中不允许使用类型形参,如:

public class R
{
    // 下面代码错误,不能在静态变量声明中使用类型形参
    //  static T info;
    T age;
    public void foo(T msg){}
    // 下面代码错误,不能在静态方法声明中使用类型形参
    //  public static void bar(T msg){}

    //instanceof也不可用于判断泛型
}

(4)类型通配符

List<String> list = new ArrayList<String>();
//错误代码
List<Object> obj = list;

List并不是List的子类,如果一个Foo是Bar的一个子类型(子类或者子接口),而G是具有泛型声明的类或者接口,G并不是G的子类型。

类型通配符是?,它可以匹配任何类型的元素,如List,Map等,但这种带通配符的的List表示各种泛型List的父类,不可将元素加入其中。

List> list1 = new ArrayList();
//以下代码是报错的
list1.add("1");
list1.add("2");
list1.add("3");

(4)类型通配符上限

// 定义一个抽象类Shape
public abstract class Shape
{
    public abstract void draw(Canvas c);
}

// 定义Shape的子类Circle
public class Circle extends Shape
{
    // 实现画图方法,以打印字符串来模拟画图方法实现
    public void draw(Canvas c)
    {
        System.out.println("在画布" + c + "上画一个圆");
    }
}

// 定义Shape的子类Rectangle
public class Rectangle extends Shape
{
    // 实现画图方法,以打印字符串来模拟画图方法实现
    public void draw(Canvas c)
    {
        System.out.println("把一个矩形画在画布" + c + "上");
    }
}
public class Canvas{
    //List不是List的子类,无法添加
    public void drawAll(List shapes)
    {
        for (Shape s : shapes)
        {
            s.draw(this);
        }
    }
    //类型丢失,需要强转
    public void drawAll(List shapes)
    {
        for (Object obj : shapes)
        {
            Shape s = (Shape)obj;
            s.draw(this);
        }
    }
}
//最合理的使用方法,使用类型通配符上限
public void drawAll(List shapes)
    {
        //由于这里无法确定具体类型,所以不能将Circle添加进去
        //list.add(new Circle());
        for (Shape s : shapes)
        {
            s.draw(this);
        }
    }

泛型不仅允许在使用通配符形参时设定上限,在定义类型形参时也可设定上限。

public class Apple<T extends Number>
{
    T col;
    public static void main(String[] args)
    {
        Apple ai = new Apple<>();
        Apple ad = new Apple<>();
        // 下面代码将引起编译异常,下面代码试图把String类型传给T形参
        // 但String不是Number的子类型,所以引发编译错误
        Apple as = new Apple<>();       // ①
    }
}

(5)泛型方法

//(1)
static void fromArrayToList(Object[] a, List<Object> b) {
        for (Object o : a) {
            b.add(o);
        }
    }

//(2)
static void fromArrayToList(Object[] a, List b) {
        for (Object o : a) {
            //无法将对象加入通配符集合
            //b.add(o);
        }
    }

(1)

String[] arr = {"a", "b"};
List<String> list = new ArrayList<String>();
//报错,List不是List的子类,无法赋值
//fromArrayToList(arr,list); 
  

泛型方法的格式如下:

修饰符 <T,S> 返回值类型 方法名(形参列表){

}

上面的方法将可以修改成如下方式:

static  void fromArrayToList(T[] a, List b) {
        for (T o : a) {
            b.add(o);
        }
    }

//调用
String[] arr = {"a", "b"};
List list = new ArrayList();
fromArrayToList(arr, list);

上面的这种方式需要传入的数组和集合是同一种数据类型,也可使用如下方式。

static  void fromArrayToList(List extends T> a, List b) {
        for (T o : a) {
            b.add(o);
        }
    }

(6)通配符下限

//src元素复制到dest中,dest元素应该是src元素的父类,同时使用通配符和泛型参数来表示
public static  T copy(List dest, List extends T> src) {
        for (T ele : src) {
            dest.add(ele);
        }
    }

现在假设该方法需返回最后一个被复制的元素,则可以如下写法。

public static  T copy(List dest, List extends T> src) {
        T last = null;
        for (T ele : src) {
            last = ele;
            dest.add(ele);
        }
        return last;
    }

//调用
List list1 = new ArrayList();
List list2 = new ArrayList();
//这句将有错误
Integer last = copy(list1, list2);

最后返回的元素是Number型,并不是我们需要的Integer型,但实际上复制的最后一个元素确实是Integer型。

通配符下限:

public static  T copy(List super T> dest, List src) {
        T last = null;
        for (T ele : src) {
            last = ele;
            dest.add(ele);
        }
        return last;
    }

//调用
List list1 = new ArrayList();
List list2 = new ArrayList();
Integer last = copy(list1, list2);

我们来看一下Collections类中原先的copy原型。

public static  void copy(List super T> dest,
                            List extends T> src)

泛型擦除

List<String> list = new ArrayList<String>();
List list1 = list;

泛型常用标记符含义:

E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? -  表示不确定的java类型

你可能感兴趣的:(Java)