15 泛型 Generics

一般的类型和方法, 只能使用具体的类型: 要么是基本类型, 要么是自定义的类. 如果要编写可以应用于多种类型的代码, 这种刻板的

限制对代码的束缚就会很大

在面向对象编程中, 多态算是一种泛化机制, 你可以在方法的参数里使用基类的引用, 那么这个方法就可以使用这个基类的任何派生

类做参数, 如果是一个接口做参数, 限制就更小, 甚至可以写一个还不存在的类, 客户端程序员可以自己实现一个接口的实现类来满足

类或方法

但接口的限制还是太大了, 一旦指明了接口, 就必须用某种特定的接口或其实现类, 我们想要的是, 某种不具体的类型, 而不是某个具

体的类或接口

泛型实现了参数化类型的概念

泛型这个术语的意思是, 适用于许多许多的类型

 

  • 简单泛型

把成员设置成Object类型的,初始化,赋值,取值的时候可以用不同的类型来定义这个成员

public class A {

      private Object a;

      A(Object a) { this.a = a; }

     public void set(Object a) { this.a = a; }

     public Object get() { return a; }

}

 

使用类型参数T, 声明类的时候写在类名后面<T>

public class B<T> {

     private T a;

     A(T a) { this.a = a; }

     public void set(T a) { this.a = a; }

     public T get() { return a; }

}

 

这就是Java泛型的核心概念: 告诉编译器想使用什么样的类型, 然后编译器来处理一切的细节

一般我们可以把泛型看成和其他类型差不多, 只不过多了个类型参数

 

元组是将一组对象直接打包存储于其中的一个单一对象,这个容器对象允许读取其中的元素, 但不允许存入新元素

元组可以取任意长度, 每个对象可以是不同的类型, 声明对象时, 我们希望确定每一个元组中元素的具体类型

class X<A,B>

class Y<A,B,C> extends X<A,B>

static X<String,Integer> f() { return new X<String,Integer>("str",12); }

泛型实现链表型栈

class Stack<T> {

      private class Node<U> {

            U item;

            Node<U> next;

            Node() { this.item = null; this.next = null; }

            Node(U item, Node<U> next) {

                  this.item = item;

                  this.next = next;

           }

           public boolean end() { return item == null && next == null; }

           //这是一个末端哨兵,用来判断栈何时为空

      }

      private Node<T> top = new Node<T>();

      public void push(T item) {

             top = new Node<T>(item, top);

      }

      public T pop() {

              T result = top.item;

              if(!top.end())

                    top = top.next;

             return result;

      }

}

每个Stack对象里有一个节点Node,里面存了一个泛型的数值和一个表示下一个节点的对象

  • 泛型接口

生成器是一种创建对象的类, 属于工厂设计模式, 但它不需要参数

generator

public interface Generator<T> { T next(); }

public class CoffeeGenerator implements Generator<Coffee> {

       public Coffee next() {

              return (Coffee)types[rand.nextInt(types.length)].newInstatnce();

      }

}

  • 泛型方法

public <T> f(T x) {}

可以在类中包含参数化方法, 泛型方法, 与其所在的类是否是泛型没有关系

无论何时, 只要你能做到, 你就应该尽量使用泛型方法, 如果只用泛型方法就可以, 没必要在类上加泛型

泛型类在自己创建对象时, 必须指定一个具体的类型, 泛型方法则不需要,

编译器会根据你传递进来的参数为你找出具体的类型, 这叫做类型参数推断

class New {

      public static <U,V> Map<U,V> map() { return new HashMap<U,V>(); }

}

Map<String,List<String>> map = New.map();

类型参数推断可以用于简化这种容器类型的创建

但是它只能用于赋值操作, 如果把它当做方法中的参数, 是不能自动识别成方法中所需要的参数的

在泛型方法中, 可以显式地指明类型

public static void f(Map<String,List<String> map) {}

f(New.<String,List<String>.map());

一个通用的生成器, 可以通过泛型方法实现, 传入一个类型的class对象, 从而创建这个类型的对象

public class BasicGenerator<T> implements Generator<T> {

      private Class<T> type;

      public BasicGenerator(Class<T> type) { this.type = type; }

      public T next() {

            try {

                  return type.newInstance();

           } catch(Exception e) {

                 throw new RuntimeException(e);

           }

      }

      public static <T> Generator<T> create(Class<T> type) {

              return new BasicGenerator<T>(type);

      }

}

public void test() {
      Generator<X> gen = BasicGenerator.create(X);

      for(int i = 0;i < 5;i++)

             print(gen.next());

      }

}

匿名内部类

泛型还可以运用于内部类以及匿名内部类

class Customer {

