Java泛型(8):自限定&参数协变
自限定
自限定将强制泛型当做自己的边界参数来使用。自限定所做的,就是要求在继承关系中,像下面这样使用这个类:
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):自限定&参数协变的更多相关文章
- Java泛型之自限定类型
在<Java编程思想>中关于泛型的讲解中,提到了自限定类型: class SelfBounded<T extends SelfBounded<T>> 作者说道: 这 ...
- Java泛型通配符以及限定
摘抄笔记 A:泛型的限定 /* * 将的酒店员工,厨师,服务员,经理,分别存储到3个集合中 * 定义方法,可以同时遍历3集合,遍历三个集合的同时,可以调用工作方法 */ import java.uti ...
- Java泛型 类型变量的限定
有时候,类和方法须要对类型变量加以约束.比方你有一个方法,你仅仅希望它接收某个特定类型及其子类型作为參数. 以下就举一个方法限定接收參数的类型的样例来说明怎样限定类型变量. 首先有几个简单的辅助类: ...
- Java泛型中的协变和逆变
Java泛型中的协变和逆变 一般我们看Java泛型好像是不支持协变或逆变的,比如前面提到的List<Object>和List<String>之间是不可变的.但当我们在Java泛 ...
- java 泛型基础问题汇总
泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法. Java语言引 ...
- Java泛型主题讨论
说明:在学习泛型这一知识点中,主要参考自<疯狂Java讲义>第7章P307-P330的泛型内容,因为是跳着阅读,所以前面的一些名词不是特别清楚,这里也做出适当备注,供自己识记与理解. 1. ...
- Java泛型总结---基本用法,类型限定,通配符,类型擦除
一.基本概念和用法 在Java语言处于还没有出现泛型的版本时,只能通过Object是所有类型的父类和类型强制转换两个特点的配合来实现类型泛化.例如在哈希表的存取中,JDK1.5之前使用HashMap的 ...
- JAVA泛型——协变
在上篇<JAVA泛型——基本使用>这篇文章中遗留以下问题,即将子类型Table或者也能添加到父类型Auction的泛型中,要实现这种功能必须借助于协变. 实验准备 现在在<JAVA泛 ...
- Java泛型的协变
在上篇<Java泛型的基本使用>这篇文章中遗留以下问题,即将子类型也能添加到父类型的泛型中,要实现这种功能必须借助于协变. 实验准备 现在在上篇文章展示的Decorator类型的基础上,增 ...
随机推荐
- 洛谷P2341 [HAOI2006]受欢迎的牛 (Tarjan,SCC缩点)
P2341 [HAOI2006]受欢迎的牛|[模板]强连通分量 https://www.luogu.org/problem/P2341 题目描述 每头奶牛都梦想成为牛棚里的明星.被所有奶牛喜欢的奶牛就 ...
- 如何将html页面导出word格式?
近期做的项目也是奇葩,页面上需要导出excel,pdf,还有导出图片等...,刚把前几个怂好,还有下载成word文件,如何处理? function getOutword (id, fileName) ...
- golang embedded structs
golang 中把struct 转成json格式输出 package main import ( "encoding/json" "fmt" ) type Pe ...
- RAID 5 是一种存储性能、数据安全和存储成本兼顾的存储解决方案
可用容量:(n-1)/n的总磁盘容量(n为磁盘数) 原因:RAID5把数据和相对应的奇偶校验信息存储到组成RAID5的各个磁盘上,并且奇偶校验信息和相对应的数据分别存储于不同的磁盘上,其中任意N-1块 ...
- 【HDU5952】Counting Cliques
题目大意:给定一个\(N\)个点,\(M\)条边的无向图,求图中有多少个大小为\(S\)的团.\(N \le 100,deg(i)\le 20,i\in [1,n]\). 题解: 考虑搜索. 需要确定 ...
- nginx与php之间的交互方式
1. 2种方式 TCP的socket 跟 UNIX的socket 2.TCP的socket 首先进入容器然后修改nginx下的配置文件 3. 修改/usr/local/nginx/conf/ng ...
- CentOS上部署Kubernetes集群
1.开始前系统环境准备 # 1.设置基本环境 yum install -y net-tools conntrack-tools wget vim ntpdate libseccomp libtool- ...
- map(callback)将一组元素转换成其他数组(不论是否是元素数组)
map(callback) 概述 将一组元素转换成其他数组(不论是否是元素数组) 你可以用这个函数来建立一个列表,不论是值.属性还是CSS样式,或者其他特别形式.这都可以用'$.map()'来方便的建 ...
- yarn是什么?
yarn是个包管理器.你可以通过它使用全世界开发者的代码, 或者分享自己的代码. 从 npm 安装软件包并保持相同的包管理流程. 优点: 1.速度超快. Yarn 缓存了每个下载过的包 ...
- javascript数据结构之单链表
下面是用javascript实现的单链表,但是在输出的时候insert方法中存在问题,chrome的console报错说不能读取空的属性,调试了很久都没有通过,先在这里存着,以后再来修改一下. //数 ...