Java编程思想 第15章泛型 学习笔记

基本定义

  • 泛型实现了参数化类型的概念,是代码可以应用于多种类型。“泛型”这个术语的意思是:“应用于许多许多的类型”
  • 在你创建参数化类型的一个实例时,编译器会为你负责转型操作,并且保证类型的正确性,这应该是一个进步

简单的泛型

  • 当创建Holder3对象时,必须指明想要持有什么类型的对象,将其置于尖括号内。

  • 在存入和取出指定对象的时候,就会自动选择该类型。

    public class Holder3<T> {
      private T a;
    
      public Holder3(T a) {
        this.a = a;
      }
    
      public T getA() {
        return a;
      }
    
      public void setA(T a) {
        this.a = a;
      }
    
      public static void main(String[] args) {
        Holder3<Fruit> fruitHolder3 = new Holder3<Fruit>(new Fruit());
        Fruit a = fruitHolder3.getA();  // 无需强转,直接使用
      }
    }
    
    class Fruit {
      String name;
    }
    
  • 自定义一个元组类库,类似于python中的元组概念。无法修改,但可以一次性返回多个值。

    public class TwoTuple<A, B> {
      public final A first;
    public final B second;
    
      public TwoTuple(A first, B second) {
        this.first = first;
        this.second = second;
      }
    
      @Override
      public String toString() {
        return "(" + first + "," + second + ")";
      }
        
      public static void main(String[] args) {
        TwoTuple<Fruit, Fruit> fruitFruitTwoTuple = new TwoTuple<>(new Fruit(), new Fruit());  // 使用
      }
    }
    

    利用final声明对象,因此无法用户无法修改该对象,因此直接使用public使用即可

    public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
      public final C third;
    
      public ThreeTuple(A first, B second, C third) {
        super(first, second);
        this.third = third;
      }
    
      @Override
      public String toString() {
        return "(" + first + "," + second + "," + third + ")";
      }
    
      public static void main(String[] args) {
        ThreeTuple<Integer, Integer, Integer> tuple = new ThreeTuple<>(1, 2, 3);
        System.out.println(tuple);
      }
    }
    
  • 一个堆栈类,自己实现stack

    public class LinkedStack<T> {
      // 内部类,节点
      private class Node<U> {
        U item;
        Node<U> next;  // 下一个节点
    
        // 构造器,设置值及下一个节点
        public Node(U item, Node<U> next) {
          this.item = item;
          this.next = next;
        }
    
        public Node() {
          this.item = null;
          this.next = null;
        }
    
        //判断是否为空,即最后一个节点 
        boolean end() {
          return item == null && next == null;
        }
      }
    
      private Node<T> top = new Node<>();  // top指向末端哨兵。后面top一直往后移动,指向栈顶。该节点就是一直存在于栈底,叫做末端哨兵
    
      public void push(T item) {
        top = new Node<T>(item, top);  // 新节点指向栈顶。top移动,指向最新节点,即栈顶
      }
    
      public T pop() {
        T item = top.item;  // 获取当前栈顶元素
        if (!top.end())  // 如果不是最后一个,那么还有会next,将top移动到next
          top = top.next;
        return item;  // 返回当前栈顶元素
      }
    
      public static void main(String[] args) {
        LinkedStack<String> stack = new LinkedStack<>();
        for (String s : "hello world ni hao shi jie".split(" ")) {
          stack.push(s);  // 遍历push
        }
    
        String s;
        // 遍历pop,直到末端哨兵
        while ((s = stack.pop()) != null) {
          System.out.println(s);
        }
      }
    }
    

    使用末端哨兵来判断堆栈何时为空。

    每次push(),就会创建一个Node<T>对象,并将其链接到前一个Node对象。

    每次pop(),总会返回top.item(),然后丢弃当前top所指的Node<T>,并将top转移到下一个Node<T>,除非已经碰到了末端哨兵。

