Java泛型extends关键字设置边界的实现

本文主要介绍在泛型定义中的< >中的占位符如何配合extends关键字使用,形如<T extends Integer>。泛型定义存在于这三种形式中:泛型类、泛型接口、泛型方法。

  • 一般的泛型定义中的<T>,相当于<T extends Object>,而类型擦除则会将类型参数擦除成T的上界,即Object。则在泛型定义中作为T类型的对象可以调用Object的函数和属性。
  • 使用了extends的泛型定义中的<T extends Integer>,其上界已被明显定义了,此时会将类型参数擦除成Integer。则在泛型定义中作为T类型的对象可以调用Integer的函数和属性。

接下来本文将以几个示例和具体分析来讲解剩下的知识点。

类型参数多边界的分析

此例中的泛型类,类型参数带有多个边界。讲下类的实际意义:Dimension代表物体的方位、HasColor代表物体的颜色、Weight代表物体的重量。

interface HasColor { java.awt.Color getColor(); }

class Colored<T extends HasColor> {
  T item;
  Colored(T item) { this.item = item; }
  T getItem() { return item; }
  // The bound allows you to call a method:
  java.awt.Color color() { return item.getColor(); }
}

class Dimension { public int x, y, z; }

// This won't work -- class must be first, then interfaces:
// class ColoredDimension<T extends HasColor & Dimension> { }

// Multiple bounds:
class ColoredDimension<T extends Dimension & HasColor> {
  T item;
  ColoredDimension(T item) { this.item = item; }
  T getItem() { return item; }
  java.awt.Color color() { return item.getColor(); }
  int getX() { return item.x; }
  int getY() { return item.y; }
  int getZ() { return item.z; }
}

interface Weight { int weight(); }

// As with inheritance, you can have only one
// concrete class but multiple interfaces:
class Solid<T extends Dimension & HasColor & Weight> {
  T item;
  Solid(T item) { this.item = item; }
  T getItem() { return item; }
  java.awt.Color color() { return item.getColor(); }
  int getX() { return item.x; }
  int getY() { return item.y; }
  int getZ() { return item.z; }
  int weight() { return item.weight(); }
}

class Bounded extends Dimension implements HasColor, Weight {
  public java.awt.Color getColor() { return null; }
  public int weight() { return 0; }
}

public class BasicBounds {
  public static void main(String[] args) {
    Solid<Bounded> solid = new Solid<Bounded>(new Bounded());
    solid.color();
    solid.getY();
    solid.weight();
  }
} ///:~
  • class Colored<T extends HasColor>这个泛型类的泛型定义中,要求了类型参数T的边界为HasColor,正因如此,在函数java.awt.Color color() { return item.getColor(); }中便可以通过一个T类型的变量item来调用属于HasColor的方法。
  • class ColoredDimension<T extends HasColor & Dimension> { },此时定义了边界同时为HasColor & Dimension,但是由于编译器要求占位符后的extends后第一个必须是类,之后的必须是接口(这就和正常的类的继承规则一样),所以此句通不过编译。而class ColoredDimension<T extends Dimension & HasColor>给出了正确的定义,即第一个必须是类,之后的必须是接口。
  • class ColoredDimension<T extends Dimension & HasColor>的类定义中,因为T的边界是HasColor & Dimension,所以在类定义中,既可以获取Dimension的属性,也可以调用HasColor的方法。
  • class Solid<T extends Dimension & HasColor & Weight>的类定义中,extends后第一个是类,之后的都是接口,符合刚才讲的规则。同理,也可以从这些边界中,获取属性,调用方法。
  • class Bounded extends Dimension implements HasColor, Weight这个类将在生成泛型类对象,用来指定具体类型为Bounded。因为class Solid<T extends Dimension & HasColor & Weight>的类型参数T的要求是extends Dimension & HasColor & Weight,所以指定具体类型为new Solid<Bounded>,是可以的。因为类定义中构造器的声明为Solid(T item),且具体类型为new Solid<Bounded>中指定的Bounded,所以要求构造器的实参为Bounded或者Bounded的子类。
class derivedBounded extends Bounded {}

class Bounded1 extends Dimension implements HasColor, Weight {
  public java.awt.Color getColor() { return null; }
  public int weight() { return 0; }
}

public class BasicBounds {
  public static void main(String[] args) {
    //Solid<Bounded> solid = new Solid<Integer>(new derivedBounded());//给定的具体类型不符合边界
    Solid<Bounded> solid1 = new Solid<Bounded>(new derivedBounded());//可以传递具体类型Bounded的子类
    //Solid<Bounded> solid2 = new Solid<Bounded>(new Bounded1());//编译报错,因为泛型的静态类型检查
    solid1.color();
    solid1.getY();
    solid1.weight();
  }
} ///:~

