《Thinkinginjava》第15章-泛型

15.1 泛型的出现

15.1.1 为什么要用泛型

一般的类和方法,只能使用具体的类型:要么是基本类型,要么是自定义的类。如果需要编写可以应用于多种类型的代码。这种刻板的限制对代码的束缚就很大。所以从Java SE5开始,泛型出现了,泛型实现了参数化类型的概念,使代码可以应用于多种类型。

15.1.2 什么是泛型

泛型这个术语的意思是:“适用于许多许多的类型”,就是一种宽泛的类型,适用于很多的类型。

泛型可以用于类、接口、方法等

15.2 泛型类

有时候,我们希望一个类可以用于不同类型的对象,通常而言,在使用时用来表示一种类型的对象,泛型就可以用来指定这个类用于什么类型的对象,而且由编译器来保证类型的正确性。

遇到上面的需求时,通常我们会暂时不指定类型,而是稍后使用的时候再决定使用什么类型,要达到这个目的就需要使用类型参数,用尖括号括住,放在类名后面。然后在使用这个类的时候,再用实际的类型替换此参数类型,下面的例子中,T就是类型参数:

/**
 * 泛型类
 *
 * @author huxiong
 * @date 2016/07/07 15:17
 */
public class GenericClass<T> {
    private T field;

    public T getField() {
        return field;
    }

    public void setField(T field) {
        this.field = field;
    }

    @Override
    public String toString() {
        return "GenericClass{" +
                "field=" + field +
                '}';
    }

    public static void main(String[] args) {
        GenericClass<String> s = new GenericClass<>();
        s.setField("gaga");
        System.out.println("s:" + s);
        GenericClass<Integer> i = new GenericClass<>();
        i.setField(100);
        System.out.println("i:" + i);
    }
}

输出:
s:GenericClass{field=gaga}
i:GenericClass{field=100}

GenericClass类可以用于任意类型的对象,它是一个泛型类,我们只要在使用的时候指定其具体类型就可以了。

15.3 泛型接口

同样,泛型也可以用于接口,例如生成器,这是一种专门负责创建对象的类,这也是工厂方法设计模式的一种应用,不过在使用时,不需要任何参数,下面是一个制造玩具的程序:

/**
 * 生产接口
 *
 * @param <T>
 */
interface Builder<T> {
    T next();
}

/**
 * 玩具
 */
class Toy {
    private static long counter = 0;
    private final long id = counter++;

    @Override
    public String toString() {
        return getClass().getSimpleName() + id;
    }
}

/**
 * 玩具狗
 */
class DogToy extends Toy {
}

/**
 * 玩具猫
 */
class CatToy extends Toy {
}

/**
 * 玩具熊
 */
class BearToy extends Toy {
}

/**
 * 玩具制造器
 */
class ToyBuilder implements Builder<Toy>, Iterable<Toy> {
    private Class[] types = {DogToy.class, CatToy.class, BearToy.class};
    private int size = 0;//for iteration
    private final Random rand = new Random(47);

    public ToyBuilder() {
    }

    public ToyBuilder(int size) {
        this.size = size;
    }