泛型接口

  • 泛型也可以应用于接口

  • 生成器(generator),专门负责创建对象的类,工厂方法设计模式的一种应用

    public interface Generator<T> {
      // 生成一个T
      T next();
    }
    
    public abstract class Coffee {
      private static long counter = 0;
      private final long id = counter++;  // 每杯咖啡都有一个独特的id
    
      @Override
      public String toString() {
        return getClass().getSimpleName() + " " + id;
      }
    }
    
    // Coffee的实现类
    public class Cappuccino extends Coffee {
    }
    // .... 更多实现类
    
    public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {
      private final Class<?>[] types = {Latte.class, Mocha.class, Cappuccino.class, Americano.class, Breve.class};
      private static Random rand = new Random(47);
      private int size = 0;
      public CoffeeGenerator() {
      }
    
      public CoffeeGenerator(int size) {
        this.size = size;
      }
    
      /**
       * 随机生成一个咖啡实例
       * @return 咖啡
       */
      @Override
      public Coffee next() {
        try {
          return (Coffee)types[rand.nextInt(types.length)].getDeclaredConstructor().newInstance();
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    	// Coffee迭代器 
      class CoffeeIterator implements Iterator<Coffee> {
        int count = size;   // 数量为size个数
        @Override
        public boolean hasNext() {
          return count > 0;
        }
    
        @Override
        public Coffee next() {
          count--;
          return CoffeeGenerator.this.next(); // 该实例化对象的next()方法
        }
      }
    
      @Override
      public Iterator<Coffee> iterator() {
        return new CoffeeIterator();
      }
    
      public static void main(String[] args) {
        CoffeeGenerator gen = new CoffeeGenerator(5);
        System.out.println(gen.next());
        System.out.println(gen.next());
        System.out.println(gen.next());
        System.out.println(gen.next());
        System.out.println(gen.next());
        System.out.println(gen.next());
        // 迭代器
        Iterator<Coffee> iterator = gen.iterator();
        while (iterator.hasNext()) {
          System.out.println(iterator.next());
        }
      }
      }
    
  • 斐波拉契数列生成器

    public class Fabonacci implements Generator<Integer> {
    private int count = 0;
    private Map<Integer, Integer> record = new HashMap<>();  // 为了防止重复工作,将已经计算过的结果保存在HashMap中,后面递归的时候直接调用就好了
    
    public Fabonacci() {
      record.put(0, 1);
      record.put(1, 1);
    }
    
    @Override
    public Integer next() {
      return fib(count++);
    }
    
    private int fib(int n) {
      if (n < 2) return record.get(n);
      if (!record.containsKey(n)) record.put(n, fib(n - 1) + fib(n - 2));  // 若尚未存在该数字的结果,则通过计算获得,并保存在Map中
      return record.get(n);
    }
    
    public static void main(String[] args) {
      Fabonacci fabonacci = new Fabonacci();
      for (int i = 0; i < 100; i++) {
        System.out.println(fabonacci.next());
      }
    }
    }
    

    但是,目前没有在代码中添加迭代器,为了添加迭代器,又不修改代码本身,可以通过适配器设计模式(adapter)实现。具体来说,是类适配器模式

    public class IterableFabonacci extends Fabonacci implements Iterable<Integer> {
      private int maxSize;  // 边界值
    
      public IterableFabonacci(int maxSize) {
        this.maxSize = maxSize;
      }
    
      @Override
      public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
          @Override
          public boolean hasNext() {
            return maxSize > 0;
          }
    
          @Override
          public Integer next() {
            maxSize--;
            return IterableFabonacci.this.next(); // 持续返回下一个值
          }
        };
      }
    
      public static void main(String[] args) {
        Iterator<Integer> iterator = new IterableFabonacci(10).iterator();
        while (iterator.hasNext()) {
          System.out.println(iterator.next());
        }
      }
    }
    

