java泛型(二)

泛型方法和类型通配符的区别

本模块讨论的是在定义方法时为了保证通用性,方法的类型形参到底用List<?>还是List<T>

  • 通配符被设计用来支持灵活的子类化
  • 泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系
  • 大多数情况下,都可以使用泛型方法来代替类型通配符。如果没有这种依赖关系,就不应该使用泛型方法
    例1:应使用类型通配符的情况
// java Collection接口中部分方法的定义
public abstract interface Collection<E> extends Iterable<E>
{
    public abstract boolean containsAll(Collection<?> paramCollection);
    public abstract boolean addAll(Collection<? extends E> paramCollection);
}
// 使用泛型方法代替java Collection中的类型通配符
public abstract interface Collection<E> extends Iterable<E>
{
    public abstract boolean containsAll(Collection<T> paramCollection);
    public abstract boolean addAll(Collection<T extends E> paramCollection);
}
// 但上面两个方法中,T只用了一次,它产生的唯一效果是可以在不同的调用点传入不同的实际类型
// 这种情况应该用使用类型通配符

例2:必须使用泛型的情况
若一个方法中有两个形参a、b,a依赖于b,此时b的类型声明不能使用通配符。因为若b的类型无法确定,那么依赖于它的a的类型将无法确定。

public static <T> void copy(List<T> dest,List<? extends T> src){...}

要想实现集合的复制,源集合的元素类型必须是目标集合的元素类型的子类或它本身

  • src依赖于dest,因此dest的声明不能使用通配符
  • 对于src,无须向src集合添加/修改元素,因此使用类型通配符,无须使用泛型(由java泛型一知,不能向未知类型的集合加入/修改元素)

从泛型类派生子类

  • 创建带泛型声明的接口,可以为该接口创建实现类
  • 创建带泛型声明的父类,可以从该父类派生子类
public class A extends Apple<T>{}// 错误!定义类A继承类Apple,Apple类不能跟类型形参
public class A extends Apple<String>{}// OK 传入类型实参,Apple类中所有使用T类型形参的地方都被替换成String
public class A extends Apple{}// 不传类型实参,编译器会发出警告。但凡涉及父类方法,形参的实际类型都为Object

泛型方法与方法重载

// 若一个类中有以下两个方法,参数列表因为通配符的上下限写法不一样而不一样
public <T> void copy(Collection<T> dest,Collection<? extends T> src);
public <T> T copy(Collection<? super T>,Collection<T> src);
// 类中定义这两个不会出错,但调用的时候会出错,因为编译器无法确定代码到底想调用哪个copy()方法

java8改进的类型推断

java8改进了泛型方法的类型推断能力,主要有两方面

  • 通过调用方法的上下文
  • 方法调用链中,将推断得到的类型参数传递到最后一个方法

数组泛型

Java中没有所谓的泛型数组一说。
可以用带泛型参数的类声明数组,但是不可创建数组。

 List<Integer>[] iListArray;
 new ArrayList<Integer>[10];//编译时错误

实现原理

一个泛型类被其所有调用共享

List<String> strList = new ArrayList<String>();// 可以把List<String>想象成E被全部替换成String的特殊List子接口
strList.add("a");
List<Integer> intList = new ArrayList<Integer>();// 可以把List<Integer>想象成E被全部替换成Integer的特殊List子接口
intList.add(1);
System.out.println(strList.getClass() == intList.getClass());// 猜一猜两个变量的类是否相等?

// 输入结果:true

以上程序说明:

  • 包含泛型声明的类型可以在定义变量、创建对象时传入一个类型实参,从而动态生成无数多个逻辑上的子类,但这种子类在物理上并不存在
  • List<String>或List<Integer> 绝不会被替换成ListString或ListInteger子类,系统没有进行源代码复制,二进制代码中没有,磁盘中没有,内存中也没有
  • 不管为泛型的类型形参传入哪种类型实参,对于java来说,他们依然被当成一个类处理,在内存中也只占用一块内存空间,因此不能在静态变量、静态方法的声明中使用类型形参
public class R<T>{
    static T info;// 错误,不能在静态变量声明中使用类型形参
    public static void bar(T msg){}// 错误,不能在静态方法的声明中使用类型形参
}

原因在于java泛型概念提出的目的:

  • 为了在编译时进行类型检查,保证程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。因此,在编译过程中正确检查泛型结果后,通过java编译器的称为擦除 功能,去掉所有泛型类型信息,即所有括号之间的类型信息都被扔掉。例如: List<String>被替换成List
  • 可以认为擦除是一个从源码到源码的转换,把泛型版本转换成非泛型版本。

类型参数在运行时并不存在,这意味着它们不会添加任何时间或者空间上的负担,但不幸的是,这也意味
着你不能依靠他们进行类型转换

instanceof

泛型类被其所有实例共享的另一个暗示:instanceof运算符后不能使用泛型类,因为系统中不会真正生成泛型类

List<String> list = new ArrayList<String>();
if(list instanceof java.util.ArrayList<String>){...}// 编译错误!instanceof 后不能使用泛型

类似的,如下的类型转换会给出unchecked warning

List<?> list2 = new ArrayList<Object>();
List<String> list1 = (List<String>) list2;// 工具给出unchecked warning,因为运行时环境不会为你做这样的检查

Class的泛型处理

JDK1.5的一个变化是java.lang.java类变成里泛型类,这是把泛型扩展到容器类之外的一个很有意思的例子。

// T 表示Class对象代表的类型,例如:String.class类型代表Class<String> 
public final class Class<T> implements Serializable, GenericDeclaration, Type, AnnotatedElement
{
    ...
}
// Class的泛型处理可以提高反射代码的类型安全
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值