Java基础之泛型

一般的类或方法,只能使用具体类型:要么是基本类型,要么是自定义类型。如果要编写应用于多种类型的代码,这种刻板的限制对代码的束缚很大。有时候我们希望编写一个更通用的代码,使得代码能够应用于“某种不具体类型”,而不是具体的接口或类。--所以使用泛型。

下面举一些例子说明泛型的基本用法。
泛型类

package com.john.generic;

public class Pair<T> {

    public final T a;
    public final T b;
    protected Pair(T a, T b) {
        super();
        this.a = a;
        this.b = b;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "Pair [a=" + a + ", b=" + b + "]";
    }
    public static void main(String[] args) {
        System.out.println(new Pair<String>("hi", "hello"));
    }
}

泛型的主要目的之一:用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。
上述Pair类的类型参数T最后被擦除成Object,有编译器负责在边界插入类型转换代码。

泛型接口

package com.john.generic;
//定义一个泛型接口,工厂接口,用于产生T类型对象
public interface Factory<T> {

    T create();
}

package com.john.generic;
//Factory实现类,Integer对象工厂
public class IntegerFactory implements Factory<Integer> {

    @Override
    public Integer create() {
        return new Integer(0);
    }

}
//持有T类型对象的类,需要从对象工厂中获得对象,切记这里不能使用T t= new T();来生成对象,因为类型信息被擦除。
public class Foo<T> {

    //假设这个类要使用T这个实例,不能使用 T t=new T() 由于运行时类型擦除。
    //只能使用Class的newInstance()方法。
    private T t;
    //传入的参数可以是Factory接口的实现类
    public <F extends Factory<T> > Foo(F factory) {
        t=factory.create();
    }
    @Override
    public String toString() {
        return "t:"+t;
    }
    public static void main(String[] args) {
        Factory factory=new IntegerFactory();
        Foo<Integer> foo=new Foo<>(factory);
        System.out.println(foo);
    }
}

泛型方法

package com.john.generic;

public class GenericMethods {

    public static  <T> void f(T t)
    {
        System.out.println(t.getClass().getName());
    }
    public static void main(String[] args) {
        //使用泛型方法时通常不用指定类型参数,好像f()被无限次重载过,实际上编译器会根据输入参数为我们找出具体的参数类型,这个叫类型参数推断。
        //类型推断只对赋值操作有效,其他时候不起作用。
        f("");
        //有些情况编译器无法正确推断类型参数时,我们可以显示的指定它
        GenericMethods.<Double>f(1.0);
    }
}

上面举例类一些泛型的简单实用,下面介绍一下擦除的神秘之处


接上面Pair<T>做一个测试

    public static void main(String[] args) {
        Pair<String>p=new Pair<String>("hi", "hello");
        System.out.println(p.getClass().getTypeParameters()[0]);
    }

结果居然返回了T,即仅仅返回了占位符标识,并没有有用的信息。我之前做过一个类似 class Pair2 extents Pair<String>{}如何在Pair2对象中获得泛型String.class,可以参考 java 基础之ParameterizedType
回归正题,也就是Pair<String>最终编译后为Pair.class而不是Pair<String>.class最后在写代码时,Pair对象中的T无法使用真实类型中的方法,由于类型被擦除,若想使用T中的方法或数据,必须给T设置边界,具体怎么设置边界看一个例子。

public class Animal {
   public void eat(){
       System.out.println("i'am animal ,i'am eating food...");
   }
}

public class Cat extends Animal{
    public void eat(){
           System.out.println("i'am cat ,i'am eating food...");
       }
    public void miao(){
        System.out.println("miao~~~");
    }
}

//动物商店
public class AnimalStore<T extends Animal> {

    //动物
    private T t;

    protected AnimalStore(T t) {
        super();
        this.t = t;
    }
    //让动物商店的动物吃饭
    public void eat(){
        //若把T extends Animal 改成 T将不能调用t.eat(),因为类型参数会被擦除到第一个边界
        t.eat();
    }
    public static void main(String[] args) {
        AnimalStore<Cat> store =new AnimalStore<>(new Cat());
        store.eat();
    }
}

泛型类型只有在静态类型检查期间才出现,在之后,程序中的所有泛型类型都将被擦除,替换为非泛型上界,如Pair<T>被擦除为Pair,即T变为Object,而AnimalStore<T extends Animal>中的T被擦除到Animal,还可以使用期Animal中申明的方法。

上面介绍了Java泛型一些奇怪的擦除,下面谈谈它将产生一些问题,以及如何解决


1、 instance of
arg instance of T尝试最终将失败,解决方法就是使用Class中的动态的isInstance()方法
2、 new 创建一个实例
new T()无法实现,可以传递T的Class进去,使用Class 中的 newInstance()方法,这就必须保证该类必须有无参构造器,否则另一个实现方法就是如上面讲得利用工厂方法传递一工厂进去,由工厂来创建对象。
3、 泛型数组
T[] array=new T[]是错误的,若T[] array=(T[])new Object[Size]照样无法转型为T[],因为数组在穿监事,类型就被确定,所以这里为Object[]。一般解决方法时避免使用泛型数组,使用ArrayList。另一种解决方法是(T[])Array.newInstance(ClassType,size);,这里的数组类型为ClassType[]类型。


谈谈通配符

我们知道数组是可协变的即Object[] array=String[]是可行的的(题外话,如果大家对协变和逆变不是很理解可以看再谈对协变和逆变的理解)但是Java泛型是不支持协变的,由于类型擦除之后,泛型是没有内建的协变类型,即List<Object> l=new ArrayList<String>()是不能通过编译的,即使String是Object的子类但是不行。那怎么解决呢?

List<? extends Object> l =new ArrayList<String>()可以读作‘具有任何从Object继承的类型的列表’。但是,这实际上并不意味着List可以持有任何类型的Object。通配符引用的是明确的类型,它意味着‘l 引用某种指定的具体类型’。在这里? 指定的是String类型。在举个例子假设Fruit的子类有Apple和Orange。那么List<? extends Fruit> fruit=new ArrayList<Apple>();这样可以从ArrayList<Apple>()取出Fruit类型,有点类似前面介绍的边界<T extends Fruit>但是通配符只能有一个边界。由于fruit是给定的“任何扩展自Fruit的队象”,所以编译器无法验证“任何事物的安全性”所以extends 确认的上界使得可以从中取出Fruit类型的对象,但是不能往里放任何东西。相反下面要说的上界则恰恰相反。

下面讲讲逆变
使用超类型通配符<? extends Apple>那么 Apple就是上界例如如下

   static <T> void f1(List<T> list,T t)
    {
        list.add(t);
    }
    static <T> void f2(List<? super T> list,T t)
    {
        list.add(t);
    }


f1(l,new Cat());
f2(l,new Animal());
按照think in java 中表述第一条语句是编译不通过的,但我在Java1.8版本中却可以,估计是改进了。有待考究。。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值