Java—泛型

21 篇文章 6 订阅

泛型

使用泛型机制编写的程序代码要比那些杂乱地使用Object变量,然后再进行强制类型转换的代码具有更好的安全性和可读性

使用泛型之前,ArrayList类只维护一个Object引用的数组,这导致

  • 获取一个值时必须进行强制类型转换
  • 可以向数组列表中添加任何类的对象

JAVA SE 7 后 构造函数中可以省略泛型类型

类型参数的好处:使得程序具有更好的可读性和安全性

1.泛型类

泛型类就是具有一个或多个类型变量的类。
  • 泛型类可以有多个类型变量 例如:public class Pair< T,U >{ … }

2.泛型方法

  • 泛型方法的类型变量放在修饰符的后面,返回类型的前面
  • 泛型方法可以定义在普通类中,也可以定义在泛型类中
  • 当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型,但大多数情况下,编译器可以推断出调用的方法的参数类型,所以可以省略< >类型参数

3.类型变量的限定

有时,类或方法需要对类型变量加以约束,例如:
class ArrayAlg{
    public static <T> T min(T[] a){
        if(a == null || a.length == 0)
            return null;
        T smallest = a[0];
        for(int i = 1; i < a.length; i++){
            if(smallest.compareTo(a[i]) > 0){
                smallest = a[i];
            }
        }
        return smallest;
    }
}
但是这里有一个问题,变量smallest的类型为T,这意味着它可以是任何一个类的对象,那又如何确定T所属的类有compareTo方法呢
解决这个问题的方案是将T限制为实现了Comparable接口的类,可以通过对类型变量T设置限定来实现这一点
public static <T extends Comparable> T min(T[] a)

4.泛型代码和虚拟机

虚拟机没有泛型类型对象,所有对象都属于普通类

1> 类型擦除

使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定的变量用Object)
  • 在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 < T > 则会被转译成普通的 Object 类型,如果指定了上限如 < T extends String> 则类型参数就被替换成类型上限。

2> 翻译泛型表达式

当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。例如:

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

擦除getFirst 的返回类型后将返回Objec类型。编译器自动插入Employee的强制类型转换。也就是说,编译器把这个方法调用翻译成两条虚拟机指令

  • 对原始方法 Pair.getFirst 的调用

  • 将返回的Object 类型强制转化为Employee 类型


3> 桥方法(bridge method)

由于在类型擦除后类中会缺少某些必须的方法,这时就由编译器来动态生成这些方法。

例如:

public class MyString implements Comparable<String>{
    public int compareTo(String str){
        return 0;
    }
}
上述代码会出现一个问题,就是在类型擦除之后,实际上类变成了:

public class MyString implements Comparable

这样的话,类MyString就会有编译错误因为没有实现接口Comparable中声明的 int compareTo(Object) 方法,这个时候就由编译器来动态生成这个方法。

5.约束与局限性

1> 不能用基本类型实例化类型参数

不能用类型参数代替基本类型。因此,没有Pair< double>,只有Pair< Double>。这是因为类型擦除,擦出后,Pair类含有Object类型的域,而Object不能存储double值

2> 运行时的类型查询只适用于原始类型

下面是一个典型的例子

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();

System.out.println(l1.getClass() == l2.getClass());

结果是什么呢?答案是true

虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型

  • 所以在试图查询一个对象是否属于某个泛型类型时,倘若使用instanof会得到一个编译器错误,如果使用泛型类型进行强制类型转换会的得到一个警告
  • 同样的道理,getClass方法总是返回原始类型

3> 不能创建泛型数组

需要说明的是,只是不允许创建这些数组,而声名类型为Pair< String >[] 的变量仍是合法的。不过不能用 new Pair< String >[10] 来初始化这个变量

4> 在可变参数列表中使用泛型

这里有个例子:

public static <T> void addAll(Collection<T>coll, T... ts){
    for(t:ts){
        coll.add(t);
    }

}

应该记得,可变参数ts实际上是一个数组,包含提供的所有实参
现在这样调用:

Collection<Pair<String>> table = ...;
Pair<String> pair1 = ...;
Pair<String> pair2 = ...;
addAll<table,pairs,pair2>;

为了调用这个方法,虚拟机必须建立一个Pair< String >数组,这就违反了前面的规则。不过对于这种情况,规则会有所放松,你会饿到一个警告而不是错误。
可以用注解 @SuppressWarnings(“unchecked”) 或者 @SafeVarargs直接标注addAll方法


5> 不能实例化类型变量

不能使用像new T(…),new T[…] 或T.class这样的表达式中的类型变量。

6> 泛型类的静态上下文中类型变量无效

不能在静态域或方法中引用类型变量

7> 不能抛出或捕获泛型类的实例

6通配符类型

这里我在上一篇文章中写了比较详细的解析,链接:
Java—泛型与通配符

至于反射与泛型的知识我们以后再分享

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值