死磕Java之泛型(二)

死磕Java之泛型(二)

在<<死磕Java之泛型(一)>>中,已经简单的讲解了泛型的概念,泛型原理以及泛型边界和通配符。本文将讲解泛型的高级用法和弥补泛型带来的缺陷。

01

泛型带来的问题

    Java引入泛型的目的是呈现编译时多态。尽管泛型的引入,给编码带来了很多便利之处,但是往往带来很多困惑。

    泛型主要的限制之一是,不能使用基本类型,例如int、long等作为泛型参数。不过对于这一限制,我们可以使用基本类型的包装类型,拆箱动作可以交给JVM虚拟机来完成。

    由于泛型是编译时多态,所以需要特别注意的是,下面的代码是不能编译的:

代码一:

public interface Generic <T>{
};
//程序猿技术
class A implements Generic<String>{}
//编译错误
//class B extends A implements Generic<Long>{}

    由于擦除的原因,一个类实现了同一泛型接口的变体。这样导致了编译时异常,不可思议的是,当擦除方向参数时,上面的代码可以编译通过。

02

自限定类型

    在Java的源码解析中,常常会看到以下让人困惑的代码

代码二:

class SelfBounded<T extends SelfBounded<T>>

    这就像两面镜子互相照射,是一种无限反射。我们可以这样理解,SelfBounded类接受泛型参数,什么样的泛型参数呢?这个泛型参数边界是SelfBounded。

代码三:

public  class Generic <T extends Generic<T>> {
    private T arg;

    public T get() {
        return arg;
    }

    public void set(T args) {
        arg = args;
    }
}

class B extends Generic<B>
{
    public static void main(String[] args)
    {
        B b1=new B();
        b1.set(new B());
    }
}

    从代码可以看出,泛型参数类成为了导出类(这里的代码是B)的公共模板,这些函数使用导出类作为参数和返回值。

    自限定类型的本质是基类用导出类作为其参数。不同以往的其他泛型类,自限定类型必须强制自限定类型作为边界类来使用。

代码四:

public  class Generic <T extends Generic<T>> {}
class B extends Generic<B> {}
class C  extends  Generic<B>{}
class D {}
//编译错误
//class E extends Generic<D>{}
class F extends Generic{}

    自限定类型的主要用法类似于类A的用法,这强制要求定义的类作为泛型参数传递给基类(也就是需要被擦除的T)。

    自限定类型的主要的价值是产生协变参数类型,也就是方法参数类型随着子类而变化。由于自限定类型是主要的用法是定义的类作为泛型参数来进行传递,所以在协变参数类型。你可能可以给出如下的代码:

代码五:

interface Genenator<T extends Genenator<T>>{ T get();}
interface Foo extends Genenator<Foo>{}

    在泛型引入之前,可以使用多态,即实现多个接口,接口参数的类型不一样来实现,尽管这样也是实现协变参数的可行方法之一,如果当方法变多,在软件设计领域可能变得不现实。你可能写下如下的代码

代码六:

class  OrdinarySetter
{
    public void set(Hold2 hold)
    {
        System.out.println("the OrdinarySetter set(Hold2)");
    }
}

class  DerivedSetter extends OrdinarySetter
{
    public void set(Account account)
    {
        System.out.println("the DerivedSetter set(Account)");
    }
}

public class Patala {
    public static void main(String[] args)
    {
        Account account=new Account("bob",23,"wer");
        Hold2 hold2=new Hold2(new Object());
        new DerivedSetter().set(account);
        new DerivedSetter().set(hold2);
    }
}

    main方法中的两个set都是合法的,可见只是简单的重载。如果使用自限定泛型参数来实现,可以这样

代码七:

interface SelfBounded<T extends SelfBounded<T>>{ void set(T t);}

interface Setter extends  SelfBounded<Setter>{};
public class Patala {

    public void test(Setter s1,Setter s2,SelfBounded sb)
    {
        s1.set(s2);
        //编译错误
        //s1.set(sb);
    }
}

 

03

总结

    泛型最吸引人的地方是容器类的使用。泛型的引入很大的程度上减少了代码量,使代码变得更加精简。然而,由泛型擦除的缺陷,在泛型代码内部,如果遇到使用具体类型的地方,不能使用泛型,因为你不知道具体的类型是否允许这样做。当然,如果没有在泛型参数中定义泛型的规则,那么持有父对象的容器引用不能指向持有子类对象的容器空间。

    泛型是由编译器在编译期间擦除的,对于继承同一泛型的不同变体,在编译期间被擦除后,就会变成相同的类型,会产生编译时异常。对于自限定泛型,给我们的印象是,无限制的反射;在大多数情况下,主要是使用协变参数,也就是方法参数随着子类变化而变化。

2019_02_22_1933083452.png

点击上方二维码,关注我们

15

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值