      private Customer() {}

      public static Generator<Customer> generator() {

            return new Generator<Customer>() {

                   public Customer next() { return new Customer(); }

            }

      }

}

  • 擦除

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

Java泛型是用擦除来实现的, 在使用泛型时, 任何的类型信息都被擦除了

因此List<String>和List<Integer>在运行时实际上是相同的类型

Class.getTypeParameters()可以返回一个TypeVariable对象数组, 表示泛型声明所声明的类型参数

但它只返回做参数占位符的标识符, 并不返回你实例化时传进去实际的类型参数orz

class Exam { public void f(); }

class Gen<T> {

      private T obj;

      Gen(T obj) { this.obj = obj; }

      public void g() { obj.f(); }

      //这里编译不通过, obj不知道它的类型的边界, 所以不能确定它可以调用f()方法

}

class Gen<? extends Exam> {

      private T obj;

      Gen(T obj) { this.obj = obj; }

      public void g() { obj.f(); }

      //这样就能通过了, <? extends Exam>把泛型的类型参数擦除到了它的第一个边界, 即Exam, 就可以调用Exam中的方法了

}

擦除减少了泛型的泛化性, 泛型类型被当作第二类类型处理, 即不能在某些重要的上下文环境中使用, 只有在静态类型检查期间才

出现

泛型不能用于显式地引用运行时类型的操作, 比如new, instanceof, 转型, 实际上

你只是看上去拥有有关参数的类型信息而已

class Fool<T> {

   private T obj;

   public T f() {  return obj; }

}

这时的T对你来说, 就是一个Object, 没有更多了

泛型可以创建毫无意义的事物

private Class<T> kind;

T[] create(int size) {
        return (T[])Array.newInstance(kind, size); 

}

使用Array.newInstance()创建泛型数组是好的

  • 擦除的补偿

1.使用类型标签创建对象

class ClassAsFactory<T> {

      T x;

      public ClassAsFactory((Class<T> kind)) {

            try {

                  x = kind.newInstance();

            } catch(Exception e) {

                  throw new RuntimeException(e);

            }

      }

}

使用Class.newInstance()为类型创建对象

这种方式, 如果用在像Integer这样没有默认构造器的类上就会出错

2.使用显式的工厂

为每种想要创建对象的类型实现工厂接口

interface Factory<T> {

      T creat();

}

class IntegerFactory implements Factory<Integer> {

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

class Foo<T> {

      private T x;

      public <F extends Factory<T>> Foo(F factory) {

             x = factory.create();

     }

}

new Foo<Integer> = new Foo(new IntegerFactory());

用相同的或派生类的工厂类对象去执行相应的create, 从而正确地产生对象

3.模板方法

abstract class GenericWithCreator<T> {
    final T element;
    GenericWithCreator() { element = create(); }
    abstract T create();
}
class X {}
class Creator extends GenericWithCreator<X> {
    X create() { return new X(); }
    void f() {
        System.out.println(element.getClass().getSimpleName());
    }
}

泛型数组

直接返回的泛型将丢失类型, 必须加上类型标记

class A<T> {
    private T[] array;

    public A(Class<T> type, int sz) {  

        array = (T[])Array.newInstance(type, sz);

        //这里的T[]在运行时是被擦除的,只剩下生成的Object[], 所以这里必须指定type, 使其具有特定类型

    }

    public void put(int index, T item) {

        array[index] = item;

    }

    public T get(int index) { return array[index]; }

    public T[] rep() { return array; }

  • 边界

边界使得你可以在用于泛型的参数类型上设置限制条件, 你可以按照自己的边界类型来调用方法

如果将这个参数设定为某个类型子集, 就可以用这些类型子集来调用该类型下的方法, 访问它的成员

class Dimension (

    int x;

    public void weight() {}

}

public Color { public void color() {} }

class GenericDimension<T extends Dimension & color> {

    T item;

    public GenericDimension(T item) { this.item = item; }

    public int getX() { return item.x; } 

    public void weight() { item.weight(); }

    public void color() { item.color(); }

}

可以extends多个类型, 每个类型下的方法都可以调用

