泛型

java的泛型编程的笔记

概述

java5开始引入了参数化类型,使用起来和c++的模板有一些相似之处。java7开始,可以不必在构造器中的尖括号中带类型信息,编译器可以推断出来,即可以这样用:

List books = new ArrayList<>();

这里用了List接口声明了一个变量,也可以用ArrayList声明变量,效果是一样的,后面的是前面的实现类。接口经常这么用。

深入

定义泛型类

java可以为任何接口、类增加泛型声明。为类增加泛型声明时,其构造器名称还是类名,不需要加泛型声明,而调用时可以传入类型参数:

public class Apple<T>
{
    // 使用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());
    }
}

这里,用了概述中说的省略语法。

泛型类的继承

创建泛型类或接口后,可以为该接口创建实现类、或从该父类派生子类。注意,不能用一个非泛型类去继承一个泛型类或实现一个泛型接口。要么给继承的泛型类传入实际类型,要么子类也是泛型类。

public class A1<T> extends Apple<T>;
public class A1 extends Apple<String>;

上面两种都可以。

public class A2 extends Apple;

其实,这一种也可以。严格的泛型代码中,泛型类总应该带着类型参数。如果没有带类型参数,默认是声明该类型参数时指定的第一个上限类型。即这样做的后果是,编译器把T当做Object处理。

泛型类并不存在

不管为泛型类的类型形参传入哪一种实际类型,依然是同一个类,在内存中也只有一块内存空间。所以,静态方法、静态初始化块或静态变量的声明和初始化中不允许用类型参数。

因为并不会真正生成类,所以instanceof运算符后面也不能用泛型类。

类型通配符

首先说明一下一个定义:

如果A是B的子类型(子类或者子接口),而G是具有泛型声明的类或接口,G并不是G的子类型!但是对于数组来说,A[]仍然是B[]的子类型。

类型通配符是一个?,表示可以匹配任何类型。

注意,java集合中,类型必须是一致或相容的(加入的是声明类型的子类型),否则不能被放入集合中。因此,类型通配符的主要作用在于匹配任意类型,但是不知道匹配到的具体类型是啥。

类型通配符可以指定上下限。

public abstract class Shape
{
    public abstract void draw(Canvas c);
}
public class Circle extends Shape
{
    // 实现画图方法,以打印字符串来模拟画图方法实现
    public void draw(Canvas c)
    {
        System.out.println("在画布上画一个圆");
    }
}
public class Rectangle extends Shape
{
    // 实现画图方法,以打印字符串来模拟画图方法实现
    public void draw(Canvas c)
    {
        System.out.println("把一个矩形画在画布上");
    }
}
public class Canvas
{
//  // 同时在画布上绘制多个形状
//  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)
    {
        for (Shape s : shapes)
        {
            s.draw(this);
        }
    }
    public static void main(String[] args)
    {
        List circleList = new ArrayList();
        Canvas c = new Canvas();
         Circle d=new Circle();
        circleList.add(d);
        c.drawAll(circleList);//这里Circle是Shape的子类型,所以可以通过。
    }
}
输出:
在画布上画一个圆

这里,?表示任意类型,但是必须是Shape的子类型或自身。

public class MyUtils
{
    // 下面dest集合元素类型必须与src集合元素类型相同,或是其父类
    public static  T copy(Collectionsuper T> dest, Collection src)
    {
        T last = null;
        for (T ele  : src)
        {
            last = ele;
            dest.add(ele);
        }
        return last;
    }
    public static void main(String[] args)
    {
        List ln = new ArrayList<>();
        List li = new ArrayList<>();
        li.add(5);
        // 此处可准确的知道最后一个被复制的元素是Integer类型
        // 与src集合元素的类型相同
        Integer last = copy(ln , li);    // ①
        System.out.println(ln);
    }
}
输出[5]

这里,super表示了下限。另外,此处还用了泛型方法。


不仅可以为类型通配符指定上下限,类型参数也可以。

