Skip to content

泛型

  • 基本类型不能作为泛型参数

  • 泛型类

    public class Position <T>{
        private T sth;
    }
    

  • 泛型方法

    public class GenericMethods {
        public <T> void f(T x) {
            System.out.println(x.getClass().getName());
        }
    }
    

  • 通常来说使用泛型方法时不需要手动指出参数类型,可以自动推断

  • public <A,B> Tuple2<A,B>tuple(A a,B b)
  • 第一个<>是方法的参数,在调用时可以自动推断
  • 第二个<>是返回值的参数,应该指出Tuple2表示为参数化的对象,类似一种向上转型

  • 允许多参数,但不能重写一个函数

    public class UseList <W,T>{
        void f(List <T> v){}
        void f(List <W> v){}//错误
    }
    

  • 泛型接口

  • 生成器模式,Spplier<T>,要求重写实现public T get(){}

    public class Fibonacci implements Supplier <Integer> {
      private int count = 0;
      @Override
      public Integer get() { return fib(count++); }
      private int fib(int n) {
        if(n < 2) return 1;
        return fib(n-2) + fib(n-1);
      }
      public static void main(String [] args) {
        Stream.generate(new Fibonacci())
          .limit(18)
          .map(n -> n + " ")
          .forEach(System.out:: print);
      }
    }
    
    //通过适配器再实现可迭代
    public class IterableFibonacci
    extends Fibonacci implements Iterable <Integer> {
      private int n;
      public IterableFibonacci(int count) { n = count; }
      @Override public Iterator <Integer> iterator() {
        return new Iterator <Integer>() {
          @Override
          public boolean hasNext() { return n > 0; }
          @Override public Integer next() {
            n--;
              //内部类访问外部类对象
            return IterableFibonacci.this.get();
          }
          @Override
          public void remove() { // Not implemented
            throw new UnsupportedOperationException();
          }
        };
      }
      public static void main(String [] args) {
        for(int i : new IterableFibonacci(18))
          System.out.print(i + " ");
      }
    }
    

  • 扁平化多级集合

    class Shelf extends ArrayList <Product> {
      Shelf(int nProducts) {
        Suppliers.fill(this, Product.generator, nProducts);
      }
    }
    
    class Aisle extends ArrayList <Shelf> {
      Aisle(int nShelves, int nProducts) {
        for(int i = 0; i < nShelves; i++)
          add(new Shelf(nProducts));
      }
    }
    

  • 一个类不能同时实现一个泛型接口的两种变体(不同泛型参数的泛型接口),因为他们在类型擦除后实际上是相同的

  • 针对特定类型的非泛型实现

    • 实现为特定的类型
      public class CharSet1 implements Set<Character> {
          private String s = "";
      
          @Override
          public boolean contains(Character e) {
              return s.indexOf(e) != -1;
          }
      
          @Override
          public void add(Character e) {
              if (!contains(e)) s += e;
          }
          // ...
      }
      
  • 泛型实现

    • 实现之后仍为泛型 (如ArrayList)
      public class HashSet<E> implements Set<E> { 
          // ... 
      }
      

类型擦除

  • 类型擦除
  • Java泛型内部不存在有关泛型参数类型的信息

    class Manipulator <T> {
      private T obj;
      Manipulator(T x) { obj = x; }
      // Error: cannot find symbol: method f():
      public void manipulate() { obj.f(); }
    }
    
    public class Manipulation {
      public static void main(String [] args) {
        HasF hf = new HasF();
        Manipulator <HasF> manipulator =
          new Manipulator <>(hf);
        manipulator.manipulate();
      }
    }
    //java 中只知道是一个未知的 T(会被认为是 Object),并不知道具体是什么,因此不认为有 f
    class Manipulator2 <T extends HasF> {
      private T obj;
      Manipulator2(T x) { obj = x; }
      public void manipulate() { obj.f(); }
    }
    //这样就知道是 HasF 了!
    

  • java的字节码中实际上没有泛型,是将类型参数用边界类型替换(类型替换)

  • 如无限制的泛型\会替换为Object,而有限制的\会被替换为X
  • 也就是说T类型并不是真正存在的,不能直接使用T进行实例,使用T作为参数class<T>也是存在问题的
  • 但是T作为返回值时可以正确处理,会返回传入的类型
    public class Holder <T> {
        private T obj;
        public void set(T obj){ this.obj = obj; }
        public T get(){ return obj; }
        public void testT(Object arg){
            if (arg instanceof T){ ... } //编译错误
            T var = new T(); //编译错误
            T [] array = new T [100]; //编译错误
            }
        }
    }
    
