泛型规范
包括泛型类型的一些限定以及泛型的一些类型限定、使用规范和继承规范
泛型类型限定
有时,类或方法需要对类型变量加以约束,否则传入类型可能不一定满足泛型使用条件:
- 对类的限定:
public class TypeLimitForClass<T extends List & Serializable>{}
- 对方法的限定:
public static<T extends Comparable<T>>T getMin(T a, T b) {}
其中对方法的限定表示,泛型参数类型必须实现了Comparable接口,否则就没有办法进行比较了
public class ArrayAlg { public static <T extends Comparable> T min(T[] array){ if (array == null || array.length == 0){ return null; } T smallest = array[0]; for (int i=0;i<array.length;i++){ if (smallest.compareTo(array[i])>0){ smallest = array[i]; } } return smallest; } }
上述代码中的限制了用于实例化类型参数T的类型,必须是实现Comparable接口(只含有compareTo方法的标准接口)的类。如果没有对T进行限制,那么无法确保实例化T的类型具有compareTo方法。
对于类的限定中:一个类型变量可以有多个限定,例如:
<T extends Comparable & Serializable , U extends Comparable>//限定类型使用 “&”分隔,而“,”用于分隔类型参数。
在Java中,一个类可以实现多个接口,但只能有一个父类,所以在类型参数的限定中,可以有多个接口,但只能有一个类。
<T extends 接口1 & 接口2 & ... & 接口n & 类型1>
泛型使用规范
泛型有如下的使用规范,使用的时候需要注意:
- 不能实例化泛型类
- 静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的,动态变量和方法可以直接引用泛型类型变量
- 基本类型无法作为泛型类型,例如int必须转为integer
- 无法使用instanceof关键字或==判断泛型类的类型
- 泛型类的原生类型(反射获取的类对象)与所传递的泛型无关,无论传递什么类型,原生类是一样的
- 泛型数组可以声明但无法实例化,只能具体的new一个数组
- 泛型类不能继承Exception或者Throwable
- 不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出
以上就是泛型的使用规范。其实 泛型仅仅是java的语法糖,它不会影响java虚拟机生成的汇编代码,在编译阶段,虚拟机就会把泛型的类型擦除,还原成没有泛型的代码,顶多编译速度稍微慢一些,执行速度是完全没有什么区别的.
泛型的继承规则
泛型有如下三个继承规则:
- 对于泛型参数是继承关系的泛型类之间是没有继承关系的
- 泛型类可以继承其它泛型类,例如:
public class ArrayList<E> extends AbstractList<E>
- 泛型类的继承关系在使用中同样会受到泛型类型的影响
通过代码实现如下:
//继承泛型类 private static class SubGenericInherit<T> extends GenericInherit<T> { } //父类泛型类 public class GenericInherit<T> { private T data1; private T data2; public T getData1() { return data1; } public void setData1(T data1) { this.data1 = data1; } public T getData2() { return data2; } public void setData2(T data2) { this.data2 = data2; } public static <V> void setData2(GenericInherit<Father> data2) { } public static void main(String[] args) { //Son 继承自 Father,泛型参数是继承关系 Father father = new Father(); Son son = new Son(); //泛型父类传递不同类型参数,一个是父类,一个是子类 GenericInherit<Father> fatherGenericInherit = new GenericInherit<>(); GenericInherit<Son> sonGenericInherit = new GenericInherit<>(); //泛型子类传递不同类型参数,一个是父类,一个是子类 SubGenericInherit<Father> fatherSubGenericInherit = new SubGenericInherit<>(); SubGenericInherit<Son> sonSubGenericInherit = new SubGenericInherit<>(); /** * 对于传递的泛型类型是继承关系的泛型类之间是没有继承关系的 * GenericInherit<Father> 与GenericInherit<Son> 没有继承关系 * Incompatible types. */ father = new Son(); //fatherGenericInherit=new GenericInherit<Son>(); //编译报错 /** * 泛型类可以继承其它泛型类,例如: public class ArrayList<E> extends AbstractList<E> */ fatherGenericInherit=new SubGenericInherit<Father>(); /** *泛型类的继承关系在使用中同样会受到泛型类型的影响 */ setData2(fatherGenericInherit); // setData2(sonGenericInherit); //泛型父类的setData2方法要求传递一个泛型父类对象 setData2(fatherSubGenericInherit); // setData2(sonSubGenericInherit); }
通配符泛型
泛型中可以使用通配符来解决集合的不变性,先来解释下集合的不变性和数组的协变性: 假设有一个函数 fun(Animal animal)
,如果我们传入一个Dog d
对象进去,编译器是不会报错的,这是多态的概念; 假设有一个函数 fun(Animal[] animals)
,如果我们传如一个Dog[] dogs
数组进去,编译器也不会报错,这就是数组的协变性;假设有一个函数 fun(List<Animal> animal)
,如果我们传如一个List <Dog> dog
集合进去,编译器就会报错了,这就是集合泛型的不变性;那么该怎么办呢?我们可以将泛型改成这样
fun (List <? extends Animal> animal)
,这样之后,当我们再传入一个List dog 集合进去,编译器就就不会报错了。也就是说可以传入包含Animal的子类的List了。
这里就用到了通配符的概念,通配符有三种指定方式:
<? extends Parent>
指定了泛型类型的上届<? super Child>
指定了泛型类型的下届<?>
指定了没有限制的泛型类型
其实通配符解决的是同一泛型类中的泛型参数的继承关系,接下来以此下图的继承关系为例来介绍下:
代码逻辑如下:
public class GenericByWildcard { private static void print(GenericClass<Fruit> fruitGenericClass) { System.out.println(fruitGenericClass.getData().getColor()); } /** * 问题的提出,因为泛型参数的继承关系在使用中并不表示泛型类的继承关系,而且还得遵守泛型规则,所以Orange传进去不认。 */ private static void use() { GenericClass<Fruit> fruitGenericClass = new GenericClass<>(); print(fruitGenericClass); GenericClass<Orange> orangeGenericClass = new GenericClass<>(); //类型不匹配,可以使用<? extends Parent> 来解决 //print(orangeGenericClass); } /** * <? extends Parent> 指定了泛型类型的上届,子类可以正常使用 */ private static void printExtends(GenericClass<? extends Fruit> genericClass) { System.out.println(genericClass.getData().getColor()); } public static void useExtend() { GenericClass<Fruit> fruitGenericClass = new GenericClass<>(); printExtends(fruitGenericClass); GenericClass<Orange> orangeGenericClass = new GenericClass<>(); printExtends(orangeGenericClass); GenericClass<Food> foodGenericClass = new GenericClass<>(); //Food是Fruit的父类,超过了泛型上届范围,类型不匹配 //printExtends(foodGenericClass); //表示GenericClass的类型参数的上届是Fruit GenericClass<? extends Fruit> extendFruitGenericClass = new GenericClass<>(); Apple apple = new Apple(); Fruit fruit = new Fruit(); /* * 道理很简单,? extends X 表示类型的上界,类型参数是X的子类,那么可以肯定的说, * get方法返回的一定是个X(不管是X或者X的子类)编译器是可以确定知道的。 * 但是set方法只知道传入的是个X,至于具体是X的那个子类,不知道。 * 总结:主要用于安全地访问数据,可以访问X及其子类型,并且不能写入非null的数据。 */ // extendFruitGenericClass.setData(apple); // extendFruitGenericClass.setData(fruit); fruit = extendFruitGenericClass.getData(); } /** * <? super Child> 指定了泛型类型的下届 */ public static void printSuper(GenericClass<? super Apple> genericClass) { System.out.println(genericClass.getData()); } public static void useSuper() { GenericClass<Food> foodGenericClass = new GenericClass<>(); printSuper(foodGenericClass); GenericClass<Fruit> fruitGenericClass = new GenericClass<>(); printSuper(fruitGenericClass); GenericClass<Apple> appleGenericClass = new GenericClass<>(); printSuper(appleGenericClass); GenericClass<HongFuShiApple> hongFuShiAppleGenericClass = new GenericClass<>(); // HongFuShiApple 是Apple的子类,达不到泛型下届,类型不匹配 //printSuper(hongFuShiAppleGenericClass); GenericClass<Orange> orangeGenericClass = new GenericClass<>(); // Orange和Apple是兄弟关系,没有继承关系,类型不匹配 // printSuper(orangeGenericClass); //表示GenericClass的类型参数的下界是Apple GenericClass<? super Apple> supperAppleGenericClass = new GenericClass<>(); supperAppleGenericClass.setData(new Apple()); supperAppleGenericClass.setData(new HongFuShiApple()); /* * ? super X 表示类型的下界,类型参数是X的超类(包括X本身), * 那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?不知道, * 但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。 * 编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是X和X的子类可以安全的转型为X。 * 总结:主要用于安全地写入数据,可以写入X及其子类型。 */ //supperAppleGenericClass.setData(new Fruit()); //get方法只会返回一个Object类型的值。 Object data = supperAppleGenericClass.getData(); } /** * <?> 指定了没有限定的通配符 */ public static void printNonLimit(GenericClass<?> genericClass) { System.out.println(genericClass.getData()); } public static void useNonLimit() { GenericClass<Food> foodGenericClass = new GenericClass<>(); printNonLimit(foodGenericClass); GenericClass<Fruit> fruitGenericClass = new GenericClass<>(); printNonLimit(fruitGenericClass); GenericClass<Apple> appleGenericClass = new GenericClass<>(); printNonLimit(appleGenericClass); GenericClass<?> genericClass = new GenericClass<>(); //setData 方法不能被调用, 甚至不能用 Object 调用; //genericClass.setData(foodGenericClass); // genericClass.setData(new Object()); //返回值只能赋给 Object Object object = genericClass.getData(); } }
下列哪些继承关系是正确的?
class A {} class B extends A {} class C extends A {} class D extends B {} Which four statements are true ? A,The type List<A>is assignable to List. //true B,The type List<B>is assignable to List<A>. C,The type List<Object>is assignable to List<?>. //true D,The type List<D>is assignable to List<?extends B>.//true E,The type List<?extends A>is assignable to List<A>. F,The type List<Object>is assignable to any List reference. G,The type List<?extends B>is assignable to List<?extends A>.//true
判断的时候有如下准则:
- 只看尖括号里边的,明确点和范围两个概念 ,List既是点也是范围,当表示范围时,表示最大范围,List<?>和List 是相等的,都代表最大范围和最大点
- 如果尖括号里的是一个类,那么尖括号里的就是一个点,比如List,List,List
- 如果尖括号里面带有问号,那么代表一个范围,<? extends A>代表小于等于A的范围,<? super A>代表大于等于A的范围,<?>代表全部范围
- 尖括号里的所有点之间互相继承都是错,除非是俩相同的点,因为泛型参数继承关系不代表泛型类有基础关系
- 尖括号小范围赋值给大范围,对,大范围赋值给小范围,错。如果某点包含在某个范围里,那么可以赋值,否则,不能赋值
以上就是在有通配符的情况下的泛型继承关系。
泛型原理
虚拟机是如何实现泛型的,其实泛型在Java里更像是一种语法糖。Java泛型是Java1.5之后才引入的,为了向下兼容。Java采用了C++完全不同的实现思想。Java中的泛型更多的看起来像是编译期用的,Java中泛型在运行期是不可见的,会被擦除为它的上级类型。如果是没有限定的泛型参数类型,就会被替换为Object.
GenericClass<String> stringGenericClass=new GenericClass<>(); GenericClass<Integer> integerGenericClass=new GenericClass<>();
CJava进行了类型擦除之后统一改为GenericClass<Object>