美文网首页
Java核心技术(卷I) 18、泛型

Java核心技术(卷I) 18、泛型

作者: kaiker | 来源:发表于2021-04-11 19:36 被阅读0次

泛型使用类型参数,指示元素类型。
泛型设计意味着编写的代码可以对多种不同类型的对象重用

1、泛型类

  • 使用T代表类型变量,用尖括号括起来
public class Pair<T> 
{
   private T first;
   private T second;

   public Pair() { first = null; second = null; }
   public Pair(T first, T second) { this.first = first;  this.second = second; }

   public T getFirst() { return first; }
   public T getSecond() { return second; }

   public void setFirst(T newValue) { first = newValue; }
   public void setSecond(T newValue) { second = newValue; }
}
  • 这个T可以使用具体的类型变量来进行实例化,可以是Pair<String>也可以是Pair<Integer>,这个例子就是使用String进行实例化
class ArrayAlg
{
   /**
    * Gets the minimum and maximum of an array of strings.
    * @param a an array of strings
    * @return a pair with the min and max value, or null if a is null or empty
    */
   public static Pair<String> minmax(String[] a)
   {
      if (a == null || a.length == 0) return null;
      String min = a[0];
      String max = a[0];
      for (int i = 1; i < a.length; i++)
      {
         if (min.compareTo(a[i]) > 0) min = a[i];
         if (max.compareTo(a[i]) < 0) max = a[i];
      }
      return new Pair<>(min, max);
   }
}

2、泛型方法

class ArrayAlg{
  public static <T> T getMiddle(T... a){
    return a[a.length/2];
  }
}
  • 这里面有很多T,<T>是指这个方法方法是个泛型方法,方法名前的T是指返回是T,T... a是指传入参数也可以是泛型

https://www.cnblogs.com/iyangyuan/archive/2013/04/09/3011274.html
https://segmentfault.com/a/1190000014120746

public <T> void show(T t) {
    System.out.println(t);
}
public static void main(String[] args) {
  //创建对象
  ObjectTool tool = new ObjectTool();
  //调用方法,传入的参数是什么类型,返回值就是什么类型
  tool.show("hello");
  tool.show(12);
}

3、类型变量限定

泛型非常的灵活,有时需要对类型变量加以约束否则会出很多问题。

  • 这个例子中没办法保证a这个数组都有compareTo这个方法,这个方法只有实现了Comparable接口的类才有。
class ArrayAlg{
  public static <T> T min(T[] a) {
    if (a == null || a.length == 0) return null;
    T smallest = a[0];
    for (int i = 1; i < a.length; i++)
      if (smallest.compareTo(a[i]) > 0) smallest = a[i];
    return smallest;
  }
}
  • 因此可以进行类型的限制 public static <T extends Comparable> T min(T[] a)这样就可以限制类型是实现了Comparable接口的。这种情况下
  • 如果有多个类型限制,使用&分隔,<T extends Comparable & Serializable>
class ArrayAlg
{
   /**
      Gets the minimum and maximum of an array of objects of type T.
      @param a an array of objects of type T
      @return a pair with the min and max value, or null if a is 
      null or empty
   */
   public static <T extends Comparable> Pair<T> minmax(T[] a) 
   {
      if (a == null || a.length == 0) return null;
      T min = a[0];
      T max = a[0];
      for (int i = 1; i < a.length; i++)
      {
         if (min.compareTo(a[i]) > 0) min = a[i];
         if (max.compareTo(a[i]) < 0) max = a[i];
      }
      return new Pair<>(min, max);
   }
}

4、类型擦除

  • 无论何时定义一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型名字是去掉类型参数后的泛型类型名。
  • 类型变量会被擦除,并替换为限定类型(就是extends的那个),如果没有限定,就是Object,Pair<T>就变成这样,这个就是原始类型
public class Pair
{
   private Object first;
   private Object second;

   public Pair() { first = null; second = null; }
   public Pair(Object first, Object second) { this.first = first;  this.second = second; }

   public Object getFirst() { return first; }
   public Object getSecond() { return second; }

   public void setFirst(Object newValue) { first = newValue; }
   public void setSecond(Object newValue) { second = newValue; }
}
  • <T extends Comparable & Serializable>,这种,会把T替换成Comparable,限定里面谁写前面替换成谁
  • 擦除后,做返回的时候,就会进行一个强制转换
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst(); //这里会有从Object到Employee的转换

5、转换泛型方法、桥方法

class DateInterval extends Pair<LocalDate> {
  public void setSecond(LocalDate second) {
    if (second.compareTo(getFirst()) >= 0) super.setSecond(second);
  }
}

