java 泛型 自限定_Java泛型 自限定类型(Self-Bound Types)详解

简介

java泛型里会有class SelfBounded> { }这种写法,泛型类有一个类型参数T,但这个T有边界SelfBounded。这边界里有两个疑问:

SelfBounded已经在左边出现,但SelfBounded类还没定义完这里就用了;

同样,T也在左边出现过了,这是该泛型类的类型参数标识符。

这两点确实挺让人疑惑,思考这个类定义时容易陷入“死循环”。

注意,自限定的要点实际就是这两个“疑问”。

普通泛型类——构成自限定

class BasicHolder {

T element;

void set(T arg) { element = arg; }

T get() { return element; }

void f() {

System.out.println(element.getClass().getSimpleName());

}

}

class Subtype extends BasicHolder {}

public class CRGWithBasicHolder {

public static void main(String[] args) {

Subtype st1 = new Subtype(), st2 = new Subtype(), st3 = new Subtype();

st1.set(st2);

st2.set(st3);

Subtype st4 = st1.get().get();

st1.f();

}

} /* Output:

Subtype

*///:~

BasicHolder泛型类的类型参数并没有什么边界,在继承它的时候,你本可以class A extends BasicHolder {}这样普普通通的用。

但class Subtype extends BasicHolder {}这样用,就构成自限定了。从定义上来说,它继承的父类的类型参数是它自己。从使用上来说,Subtype对象本身的类型是Subtype,且Subtype对象继承而来的成员(element)、方法的形参(set方法)、方法的返回值(get方法)也是Subtype了(这就是自限定的重要作用)。这样Subtype对象就只允许和Subtype对象(而不是别的类型的对象)交互了。

虽然class Subtype extends BasicHolder {}这样用,看起来是类定义还没有结束,就把自己的名字用到了边界的泛型类的类型参数。虽然感觉稍微有点不合理,但这里就强行理解一下吧。自限定的用法:父类作为一个泛型类或泛型接口,用子类的名字作为其类型参数。

以上两点,就解释了简介里的第二个“疑问”。正因为class Subtype extends BasicHolder这样用可以让Subtype对象只允许和Subtype对象交互,这里再把Subtype抽象成类型参数T,不就刚好变成了T extends SelfBounded这样的写法。

在主函数里,根据自限定的重要作用,且由于BasicHolder泛型类有个成员变量和set方法,所以st1.set(st2); st2.set(st3);可以像链表一样,节点的后继指向一个节点,后者又可以指向另外的节点。

自限定类型的泛型类

下面是自限定类型的标准用法。

class SelfBounded> {//自限定类型的标准用法

//所有

T element;

SelfBounded set(T arg) {

element = arg;

return this;

}

T get() { return element; }

}

class A extends SelfBounded {}

public class SelfBounding {

public static void main(String[] args) {

A a = new A();//a变量只能与A类型变量交互,这就是自限定的妙处

SelfBounded b = new SelfBounded();

}

} ///:~

抛开自限定类型的知识点,观察SelfBounded泛型类,发现该泛型类的类型参数T有SelfBounded的边界要求。根据上个章节的讲解,一个普通的泛型类我们都可以继承它来做到自限定,且因为要使用SelfBounded泛型类之前,我们必须有一个实际类型能符合SelfBounded的边界要求,所以这里就模仿上一章,创建一个新类来符合这个边界,即class A extends SelfBounded {},这样新类A便符合了SelfBounded的边界。

这时你觉得终于可以使用SelfBounded泛型类了,于是你便SelfBounded b = new SelfBounded();,但是这个b变量本身的类型是SelfBounded,成员函数的形参或返回值的类型却是A,这个效果看起来不是我们想要的自限定的效果。(b变量不可以和别的SelfBounded对象交互,因为它继承来的成员函数的类型限定是A,这样把别的SelfBounded对象传给成员函数会造成ClassCastException,这属于父类对象传给子类引用,肯定不可以。所以说没有达到自限定。)

其实,这里是我们多此一举了,新类class A extends SelfBounded {}创建的时候就已经一举三得了。1.出现SelfBounded尖括号里面的A需要满足边界SelfBounded,它自己的类定义已经满足了。2.给了SelfBounded泛型类的定义是为了使用它,新类A的对象也能使用到它,只不过这里是继承使用。3.根据上一章的讲解,新类A的类定义形成了自限定。

可能一般我们以为要使用SelfBounded泛型类要有两步(1.创建新类型以符合边界 2.以刚创建的新类型的名字来创建SelfBounded泛型类对象),但由于class SelfBounded>类定义中,SelfBounded作为了自己的泛型类型参数的边界,这样,想创建一个新类作为T类型参数以符合边界时,这个新类就必须继承到SelfBounded的所有成员(这也是我们想要的效果)。所以就可以class A extends SelfBounded {}这样一步到位。这也解释了简介里的第一个“疑问”。

对了,对于第一个疑问,你可能想看一下,如果边界里的泛型类不是自己,会是什么情况:

class testSelf {

//假设这里也有一些成员变量,成员方法

}

class SelfBounded> {//类型参数的边界不是自己的名字SelfBounded

T element;

SelfBounded set(T arg) {

element = arg;

return this;

}

T get() { return element; }

}

class testA extends testSelf {}//这个新类可作为SelfBounded的类型参数T,因为符合了边界

public class SelfBounding {

public static void main(String[] args) {

SelfBounded a = new SelfBounded();

}

} ///:~

按照一般使用SelfBounded泛型类的两个步骤,首先需要创建新类class testA extends testSelf {}来符合边界,然后新类型作为类型参数使用来创建SelfBounded对象,即SelfBounded a = new SelfBounded()。但a变量的效果却不是我们想要的自限定的效果,总之看起来很奇怪。

