参数协变

不得不吐槽一下,看《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类自身的方法,很好理解,自限定的作用就是缩小参数范围,所以这个时候方法是被覆盖而不是重载。
总的来说,我认为自限定是一个不太实用的技巧,因为我对泛型的理解是增大方法和类的适用范围,但是自限定明显在缩小适用范围。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值