Thinking in Java 15章-泛型自限定

自限定的泛型Enum<E extends Enum<E>>


自限定泛型的作用

自限定所做的就是要求在继承关系中,强制要求将正在定义的类当做参数传递给基类。可以保证类型参数必须与正在被定义的类相同,如
public class Fruit<E extends Fruit<E>>
1. 限定了泛型参数的种类,提高了可读性,一看便知道里面放的是什么,为泛型参数调用者提供了更精确的编译能力。
2. 提高了安全性,防止对象的转换出错。
3. 保证泛型参数可以调用方法,比如:

  public int compareTo(E o){
     return  o.osVersion();
        }

古怪的泛型循环

为了理解自限定,我们先要了解一个泛型的简单形式,在继承关系中,基类的泛型参数用的是导出类,形如 Class 基类<导出类>,代码如下,

package Test;//: generics/CRGWithBasicHolder.java


class BasicHolder<T> {
    T element;
    void set(T arg){
        element = arg;
    }
    T get() { return element; }
  void f() {
    System.out.println(element.getClass().getSimpleName());
  }
}

/**Subtype继承BasicHolder,并往泛型参数中传入了自己的类型,
 * 这样子类所有继承自父类的属性和方法都可以用自己实际的类型,父类变成了模板,
 * 子类实现自己独特的功能
 */
class Subtype extends BasicHolder<Subtype> {
    Subtype element;
    void set(Subtype arg){
        element = arg;
    }
    Subtype get() { return element; }
    void f() {
        System.out.println(element.getClass().getSimpleName());
    }
}

public class CRGWithBasicHolder {
  public static void main(String[] args) {
    Subtype st1 = new Subtype(),st2 = new Subtype();
    st1.set(st2);
    Subtype st3 = st1.get();
    st1.f();
  }
}
//output:Subtype

这里体现了自限定的本质:基类用导出类替代其泛型参数。基类变成了一种其所有导出类的公共功能的模板,但这些功能对于参数和返回值来说,使用的都是导出类型而非基类。在Subtype中,set和get方法的参数或返回类型都是Subtype而非BasicHolder或者其他类作为参数。这里只是把泛型参数T用子类Subtype来代替了。如果不限定T的取值空间(Object类下的所有类),那么T可以使用任何类来进行填充。


自限定

上面讲到,BasicHolder可以使用任何类型为其泛型参数填充,如果我们加上自限定的话,T的取值就不能是任意的类了。

package Test;//: generics/SelfBounding.java

import java.util.ArrayList;
import  java.util.List;
//这里我限定了可以放入SelfBounded的参数类型,只可以是SelfBounded的子类型
class SelfBounded<T extends SelfBounded<T> > {
  T element;
  SelfBounded<T> set(T arg) {
    element = arg;
    return this;
  }
  T get() { return element; }
}
//A可以放入,因为A是SelfBounded的子类型
class A extends SelfBounded<A> {}
class B extends SelfBounded<C> {} // Also OK
class C extends SelfBounded<C> {
  C setAndGet(C arg) { set(arg); return get(); }
}
class D {}

  //不能编译class E extends SelfBounded<D> {}
  //不能放入D,因为D不是SelfBounded的子类
//F可以编译,自限定不是强制必须用的  
class F extends SelfBounded {}
public class SelfBounding {
  public static void main(String[] args) {
    A a = new A();
    SelfBounded sfb = new SelfBounded();
    a.set(new A());
    //a对象可以使用其父类的所有方法,但是它的参数和返回值都不需要转型,用的就是A类型
    System.out.println(a.get().getClass());
    B b = new B();
    C c = new C();
    b.set(c);
    System.out.println(b.get().getClass());

  }
}

从上面代码可以看到,实际上SelfBounded的泛型参数E被我们限定到只有继承SelfBounded的几个子类A,B,C,而D并没有继承SelfBounded,所以D无法作为参数使用。我们最想要的形式是:
class A entends SelfBounded<A>{}
因为这样我们就明确了使用的是A的对象,方便调用。在A中可以使用所有继承自SelfBounded的方法并传入自己的实际类型。当然自限定也不强制你使用其他子类对象:
class B extends SelfBounded<C> {} // Also OK
不过第一种是我们最推荐使用的用法。
其实我们还可以这样来看泛型,class A entends SelfBounded<A>{},把加粗部分与class SelfBounded <E entends SelfBounded<E> 的加粗部分正好匹配,泛型参数正好能够接受A来填充。


自限定常用的方式,实现Comparable接口同类型的比较

下面手机类来进行测试。同类手机可以进行系统版本号的比较,不同类的手机系统版本号比较没有意义。通过 E extends Handphone<E>>的泛型限定,我们就可以阻止编译器在 Iphone 和 Android 手机之间进行系统版本号的比较。

package Test;
import static net.mindview.util.Print.*;
abstract class Handphone<E extends Handphone> implements Comparable<E>{
    public abstract int osVersion();
    public int compareTo(E o){
    //这里泛型o对象拥有了类型信息,并且使用了方法,这是因为E extends Handphone,所以E里保存着Handphone的方法
        return  osVersion() - o.osVersion();

    }
}

class Iphone extends Handphone<Iphone>{
        private int version;
        public Iphone(int version){
            this.version = version;
            }
        public int osVersion(){
            return version;
            }
}

class Android extends Handphone<Android>{
        private int version;
       public Android(int version){
            this.version = version;
            }
        public int osVersion(){
            return version;
            }
        }

public class WeirdGenerics {
    public static void main(String[] args) {
        Iphone i4 = new Iphone(4), i5 = new Iphone(5);
        Android a2 = new Android(2), a3 = new Android(3);

        System.out.println(i5.compareTo(i4));
        System.out.println(a3.compareTo(a2));
        // 错误,两种手机之间不能比较系统版本大小
        //System.out.println(i5.compareTo(a3));
            }
        }

通过上述代码我们可以看出,为什么要加限定Handphone<E extends Handphone> ,而不是直接使用Handphone<E> implements Comparable<E> 来实现比较接口,如果不限定,一是不可以在泛型中使用方法,二是不同类型的安卓和苹果手机都可以比较,而这是没有意义的。


总结

这里有一篇外文讲解该问题的看法,可以参考
http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106
我大致总结一下观点:
Enum<E extends Enum<E>> 可写成Enum<E extends Enum>
1. 首先我们必须明白Enum的泛型参数是E,但是这个E有限制,必须是Enum的子类;同样,第二E可以不写,但是它也被限制成必须是Enum的子类,因为它属于Enum的类定义,把第二个E代到前面定义中去,是一个循环。
2. Enum<E>类的参数被限定了,不再是可以传入任意类型了,而是只可以传入其子类型参数
3. 自限定是强制用于继承关系上的,自限定的本质思想是基类用导出类代替其参数 ,泛型基类变成了一种所有导出类的公共功能的模板,但是这些功能对于其所有参数和返回值,将使用到处类型,在所产生的类中使用确切类型而非基类型。
4. 明确对象,方便使用,不然没有泛型用object对象,还要进行装包、拆包或强转操作,没有效率。并且在实现Comparable<E> 接口中,可以限制同类型进行比较。

才疏学浅、学艺不精、如有疑问、欢迎提问

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值