Java泛型简介–第6部分

这是对泛型的介绍性讨论的延续, 此处的先前部分可以在此处找到。

上一篇文章中,我们讨论了关于类型参数的递归边界。 我们看到了递归绑定如何帮助我们重用了车辆比较逻辑。 在该文章的结尾,我建议当我们不够小心时,可能会发生类型混合。 今天我们将看到一个例子。

如果有人错误地通过以下方式创建了Vehicle的子类,则可能会发生混合:

/**
 * Definition of Vehicle
 */
public abstract class Vehicle<E extends Vehicle<E>> implements Comparable<E> {
    // other methods and properties

    public int compareTo(E vehicle) {
        // method implementation
    }
}

/**
 * Definition of Bus
 */
public class Bus extends Vehicle<Bus> {}

/**
 * BiCycle, new subtype of Vehicle
 */
public class BiCycle extends Vehicle<Bus> {}

/**
 * Now this class’s compareTo method will take a Bus type
 * as its argument. As a result, you will not be able to compare
 * a BiCycle with another Bicycle, but with a Bus.
 */
cycle.compareTo(anotherCycle);  // This will generate a compile time error
cycle.compareTo(bus);    // but you will be able to do this without any error

枚举不会发生这种类型的混淆,因为JVM负责子类化和为枚举类型创建实例,但是如果我们在代码中使用此样式,则必须小心。

让我们谈谈递归边界的另一个有趣的应用。 考虑以下类别:

public class MyClass {
  private String attrib1;
  private String attrib2;
  private String attrib3;
  private String attrib4;
  private String attrib5;

  public MyClass() {}

  public String getAttrib1() {
    return attrib1;
  }

  public void setAttrib1(String attrib1) {
    this.attrib1 = attrib1;
  }

  public String getAttrib2() {
    return attrib2;
  }

  public void setAttrib2(String attrib2) {
    this.attrib2 = attrib2;
  }

  public String getAttrib3() {
    return attrib3;
  }

  public void setAttrib3(String attrib3) {
    this.attrib3 = attrib3;
  }

  public String getAttrib4() {
    return attrib4;
  }

  public void setAttrib4(String attrib4) {
    this.attrib4 = attrib4;
  }

  public String getAttrib5() {
    return attrib5;
  }

  public void setAttrib5(String attrib5) {
    this.attrib5 = attrib5;
  }
}

如果我们要创建此类的实例,则可以执行以下操作:

MyClass mc = new MyClass();
mc.setAttrib1("Attribute 1");
mc.setAttrib2("Attribute 2");

上面的代码创建该类的实例并初始化属性。 如果我们可以在此处使用方法链接 ,那么我们可以编写:

MyClass mc = new MyClass().setAttrib1("Attribute 1")
    .setAttrib2("Attribute 2");

显然比第一个版本好得多。 但是,要启用这种方法链接,我们需要通过以下方式修改MyClass

public class MyClass {
  private String attrib1;
  private String attrib2;
  private String attrib3;
  private String attrib4;
  private String attrib5;

  public MyClass() {}

  public String getAttrib1() {
    return attrib1;
  }

  public MyClass setAttrib1(String attrib1) {
    this.attrib1 = attrib1;
    return this;
  }

  public String getAttrib2() {
    return attrib2;
  }

  public MyClass setAttrib2(String attrib2) {
    this.attrib2 = attrib2;
    return this;
  }

  public String getAttrib3() {
    return attrib3;
  }

  public MyClass setAttrib3(String attrib3) {
    this.attrib3 = attrib3;
    return this;
  }

  public String getAttrib4() {
    return attrib4;
  }

  public MyClass setAttrib4(String attrib4) {
    this.attrib4 = attrib4;
    return this;
  }

  public String getAttrib5() {
    return attrib5;
  }

  public MyClass setAttrib5(String attrib5) {
    this.attrib5 = attrib5;
    return this;
  }
}

然后我们将可以对此类的实例使用方法链接。 但是,如果我们想在涉及继承的地方使用方法链接,那么事情就会变得混乱:

public abstract class Parent {
  private String attrib1;
  private String attrib2;
  private String attrib3;
  private String attrib4;
  private String attrib5;

  public Parent() {}

  public String getAttrib1() {
    return attrib1;
  }

  public Parent setAttrib1(String attrib1) {
    this.attrib1 = attrib1;
    return this;
  }

