《Java编程思想》参数协变

来源:互联网 发布:双色球分析软件 编辑:程序博客网 时间:2024/04/29 05:09

不得不吐槽一下,看《Java编程思想》收获的最多的当然是Java的知识,其次就是翻译的各种各样奇奇怪怪的单词。好了废话不多说,我们进入今天的正题:
解释一下参数协变或者协变参数类型其大概意思就是:方法的参数类型会随子类而改变,协变返回类型是Java SE5引入的。来看看下面的代码:

class Base {}class Derived extends Base{}interface OrdinaryGetter{    Base get();}interface  DerivedGetter extends OrdinaryGetter{    Derived get();}class ConvarianReturnTypes{    @SuppressWarnings("unused")    void test(DerivedGetter d)    {        Base d1 = d.get();        //下面的代码在早先的Java代码中是错误的        Derived d2 = d.get();    }}

可以看见,子类接口覆盖了基类接口的方法,从而可以返回子类接口指定的类型。再看看下面的代码:

interface GenericGetter<T extends GenericGetter<T>>{    T get();}interface Getter extends GenericGetter<Getter> {}class GenericsAndReturnTypes{    @SuppressWarnings("unused")    void test(Getter g)    {        Getter result = g.get();//产生更加准确的导出类        GenericGetter gg = g.get();//所以可以转换为基类    }}

一定要注意,这个时候并不是一个方法能够返回不同的类型,而是只能返回一个确定的Getter这个子类,但是子类却可以被GenericGetter转型。
这段代码在Java SE5的环境下可以通过编译。再看看下面的代码:

class Base {}class Derived extends Base{}class OrdinarySetter{    void set(Base base)    {        System.out.println("OrdinarySetter.set(Base)");    }}class DerivedSetter extends OrdinarySetter{    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);        ds.set(base);    }}

注意这两行代码:

ds.set(derived);ds.set(base);

DerivedSetter类并没有使用泛型,所以也不存在协变,这是重载的结果。接下来看一看参数协变对于子类参数的影响,代码如下:

interface SelfBoundSetter<T extends SelfBoundSetter<T>>{    void set(T arg);}interface Setter extends SelfBoundSetter<Setter> {}class SelfBoundingAndCovariantArguments{    void testA(Setter s1,Setter s2,SelfBoundSetter sbs)    {        s1.set(s2);        s1.set(sbs);//子类将不接受基类作为自己的参数    }}

可以看到,使用了自限定的语法之后,子类方法将只接受将自己作为参数,当然如果只是用泛型而不是自限定语法,这个目的同样能达到:

interface SelfBoundSetter<T>{    void set(T arg);}interface Setter extends SelfBoundSetter<Setter> {}class SelfBoundingAndCovariantArguments{    void testA(Setter s1,Setter s2,SelfBoundSetter sbs)    {        s1.set(s2);        s1.set(sbs);//子类将不接受基类作为自己的参数    }}

两者没什么区别,甚至连报错原因都一样。我们可以这么理解——其实这里原来的 SelfBoundSetter的set方法是被子类给覆盖了,当然详细的解释还要看源码,在我把整本书看完之后,我会陆续填坑。当不使用自限定类型时,我们来看一看:

class Base {}class Derived{}class GenericSetter<T>//未使用自限定{    void set(T arg)    {        System.out.println("GenericSetter.set(Base)");    }}class DerivedGS extends GenericSetter<Base>{    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);//ok        dgs.set(base);//ok    }}

可以看到未使用自限定的时候,因为DerivedGS类的.set()方法可以接受两种参数,所DerivedGS的方法是被重载的。再来看看使用自限定方法的时候:

class Base {}class Derived{}class GenericSetter<T extends GenericSetter<T>>{    void set(T arg)    {        System.out.println("GenericSetter.set(Base)");    }}class DerivedGS extends GenericSetter<DerivedGS>//唯一的方法{    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);//这个时候出现了参数不匹配    }}

这个时候只获得了一种方法,就是DerivedGS类自身的方法,很好理解,自限定的作用就是缩小参数范围,所以这个时候方法是被覆盖而不是重载。
总的来说,我认为自限定是一个不太实用的技巧,因为我对泛型的理解是增大方法和类的适用范围,但是自限定明显在缩小适用范围。

原创粉丝点击