根据上一条,那么new Solid<Integer>(new Bounded())这里指定的具体类型,由于和泛型类定义的T类型参数的要求extends Dimension & HasColor & Weight不相符,所以编译会报错;给构造器传值时,实参可以是Bounded的子类;一个同样继承了相同边界的类Bounded1 ,不能传递给构造器,因为类型已经被指定为Bounded了。

但是类型参数有多个边界时,java内部即java字节码到底是怎么处理的呢:

 public static void main(java.lang.String[]);
  Code:
    0: new      #2         // class Solid
    3: dup
    4: new      #3         // class Bounded
    7: dup
    8: invokespecial #4         // Method Bounded."<init>":()V
   11: invokespecial #5         // Method Solid."<init>":(LDimension;)V
   14: astore_1

从Method Solid."<init>":(LDimension;)V可以看到,给Solid的构造器传递参数时,编译器认为这个形参是个Dimension,这就是编译器处理多个边界的方法,永远处理为第一个边界,即类型擦除为第一个边界。但剩下的两个边界怎么办呢,这里都被处理第一个边界了,我们再去看一下Solid.class的反编译代码就能找到答案:

// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)

import java.awt.Color;

class Solid<T extends Dimension & HasColor & Weight> {
  T item;

  Solid(T item) {
    this.item = item;
  }

  T getItem() {
    return this.item;
  }

  Color color() {
    return ((HasColor)this.item).getColor();//类型转换为其他边界,再调用方法
  }

  int getX() {
    return this.item.x;
  }

  int getY() {
    return this.item.y;
  }

  int getZ() {
    return this.item.z;
  }

  int weight() {
    return ((Weight)this.item).weight();//类型转换为其他边界,再调用方法
  }
}

当调用的方法不属于第一个边界时,就进行类型转换处理为其他边界就行,反正T肯定是符合extends Dimension & HasColor & Weight的。

继承有边界要求的泛型类

通过观察上例可知,Colored、ColoredDimension、Solid这三个类在持有对象的方面有冗余的地方:都有同一个成员变量、同一个构造器、同一个get函数。而类型参数上,边界也是依次叠加的。同样,对于这些边界所带来的属性和方法,也是冗余的。
所以下例对其进行了修改,通过继承来消除冗余,注意,下面继承的泛型类对类型参数是有边界要求的:

//HoldItem对边界T没有要求
class HoldItem<T> {
  T item;
  HoldItem(T item) { this.item = item; }
  T getItem() { return item; }
}
//Colored2对边界T有HasColor的要求
class Colored2<T extends HasColor> extends HoldItem<T> {
  Colored2(T item) { super(item); }
  java.awt.Color color() { return item.getColor(); }
}
//ColoredDimension2对边界T有Dimension & HasColor的要求
class ColoredDimension2<T extends Dimension & HasColor>
    extends Colored2<T> {
  ColoredDimension2(T item) { super(item); }
  int getX() { return item.x; }
  int getY() { return item.y; }
  int getZ() { return item.z; }
}
//Solid2对边界T有Dimension & HasColor & Weight的要求,不过没有类继承它了
class Solid2<T extends Dimension & HasColor & Weight>
    extends ColoredDimension2<T> {
  Solid2(T item) { super(item); }
  int weight() { return item.weight(); }
}

public class InheritBounds {
  public static void main(String[] args) {
    Solid2<Bounded> solid2 =
        new Solid2<Bounded>(new Bounded());
    solid2.color();
    solid2.getY();
    solid2.weight();
  }
} ///:~
  • HoldItem这个泛型类通过类型参数T把成员变量、构造器、get函数都定义好了,之后的类通过继承它就可以获得这些属性和方法。
  • Colored2泛型类继承了HoldItem,获得了后者的属性和方法从而减少了冗余。同时,class Colored2<T extends HasColor>属于“定义泛型类”,extends HoldItem<T>属于“使用泛型类”,使用泛型类需要指定具体类型,现在确定具体类型的任务延后到了<T extends HasColor>的确认。再从边界是否符合的情况分析,HoldItem的要求是<T>属于无边界,<T extends HasColor>这样的边界定义属于HasColor边界,从范围上说<T extends HasColor>是小于等于<T>的,这样是可以的。由于T添加了HasColor边界,所以可以调用item.getColor()方法了。
  • ColoredDimension2泛型类继承了Colored2。从边界是否符合的情况分析,Colored2对T的边界要求是<T extends HasColor>,而ColoredDimension2定义中的T的边界是<T extends Dimension & HasColor>,<T extends Dimension & HasColor>小于等于<T extends HasColor>,符合要求。换句话说,ColoredDimension2定义中的T的边界必须比Colored2的边界要求一致,或者范围更小。
  • Solid2泛型类继承了ColoredDimension2。从边界是否符合的情况分析,<T extends Dimension & HasColor & Weight>小于等于ColoredDimension2的边界要求<T extends Dimension & HasColor>,符合要求。

