前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java基础系列(三十七):泛型继承,通配符,泛型反射

Java基础系列(三十七):泛型继承,通配符,泛型反射

作者头像
山禾说
发布2019-01-21 10:12:17
6160
发布2019-01-21 10:12:17
举报
文章被收录于专栏:Vi的技术博客

泛型类型的继承规则

首先,我们来看一个类和它的子类,比如 FruitApple。但是 Pair<Apple>Pair<Fruit>的一个子类么?并不是。比如下面的这段代码就会编译失败:

代码语言:javascript
复制
Apple[] apples = ...;Pair<Fruit> answer = ArrayAlg.minmax(apples);   //ERROR

我们需要记住:无论S和T有什么联系, Pair<S>Pair<T>没有什么联系。

这里需要注意泛型和Java数组之间的区别,可以将一个 Apple[]数组赋给一个类型为 Fruit[]的变量:

代码语言:javascript
复制
Apple[] apples = ...;Fruit[] fruit = apples;

然而,数组带有特别的保护,如果试图将一个超类存储到一个子类数组中,虚拟机会抛出 ArrayStoreException异常。

永远可以将参数化类型转换为一个原始类型,比如, Pair<Fruit>是原始类型 Pair的一个子类型。在与遗留代码对接的时候,这个转换非常必要。

泛型类可以扩展或实现其他的泛型类,比如, ArrayList<T>类实现了 List<T>接口,这意味着,一个 ArrayList<Apple>可以转换为一个 List<Apple>。但是,如前面所见,一个 ArrayList<Apple>不是一个 ArrayList<Fruit>List<Fruit>

通配符类型

通配符类型中,允许类型参数变化。比如,通配符类型:

代码语言:javascript
复制
Pair<? extends Fruit>

表示任何泛型类型,它的类型参数是Fruit的子类,如 Pair<Apple>,单不会是 Pair<String>

假如现在我们需要编写一个方法去打印一些东西:

代码语言:javascript
复制
public static void printBuddies(Pair<Fruit> p) {      Employee first = p.getFirst();      Employee second = p.getSecond();      System.out.println(first.getName() + " and " + second.getName() + " are buddies.");}

正如前面所讲到的,不能将 Pair<Apple>传递给这个方法,这一点很受限制。解决的方案很简单,使用通配符类型:

代码语言:javascript
复制
public static void printBuddies(Pair< ? extends Fruit> p) 

Pair<Apple>Pair<?extendsFruit>的子类型。

我们接下来来考虑另外一个问题,使用通配符会通过 Pair<?extendsFruit>的引用破坏 Pair<Apple>吗?

代码语言:javascript
复制
Pair<Apple> applePair = new Pair<>(apple1, apple2);Pair<? extends Fruit> sonFruitPair = applePair;sonFruitPair.setFirst(banana);

这样可能会引起破坏,但是当我们调用 setFirst的时候,如果调用的不是 Fruit的子类 Apple类的对象,而是其他 Fruit子类的对象,就会出错。 我们来看一下 Pair<?extendsFruit>的方法:

代码语言:javascript
复制
? extends Fruit getFirst();void setFirst(? extends Fruit);

这样就会看的很明显,因为如果我们去调用 setFirst()方法,编译器之可以知道是某个 Fruit的子类型,而不能确定具体是什么类型,它拒绝传递任何特定的类型,因为 ? 不能用来匹配。 但是使用 getFirst就不存在这个问题,因为我们无需care它获取到的类型是什么,但一定是 Fruit的子类。

通配符限定与类型变量限定非常相似,但是通配符类型还有一个附加的能力,即可以指定一个超类型限定:

代码语言:javascript
复制
? super Apple

这个通配符限制为 Apple的所有父类,为什么要这么做呢?带有超类型限定的通配符的行为与子类型限定的通配符行为完全相反,可以为方法提供参数,但是却不能获取具体的值,即访问器是不安全的,而更改器方法是安全的:

代码语言:javascript
复制
? extends Fruit getFirst();void setFirst(? extends Fruit);

编译器无法知道 setFirst方法的具体类型,因此调用这个方法时不能接收类型为 FruitObject的参数。只能传递 Apple类型的对象,或者某个子类型( Banana)对象。而且,如果调用 getFirt,不能保证返回对象的类型。

总结一下,带有超类型限定的通配符可以想泛型对象写入,带有子类型限定的通配符可以从泛型对象读取。

还可以使用无限定的通配符,例如, Pair<?>。初看起来,这好像与原始的Pair类型一样,实际上,有很大的不同。类型 Pair<?>有以下方法:

代码语言:javascript
复制
? getFirst();void setFirst(?);

getFirst的返回值只能返回一个 ObjectsetFirst方法甚至不能被调用,甚至不能用 Object调用。 Pair<?>Pair的本质的不同在于:可以用任意 Object对象调用原始 Pair类的 setObject方法。

可以调用 setFirst(null)

为什么要使用这样脆弱的类型?

代码语言:javascript
复制
//判断pair是否包含一个null引用 public static boolean hasNulls(Pair<?> p) {      return p.getFirst() == null || p.getSecond() == null;   }

通过将 hasNulls转换为泛型方法,可以避免使用通配符类型:

