死磕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
总结

泛型最吸引人的地方是容器类的使用。泛型的引入很大的程度上减少了代码量,使代码变得更加精简。然而,由泛型擦除的缺陷,在泛型代码内部,如果遇到使用具体类型的地方,不能使用泛型,因为你不知道具体的类型是否允许这样做。当然,如果没有在泛型参数中定义泛型的规则,那么持有父对象的容器引用不能指向持有子类对象的容器空间。
泛型是由编译器在编译期间擦除的,对于继承同一泛型的不同变体,在编译期间被擦除后,就会变成相同的类型,会产生编译时异常。对于自限定泛型,给我们的印象是,无限制的反射;在大多数情况下,主要是使用协变参数,也就是方法参数随着子类变化而变化。

、

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

15
678

被折叠的 条评论
为什么被折叠?



