《疯狂JAVA讲义》第8章 泛型 学习笔记

时间:2023-02-25 14:32:03

1.深入泛型

当使用List类型时,如果为E形参传入String类型实参,则产生了一个新的类型:List<String>类型。可以为任何类、接口增加泛型声明。

从泛型类派生子类

使用接口、父类时不能再包含类型形参。例如下面的代码就时错误的:

public class A extends Apple<T>{}

可以改为:

public class A extends Apple<String>

不管泛型的实际类型参数是什么,它们在运行时总有同样的类,在内存中也只占用一块内存空间,如下所示:

List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
System.out.println(l1.getClass() == l2.getClass());//输出true

在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参,同时由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。

2.类型通配符

现有如下代码将会发出泛型警告:

public void test(List c)
{
    for(int i = 0;i < c.size();i++)
    {
        System.out.println(c.get(i));
    }
}

如果改为如下形式:

public void test(List<Object> c)
{
    for(int i = 0;i < c.size();i++)
    {
        System.out.println(c.get(i));
    }
}

调用该方法是将会引发错误,例如:

无法将Test中的test(java.util.List<java.lang.Object>)
应用于(java.util.List<java.lang.String>)

如果Foo是Bar的一个子类,而G是具有泛型声明的类或接口,G<Foo>并不是G<Bar>的子类型,而数组则不同,Foo[]依然是Bar[]的子类型

为了表示各种泛型的父类,可以使用类型通配符:

public void test(List<?> c)
{
    for(int i = 0;i < c.size();i++)
    {
        System.out.println(c.get(i));
    }
}

现在便可以使用任何类型的List调用它,对于Set<?>、Collection<?>、Map<?,?>同理

但这种带通配符的List仅表示各种泛型List的父类,并不能把元素加入其中,例如,如下代码将会引起编译错误:

List<?> c = new ArrayList<String>();
c.add(new Object());

唯一的例外是null,因为它是所有引用类型的实例

设定类型通配符的上限

List<? extends Shape>可以表示List<Circle>、List<Rectangle>的父类——只要List后尖括号里的类型是Shape的子类型即可。可以把Shape称为这个通配符的上限。类似地,不能把Shape对象或其子类的对象加入这个泛型集合中。例如,以下代码就时错误的:

public void addRectangle(List<> extends Shape> shapes)
{
    shapes.add(0,new Rectangle());
}
设定类型形参的上限
public chasse Apple<T extends Number>

在另一种更极端的情况下,程序需要为类型形参设定多个上限:

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

3.泛型方法

定义泛型方法

所谓泛型方法,就是在声明方法时定义一个或多个类型形参,语法格式如下:

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

例如:

static <T> void fromArrayToCollection(T[] a,Collection<T> c)
{
    for(T o:a)
    {
        c.add(o);
    }
}
与类、接口中使用泛型参数不同的是,方法中的泛型参数无须显式传入实际类型参数