public class Holder <T> {
    private T obj; //在编译时,该类中的所有的 T 都会被替换为边界类型 Object。
    public void set(T obj){ this.obj = obj; }
    public T get(){ return obj; }
    public static void main(String [] args){
        Holder <Integer> holder = new Holder <>();
        //编译器会检查实参是不是一个 Integer,
        //虽然这里的 1 是 int 类型,但是因为自动包装机制的存在,
        //他会被转化为一个 Integer,因此能够通过类型检查。
        holder.set(1); 
        //编译器也会进行类型检查,
        //并且自动插入一个 Object 类型到 Integer 类型的转型操作。
        Integer obj = holder.get();
    }       
}
  • 对泛型的处理全部集中在编译期,在编译时,编译器会执行如下操作。
  • 会将泛型类的类型参数都用边界类型替换。
  • 对于传入对象给方法形参的指令,编译器会执行一个类型检查,看传入的对象是不是类型参数所指定的类型
  • 对于返回类型参数表示对象的指令,也会执行一个类型检查,还会插入一个自动的向下转型,将对象从边界类型向下转型到类型参数所表示的类型。
  • 任何需要在运行时知道确切类型的操作都无法运行(因为 T 的具体类型信息只存在于编译时期的检查,在运行时期信息已经丢失了)[[java泛型]]
  • new T()x instanceof T
  • 虽然不能使用T进行类型检查,但是可以通过传入class<T>实现
class Building {}
class House extends Building {}

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 <Building> ctt1 =
            new ClassTypeCapture <>(Building.class);
        System.out.println(ctt1.f(new Building()));
        System.out.println(ctt1.f(new House()));
        ClassTypeCapture <House> ctt2 =
            new ClassTypeCapture <>(House.class);
        System.out.println(ctt2.f(new Building()));
        System.out.println(ctt2.f(new House()));
    }
}
/* Output:
true
true
false
true
*/