代码语言:javascript
复制
public static <T> boolean hasNulls(Pair<T> p)

但是,带有通配符的版本可读性更强。

那么通配符该怎么去捕获呢?

代码语言:javascript
复制
public static void swap(Pair<?> p)

通配符不是类型变量,所以,我们在编写代码的时候不能使用 "?"作为一种类型,也就是说,下面的代码是错误的:

代码语言:javascript
复制
? t = p.getFirst();

这里有一个问题,因为在交换的时候必须临时保存第一个元素,我们这里可以写一个辅助方法 swapHelper:

代码语言:javascript
复制
public static <T> void swapHelper(Pair<T> p){    T t = p.getFirst();    p.setFirst(p.getSecond());    p.setSecond(t);}

注意, swapHelper是一个泛型方法,而 swap不是,它具有固定的 Pair<?>类型的参数。现在我们可以由 swap调用 swapHelper

代码语言:javascript
复制
public static void swap(Pair<?> p) {    swapHelper(p);}

在这种情况下, swapHelper方法的参数 T捕获通配符,它不知道是哪种类型的通配符,但是,这是一个明确的类型,并且 <T>swapHelper的定义只有在 T指出类型时才有明确的含义。

通配符捕获只有在有许多限制的情况下才是合法的。编译器必须能够确信通配符表达的是单个,确定的类型。例如, ArrayList<Pair<T>>中的 T永远不能捕获 ArrayList<Pair<?>>中的通配符。数组列表可以保存两个 Pair<?>,分别针对 的不同类型。

反射与泛型

反射允许我们在运行时分析任意的对象,但是如果对象是泛型类的实例,关于泛型类型参数则得不到太多信息,因为它们会被擦除。

为了表达泛型类型声明,使用 java.lang.reflect包中提供的接口 Type,这个接口包含下列子类型:

Class类,描述具体类型 TypeVariable接口,描述类型变量(如 TextendsComparable<?superT>) WildcardType接口,描述通配符 ParameterizedType接口,描述泛型类或接口类型 GenericArrayType接口,描述泛型接口

下面是一个使用泛型反射API打印出给定类的有关内容的程序:

代码语言:javascript
复制
public class GenericReflectionTest{   public static void main(String[] args)   {      String name;      if (args.length > 0) name = args[0];      else      {         try (Scanner in = new Scanner(System.in))         {            System.out.println("Enter class name (e.g. java.util.Collections): ");            name = in.next();         }      }      try      {         // print generic info for class and public methods         Class<?> cl = Class.forName(name);         printClass(cl);         for (Method m : cl.getDeclaredMethods())            printMethod(m);      }      catch (ClassNotFoundException e)      {         e.printStackTrace();      }   }   public static void printClass(Class<?> cl)   {      System.out.print(cl);      printTypes(cl.getTypeParameters(), "<", ", ", ">", true);      Type sc = cl.getGenericSuperclass();      if (sc != null)      {         System.out.print(" extends ");         printType(sc, false);      }      printTypes(cl.getGenericInterfaces(), " implements ", ", ", "", false);      System.out.println();   }   public static void printMethod(Method m)   {      String name = m.getName();      System.out.print(Modifier.toString(m.getModifiers()));      System.out.print(" ");      printTypes(m.getTypeParameters(), "<", ", ", "> ", true);      printType(m.getGenericReturnType(), false);      System.out.print(" ");      System.out.print(name);      System.out.print("(");      printTypes(m.getGenericParameterTypes(), "", ", ", "", false);      System.out.println(")");   }   public static void printTypes(Type[] types, String pre, String sep, String suf,          boolean isDefinition)   {      if (pre.equals(" extends ") && Arrays.equals(types, new Type[] { Object.class })) return;      if (types.length > 0) System.out.print(pre);      for (int i = 0; i < types.length; i++)      {         if (i > 0) System.out.print(sep);         printType(types[i], isDefinition);      }      if (types.length > 0) System.out.print(suf);   }   public static void printType(Type type, boolean isDefinition)   {      if (type instanceof Class)      {         Class<?> t = (Class<?>) type;         System.out.print(t.getName());      }      else if (type instanceof TypeVariable)      {         TypeVariable<?> t = (TypeVariable<?>) type;         System.out.print(t.getName());         if (isDefinition)            printTypes(t.getBounds(), " extends ", " & ", "", false);      }      else if (type instanceof WildcardType)      {         WildcardType t = (WildcardType) type;         System.out.print("?");         printTypes(t.getUpperBounds(), " extends ", " & ", "", false);         printTypes(t.getLowerBounds(), " super ", " & ", "", false);      }      else if (type instanceof ParameterizedType)      {         ParameterizedType t = (ParameterizedType) type;         Type owner = t.getOwnerType();         if (owner != null)         {            printType(owner, false);            System.out.print(".");         }         printType(t.getRawType(), false);         printTypes(t.getActualTypeArguments(), "<", ", ", ">", false);      }      else if (type instanceof GenericArrayType)      {         GenericArrayType t = (GenericArrayType) type;         System.out.print("");         printType(t.getGenericComponentType(), isDefinition);         System.out.print("[]");      }   }}

比如,我们输入 java.util.Collections 打印结果:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-10-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Vi的技术博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档