泛型方法

  • 泛型方法使得该方法能够独立于类而产生变化。

  • 无论何时,只要你能做到,就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法。

  • 对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。(也就是说,泛型类的泛型参数是给对象用的,不实例化没办法泛型参数。除非static方法自己就是泛型方法)

    public class GenericMethods {
      /**
       * 功能,获取参数的类型的名称
       * @param x
       * @param <T>
       */
      public <T> void f(T x) {
        System.out.println(x.getClass().getName());
      }
    
      public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        //无需指定类型参数 
        gm.f("");  // java.lang.String
        gm.f(1);  // java.lang.Integer
        gm.f(1.0);  // java.lang.Double
        gm.f(1.0f);  // java.lang.Float
        gm.f('c');  // java.lang.Character
        gm.f(gm);  // chapter15.generator.GenericMethods
      }
    }
    
  • 当使用泛型类时,必须在创建对象的时候指定类型参数的值。

  • 在使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这成为类型参数推断(type argument inference)。因此,我们可以像调用普通方法一样调用f(),就像f()被无限次重载过。

  • 如果调用f()时传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。

  • 杠杆利用类型参数推断

    public class New {
      public static <K, T> Map<K, T> map() {
        return new HashMap<>();
      }
      public static <T> List<T> list() {
        return new ArrayList<>();
      }
    
      public static void main(String[] args) {
        Map<Integer, Integer> map = New.map();
      }
    }
    
  • 可变参数与泛型方法

    public class GenericVarargs {
      public static <T> List<T> makeList(T... args) {
        List<T> result = new ArrayList<>();
        for (T item : args) {
          result.add(item);
        }
        return result;
      }
    
      public static void main(String[] args) {
        List<Integer> list = makeList(1, 2, 3, 4, 5, 6);
        System.out.println(list);
    
        List<? extends Serializable> a = makeList(1, "a");
        System.out.println(a);
        
        List<? extends Number> numbers = makeList(1, 1.0);
        System.out.println(numbers);
      }
    }
    

    它会自动找到输入内容的最小的公共基类/接口,作为接受的类型参数

  • 利用生成器,填充一个Collection

    public class Generators {
      /**
       * 根据生成器,给指定的集合填充n个值
       * @param coll
       * @param gen
       * @param n
       * @param <T>
       * @return
       */
      public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen, int n) {
        for (int i = 0; i < n; i++) {
          coll.add(gen.next());
        }
        return coll;
      }
    
      public static void main(String[] args) {
        Collection<Coffee> coffees = fill(new ArrayList<Coffee>(), new CoffeeGenerator(), 10);
        for (Coffee coffee : coffees) {
          System.out.println(coffee);
        }
      }
    }
    
  • 一个通用的Generator。很方便就可以创建出指定数目的bean

    public class BasicGenerator<T> implements Generator<T> {
      private Class<T> type;
    
      public BasicGenerator(Class<T> type) {
        this.type = type;
      }
    
      @Override
      public T next() {
        try {
          return type.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    
      public static <T> Generator<T> create(Class<T> type) {
        return new BasicGenerator<T>(type);
      }
    
      public static void main(String[] args) {
        Generator<Mocha> mochaGenerator = BasicGenerator.create(Mocha.class);
        for (int i = 0; i < 10; i++) {
          System.out.println(mochaGenerator.next());
        }
      }
    }
    
  • 简化元组的使用

    public class Tuple {
      public static <A, B> TwoTuple<A, B> tuple(A a, B b) {
        return new TwoTuple<>(a, b);
      }
    
      public static <A, B, C> ThreeTuple<A, B, C> tuple(A a, B b, C c) {
        return new ThreeTuple<>(a, b, c);
      }
    
      public static void main(String[] args) {
        ThreeTuple<Integer, Integer, Integer> tuple = tuple(1, 2, 3);
        System.out.println(tuple);
      }
    }
    

    Tuple包装成一个重载的泛化方法tuple,根据不同的输入参数,构造不同的Tuple

匿名内部类

  • 匿名内部类
    public class Customer {
      private static long counter = 1;
      private final long id = counter++;
    
      public Customer() {
      }
    
      @Override
      public String toString() {
        return "Customer{" +
                "id=" + id +
                '}';
      }
    
      public static Generator<Customer> generator() {
        // 匿名内部类
        return new Generator<Customer>() {
          @Override
          public Customer next() {
            return new Customer();
          }
        };
      }
    }
    

擦除的神秘之处

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

  • Java泛型是使用擦除来实现的,这意味着当你使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List<String>List<Integer>在运行时事实上是相同的类型。

    Class cls = new ArrayList<Integer>().getClass();
    Class cls2 = new ArrayList<String>().getClass();
    System.out.println(cls == cls2);  // true
    
  • 有了擦除,Java编译器无法知道当前类型参数,也无法知道该泛型是否具有某个属性或方法,因此以下操作会造成编译错误

    class Manipulator<T> {
        private T obj;
        public Manipulator(T x) {obj = x;}
        public void manipulate() {obj.f();}  // 会造成编译错误,即使另一个类的确含有该方法
    }
    

    为了调用f(),我们必须协助泛型类,给定泛型类的边界,以此告诫编译器只能接受遵循这个边界的类型。这里重用了extends关键字

    class Manipulator2<T extends HasF> {  // 给定边界,且该边界含有下面使用的属性或方法
        private T obj;
        public Manipulator2(T x) { obj = x; }
        public void manipulate() { obj.f(); } // 编译通过
    }
    
  • 即使kind被存储为Class,擦除也意味着它实际将被存储为Class,没有任何参数,因此当你使用它时,例如在创建数组时,Array.newInstance()实际上并未拥有kind所蕴含的类型信息,因此这不会产生具体的结果,所以必须转型

  • 创建一个泛型数组

    public class ArrayMaker<T> {
      private Class<T> kind;
    
      public ArrayMaker(Class<T> kind) {
        this.kind = kind;
      }
    
      @SuppressWarnings("unchecked")
      T[] create(int size) {
        return (T[]) Array.newInstance(kind, size);  // 创建泛型数组的方式
      }
    
      public static void main(String[] args) {
        ArrayMaker<String> maker = new ArrayMaker<>(String.class);
        String[] strings = maker.create(10);
        System.out.println(Arrays.toString(strings));
        strings[0] = "nihao";
        System.out.println(Arrays.toString(strings));
      }
    }
    

擦除的补偿

  • 任何在运行时需要知道确切类型信息的操作都将无法工作

    public class Erased<T> {
      private final static int SIZE = 100;
    
      public void f(Object arg) {
        if (arg instanceof T) {
        } // ERROR
        T t = new T(); // ERROR
        T[] array = new T[SIZE]; // ERROR
        T[] ts = (T[]) new Object[SIZE];  // warning
      }
    }
    
  • 使用instanceof的尝试最终失败了,因为其类型信息已经被擦除了。如果引入类型标签,就可以转而使用动态的isInstance()

    public class ClassTypeCapture<T> {
      Class<T> kind;
    
      public ClassTypeCapture(Class<T> kind) {
        this.kind = kind;
      }
    
      public boolean f(Object arg) {
        return kind.isInstance(arg);
      }
    
      public static void main(String[] args) {
        ClassTypeCapture<List> listClassTypeCapture = new ClassTypeCapture<List>(List.class);
        System.out.println(listClassTypeCapture.f(new ArrayList<>()));
      }
    }
    
  • 创建泛型实例

    new T();  // 会发生错误
    

    Java中的解决方案是,传递一个工厂对象,并使用它来创建新的实例。最便利的工厂对象就是Class对象,因此如果使用类型标签,那么你就可以用newInstance()来创建这个类型的新对象。

    public class InstantiateGenericType {
      public static void main(String[] args) {
        try {
          ClassAsFactory<Employee> factory = new ClassAsFactory<>(Employee.class);
        } catch (RuntimeException e) {
          e.printStackTrace();
        }
      }
    }
    
    // bean
    class Employee {
    }
    
    // 工厂
    class ClassAsFactory<T> {
      T x;
      public ClassAsFactory(Class<T> kind) {
        try {
          x = kind.getDeclaredConstructor().newInstance();
        } catch (Exception e) {
          throw new RuntimeException(e);
        }
      }
    }
    
  • 泛型数组

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值