    @Override
    public Toy next() {
        try {
            // make a toy randomly
            return (Toy) types[rand.nextInt(types.length)].newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 自定义迭代器
     */
    class ToyIterator implements Iterator<Toy> {
        int count = size;

        @Override
        public boolean hasNext() {
            return count > 0;
        }

        @Override
        public Toy next() {
            count--;
            return ToyBuilder.this.next();
        }

        @Override
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

    @Override
    public Iterator<Toy> iterator() {
        return new ToyIterator();
    }

    public static void main(String[] args) {
        ToyBuilder tb = new ToyBuilder();
        for (int i = 0; i < 3; i++) {
            System.out.println(tb.next());
        }
        System.out.println("------------------------");
        for (Toy t : new ToyBuilder(3)) {
            System.out.println(t);
        }
    }
}

输出:
BearToy0
BearToy1
CatToy2
------------------------
BearToy3
BearToy4
CatToy5

15.4 泛型方法

泛型方法所在的类可以是泛型类也可以不是泛型类,也就是说是否拥有泛型方法,与其所在的类是否是泛型没有关系。

泛型方法的定义就是将泛型参数列表置于返回值之前,像这样:

/**
 * 泛型方法
 * @author huxiong
 * @date 2016/07/07 15:00
 */
public class GenericMethod {
    public <T> void f(T x){
        System.out.println(x.getClass().getName());
    }

    public static void main(String[] args) {
        GenericMethod gm = new GenericMethod();
        gm.f("baby");
        gm.f(1);
        gm.f(3.0f);
        gm.f(5.0d);
        gm.f(gm);
    }
}

输出:
java.lang.String
java.lang.Integer
java.lang.Float
java.lang.Double
com.mrdios.competencymatrix.java.readingnotes.ThinkingInJava.chapter15.GenericMethod

通过程序我们看到,当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法时通常不需要,因为编译器会为我们找出正确的类型。这成为类型参数推断

15.5 泛型擦除

在泛型代码中,无法获得任何有关泛型类型参数的信息。

因此,我们可以知道如类型参数标识符和泛型类型边界这类的信息,但是却无法知道用来创建某个特定实例的实际的类型参数。之所以会这样,是因为java泛型是使用擦除来实现的,当使用泛型时,任何具体的类型信息都被擦除了,唯一知道的就是在使用一个对象,因此List<String>List<Integer>在运行时事实上是相同的类型,这两种形式都被擦除成它们的“原生”类型List

class SuperMan {
    public void fly() {
        System.out.println("超人在飞...");
    }
}

class Hero<T> {
    private T someone;

    public Hero(T someone) {
        this.someone = someone;
    }

    public void savePeople() {
//        someone.fly(); // 此句不能通过编译
    }
}

class Hero2<T extends SuperMan> {
    private T someone;

    public Hero2(T someone) {
        this.someone = someone;
    }

    public void savePeople() {
        someone.fly(); // 此句通过编译
    }
}

public class GenericWipe {
    public static void main(String[] args) {
        SuperMan sm = new SuperMan();
        Hero2<SuperMan> hero = new Hero2<>(sm);
        hero.savePeople();
    }
}

就以上程序而言,由于有了擦除,java编译器无法将savePeople()必须能够在someone上调用fly()这一需求映射到SuperMan拥有fly()这一事实上。所以Hero中的savePeople()将不能通过编译,为了调用fly(),我们必须协助泛型类,给定泛型的边界,以告知编译器只能接收遵循这个边界的类型,所以Hero2中相应的代码编译通过,

编译器实际上会把类型参数替换为它的擦除,就像以上程序中一样,T擦除到了SuperMan,就好像在类的声明中用SuperMan替换了T一样。

15.5.1 擦除与迁移兼容性

擦除并不是一个语言特性,它是java的泛型实现中的一种折中,因为java刚出现时并没有这个组成部分,所以这种折中也是必须的。

在基于擦除的实现中,泛型类型被当做第二类类型处理,就是不能再某些重要的上下文环境中使用的类型。泛型类型只有在静态类型检查期间才出现,在此之后程序中所有的泛型类型都将被擦除,替换为他们的泛型上界,如List<T>被替换成List

擦除也是java语言的一种“迁移兼容性”,如果某个类库要迁移到泛型上的话,为了实现迁移兼容性,这个类库和应用程序都必须与其他所有的部分是否使用了泛型无关。它们必须不具备探测其它类库是否使用了泛型的能力,所以必须将这些证据“擦除”。

15.6 使用泛型的问题

  • 任何基本类型都不能作为泛型参数,诸如List<int>,可以使用包装类如List<Integer>
  • 一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口,如interface A<T>,class B implements A<B>,class C extends B implements A<C>,此时C将不能编译,因为擦除会将A<B>A<C>简化为相同的类A
  • 在泛型中,拥有方法void f(List<T> v)void f(List<W> v)的一个类UserList<T,W>是不能编译通过的,因为由于擦除的原因,这个两个重载方法将产生相同的类型签名,所以这时候必须提供有明显区别的方法名。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值