public class Apple<T extends Number>//这里指定类型参数必须是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<>();       // ①
    }
}

更极端的情况下,可以至多一个父类上限,多个接口上限:

public class Apple<T extends Number & java.io.Serializable>

泛型方法

通配符相当于一把万能钥匙,但是匹配到啥类型自己也不知道。泛型方法有点像类型参数一般,语法如下:

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

和普通方法比只是多了类型参数声明。

public class GenericMethodTest
{
    // 声明一个泛型方法,该泛型方法中带一个T类型形参,
    static  void fromArrayToCollection(T[] a, Collection c)
    {
        for (T o : a)
        {
            c.add(o);
        }
    }
    public static void main(String[] args)
    {
        Object[] oa = new Object[100];
        Collection co = new ArrayList<>();
        // 下面代码中T代表Object类型
        fromArrayToCollection(oa, co);
        String[] sa = new String[100];
        Collection cs = new ArrayList<>();
        // 下面代码中T代表String类型
        fromArrayToCollection(sa, cs);
        // 下面代码中T代表Object类型
        fromArrayToCollection(sa, co);
        Integer[] ia = new Integer[100];
        Float[] fa = new Float[100];
        Number[] na = new Number[100];
        Collection cn = new ArrayList<>();
        // 下面代码中T代表Number类型
        fromArrayToCollection(ia, cn);
        // 下面代码中T代表Number类型
        fromArrayToCollection(fa, cn);
        // 下面代码中T代表Number类型
        fromArrayToCollection(na, cn);
        // 下面代码中T代表Object类型
        fromArrayToCollection(na, co);
        // 下面代码中T代表String类型,但na是一个Number数组,
        // 因为Number既不是String类型,
        // 也不是它的子类,所以出现编译错误
//      fromArrayToCollection(na, cs);
    }
} 
  

方法中的泛型参数不必显式指定,可以由编译器推断出。

泛型方法和通配符主要区别在于:

如果一个形参类型或返回值的类型依赖于另一个形参的类型,那么就应该用泛型方法。因为通配符不知道自己匹配了啥。

一般可以通用混用:

public interface Collection<E>
{
  boolean containsAll(Collection c);
  boolean addAll(Collection c);
}

public interface Collection<E>
{
     boolean containsAll(COllection c);
     boolean addAll(Collection c);
}

泛型构造器和自动推导

class Foo
{
    public  Foo(T t)
    {
        System.out.println(t);
    }
}
public class GenericConstructor
{
    public static void main(String[] args)
    {
        // 泛型构造器中的T参数为String。
        new Foo("疯狂Java讲义");
        // 泛型构造器中的T参数为Integer。
        new Foo(200);
        // 显式指定泛型构造器中的T参数为String,
        // 传给Foo构造器的实参也是String对象,完全正确。
        new  Foo("疯狂Android讲义");
        // 显式指定泛型构造器中的T参数为String,
        // 但传给Foo构造器的实参是Double对象,下面代码出错
        //new  Foo(12.3);
    }
}

如上,可以定义泛型构造器,使用时不仅可以显示指定还可以让编译器推断。

class MyClass
{
    public  MyClass(T t)
    {
        System.out.println("t参数的值为:" + t);
    }
}
public class GenericDiamondTest
{
    public static void main(String[] args)
    {
        // MyClass类声明中的E形参是String类型。
        // 泛型构造器中声明的T形参是Integer类型
        MyClass mc1 = new MyClass<>(5);
        // 显式指定泛型构造器中声明的T形参是Integer类型,
        MyClass mc2 = new  MyClass(5);
        // MyClass类声明中的E形参是String类型。
        // 如果显式指定泛型构造器中声明的T形参是Integer类型
        // 此时就不能使用"菱形"语法,下面代码是错的。
//      MyClass mc3 = new  MyClass<>(5);
    }
}

如上,当显式指定了泛型构造器实际类型后,不能再用前面的简写法。

你可能感兴趣的:(java,Java)