https://www.cnblogs.com/xz816111/p/7643323.html 桥方法

  • 类型擦除之后会变成下面这样,但是这个类还会有个方法,public void setSecond(Object second) {...}
  • 因为如果出现多态Pair<LocalDate> pair = new DateInterval(),调用pair.setSecond(),实质上不会调用setSecond(LocalDate second),这个其实不是多态里重写出来的方法,是个完全新的方法。那重写的方法其实就是public void setSecond(Object second) {setSecond((LocalDate) second);},这个是自动生成的,被叫做桥方法。

可以看出,这个桥方法实际上就是对超类中sayHello(Obejct)的重写。这样做的原因是,当程序员在子类中写下以下这段代码的时候,本意是对超类中的同名方法进行重写,但因为超类发生了类型擦除,所以实际上并没有重写成功,因此加入了桥方法的机制来避免类型擦除与多态发生冲突。

class DateInterval extends Pair{
  public void setSecond(LocalDate second) {...}
}
  • 那为什么有两个返回类型不一样,但是参数一样的方法存在于同一个类,是因为

JVM是用返回值+方法名+参数的方式来计算函数签名的

6、泛型的局限与限制

  • 不能使用基本类型实例化类型参数
  • 运行时类型查询只适用于原始类型,Pair<String> Pair<Employee> 用getClass()调,结果是一样的
  • 不能创建参数化类型的数组 new Pair<String>[10],擦除之后,Object都能存,会出错
  • public static <T> void addAll(T... ts) 这样的参数,实际上是创建了一个泛型数组,会得到警告
  • 不能在构造器中使用类型变量 public Pair() {first = new T(); second = new T();},可以写成这样
public static <T> Pair<T> makePair(Supplier<T> constr){
  return new Pair<>(constr.get(), constr.get());
}
  • 也不能够构造泛型数组,比如下面这个,如果一个String类型的minmax,最后从Comparable转String会报错
public static <T extends Comparable> T[] minmax(T... a) {
  var result = new Comparable[2];
  return (T[]) result
}
  • 泛型类的静态上下文中类型变量无效
  • 不能抛出或捕获泛型类的实例

7、泛型类型的继承规则

继承规则示例1
继承规则示例2
  • var managerBuddies = new Pair<Manager>(ceo, cfo); Pair<Employee> employeeBuddies = managerBuddies 是不行的
  • 但是总是可以将参数化类型转换为一个原始类型var managerBuddies = new Pair<Manager>(ceo, cfo); Pair rawBuddies = managerBuddies是可以的

8、通配符

https://blog.csdn.net/anlian523/article/details/100865538

子类限定

  • Pair<? extends Employee>,这里用的?号
  • ? extends Apple相当于规定了读取的下界,只能够用Apple或其父类来进行读取;同时规定了引用对象的上界,只能引用Apple及其子类,这样父类对象一定能引用子类对象,但是不能做set,因为没有下限编译器不知道具体传什么参数


    子类限定
public static void printBuddies(Pair<? extends Employee> p ) {
  Employee first = p.getFirst();
  Employee second = p.getSecond();
  System.out.print(first.getName() + second.getName());
}

// 之前提到过Pair<Employee> employeeBuddies = managerBuddies是不行的
// 现在Pair<? extends Employee> employeeBuddies = managerBuddies是可以的,但是是不能set的
? extends Employee getFirst()
void setFirst(? extends Employee) // 这里的?不能匹配,不会传递任何特定的类型

超类限定

  • 通配符限定和类型变量限定很相似,但是可以进行超类限定
  • ? super Apple 相当于规定了写入的上界,只能够往里面写Apple或Apple子类;同时规定了对象的下界,只能是Apple的父类
  • 因为都是引用的父类对象,那把Apple及其子类加进去引用肯定没问题,但是没法获取(get),因为没有一个上界可以覆盖所有,除非用Object
import java.util.ArrayList;
import java.util.List;

class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

public class fxtest {
    public static void main(String[] args) {

        List<? super Apple> superList1 = new ArrayList<Jonathan>(); // error
        List<? super Apple> superList2 = new ArrayList<Apple>();
        List<? super Apple> superList3 = new ArrayList<Fruit>();
        List<? super Apple> superList4 = new ArrayList<Object>();

        superList2.add(new Jonathan());
        superList2.add(new Apple());
        superList2.add(new Fruit()); //error
        superList3.add(new Fruit()); //error
        superList4.add(new Fruit()); //error

        List<? extends Apple> extendsList1 = new ArrayList<Jonathan>();
        List<? extends Apple> extendsList2 = new ArrayList<Apple>();
        List<? extends Apple> extendsList3 = new ArrayList<Fruit>(); //error

        Jonathan a1 = extendsList1.get(0); //error
        Apple a2 = extendsList1.get(0);
        Fruit a3 = extendsList1.get(0);
    }
}
image.png

https://blog.csdn.net/yzpbright/article/details/105181476
https://www.cnblogs.com/mzzcy/p/7231892.html

相关文章

网友评论

      本文标题:Java核心技术(卷I) 18、泛型

      本文链接:https://www.haomeiwen.com/subject/pslnkltx.html