自限定

自限定将强制泛型当做自己的边界参数来使用。自限定所做的,就是要求在继承关系中,像下面这样使用这个类:

class A extends SelfBounded<A> {}

它的意义是可以保证类型参数必须与正在被定义的类相同自限定只能强制作用于继承关系。如果使用自限定,就应该了解这个类所用的类型参数将与使用这个参数的类具有相同的基本类型。

下面是一个自限定的例子【1】:

 class SelfBounded<T extends SelfBounded<T>> {
T element;
SelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() { return element; }
} class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} // It's OK. class C extends SelfBounded<C> {
C setAndGet(C arg) { set(arg); return get(); }
} class D {}
// class E extends SelfBounded<D> {} // [Compile error]: Type parameter D is not within its bound public class SelfBounding {
public static void main(String[] args) {
A a = new A();
a.set(new A());
a = a.set(new A()).get();
a = a.get();
C c = new C();
c = c.setAndGet(new C());
}
}

我们发现class E是不能编译的。如果移除自限定这个限制(class SelfBounded<T>),这样E就可以编译了。但是就不能限制E这样的非自限定类型继承SelfBounded类了。

参数协变

先看一个协变返回类型的例子【2】:

 class Base {}
class Derived extends Base {} interface OrdinaryGetter {
Base get();
} interface DerivedGetter extends OrdinaryGetter {
// DerivedGetter.get()覆盖了OrdinaryGetter.get()
@Override Derived get();
} public class CovariantReturnTypes {
void test(DerivedGetter d) {
Derived result1 = d.get(); // 调用的DerivedGetter.get()
Base result2 = d.get(); // 也调用的DerivedGetter.get()
}
}

而自限定泛型将产生确切的导出类型作为其返回值。请看例【3】:

 interface GenericGetter<T extends GenericGetter<T>> {
T get();
} interface Getter extends GenericGetter<Getter> {} public class GenericsAndReturnTypes {
void test(Getter g) {
Getter result1 = g.get();
GenericGetter result2 = g.get(); // Also the base type
}
}

例【2】可以证明,返回值并不是区分两个不同方法的途径。而下面的例【4】则说明参数类型可以区分两个不同的方法。所以例【2】是覆盖(override)而例【4】是重载(overload)。

 class OrdinarySetter {
void set(Base base) {
System.out.println("OrdinarySetter.set(Base)");
}
} class DerivedSetter extends OrdinarySetter {
// @Override // [Compile Error]: Can't override. It's overload not override!
void set(Derived derived) {
System.out.println("DerivedSetter.set(Derived)");
}
} // 在非泛型代码中,参数类型不能随子类型发生变化。
public class OrdinaryArguments {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
DerivedSetter ds = new DerivedSetter();
ds.set(derived); // 调用DerivedSetter的set
ds.set(base); // 调用OrdinarySetter的set
}
}

但是,在使用自限定类型时,在导出类中只有一个方法,并且这个方法接受导出类型而不是基类型作为参数。例子【5】:

 interface SelfBoundSetter<T extends SelfBoundSetter<T>> {
void set(T arg);
} interface Setter extends SelfBoundSetter<Setter> {} public class SelfBoundingAndCovariantArguments {
void testA(Setter s1, Setter s2, SelfBoundSetter sb1, SelfBoundSetter sb2) {
s1.set(s2);
// 编译器不能识别将基类型当做参数传递给set的尝试,因为没有任何方法具有这样的签名。事实上,这个参数已经被覆盖。
// s1.set(sb1); // [Compile Error]: set(Setter) in SelfBoundSetter<Setter> cannot be applied to (SelfBoundSetter)
sb1.set(s1);
sb1.set(sb2);
}
}

如果上例不使用自限定类型,普通继承机制就会介入,而你将能够重载,就像在非泛型的情况下一样(自限定类型可以避免这种情况发生):

 class GenericSetter<T> { // Not self-bounded
void set(T arg){
System.out.println("GenericSetter.set(Base)");
}
} class DerivedGS extends GenericSetter<Base> {
// @Override // [Compile Error]: Can't override. It's overload not override.
void set(Derived derived){
System.out.println("DerivedGS.set(Derived)");
}
} public class PlainGenericInheritance {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
DerivedGS dgs = new DerivedGS();
dgs.set(derived);
dgs.set(base); // Compiles: overloaded, not overridden!
}
}

