Java 泛型

浏览: 71 发布日期: 2017-01-04 分类: java

泛型入门

编译时不检查类型的异常

public class ListErr
{
    public static void main(String[] args)
    {
        // 创建一个只想保存字符串的List集合
        List strList = new ArrayList();
        strList.add("布达佩斯");
        strList.add("布拉格");
        // "不小心"把一个Integer对象"丢进"了集合
        strList.add(34);     // ①
        strList.forEach(str -> System.out.println(((String)str).length())); // ②
    }
}

上述程序创建了一个List集合,且该List集合保存字符串对象——但程序不能进行任何限制,如果程序在①处“不小心”把一个Integer对象"丢进"了List集合,这将导致程序在②处引发ClassCastException异常,因为程序试图把一个Integer对象转换为String类型

使用泛型

参数化类型,允许程序在创建集合时指定集合元素的类型。Java的参数化类型被称为泛型(Generic)

class GenericList
{
    public static void main(String[] args)
    {
        // 创建一个只想保存字符串的List集合
        List<String> strList = new ArrayList<>();     // ①
        strList.add("布达佩斯");
        strList.add("布拉格");
        // 下面代码将引起编译错误
        strList.add(34);     // ②
        strList.forEach(str -> System.out.println(((String)str).length())); // ③
    }
}

strList集合只能保存字符串对象,不能保存其他类型的对象。创建特殊集合的方法是:在集合接口、类后增加尖括号,尖括号里放一个数据类型,即表明这个集合接口、集合类只能保存特定类型的对象。

①类型声明,在创建这个ArrayList对象时也指定了一个类型参数;②引发编译异常;③不需要进行强制类型转换

泛型使程序更加简洁,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换

Java7泛型的“菱形”语法

Java允许在构造器后不需要带完整的泛型信息,只要给出一对尖括号(<>)即可,Java可以推断尖括号里应该是什么泛型信息

public class DiamondTest
{
    public static void main(String[] args)
    {
        // Java自动推断出ArrayList的<>里应该是String
        List<String> countries = new ArrayList<>();
        countries.add("法兰西第五共和国");
        countries.add("西班牙王国");
        // 遍历countries集合,集合元素就是String类型
        countries.forEach(ele -> System.out.println(ele.length()));
        // Java自动推断出HashMap的<>里应该是String , List<String>
        Map<String , List<String>> citiesInfo = new HashMap<>();
        // Java自动推断出ArrayList的<>里应该是String
        List<String> cities = new ArrayList<>();
        cities.add("巴黎");
        cities.add("巴塞罗那");
        citiesInfo.put("Bienvenue" , cities);
        // 遍历Map时,Map的key是String类型,value是List<String>类型
        citiesInfo.forEach((key , value) -> System.out.println(key + "-->" + value));
    }
}

深入泛型

所谓泛型:就是允许在定义类、接口、方法时指定类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定(即传入实际的类型参数,也可称为类型实参)

定义泛型接口、类

List接口、Iterator接口、Map的代码片段

// 定义接口时指定了一个类型形参,该形参名为E
public interface List<E>       
{
    // 在该接口里,E可作为类型使用
    // 下面方法可以使用E作为参数类型
    void add(E x);
    Iterator<E> iterator();    // ①
     
}

// 定义接口时指定了一个类型形参,该形参为E
public interface Iterator<E>
{
    // 在该接口里E完全可以作为类型使用
    E next();
    boolean hasNext();
}

// 定义该接口时指定了两个类型形参,其形参名为K、V
public interface Map<K, V>
{
    // 在该接口里K、V完全可以作为类型使用
    Set<K> keySet();           // ②
    V put(K key,V value);
}

上面代码说明泛型实质:允许在定义接口、类时声明类型形参,类型形参在整个接口、类体内可当成类型使用

包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而可以动态地生成无数个逻辑上的子类,但这种子类在物理上并不存在

可以为任何类、接口增加泛型声明(并不是只有集合类才可以使用泛型声明,虽然集合类是泛型的重要使用场所)

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

当创建带泛型声明的自定义类,为该类定义构造器时,构造器名还是原来的类名,不要增加泛型声明

从泛型类派生子类

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

// 定义类Shikigami继承Onmyoji类,Onmyoji类不能跟类型形参
public class Shikigami extends Onmyoji<T>{ }        // 错误

