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类型的基础上,增 ...
随机推荐
- BZOJ2140 稳定婚姻[强连通分量]
发现如果$B_i$和$G_j$配对,那么$B_j$又要找一个$G_k$配对,$B_k$又要找一个$G_l$配对,一直到某一个$B_x$和$G_i$配对上为止,才是不稳定的. 暴力是二分图匹配.匈牙利算 ...
- 记一次 解决 vue 兼容ie11 的问题
vue2.0 最近项目需要做到兼容ie11,找问题找半天,于是各种百度, 发现引入 babel-polyfill 还是有问题 空白页面 参考它的配置 ( 最后一句代码为引入的资源路径 ) 我跟他差 ...
- Linux(Ubuntu)安装ssh服务
在终端(Ctrl + Alt + T )输入 $ps -e | grep ssh 看到 “ ssh-agent ” 和 “sshd” ,表示没有安装服务,或没有开机启动 1.安装SSH 输入:sudo ...
- 串口+RS485驱动
其实RS485不算什么协议,只是物理层做了差分传输,AB两线的电压差来表示0,1,0,1,可靠性和距离更加好,因此,一个串口外设只能作为半双工使用,而RS232是可以全双工的. max485模块可以直 ...
- flutter TextField 输入框被软键盘挡住的解决方案
以前搞ionic1~4的开发中 和react-native flutter中的机制完全不同, 在flutter 中 当前页面如果存在元素被软键盘挡住 的情况 页面元素的最外层肯定得嵌套一层 S ...
- Qt 模型/视图结构
MVC是一种与用户界面相关的设计模式.通过使用此模型,可以有效地分离数据和用户界面.MVC设计模式包含三要素:表示数据的模型(Model).表示用户界面的视图(View)和定义了用户在界面上的操作控制 ...
- .net System.Net.Mail 之用SmtpClient发送邮件Demo
private static bool sendMail() { try { //接收人邮箱 string SendTo = "XXXXX@163.com ...
- 09 saltstack生产实例--Haproxy+keepalived
1.如何理解haproxy+keepalived https://www.cnblogs.com/zs-wei/p/9213961.html HaProxy可以通过监听一个统一的端口对外提供能力,然后 ...
- [Algorithm] Chunk Array
// --- Directions// Given an array and chunk size, divide the array into many subarrays// where each ...
- gcc/g++以c++11编译
方法一: //在程序头加上预定义编译器命令 #pragma GCC diagnostic error "-std=c++11" //通过#pragma 指示 GCC编译器处理错误的 ...