一旦你把class SelfBounded>的边界改成>,那么新类testA,那么它的定义就应该是class testA extends SelfBounded {},然后正因为testA继承了SelfBounded,所以testA就获得了父类SelfBounded的成员方法且这些成员方法的形参或返回值都是testA。

通过这个反例便进一步解释了简介的第一个“疑问”。

对本章第一个例子作进一步的拓展吧:

class SelfBounded> {

T element;

SelfBounded set(T arg) {

element = arg;

return this;

}

T get() { return element; }

}

class A extends SelfBounded {}

class B extends SelfBounded {} // Also OK

class C extends SelfBounded {

C setAndGet(C arg) { set(arg); return get(); }

}

class D {}

// Can't do this:

// class E extends SelfBounded {}

// Compile error: Type parameter D is not within its bound

// Alas, you can do this, so you can't force the idiom:

class F extends SelfBounded {}

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();//最终a是null

C c = new C();

c = c.setAndGet(new C());//c换成这行新new出来的C对象了

}

} ///:~

class B extends SelfBounded这样使用也是可以的,毕竟继承SelfBounded时,给定的具体类型A确实满足了边界。不过B对象没有自限定的效果了。

class C extends SelfBounded展示了:在自己新增的成员方法里,去调用继承来的成员方法。注意,继承来的方法被限定类型为C即自身了,这就是自限定的效果。

class E extends SelfBounded无法通过编译,因为给定的具体类型A不符合边界。

class F extends SelfBounded,你可以继承原生类型,此时T会作为它的上限SelfBounded(边界)来执行。如下图:

5d3745d8d0e0e7437b86390a543d812b.png

也可以将自限定用于泛型方法:

//借用之前定义好的SelfBounded

class testNoBoundary {}//我自己新加的

public class SelfBoundingMethods {

static > T f(T arg) {

return arg.set(arg).get();

}

static > T f1(T arg) {

return arg;

}

public static void main(String[] args) {

A a = f(new A());

class selfBound extends testNoBoundary {}

selfBound b = f1(new selfBound());

}

} ///:~

f静态方法要求T自限定,且边界是SelfBounded。那么之前定义的A类型就符合要求了。

我加了个f1静态方法,它也要求T自限定,且边界是testNoBoundary。注意testNoBoundary泛型类对类型参数T没有边界要求。class selfBound extends testNoBoundary {}这里用了局部内部类创建了一个符合边界要求的新类型。

JDK源码里自限定的应用——enum

java中使用enum关键字来创建枚举类,实际创建出来的枚举类都继承了java.lang.Enum。也正因为这样,所以enum不能再继承别的类了。其实enum就是java的一个语法糖,编译器在背后帮我们继承了java.lang.Enum。

下面就是一个枚举类的使用:

public enum WeekDay {

Mon("Monday"), Tue("Tuesday"), Wed("Wednesday"), Thu("Thursday"), Fri( "Friday"), Sat("Saturday"), Sun("Sunday");

private final String day;

private WeekDay(String day) {

this.day = day;

}

public static void printDay(int i){

switch(i){

case 1: System.out.println(WeekDay.Mon); break;

case 2: System.out.println(WeekDay.Tue);break;

case 3: System.out.println(WeekDay.Wed);break;

case 4: System.out.println(WeekDay.Thu);break;

case 5: System.out.println(WeekDay.Fri);break;

case 6: System.out.println(WeekDay.Sat);break;

case 7: System.out.println(WeekDay.Sun);break;

default:System.out.println("wrong number!");

}

}

public String getDay() {

return day;

}

public static void main(String[] args) {

WeekDay a = WeekDay.Mon;

}

}

发现通过idea看WeekDay.class文件时看不出继承java.lang.Enum的。只有通过javap命令才能看出来。先看一下java.lang.Enum的定义,Enum>是自限定类型的标准写法:

public abstract class Enum> implements Comparable, Serializable { }

截取部分汇编来看:

public final class WeekDay extends java.lang.Enum {

public static final WeekDay Mon;

public static final WeekDay Tue;

public static final WeekDay Wed;

public static final WeekDay Thu;

public static final WeekDay Fri;

public static final WeekDay Sat;

public static final WeekDay Sun;

public static WeekDay[] values();

public static WeekDay valueOf(java.lang.String);

发现确实WeekDay做到了自限定,因为继承来的成员和方法的类型都被限定成WeekDay它自己了。

分析一下java.lang.Enum这么设计的好处:

Enum作为一个抽象类,我们使用enum关键字创建出来的枚举类实际都是Enum的子类,因为class Enum>的类定义是这种标准的自限定类型,所以编译器直接生成的类必须是WeekDay extends java.lang.Enum(即本文中讲的:需先创建一个符合边界条件的实际类型,但创建的同时又继承Enum本身,所以就一步到位了)。

正因为编译器生成的枚举类都是Enum的子类,结合上条分析,每种Enum子类的自限定类型都是Enum子类自身。这样WeekDay的实例就只能和WeekDay的实例交互(星期几和星期几比较),Month的实例就只能和Month的实例交互(月份和月份比较)。

JDK源码里自限定的应用——Integer

Integer的类定义是:

public interface Comparable {

public int compareTo(T o);

}

public final class Integer extends Number implements Comparable {//省略}

可以看到Integer实现了Comparable,这也是自限定,这样,从Comparable接口继承来的compareTo方法的形参类型就是Integer它自己了。和章节《普通泛型类——构成自限定》里的例子一样。

但接口Comparable的定义可没要求类型参数T必须自限定啊,它甚至连T的边界都没有,当然,这样的好处就是把决定权交给了Comparable的使用者,当使用者想要自限定时,就按照自限定的写法创建新类就好了。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值