第十六章、泛型

16.1 Java集合的泛型

在 JDK5 以前的版本中,集合中的元素都是 Object 类型,从集合中获取元素时,常常需要进行强制类型的转换。

以下代码向 ArrayList 中加入了一个 String 对象,接下来取出这个 String 对象,并试图把它强制转换为 Integer 类型。代码可以通过编译,但运行时会抛出 ClassCastException 运行时异常 。

List list = new ArrayList();
list.add("hello");
Integer i = (Integer)list.get(0);               //抛出ClassCastException

在 JDK 版本升级的过程中,致力于把一些运行时异常转变为编译时错误。在 JDK5 版本中,引入了泛型的概念,它有助于把 ClassCastException 运行时异常转变为编译时的类型不兼容错误。

从 JDK5 开始,所有 Java 集合都采用了泛型机制。在声明集合变量和创建集合对象时,可以用 <> 标记指定集合中元素的类型:

List<String> list = new ArrayList<String>();
list.add("hello");                      //合法
list.add(new Integer(11));              //编译出错,不允许Integer对象加入列表

Integer i = list.get(0);                //编译出错,列表中元素 String 类型无法转换为 Integer类型
String s = list.get(0);                 //合法
Object o = list.get(0);                 //合法,允许向上转型,无需进行强制类型转换
Set<Object> set = new HashSet<Object>();
set.add("Tom");                         //合法
set.add(new StringBuffer("Mike"));      //合法
set.add(new Object());                  //合法
set.add(new Integer(1));                //合法

16.2 定义泛型类和泛型接口

Java 集合的泛型机制到底是如何实现的呢?

public class OldBag {
    private Object content;
    public OldBag(Object content) {this.content = content;}
    public Object get() {return this.content;}
    public void set(Object content) {this.content = content;}
    public static void main(String[] args) {
        OldBag bag = new OldBag("Gucci");
        Object content = (Integer)bag.get();        //抛出ClassCastException
    }
}
//Bag类采用泛型机制改进OldBag类,避免抛出运行时异常
public class Bag<T> {
    private T content;
    public Bag(T content) {this.content = content;}
    public T get() {return this.content;}
    public void set(T content) {this.content = content;}
    public static void main(String[] args) {
        Bag<String> bag = new Bag<String>("Gucci");
        Integer content1 = bag.get();                   //编译出错
        String content2 = bag.get();                    //合法
    }
}

就像在定义方法时可以声明一些方法参数 ,同样,在定义类时,也可以通过 <T> 的形式来声明类型参数。在类的主体中可以直接引用 T 这样的类型参数。这种带有类型参数的类被称作 泛型类

一个泛型类可以有多个类型参数,语法为 ClassName<T1, T2, T3 ...>{}

public class MyMap<K, V> {
    private Map<K, V> map = new HashMap<K, V>();
    public void put(K k, V v) {
        map.put(k, v);
    }
    public V get(K k) {
        return map.get(k);
    }
    public int size() {
        return map.size();
    }
    public static void main(String[] args) {
        MyMap<Integer, String> map = new MyMap<Integer, String>();
        map.put(1, "book one");
    }
}

16.3 用extends关键字限定类型参数

在定义泛型类时,可以用 extends 关键字来限定类型参数,语法形式为:

<T extends ClassName>           //T必须是指定类或其子类
<T extends InterfaceName>       //T必须是指定接口的实现类
public class NumBag<T extends Number> {
    private T content;
    public NumBag(T content) {this.content = content;}
    public T get() {return this.content;}
    public void set(T content) {this.content = content;}
    public static void main(String[] args) {
        NumBag<List> bag1 = new LimitBag<List>(new ArrayList());      //编译出错
        NumBag<Integer> bag2 = new LimitBag<Integer>(12);             //合法
    }
}

由于对 NumBag 类的类型参数 T 做了限定 <T extends Number>,因此在 main() 方法中,NumBag<List> 是非法的,因为 List 不是 Number 类的子类,不允许把它赋值给 NumBag 类的类型参数 T。 而 NumBag<lnteger> 是合法的,因为 Integer 是 Number 类的子类,可以把它赋值给 NumBag 类的类型参数 T。

16.4 定义泛型数组

public class ArrayBag<T> {
    private T[] content;
    //private T[] content = new T[10];  编译出错,不能用泛型来创建数组实例
    public ArrayBag(T[] content) {this.content = content;}
    public T[] get() {return this.content;}
    public void set(T[] content) {this.content = content;}
    public static void main(String[] args) {
        String[] content = {"book1", "book2"};
        ArrayBag<String> bag = new ArrayBag<String>(content);
    }
}

以上 ArrayBag 类的 main() 方法先创建了一个 String 类型的数组,再把它传给 ArrayBag 类的构造方法,这是合法的。