总结一下:

当一个泛型类继承另一个泛型类时(前者属于“定义泛型类”,后者属于“使用泛型类”),且使用了同一个类型参数时,定义泛型类的类型参数边界定义一定要小于等于使用的那个泛型类的边界要求。

泛型方法中的边界定义

泛型方法中对类型参数的边界定义,同样也得符合使用的泛型类的边界要求。此例中,泛型类同样继承别的泛型类,分析同上不赘述。讲下类的实际意义:一系列接口代表了超能力、一系列类代表了超级英雄,它们拥有一个超能力的成员变量。

import java.util.*;

interface SuperPower {}
interface XRayVision extends SuperPower {
  void seeThroughWalls();
}
interface SuperHearing extends SuperPower {
  void hearSubtleNoises();
}
interface SuperSmell extends SuperPower {
  void trackBySmell();
}

class SuperHero<POWER extends SuperPower> {
  POWER power;
  SuperHero(POWER power) { this.power = power; }
  POWER getPower() { return power; }
}

class SuperSleuth<POWER extends XRayVision> extends SuperHero<POWER> {
  SuperSleuth(POWER power) { super(power); }
  void see() { power.seeThroughWalls(); }
}

class CanineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER> {
  CanineHero(POWER power) { super(power); }
  void hear() { power.hearSubtleNoises(); }
  void smell() { power.trackBySmell(); }
}

class SuperHearSmell implements SuperHearing, SuperSmell {
  public void hearSubtleNoises() {}
  public void trackBySmell() {}
}

class DogBoy extends CanineHero<SuperHearSmell> {
  DogBoy() { super(new SuperHearSmell()); }
}

public class EpicBattle {
  // Bounds in generic methods:
  static <POWER extends SuperHearing>
  void useSuperHearing(SuperHero<POWER> hero) {//泛型方法
    hero.getPower().hearSubtleNoises();
  }
  static <POWER extends SuperHearing & SuperSmell>
  void superFind(SuperHero<POWER> hero) {//泛型方法
    hero.getPower().hearSubtleNoises();
    hero.getPower().trackBySmell();
  }
  public static void main(String[] args) {
    DogBoy dogBoy = new DogBoy();
    useSuperHearing(dogBoy);
    superFind(dogBoy);
    // You can do this:
    List<? extends SuperHearing> audioBoys;
    // But you can't do this:
    // List<? extends SuperHearing & SuperSmell> dogBoys;
  }
} ///:~
  • 主函数中的useSuperHearing泛型方法中,其对T的边界定义为<POWER extends SuperHearing>。而在形参中使用了泛型类SuperHero<POWER>,其对边界的要求是<POWER extends SuperPower>。因为SuperHearing继承了SuperPower,所以边界定义符合了对边界的要求。
  • 主函数中的useSuperHearing泛型方法中,其对T的边界定义为<POWER extends SuperHearing>。正因如此,在方法中便可以调用SuperHearing的方法hearSubtleNoises了。

其他