生成泛型对象

  • 法一,工厂模式
    class Holder <T>{
        private T t;
        public void init(IFactory <T> factory){
            this.t = factory.create();  // 此处即为 new T()的工厂方法的实现
        }
    }
    interface IFactory <T>{  //接口也可以参数化
        T create();
    }
    class IntegerFactory implements IFactory <Integer>{
        public Integer create(){
            return new Integer(10);
        }
    }
    public class newTwithFactory{
        public static void main(String [] args){
            Holder <Integer> holder = new Holder <>();
            //明确支出创建对象使用的工厂方法
            holder.init(new IntegerFactory());
        }
    }
    
  • 使用一个工厂方法,如 IntegerFactory,它具体指明了要创建的对象的类型(在这个例子中是 Integer)。

  • 法二Class

  • Class对象作为类型标签来存储和使用类型信息。
  • 可以实现在运行中查询、使用对象类型。
    class Holder <T>{
        private T t;
        private Class <T> kind;
        public Holder(Class <T> kind){ this.kind = kind; }
        public void init(){
            try{
                this.t = kind.newInstance();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        public static void main(String [] args) {
            Holder <Integer> holder = new Holder <>(Integer.class);
            holder.init();
        }
    }
    
  • 只能使用无参构造

协变与逆变

  • 协变(covariant),如果它保持了子类型序关系≦。该序关系是:子类型≦基类型。
  • 逆变(contravariant),如果它逆转了子类型序关系。
  • 不变(invariant),如果上述两种均不适用。

  • 基类会劫持接口

  • 对于相同的泛型接口,子类不能实现与基类实现的接口的泛型参数不同的(类型擦除后都一样了)

泛型数组

class Fruit{}
class Apple extends Fruit{}

public class NonConvariantGeneric {
    List <Fruit> flist = new ArrayList <Apple>(); //编译错误
}
- Apple的List不是Fruit的List。Apple的List将持有Apple和Apple的子类型,Fruit的List将持有任何类型的Fruit。这包括Apple,但是它不是一个AppleList它仍然是FruitList。Apple的List类型上不等价于Fruit的List,即使Apple是一种Fruit类型。 - Java泛型中规定,即使泛型类型具有继承关系,但是并不意味着该泛型类型的容器也具有继承关系

自限定类型

  • 限制一个泛型类的类型参数,使其只能是该泛型类的子类
  • 与普通继承相比,使用了泛型来确保类型安全,并在子类中特化了基类的行为。在普通的继承中,子类继承父类的属性和方法并可以重写它们。在这种情况下,子类不仅继承了基类的属性和方法,而且通过泛型参数为这些属性和方法提供了一个具体的类型(即其自身)。
  • 这提供了额外的类型安全性,并允许在子类中为这些方法提供更具体的实现,同时不牺牲代码的复用性。

  • 泛型类强制实例时使用这种模式

    //forcing the generic to be used as its own bound argument
    class SelfBounded <T extends SelfBounded<T> > {
        T element;
        SelfBounded <T> set(T arg){
            element = arg;
            return this;
        }
        T get(){return element;}
    }
    
    class A extends SelfBounded <A> {}
    class B extends SelfBounded <A> {} //ok 
    class E extends SelfBounded <D>{} //error
    
    public static void main(String [] args){
            A a = new A();
            a.set(new A());
            a.print();
            B b = new B(), a2 = new B();
            //b.set(b2); //Error
            //b.print();
        }
    

  • 参数协变性

  • 方法参数类型会随着子类而变化
  • 允许子类方法比其重写的基类方法更具体的类型
  • 使用子限定类型就实现了将子类类型作为返回值

通配符

  • 边界
  • 可以对泛型进行一定限制

    public class Computer <T extends Disk>{//必须是 Disk 或子类
    

  • 使用类型和接口限制

    interface HasColor{ java.awt.Color getColor(); }
    
    class Colored <T extends HasColor>{...}
    
    class Dimension { public int x, y, z; }
    
    class ColoredDimension <T extends HasColor & Dimension>{...} //错误!
    class ColoredDimension <T extends Dimension & HasColor>{ //why?
    
    }
    

  • 类型在前接口在后,并且同样只能继承一个类

  • 常用用于方法参数实现泛型类型的多态,对于数组讨论集合自身的类型而不是所持有的元素类型

  • 由于list不存在直接继承关系,可以使用通配符实现通用方法
    public void print(List <?> animals) {
      animals.forEach(a -> System.out.println(((Animal) a).getFood()));
    }
    
    public static void main(String [] args) {
      Test test = new Test();
      List <Animal> animals = Arrays.asList(new Animal());
      List <Cat> cats = Arrays.asList(new Cat());
      test.print(animals);  // 正确执行
      test.print(cats); // 正确执行
    }
    
class Fruit{}
class Apple extends Fruit{}
public class GenericsAndCovariance {
    public static void main(String [] args){
        //一个能放水果以及一切是水果派生类的盘子, 啥水果都能放的盘子
        //Plate <? extends Fruit> 和 Plate <Apple> 最大的区别就是:
        //Plate <? extends Fruit> 是 Plate <Fruit> 以及 Plate <Apple> 的基类。
        Plate <? extends Fruit> p = new Plate <Apple>(new Apple());
        // a list of any type that's inherited from Fruit
        List <? extends Fruit> flist = new ArrayList <Apple>();
    }
}
  • 协变Plate<?extends Fruit>可以放子类型中任意的
  • image-20231012114311701

  • List<?>由于无法在编译期间确定泛型的实际类型,所以没法List<?>添加除了null外的任意类型元素。(即我不知道最高会是谁,因此我要是放入后得向下转型呢?但是读取可以,因为这总会是向上转型)

    class Fruit{}
    class Apple extends Fruit{}
    
           Plate <? extends Fruit> p = new Plate <Apple>(new Apple());
            //不能存入任何元素
            p.set(new Fruit());    //Error
            p.set(new Apple());    //Error
            //读取出来的东西只能存放在 Fruit 或它的基类里。
            Fruit newFruit1 = p.get();
            Object newFruit2 = p.get();
            Apple newFruit3 = p.get();    //Error
    

  • 对于Plate<? exyends fruit>编译器并不知道具体会被初始化为什么类型,因此如果允许放入元素则可能造成不匹配(类型错误)
  • 而取出时会获得上界的类型,构成了多态
  • 对于非集合类型也会有限制,如Tile<? extends Thing> tile;
  • 注意,这种参数并不是针对对tile变量的赋值和访问,而是针对对tile内部含有泛型参数/返回值的方法的调用

  • 逆变Plate<?super Fruit>可以放基类中任意

  • image-20231012114813621
  • 我们在编译期只能知道下界通配符的下界是什么类型,所以在添加元素时,只能向其中添加下界类型。由于编译期无法知晓具体的实际类型,所以只能使用Object来**接收获取的元素

    class Fruit{}
    class Apple extends Fruit{}
    public class GenericsAndCovariance {
        public static void main(String [] args){
           Plate <? super Fruit> p = new Plate <Fruit>(new Fruit());
            //存入元素正常
            p.set(new Fruit());
            p.set(new Apple());
            //读取出来的东西只能存放在 Object 类里。
            Apple newFruit3 = p.get();    //Error
            Fruit newFruit1 = p.get();    //Error
            Object newFruit2 = p.get();
        }
    }
    

  • List<?>(无界通配符):

  • 这表示一个未知类型的列表。? 是一个通配符,代表任何类型。
  • 你可以从 List<?> 读取数据,读取的数据将被视为 Object 类型,但你不能向其中写入除 null 之外的任何数据,因为你不知道列表的确切类型。

  • List(原生类型):

  • 这是一个原生类型的列表,没有泛型信息。在泛型被引入Java之前就存在。
  • 你可以向其中添加任何类型的对象,也可以从中读取数据,读取的数据被视为 Object 类型。
  • 它不安全,因为它不提供类型检查,可能导致运行时错误(例如,ClassCastException)。

  • List<Object>:

  • 这表示一个可以包含任何类型对象的列表。
  • List<?> 不同,你可以安全地向 List<Object> 添加任何类型的对象(除了基本类型,它们需要被装箱)。
  • 这是一个明确声明你可以存储任何类型对象的列表,提供了类型安全性(即使是 Object 级别的)。

混型

  • 混合多个类的能力,生成一个可以代表混型中全部类型的类
  • 使用接口进行混合
  • 因为接口可以多继承,并且在类内保存所有接口的实例

    class Mixin extends BasicImp
    implements TimeStamped, SerialNumbered {
      private TimeStamped timeStamp = new TimeStampedImp();
      private SerialNumbered serialNumber =
        new SerialNumberedImp();
      @Override public long getStamp() {
        return timeStamp.getStamp();
      }
      @Override public long getSerialNumber() {
        return serialNumber.getSerialNumber();
      }
    }
    

  • 代理模式

潜在类型机制

一个东西长得像鸭子,行为像鸭子,那他就是鸭子

  • 不一定是同一类型,有正确方法就可以用

    class Dog {
    public:
      void speak() { cout << " Arf!" << endl; }
      void sit() { cout << " Sitting " << endl; }
      void reproduce() {}
    };
    
    class Robot {
    public:
      void speak() { cout << " Click!" << endl; }
      void sit() { cout << " Clank!" << endl; }
      void oilChange() {}
    };
    
    template <class T> void perform(T anything) {
      anything.speak();
      anything.sit();
    }
    

  • 在Java中没法实现,因此叫辅助潜在类型机制

  • 使用方法引用:不是传入对象,而是传入方法实现调用
    class PerformingDogA extends Dog {
      public void speak() { System.out.println("Woof!"); }
      public void sit() { System.out.println("Sitting"); }
      public void reproduce() {}
    }
    
    class RobotA {
      public void speak() { System.out.println("Click!"); }
      public void sit() { System.out.println("Clank!"); }
      public void oilChange() {}
    }
    
    class CommunicateA {
      public static <P> void perform(P performer,
        Consumer <P> action1, Consumer <P> action2) {
        action1.accept(performer);
        action2.accept(performer);
      }
    }
    
    public class DogsAndRobotMethodReferences {
      public static void main(String [] args) {
        CommunicateA.perform(new PerformingDogA(),
          PerformingDogA:: speak, PerformingDogA:: sit);
        CommunicateA.perform(new RobotA(),
          RobotA:: speak, RobotA:: sit);
        CommunicateA.perform(new Mime(),
          Mime:: walkAgainstTheWind,
          Mime:: pushInvisibleWalls);
      }
    }