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

泛型类型的继承规则

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

Apple[] apples = ...;
Pair<Fruit> answer = ArrayAlg.minmax(apples);   //ERROR

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

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

Apple[] apples = ...;
Fruit[] fruit = apples;

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

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

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

通配符类型

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

Pair<? extends Fruit>

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

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

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>传递给这个方法,这一点很受限制。解决的方案很简单,使用通配符类型:

public static void printBuddies(Pair< ? extends Fruit> p) 

Pair<Apple>Pair<? extends Fruit>的子类型。

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

Pair<Apple> applePair = new Pair<>(apple1, apple2);
Pair<? extends Fruit> sonFruitPair = applePair;
sonFruitPair.setFirst(banana);

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

? extends Fruit getFirst();
void setFirst(? extends Fruit);

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

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

? super Apple

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

? extends Fruit getFirst();
void setFirst(? extends Fruit);

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

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

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

? getFirst();
void setFirst(?);

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

可以调用setFirst(null)

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

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

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

public static <T> boolean hasNulls(Pair<T> p)

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

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

public static void swap(Pair<?> p)

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

? t = p.getFirst();

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

public static <T> void swapHelper(Pair<T> p){
    T t = p.getFirst();
    p.setFirst(p.getSecond());
    p.setSecond(t);
}

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

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接口,描述类型变量(如 T extends Comparable<? super T>
WildcardType接口,描述通配符
ParameterizedType接口,描述泛型类或接口类型
GenericArrayType接口,描述泛型接口

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

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
打印结果:

原创文章,文笔有限,才疏学浅,文中若有不正之处,万望告知


公众号

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容