本文例子均来自java编程思想,例子本身不错,但奈何作者对其做的讲解很少,所以本人为其加上了详细的分析。其实这些例子都需要反复琢磨,精读之后才会对泛型的extends关键字有深刻的理解。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java继承extends与super关键字详解

    函数也称为方法! 继承:在java中使用extends关键字来表示继承关系.super用来继承父类方法和参数. 继承就是子类继承父类的特征和行为,使得子类具有父类相同的行为. 注意事项: 1.当一个类没有继承任何一个类时,系统默认继承Object. 2.父类又被称为基类.超类.super类,子类又被称为派生类,这是由于翻译问题而导致的. 3.Java的继承是单一性的. 4.子类不能继承父类的构造方法,但是可以继承构造方法类的参数. 5.子类可以拥有自己的属性和方法,即子类可以对父类进行扩展.但子

  • Java泛型extends关键字设置边界的实现

    本文主要介绍在泛型定义中的< >中的占位符如何配合extends关键字使用,形如<T extends Integer>.泛型定义存在于这三种形式中:泛型类.泛型接口.泛型方法. 一般的泛型定义中的<T>,相当于<T extends Object>,而类型擦除则会将类型参数擦除成T的上界,即Object.则在泛型定义中作为T类型的对象可以调用Object的函数和属性. 使用了extends的泛型定义中的<T extends Integer>,其上界

  • Java泛型extends及super区别实例解析

    <? extends T>和<? super T>是Java泛型中的"通配符(Wildcards)"和"边界(Bounds)"的概念. <? extends T>:是指"上界通配符(Upper Bounds Wildcards)" <? super T>:是指"下界通配符(Lower Bounds Wildcards)" 为什么要用通配符和边界? 使用泛型的过程中,经常出现一种很

  • Java泛型之协变与逆变及extends与super选择

    目录 什么是不变 什么是协变 什么是逆变 extends 和 super 使用extends还是super呢 要了解协变与逆变,首先要引入: 根据 Liskov替换原则,如果C是P的子类,则P可以代替C,即 P p = new C();C继承于P,记做为 C < P 什么是不变 如果F是不变,当 C <= P 时,那么 F(C) 和 F(P) 没有任何继承关系 除例如 Integer是 Number的子类,根据 Liskov替换原则 Number number = new Integer(1)

  • 重新理解Java泛型

    这篇文章的目的在于介绍Java泛型,使大家对Java泛型的各个方面有一个最终的,清晰的,准确的理解,同时也为下一篇<重新理解Java反射>打下基础. 简介 泛型是Java中一个非常重要的知识点,在Java集合类框架中泛型被广泛应用.本文我们将从零开始来看一下Java泛型的设计,将会涉及到通配符处理,以及让人苦恼的类型擦除. 泛型基础 泛型类 我们首先定义一个简单的Box类: public class Box { private String object; public void set(St

  • 详解Java 10 var关键字和示例教程

    关键要点 Java 10引入了一个闪亮的新功能:局部变量类型推断.对于局部变量,现在可以使用特殊的保留类型名称"var"代替实际类型. 提供这个特性是为了增强Java语言,并将类型推断扩展到局部变量的声明上.这样可以减少板代码,同时仍然保留Java的编译时类型检查. 由于编译器需要通过检查赋值等式右侧(RHS)来推断var的实际类型,因此在某些情况下,这个特性具有局限性,例如在初始化Array和Stream的时候. 如何使用新的"var"来减少样板代码. 在本文中,

  • java泛型基本知识及通用方法

    泛型的基本使用 泛型是Java SE 1.5的新特性, 泛型的本质是参数化类型, 也就是说所操作的数据类型被指定为一个参数. 这种参数类型可以用在类.接口和方法的创建中, 分别称为泛型类.泛型接口.泛型方法.  Java语言引入泛型的好处是安全简单. 今天就从以下几个方面介绍一下java的泛型: 基础, 泛型关键字, 泛型方法, 泛型类和接口. 基础: 通过集合的泛型了解泛型的基本使用 public void testBasis(){ List<String> list = new Array

  • 一篇文章带你搞定JAVA泛型

    目录 1.泛型的概念 2.泛型的使用 3.泛型原理,泛型擦除 3.1 IDEA 查看字节码 3.2 泛型擦除原理 4.?和 T 的区别 5.super extends 6.注意点 1.静态方法无法访问类的泛型 2.创建之后无法修改类型 3.类型判断问题 4.创建类型实例 7.总结 1.泛型的概念 泛型的作用就是把类型参数化,也就是我们常说的类型参数 平时我们接触的普通方法的参数,比如public void fun(String s):参数的类型是String,是固定的 现在泛型的作用就是再将St

  • java泛型通配符详解

    前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许开发者在编译时检测到非法的类型. 泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数. 泛型带来的好处 在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的"任意化","任意化"带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的.对于强制类型转换错误的情况,编译器可能不提示错

  • Java 泛型详解与范例

    目录 一.泛型的使用 二.泛型类的定义-类型边界 三.类型擦除 四.泛型类的使用-通配符 五.泛型方法 六.泛型的限制 一.泛型的使用 前面我们学集合的时候,简单的说过泛型的使用.如下: ArrayList<Integer> list = new ArrayList<>(); Queue<Integer> queue = new LinkedList<>(); 那么使用是这样的简单,该注意什么? 尖括号里的类型,只能写引用类型 基础数据类型的话,就需要写相应

随机推荐