  • 通配符

public class Holder<T> {
    private T value;
    public Holder() {}
    public Holder(T val) { value = val; }
    public void set(T val) { value = val; }
    public T get() { return value; }
    public boolean equals(Object obj) {
        return value.equals(obj);
    }
    public static void main(String[] args) throws Exception {
        Holder<Apple> Apple = new Holder<Apple>(new Apple());
        //由于知道T是APPLE 可以get一个Apple类型的引用, 也可以向上转型为Fruit类型的引用
        Apple d = Apple.get();
        Fruit f = Apple.get();
        //Orange o = Apple.get();
        
        //由于知道T是APPLE 可以传入一个Apple类型的引用, 但不能传入Fruit类型
        Apple.set(d);
        //Apple.set(f);
        
        //Apple是一个Holder<Apple>类型的引用 可以向上转型为一个Holder<? extends Fruit>型引用
        Holder<? extends Fruit> fruit = Apple;
        //T是 ? extends Fruit, 可以返回一个Fruit类型的引用
        Fruit p = fruit.get();
        //Apple a = fruit.get();
        d = (Apple)fruit.get();
        try {
            Orange c = (Orange)fruit.get();
        } catch(Exception e) {
            System.out.println(e);
        }
        //T是 ? extends Fruit, set方法要接受一个T, 但不知道T具体的类型, 因为可以是任何名字
        //fruit.set(new Apple());
        //fruit.set(new Fruit());
        System.out.println(fruit.equals(d));
    }
}

逆变

<? super MyClass>通配符是由某个特定类的任何基类来决定的

 

public class GenericReading {
    static <T> T readExact(List<T> list) {
        return list.get(0);
    }
    static List<Apple> apples = Arrays.asList(new Apple());
    static List<Fruit> fruit = Arrays.asList(new Fruit());
    static void f1() {
        Apple a = readExact(apples);
        Fruit f = readExact(fruit);
        f = readExact(apples);
    }

    //静态的泛型方法readExact可以有效地适应每个方法调用, 它能精确地返回所需要类型的对象
    static class Reader<T> {
        T readExact(List<T> list) {
            return list.get(0);
        }
    }
    static void f2() {
        Reader<Fruit> fruitReader = new Reader<Fruit>();
        Fruit f = fruitReader.readExact(fruit);
        //Fruit a = fruitReader.readExact(apples);
    }

    //一个泛型类中的方法readExact, 声明时为它指定了类型Fruit, 所以它只返回Fruit类型的对象, 但不允许传入一个List<Apples>来返回Fruit对象, 因为List<Fruit>和List<Apple>之间没有直接关系
    static class CovarianReader<T> {
        T readCovariant(List<? extends T> list) {
            return list.get(0);
        }
    }
    static void f3() {
        CovarianReader<Fruit> fruitReader = new CovarianReader<Fruit>();
        Fruit f = fruitReader.readCovariant(fruit);
        Fruit a = fruitReader.readCovariant(apples);
    }

    //这里相较于上一个方法, 泛型参数改成了List<? extends T>, 也就表示, 声明为Fruit类型时, 可以传入Fruit或者其派生类型的对象, 最终都会返回一个T, 也即Fruit类型的对象, 所以这里就可以用Fruit的派生类Apple类的List做参数了
    public static void main(String[] args) throws Exception {
        f1();f2();f3();
    }
}

捕获转换

static <T> void f1(Holder<T> holder) {
    T t = holder.get();

}
static void f2(Holder<?> holder) {
    f1(holder); //捕获holder的具体类型
}

Holder<?> holder = new Holder<Double>();

必须给定一个确定的类型, 在f2()中不能返回T, 因为T对f2()来说是未知的

  • 泛型的缺点

转型

class X<T> {

      private Object[] array;

      private int index = 0;

      public T pop() { return (T)array[--index]; }

}

这个转型会有未检查转型的警告, 因为泛型被擦除为Object, 所以不知道这个转型是不是安全的

直接用泛型转型会有警告, 不转型又要求转型

List<Widget> list = (List<Widget)in.readObject();

readObject只能返回Object类型, 必须要转型, 可以直接这样转型又不能确定是安全的转型

List<Widget> list = List.class.cast(in.readObject());

用Class.class.cast()来代替显式的转型

重载

class X<W,V> {

    public f(W w) {}

    public f(V v) {}

}

重载f, 但是由于擦除, W和V都没了, 都是f, 看起来是一样的

  • 自限定

古怪的循环泛型CRG

public class BasicHolder<T> {

    T element;

    void set(T arg) { element = arg; }

    T get() { return element; }

}

这是一个普通的泛型类

class Subtype extends BasicHolder<Subtype> {}

public f() {

    Subtype st1 = new Subtype(), st2 = new Subtype();

    st1.set(st2);

    Subtype st3 = st1.get();

}

从这里可以看出, 所有基类的方法都接收导出类型做为参数, 并返回导出类型作为返回类型, 它是一种导出类的公共模板, 可以让导

出类对象创建自己这个类的对象, 有点像链表结构, 每个结点对象里面包含一个next表示下一个节点对象

自限定

class SelfBounded<T extends SelfBounded<T>> {}

自限定就是强制要你在继承关系中, 使用

class A extends SelfBounding {}

还可将自限定用于泛型方法

static <T extends SelfBounded<T>> T f(T arg) {

    return arg.set(arg).get();

}

A a = f(new A());

当使用自限定时, 基类别中的方法就被限定了必须用子类做参数, 而不能传入基类对象做参数

使用普通的泛型时, 由于传入的泛型类型可以是基类, 那么基类中的方法就可以接收基类对象, 方法可以被重载

class SelfBounded<T extends SelfBounded> {