  public String getAttrib2() {
    return attrib2;
  }

  public Parent setAttrib2(String attrib2) {
    this.attrib2 = attrib2;
    return this;
  }

  public String getAttrib3() {
    return attrib3;
  }

  public Parent setAttrib3(String attrib3) {
    this.attrib3 = attrib3;
    return this;
  }

  public String getAttrib4() {
    return attrib4;
  }

  public Parent setAttrib4(String attrib4) {
    this.attrib4 = attrib4;
    return this;
  }

  public String getAttrib5() {
    return attrib5;
  }

  public Parent setAttrib5(String attrib5) {
    this.attrib5 = attrib5;
    return this;
  }
}

public class Child extends Parent {
  private String attrib6;
  private String attrib7;

  public Child() {}

  public String getAttrib6() {
    return attrib6;
  }

  public Child setAttrib6(String attrib6) {
    this.attrib6 = attrib6;
    return this;
  }

  public String getAttrib7() {
    return attrib7;
  }

  public Child setAttrib7(String attrib7) {
    this.attrib7 = attrib7;
    return this;
  }
}

/**
 * Now try using method chaining for instances of Child
 * in the following way, you will get compile time errors.
 */
Child c = new Child().setAttrib1("Attribute 1").setAttrib6("Attribute 6");

这样做的原因是,即使Child从其父级继承了所有的setter,所有这些setter方法的返回类型也都是Parent类型,而不是Child类型。 因此,第一个设置器将返回类型为Parent的引用,调用setAttrib6会导致编译错误,因为它没有任何此类方法。

我们可以通过在Parent上引入通用类型参数并在其上定义递归绑定来解决此问题。 它的所有子项从其扩展时都将自己作为类型参数传递,从而确保setter方法将返回其类型的引用:

public abstract class Parent<T extends Parent<T>> {
  private String attrib1;
  private String attrib2;
  private String attrib3;
  private String attrib4;
  private String attrib5;

  public Parent() {
  }

  public String getAttrib1() {
    return attrib1;
  }

  @SuppressWarnings("unchecked")
  public T setAttrib1(String attrib1) {
    this.attrib1 = attrib1;
    return (T) this;
  }

  public String getAttrib2() {
    return attrib2;
  }

  @SuppressWarnings("unchecked")
  public T setAttrib2(String attrib2) {
    this.attrib2 = attrib2;
    return (T) this;
  }

  public String getAttrib3() {
    return attrib3;
  }

  @SuppressWarnings("unchecked")
  public T setAttrib3(String attrib3) {
    this.attrib3 = attrib3;
    return (T) this;
  }

  public String getAttrib4() {
    return attrib4;
  }

  @SuppressWarnings("unchecked")
  public T setAttrib4(String attrib4) {
    this.attrib4 = attrib4;
    return (T) this;
  }

  public String getAttrib5() {
    return attrib5;
  }

  @SuppressWarnings("unchecked")
  public T setAttrib5(String attrib5) {
    this.attrib5 = attrib5;
    return (T) this;
  }
}

public class Child extends Parent<Child> {
  private String attrib6;
  private String attrib7;

  public String getAttrib6() {
    return attrib6;
  }

  public Child setAttrib6(String attrib6) {
    this.attrib6 = attrib6;
    return this;
  }

  public String getAttrib7() {
    return attrib7;
  }

  public Child setAttrib7(String attrib7) {
    this.attrib7 = attrib7;
    return this;
  }
}

请注意,我们已经明确地施放T类型,因为编译器不知道这种转换是否是可能的,即使它是因为牛逼的定义是由父<T>界。 同样,由于我们将对象引用转换为T ,因此编译器将发出未经检查的警告。 为了抑制这种情况,我们在设置器上方使用了@SuppressWarnings(“ unchecked”)

经过上述修改,这样做是完全有效的:

Child c = new Child().setAttrib1("Attribute 1")
  .setAttrib6("Attribute 6");

当以这种方式编写方法设置器时,我们应注意不要将递归边界用于任何其他目的,例如从父级访问子级状态,因为这会使父级暴露其子类的内部细节,并最终破坏封装。

通过这篇文章,我完成了泛型的基本介绍。 我在本系列中没有讨论太多的事情,因为我认为它们已经超出了介绍的范围。

直到下一次。

翻译自: https://www.javacodegeeks.com/2014/07/an-introduction-to-generics-in-java-part-6.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值