Java泛型(8):自限定&参数协变的更多相关文章

  1. Java泛型之自限定类型

    在<Java编程思想>中关于泛型的讲解中,提到了自限定类型: class SelfBounded<T extends SelfBounded<T>> 作者说道: 这 ...

  2. Java泛型通配符以及限定

    摘抄笔记 A:泛型的限定 /* * 将的酒店员工,厨师,服务员,经理,分别存储到3个集合中 * 定义方法,可以同时遍历3集合,遍历三个集合的同时,可以调用工作方法 */ import java.uti ...

  3. Java泛型 类型变量的限定

    有时候,类和方法须要对类型变量加以约束.比方你有一个方法,你仅仅希望它接收某个特定类型及其子类型作为參数. 以下就举一个方法限定接收參数的类型的样例来说明怎样限定类型变量. 首先有几个简单的辅助类: ...

  4. Java泛型中的协变和逆变

    Java泛型中的协变和逆变 一般我们看Java泛型好像是不支持协变或逆变的,比如前面提到的List<Object>和List<String>之间是不可变的.但当我们在Java泛 ...

  5. java 泛型基础问题汇总

    泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java语言引 ...

  6. Java泛型主题讨论

    说明:在学习泛型这一知识点中,主要参考自<疯狂Java讲义>第7章P307-P330的泛型内容,因为是跳着阅读,所以前面的一些名词不是特别清楚,这里也做出适当备注,供自己识记与理解. 1. ...

  7. Java泛型总结---基本用法,类型限定,通配符,类型擦除

    一.基本概念和用法 在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化.例如在哈希表的存取中,JDK1.5之前使用HashMap的 ...

  8. JAVA泛型——协变

    在上篇<JAVA泛型——基本使用>这篇文章中遗留以下问题,即将子类型Table或者也能添加到父类型Auction的泛型中,要实现这种功能必须借助于协变. 实验准备 现在在<JAVA泛 ...

  9. Java泛型的协变

    在上篇<Java泛型的基本使用>这篇文章中遗留以下问题,即将子类型也能添加到父类型的泛型中,要实现这种功能必须借助于协变. 实验准备 现在在上篇文章展示的Decorator类型的基础上,增 ...

随机推荐

  1. Python 字符串 (3) 持续更新

    字符串操作 虽然字符串也是一种序列,但是它和元组一样,不可变的.当你想对它修改时,会引发异常.如 >>> strings = "Let's go">> ...

  2. hbase实践之写流程

    内容提要 一.写入流程 初始化ZooKeeper Session,建立长连接,获取META Region的地址. 获取rowkey对应的Region路由信息:来自.meta. 写入region 如何快 ...

  3. vue的prop父子组件传值

    props down, events up 父组件通过 props 向下传递数据给子组件:子组件通过 events 给父组件发送消息. 静态 props 要让子组件使用父组件的数据,需要通过子组件的 ...

  4. Codeforces Round #588 (Div. 2) A. Dawid and Bags of Candies

    链接: https://codeforces.com/contest/1230/problem/A 题意: Dawid has four bags of candies. The i-th of th ...

  5. MyBatis注解Annotation介绍及Demo(转)

    MyBatis可以利用SQL映射文件来配置,也可以利用Annotation来设置.MyBatis提供的一些基本注解如下表所示. 注解 目标 相应的XML 描述 @CacheNamespace 类 &l ...

  6. 立即执行函数与For. . .in语句

    ㈠立即执行函数 ⑴定义:在函数定义完,立即被调用,这样的函数叫做立即执行函数 ⑵语法:函数对象() ⑶注意:立即执行函数往往只会执行一次 ⑷示例1: (function(){ alert(" ...

  7. Word:自动编号超过9后缩进太大

     造冰箱的大熊猫,本文适用于Microsoft Office 2007@cnblogs 2019/7/30 文中图片可通过点击鼠标右键查看大图 1.场景 如下图所示,使用Word的自动编号功能时,当编 ...

  8. 【线性代数】3-3:秩(Rank)

    title: [线性代数]3-3:秩(Rank) categories: Mathematic Linear Algebra keywords: Rank Row Reduced form Pivot ...

  9. 转:获取时间点前后一定间隔的时间 __timeShift()

    接口获取时间点前后一定间隔的时间函数: __timeShift(时间格式, 特定时间点(缺省当前时间),时间间隔,地区格式(默认),变量名( 可不填,填写后其他地方用${变量名}引用 )) 举例: 1 ...

  10. PHP 根据两个坐标计算距离 圆形围栏的计算

    可以应用于圆形电子围栏入 出围栏计算 圆形电子围栏的计算方式是: 根据圆心坐标和当前检查的坐标进行距离计算,如果距离长度超出围栏的半径,则判定为出围栏,反之是在围栏之内 <?php /** * ...