定义泛型

Java的一个聚集对象可以存储多种类型的数据,比如Set中可以存整数也可以存字符串。没有泛型之前有以下两个问题:

  • 从聚集(collection)中取出一个元素,必须把这个元素转换为具体类型。
  • 插入元素时,编译器不检查你插入的对象的类型与聚集中元素的类型是否相同。

有泛型之前:

  1. List list = new ArrayList();
  2. list.add( "hello");
  3. String s = (String) list.get( 0);

有泛型之后:

  1. List<String> list = newArrayList<String>();
  2. list.add( "hello");
  3. String s = list.get( 0); // no cast

泛型的作用就是告诉编译器你希望聚集中的元素是什么类型,这样的好处有两:编译器可以在编译阶段告诉你你要存入聚集的元素类型是否正确;从聚集中取出元素时元素不需要强制转换,因为编译器知道聚集中的元素类型。

泛型定义

所谓泛型是指数据的类型很宽泛,在定义方法的时候我们也不确定这个方法将来会操作什么具体类型的数据。因此,才有了泛型的概念。通过定义泛型,就可以使类、接口或方法可以接受任意类型或多种类型的参数。

定义泛型,我们常会碰到<?>或<T>,其中<T>中的T可以是满足类名命名规范的任意字符串,不过为了简化代码,一般是用一个大写的字母表示。因此常用的类型参数名称有:

  • E - Element
  • K - Key
  • N - Number
  • T - Type
  • V - Value

<>有点像方法的括号(),T就是参数名,因为使用泛型的时候T必须被具体的类替换。

泛型 - Generic Type

Java中的泛型是指有类型参数的类或接口,定义格式如下:

class ClassName<T1, T2, ..., Tn> { /* ... */ }

上面这段代码声明在这个类中有n个不确定的数据类型。当我们需要使用这个类时,我们就会用具体的类来替换这些类型参数,告诉这个对象,在这里,你将操作的是这些类型的数据。比如集合的定义是Set<E>,当我们使用集合时,就需要替换这个E参数:

Set<Integer> intSet;

这个例子中,用Integer替换T,表明这个Set集合我要用来存储整数。

泛型对应接口Type,都是Type的实现类。

原始类型 – Raw Type

虽然泛型声明了类型参数,但如果使用时不设置类型参数,那么这个泛型就变成了原始类型。比如Set就是Set<T>的原始类型。但需要注意,原始类型只相对泛型而言,非泛型的类或接口都不是原始类型。也就是说如果类或接口的定义没有类似Name<T>结构的都不是原始类型。

泛方法

在泛型应用之前,方法的参数类型都是确定的,如果要操作不同类型的参数就需要定义多个方法。如果方法参数的类型也可以参数化,就可以用一个方法操作多种类型的参数。

泛方法的泛类型参数需在返回类型前定义:

  1. public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
  2. returnp1.getKey().equals(p2.getKey()) &&
  3. p1.getValue().equals(p2.getValue());
  4. }

调用举例:

  1. Pair<Integer, String> p1 = new Pair<>( 1, "apple");
  2. Pair<Integer, String> p2 = new Pair<>( 2, "pear");
  3. boolean same = Util.<Integer, String>compare(p1,p2);
  4. // boolean same = Util.compare(p1, p2); // this worksin JDK1.8

受限的类型参数-上界

因为泛型定义的时候是不指定具体类型的,所以使用的时候,在默认情况下可以接受任意类型。但有时候我们需要给能接受的类型设置一个范围,比如我这个方法只接受Number或它的子类。如果我们能够这样定义,那么Number就是能接受的类型的上界。

  1. public <U extends Number> void inspect(U u){
  2. System.out.println( "U:" + u.getClass().getName());
  3. }

定义上界的另一个好处:既然因为已知上界,就可以使用上界的属性和方法。

前面只举了上界为单个类的例子,其实上界还可以更严格,比如:

<T extends B1 & B2 &B3>

这个例子中,T必须是B1,B2和B3的子类,一般来说要么B1、B2和B3都是接口,要么B1是类,B2和B3是接口。如果B1是类就必须放最前面。

注:<T>没有下界,只有通配类型<?>才有下界

子类型

知道了类型上界,当你看到List<Integer>和List<Number>,你会不会认为List<Integer>是List<Number>的子类型?告诉你,这是一种大错特错的想法。

子类型除了继承或实现关系,还需满足<>中的类型参数相同,或有扩展:

所以,List<Integer>和List<Number>是两个平行的类,在方法参数是List<Number>的地方,不能用List<Integer>对象赋值。

通配类型

根据子类型的解释,你应该已经明白方法参数是List<Number>的地方不能接受List<Integer>对象实例。但如果你确实需要使一个方法即可接受List<Number>对象做参数又可以让List<integer>做参数,你就需要使用通配类型。

通配类型的参数名是“?”,需要注意:通配符只能用在方法参数、变量或字段的类型上,不能用于定义类。另外,只有通配符才有无界和下界,其他类型只有上界

因为通配类型不能定义类,所以在工具方法中用的最多,对象的方法一般不用通配类型。

通配上界

通配类型的上界格式:

List<? extends Number>

方法的参数是上面格式,就可以接受List<Integer>、List<Double>等对象作为参数。

无界通配类型

无界通配,也就是说任何类型都可以。无界通配类型的格式:

List<?>

使用这种格式一般有两种情况:

  • 你写的方法使用Object的属性和方法就可以。
  • 你写的方法的功能与参数的类型没有关系。

通配下界

下界是指方法参数可以是该下界,也可以是该下界的父类对象。通配类型的下界格式:

List<? super Integer>

方法的参数是上面格式,可以接受List<Integer>、List<Number>、List<Object>对象。

泛型擦除器

泛型作为一种编译信息只在编译时存在,编译完成之后即被编译器擦掉。这样做的理由是保证泛型代码和没有泛型参数的旧代码兼容。缺点是运行时没法获取参数的泛型信息,自动产生的类型转换与旧代码可能不兼容。

聚集是不保证聚集中的元素都是相同类型的,比如一个字符串集合中可以插入整数。因为泛型只有编译时存在,所以运行时插入不同类型的元素不会出问题。但运行时再获取插入的元素时则会导致类型转换异常:

  1. Set strSet = new HashSet();
  2. strSet.add(newInteger( 2));
  3. strSet.add( "hello");

为了解决运行时的类型匹配问题,聚集类又提供了一些包装类来确保运行时的安全:

Set<String> s =Collections.checkedSet(new HashSet<String>(), String.class);

这些包装类后面的逻辑无非是在插入数据时实时检查插入的元素类型是否与聚集要求的元素类型相同。可想而知,检查是要付出代价的,于是拖慢了运行速度。因此,一般来说这些安全的包装类一般只用于调试,到了真正的生产环境最好去掉。

GenericDeclaration声明

获取类或方法上的泛型参数,只需调用类或方法的getTypeParameters()方法即可。

目前只有Class、Method和Counstructor实现了GenericDeclaration接口,所以只有在Class、Method和Countructor上可以调用getTypeParameters()方法。

类型变量- TypeVariable接口

类型变量是泛型以后出来才有的概念,它并不表示类型可以作为变量,而是指某个类或方法将来会操作这种类型的变量。其中方法getTypeParameters()返回的就是类或方法上的所有泛型变量。因此,类型变量和类型声明一般是在一起使用的。

GenericDeclaration接口 & Type接口 & Class的区别

  • GenericDeclaration和Type是两个平行的接口。
  • Class是GenericDeclaration接口和Type接口的实现类。因此,一个对象如果是Type(或GenericDeclaration),则不一定是Class;如果是Class则一定也是Type(或GenericDeclaration)。
  • 虽然Class实现了GenericDeclaration,但Class可以不声明泛型。

Type

Type是跟随泛型而生的,有了泛型,参数的类型变的不再确定,可能是Class也可能是接口。所以,Type成了Class的父接口,用于表示不确定的类。Type是Java中所有类型(Name<T>)的超级接口,包括原始类型、参数类型、数组类型、类型变量和基本类型。

参数化类 - ParameterizedType

ParameterizedType是Type的子接口,用于表示类似Collection<String>的类,最常用于反射中。这个类的实例必须实现equals()方法,用于比较两个拥有相同泛型声明和相同类型参数的实例。

和Type的区别:

Type是指Class<Type>中的Type,而ParameterizedType指的是Class<Colleciton<Type>>中的Collection<Type>。

泛型数组类-GenericArrayType

这种表示这个类是一个数组,而数组的元素要么是ParameterizedType,要么是TypeVariable。

通配类 – WildcardType

通配类型表示一个通配类型表达式:比如?,? extends Number等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值