一般的类和方法,只能使用具体的类型,要么是基本类型,要么是自定义的类,如果要编写可以应用于
多种类型的代码,这种刻板的限制对代码的束缚就会很大。
泛型实现了参数化类型的概念,使代码可以应用于多种类型。
容器类是引入泛型的一个重要原因
Java中,return语句只允许返回单个对象,通过泛型,则可以返回多个对象。这个概念成为元祖(tuple)
,它将一组对象
直接打包存储于其中一个单一对象。
元祖可以具有任意长度,任意类型。
泛型方法
使用泛型方法时,会进行类型参数推断,如果传入基本类型,则会自动打包
1 | public class GenericMethods { |
普通static方法无法访问泛型类的类型参数,如果要是使用泛型就要定义成泛型静态方法。
类的泛型要在创建对象时才确定,而类内的静态方法,静态域在类加载时初始化,因此如果使用类的泛型类型则初始化时无法知道具体类型是什么,
此时使用泛型方法这样就和类的泛型无关了,这样静态方法初始化时类型只和自身的泛型相关。
泛型方法返回值是泛型,那么就返回一个泛型,不能是具体类型,反之亦然。
1 | public static <T> Set<T> set(){ |
泛型的一个重要好处是能够简单而安全的创建复杂的模型
可变参数泛型方法
擦除
- jvm并不认识泛型因此需要将泛型擦除。
- ArrayList
和 ArrayList 很容易被认为是不同类型。因为他们有不同的行为,但程序却认为他们是相同的,正是因为擦除的存在。 - 擦除的结果就是把一个对象变为它的原生类
- 泛型只是用来检查类型正确性,泛型部分的代码不会参与运行,这是由于泛型的擦除作用。
- 泛型代码内部无法获得有关泛型参数的类型的信息。
泛型擦除到第一个边界
上界 意思就是T 只能为HasF或者其子类。 - 泛型只是在静态类型检查期间出现来验证类型正确性,程序一但运行后泛型都将被擦除,替换成他们的非泛型上界,
如List被擦除为List,List 被擦除为List
擦除动机
擦除使得泛化的代码变得具体,因此泛化客户端可以使用非泛化类库,非泛化客户端也可以使用泛化类库。
擦除的代价
泛型不能当做一个类去操作,如Foo<Cat> cat不能代表Cat这个类,因为它会被擦除为Object.
边界处的动作
- 边界就是对象进入和离开方法的地方,编译期执行类型检查和插入转型代码就是在边界处。
- 编译期执行了类型检查确保了数据一致性,在离开方法时由编译器为你插入转型代码执行转型,此时转型是安全的。
- 由于擦除kind实际被存储为Class,因此创建数组无法后知道要转型成什么类型,因此必须强转。但创建容器就不需要强转了,编译期可以保证类型的一致性,如果类型不一致不通过编译。
边界
强制泛型可以使用什么类型
按边界类型调用方法其方法,无边界的只能调用从Objec继承的方法。
通配符
通配符可以允许某种类型向上转型,与普通边界对比:
List
List<? extends Fruit> first = new ArrayList
List<? extends Fruit> 读作具有任何从Fruit继承的类型列表。
,则可以接收任何形式的List参数,参数是不是泛型无所谓。
- 参数的类型如果是List<? extends/super A > ,则只能接收泛型的List参数.
- 如果参数的类型是 <?> 或者 <? extends A>,则该方法无法调用
- >可以向上转型
- 多个泛型参数下只有全为?时编译器无法与原生类区分,但只要有过一个参数不是?就会有所区分如Map<String, ?>必须传入map<String,?>类型的参数,而Map,?>可以传入new HashMap();