方法中的形参(或数据形参)代表变量、常量、表达式等数据。定义方法时,可以声明数据形参;调用方法(使用方法)时,必须为这些数据形参传入实际的数据;与此类似的是,定义类、接口、方法时可以声明类型形参,使用类、接口、方法时应为类型形参传入实际的类型

// 使用Onmyoji类时为T形参传入String类型
public class Shikigami extends Onmyoji<String>{ }        // 正确

调用方法时必须为所有的数据参数传入参数值,而使用类、接口时也可以不为类型形参传入实际的类型参数

// 使用Onmyoji类时,没有为T形参传入实际的类型参数
public class Shikigami extends Onmyoji{ }        // 正确

子类需要重写父类的Getters和Setters方法

private String info;
public String getInfo() 
{
    return "子类"+ super.getInfo();
}
public void setInfo(String info)
{
    this.info = info;
}

并不存在泛型类

ArrayList<String>类,是一种特殊的ArrayList类。该ArrayList<String>对象只能添加String对象作为集合元素。但实际上,系统并没有为ArrayList<String>生成新的class文件,而且也不会把ArrayList<String>当成新类来处理。因为不管泛型的时间类型参数是什么,它们在运行时总有同样的类(class)

// 分别创建List<String>对象和List<Integer>对象
List<String> l1 = new ArrayList<>();
List<Integer> l2 = new ArrayList<>();
// 调用getClass()方法来比较l1和l2的类是否相等
System.out.println(l1.getClass() == l2.getClass());        // 输出true

不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参

由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类。

if(cs instanceof List<String>)
{
    ...
}

类型通配符

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

数组和泛型有所不同,假设Foo是Bar的一个子类型(子类或者子接口),那么Foo[]依然是Bar[]的子类型;但G<Foo>不是G<Bar>的子类型

Java泛型的设计原则是,只要代码在编译时没有出现警告,就不会遇到运行时ClassCastException异常

使用类型通配符

为了表示各种泛型List的父类,可以使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作:List<?>(意思是未知类型元素的List)。这个问号(?)被称为通配符,它的元素类型可以匹配任何类型

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

现在使用任何类型的List来调用它,程序依然可以访问集合c中的元素,其类型是Object,这永远是安全的,因为不管List的真实类型是什么,它包含的都是 Object

这种带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中

List<?> c = new ArrayList<String>();    
// 下面程序引起编译错误
c.add(new Object());

因为程序无法确认c集合里元素的类型,所以不能向其中添加对象。根据前面的List<E>接口定义的代码可以发现:add ()方法由类型参数E作为集合的元素类型,所以传给add的参数必须是E类的对象或者其子类的对象。但因为在该例中不知道E是什么类型,所以程序无法将任何对象“丢进”该集合。唯一的例外是 null,它是所有引用类型的实例

另一方面,程序可以调用get()方法来返回List<?>集合指定索引处的元素,其返回值是一个未知类型,但可以肯定的是,它总是一个Object。因此,把get()的返回值赋值给一个Object类型的变量,或者放在任何希望是Object类型的地方都可以

设定类型通配符的上限

List<Circle>并不是List<Shape>的子类型,所以不能把List<Circle>对象当成List<Shape>使用。为了表示List<Circle>的父类,使用List<? extends Shape>

List<? extends Shape>是受限通配符的例子,此处的问号(?)代表一个未知的类型,此处的未知类型一定是Shape的子类也可以是Shape,因此可以把shape称为这个通配符的上限(upper bound)

设定类型形参的上限

Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型要么是该上限类型,要门是该上限类型的子类

在一种更极端的情况下,程序需要为类型形参设定多个上限(至少有一个父类上限,可以有多个接口上限),表明该类型形参必须是其父类的子类(其父类本事也行),并且实现多个上限接口

//表明T类型必须是Number类或其子类,并必须实现java.io.Serializablepublic 
class Apple<T extends Number & java.io.Serilizable>
{
    ...
}

与类同时继承父类、实现接口类似的是:为类型形参指定多个上限,所有的接口上限必须位于类上限之后。也就是说,如果需要为类型形参指定类上限,类上限必须位于第一位

泛型方法

定义泛型方法

Java不允许把对象放进一个未知类型的集合中

所谓泛型方法,就是在声明方法时定义一个或多个类型形参

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

 

来自:https://segmentfault.com/a/1190000007983793

 

返回顶部