Java 泛型

熟悉C++模板使用的读者可能觉得java的泛型有点幼稚的感觉,但java泛型的设计思想仍是值得称道的。

Java泛型的一个重要准则原则是,如果在编译时没有任何警告,运行时是绝对没有ClassCastException异常的。

1. 新的语法

java7允许在调用构造器创建对象时构造器的后面可以直接使用<>。

List<String> list = new ArrayList<>();
如果一个类的定义使用了泛型,它的构造函数还是原来的样子。

class A<T>{
   public A(){}
}

2. 从泛型类派生子类

当要继承带泛型的类,父类不能再包含类型形参。下面的代码是错误的。

class A extends B<T>{
}
必须给出T的特定类型,当然也可以不传入实际类型,此时会发出类型未检查的警告。

3. 并不存在泛型类

这点很容易引起混淆。比如,

List<String> strList = new ArrayList<>();
List<Integer> iList = new ArrayList<>();
System.out.println(strList.getClass()==iList.getClass());
上面代码的结果是true。这表明在运行时他们具有相同的Class。所以,不管泛型的类型参数传入什么,对Java而言,它们都被当作同一个类。这样一来,程序里所有静态模块都不允许是泛型。下面的代码都是错的,

public class A<T>
{
      static T info;
      public static void f (T t){}
}

4. List<String>和List<Object>能完成向上转型吗

下面的代码是无法通过编译的,

List<String> strList = new ArrayList<>();
List<Object> list = strList;

这表明他们自己不存在继承关系。

和集合相似是数组,我们来看看数组对这种情况的处理。

Integer[] i = new Integer[5];
Number[] n = i;
n[0] = 3.14;

上面的代码能通过编译,但运行时出错。原因很简单,编译时n的类型是Number[],运行时是Integer[],无法正确转型。

一种好的语言应该避免这种情况,所以Java在设计泛型时不再允许把List<Integer>赋值给List<Number>。为了表示各种泛型的List的父类,可使用通配符?,表示未知类型。正是因为类型的未知,你并无法添加元素。

List<?> list = new ArrayList<String>();
list.add("hello");
第二句话无法编译通过。

下面介绍类型通配符的上界。

我们假设有个抽象类Shape,并有抽象方法draw(Canvas c),Circle和Rectangle继承它,有个类Canvas类。如果要求在Canvas类里有个方法drawAll绘制多个形状。从之前的介绍可以写出,

void drawAll(List<?> shapes)
{
    for( Object obj : shapes)
      {
          Shape s = (Shape) obj;
          s.draw(this);
       }
}

上面的代码有些复杂,因为不得不进行强制类型转换。实际上,使用 List<? extends Shape>就可以表示该未知类型一定是Shape的子类,包括本身。但是由于我们仍不知道它的具体类型,如果试图把Shape的对象及其子类对象加入这个集合,还是会出现异常。

void add(List<? extends Shape> shapes)
{
//无法通过编译
    shapes.add(new Rectangle());
}

解决方法可以参考下一节的泛型方法。


5. 泛型方法

考虑这么一个需求,把一个数组元素赋值给一个集合。使用泛型方法实现如下:

<T> void f (T[] a, Collection<T> b)
{
      for(T t:a)
      { 
          b.add(t);
       }
}
在使用时和使用普通方法一样,无需显式传入实际参数类型,如 String[] as = new String[100]; Collection<String> cs = new ArrayList<>(); f(sa,cs);

这里产生了一个问题,泛型方法和类型通配符的使用选择?

大多数情况下,泛型方法可以代替类型通配符。如果一个方法中的形参a的类型或返回值类型依赖另一个形参b,那么b就不该使用通配符,只能在方法签名使用泛型方法。

现在讨论一个带泛型方法的构造器。

class A{
      public <T> A(T t) {}
}

此时,new A("hello"),new <int> A(7)都是可以的。

如果上面的类修改为:

class A<E>
{
    public <T> A(T t){}
}

此时,A<String> a = new A<>(5); A<String> a = new <Integer> A<String> (5)都可以的。但是 A<String> a = new <Integer> A<> (5)会出错。(提示,结合1提到的知识分析。)


6.擦除机制

在严格的java代码里,一个使用泛型的类声明必须带参数,这也是出现警告的原因。但是为了和以前代码的兼容,不指定具体类型也是可以的。这也是擦除机制的由来。

	List<Integer> li = new ArrayList<>();
		li.add(6);
		li.add(9);
		System.out.println(li.get(0));
		List list = li;
		List<String> ls = list;
//		System.out.println(ls.get(0));
List list = li; 会使得<>内的信息丢失。这样一来 list赋值给 List<String>也是可以的,但是执行ls.get(0)是出错(编译时只有警告)。








  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值