16.5 定义泛型方法

在一个方法中,如果方法的参数或返同值的类型带有 <T> 形式的类型参数,那么这个方法称为泛型方法。 在普通的类或者泛型类中都可以定义泛型方法。

public class MethodTest {
    public static <E> void printArray(E[] array) {
        for (E element : array)
            System.out.println(element);
    }
    public static <T extends Comparable<T>> T max(T x, T y) {
        return x.compareTo(y) > 0 ? x : y;
    }
    public static void main(String[] args) {
        Integer[] array = {1, 2, 3};
        printArray(array);
        System.out.println(max(1, 2));
    }
}

MethodTest 类 的 printArray() 方法有一个类型参数 E, printArray() 方法的 array 参数为 E[] 类型。MethodTest 类的 max() 方法有一个类型参数 T,它被限定为是 Comparable 接口的实现类。 max() 方法的参数 x 和参数 y 均为 T 类型。

16.6 使用 ? 通配符

在泛型机制中,编译器认为 HashSet<String>Set<String> 之间存在继承关系,因此以下赋值是合法的。但编译器认为 HashSet<Object>HashSet<String> 之间不存在继承关系,因此以下赋值是非法的:

Set<String> set1 = new HashSet<String>();        //合法,允许向上转型
HashSet<Object> set2 = new HashSet<String>();    //编译出错,不兼容的类型
public class WildCastTest {
    public static void printer1(Collection<Object> collection) {
        for (Object obj : collection) 
            System.out.println(obj);
    }
    public static void printer2(Collection<?> collection) {
        for (Object obj : collection) 
            System.out.println(obj);
    }
    public static void main(String[] args) throws Exception {
        List<Integer> list = new ArrayList<Integer>();
        list.add(11);
        printer1(list);                     //编译出错,不兼容的类型
        printer2(list);                     //合法
    }
}

Collection<?> 表示集合中可以存放任意类型的元素,因此把 ArrayList<Integer> 类型的参数传给 printer2() 方法是合法的。

  • 通配符 ? 还可以与 extends 关键字连用,用来限定类型参数的上限,例如:
    TreeSet<? extends type1> x = new TreeSet<Type2>();
    
    以上类型1表示特定的类型,类型2只能是类型1或者是类型1的子类。例如:
    TreeSet<? extends Number> x = new TreeSet<Integer>();           //合法
    TreeSet<? extends Number> x = new TreeSet<String>();            //编译出错
    
  • 通配符 ? 还可以与 super 关键字连用,用来限定类型参数的下限,例如 :
    TreeSet<? super type1> x = new TreeSet<Type2>();
    
    以上类型1表示特定的类型,类型2只能是类型1或者是类型1的父类。例如:
    TreeSet<? super Integer> x = new TreeSet<Number>();           //合法
    TreeSet<? super Integer> x = new TreeSet<Byte>();             //编译出错
    

16.7 使用泛型的注意事项

在使用泛型时,还有以下注意事项:

  • 在程序运行时,泛型类是被所有这种类的实例共享的。例如,尽管 ArrayList<String>ArrayList< lnteger> 类型在编译时被看作不同的类型,实际上在编译后的字节码类中,泛型会被擦除,ArrayList<Strin g>ArrayList<Integer> 类型均被看作是 ArrayList 类型。因此所有泛型类的实例都共享同一个运行时类。
    //ArrayList<String> 和 ArrayList<Integer> 在运行时共享同一个ArrayList类的Class实例
    List<String> l1 = new ArrayList<String>();
    List<Integer> l2 = new ArrayList<Integer>();
    System.out.println(l1.getClass() == l2.getClass());             //打印true
    
  • 编译器不允许在一个类中定义两个同名的方法,分别以 List<String>List<Integer> 作为方法参数。例如以下是非法的方法重载 :
    public class OverloadTest{
        public void test(List<String> ls) {}
        public int test(List<Integer> li) {}            //非法重载
    }
    
  • 不能对确切的泛型类型使用 instanceof 操作。例如下面的操作是非法的,编译时会出错:
    Collection cs = new ArrayList<String>();
    if (cs instanceof Collection<String>) {...}         //编译出错
    if (cs instanceof Collection<?>) {...}              //使用通配符通过编译
    
  • 不能用泛型类型来进行强制类型转换,这样会存在安全隐患。例如下面的代码虽然编译能通过,但编译时会产生警告信息,并且运行时会抛出 ClassCastException:
    Collection cs = new ArrayList<Integer>();
    cs.add(1);
    ArrayList<String> list = (ArrayList<String>) cs;
    list.add("hello");
    for (String str : list) System.out.println(str);        //抛出异常ClassCastException
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值