    void set(T arg) {}

}

class A extends SelfBounded<A> {}

这里的泛型T被指定了只能用A的对象

class GenericSetter<T> {

     void set(T arg) {}

}

class DerivedGS extends GenericSetter(Base) {

    void set(Derived arg) {}

}

这里set被重载, 可以set(Derived)或者接受泛型set(Base)

  • 异常

interface Processor<T,E extends Exception> {
    void process(List<T> resultCollector) throws E;
}

class X<T,E extends Exception> {

      public void process() throws E {}

}

class Failure  extends Exception {}

class Sub<String, Failure> implements Processor<String, Failure> {

      public void process(List<String> list) throws Failure {}

}

通过以Exception做边界的异常泛型, 加入异常类

  • 混型

混合多个类的能力, 以产生一个可以表示混型中所有类型的类

C++中可以通过参数化类型来实现混型, 因为混型就是继承自其类型参数的类

Java中由于有擦除, 所以不能直接用泛型实现混型, 可以通过接口, 实现多个接口, 并对每一个混入的类型, 加入一个相应的域, 将

接口方法转发给恰当的对象, 使用代理来完成

  • 潜在类型机制

泛型类型只要求实现某个方法子集, 而不是某个特定的类或接口, 它使你可以横跨继承结构, 调用不属于某个公共接口的方法, 即不

关心具体的类型, 只要符合方法定义就可以调用

在Java中实现潜在类型机制, 就必须使用一个类或者接口, 并且必须在边界表达式中指定它

interface Performs {

    void speak();

    void sit();

}

class Dog implements Performs {

    public void speak() {}

    public void sit() {}

}

class Robot implements Performs [

    public void speak() {}

    public void sit() {}

}

class Communicate {

    public static <T extends Performs>

    void perform(T performer) {

        perform.speak();

        perform.sit();

    }

}

public static void main(String[] args) {

    Dog d = new Dog();

    Robot r = new Robot();

    Communicate(d);

    Communicate(r);

}

Dog和Robot被强制需要实现Performs类, 之后才可以使用这个接口里面这些通用的方法

 

可以使用映射实现潜在类型机制

class CommunicateReflectively {

     public static void perform(Object spker) {
        Class<?> spkr = speaker.getClass();

     try {

            try {

                       Method speak = spkr.getClass("speak");

                       speak.invoke(speaker);

           } catch(NoSuchMethodException e) {}

           try {

                       Method sit= spkr.getClass("sit");

                       speak.invoke(speaker);

           } catch(NoSuchMethodException e) {}

     } catch(Exception e) {}

试着创建一个apply方法, 它的作用是将任何一个方法作用于任何一个序列之中

class Apply {

    public static <T, S extends Iterable<? extends T> 

    void apply(S seq, Method f, Object... args) {

        try {

                for(T t : seq) 

                    f.invoke(t, args);

        } catch(Exception e) {}

}

这个序列seq定义为实现了Iterable<? extends T>的类型, T是它的基类型, 故它可以通过foreach遍历, 也可以调用任意方法, 且不

用在意序列的类型(只要是实现了Iterable的), 是真正的泛化实现潜在类型机制

  • 将函数对象用作策略

使用策咯设计模式, 把变化的事物完全隔离到一个函数对象中, 函数对象就是在某种程度上行为像函数的对象

函数对象的价值就在于, 它可以被传递出去, 并且拥有在多个调用之间持久化的状态

Interface Combiner<T> { T combine(T x, T y); }

public static <T> T

reduce(Iterable<T> seq, Combiner<T> combiner) {

    Iterator it = seq.iterator();

    if(it.hasNext()) {

         T result = it.next();

         while(it.hasNext())

             result = combiner.combine(result, it.next());

        return result;

    }

    return null;

}

static class IntegerAdder implements Combiner<Integer> {

    public Integer combine(Integer x, Integer y) {

         return x + y;

    }

}

List<Integer> li = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

Integer result = reduce(li, new IntegerAdder());

print(result)

//output: 28

 

泛型的目的在于可表达性, 而不仅仅是为了创建类型安全的容器

因为泛型不是一开始就添加到Java中的, 所以某些容器无法达到它们应该具有的健壮性

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值