Java和Kotlin泛型

什么是泛型?

泛型是一种参数化类型,将操作的数据类型(非基本类型)作为一个参数,这种参数类型可以在类、接口、方法中创建,分别叫泛型类、泛型接口、泛型方法。泛型是java层定义的语法,在JVM中并没有泛型这一概念,所以在代码编译阶段通过类型擦除把泛型变成基本类型。

为什么要使用泛型?

  1. 在泛型之前,只能通过Object进行参数的任意化,在这种情况使用Object参数时需要我们的强制类型转换,这种手动强制转换可能会导致转换成不兼容的其他类型,而此时代码、编译并不会报错,只能在运行时才能发现强转的错误。
  2. 泛型则是在代码的编译期间,通过类型擦除在编译器中将泛型进行转换,如果发现跟现有的类型不兼容,则会报错。这降低了通过Object的强制转换的带来的错误。
  3. 使用泛型,可以提高代码的利用率,使不同的数据类型执行相同的算法。

类型参数命名约定

类型参数是单个大写字母,通过字母的规范可以使用大家快速理解类型参数的作用:
T:type(类型)
E:element(元素,集合中就用这个)
K:key(键)
V:value(值)
S、U、V: 第二、三、四个类型

泛型接口、泛型类

OrderedPair泛型类实现泛型接口:

public interface Pair {
  K getKey();

  V getValue();
}

public class OrderedPair implements Pair {

  private K key;
  private V value;

  public OrderedPair(K key, V value) {
      this.key = key;
      this.value = value;
  }

  @Override
  public K getKey() {
      return key;
  }

  @Override
  public V getValue() {
      return value;
  }
}

public class OrderedPair2 implements Pair {

  @Override
  public String getKey() {
      return "hello";
  }

  @Override
  public String getValue() {
      return "world";
  }
}

OrderedPair泛型类的实例类:

Pair p1 = new OrderedPair("Event", 8);
Pair p2 = new OrderedPair<>("hello", "world");

原始类型

原始类型简单理解为:在泛型类中,实例化泛型类时并未给T指定特定的数据类型,如下代码:

public class Box

Box box = new Box();

这里Box的实例并没有给T指明具体的数据类型,像这种就叫原始类型,泛型类型是可以向后兼容原始类型的,比如泛型类型可以赋值给原型类型:

Box box = new Box();
Box box1 = box;

泛型方法

如果在类和接口中没有定义想要的泛型参数,又想在方法中使用泛型参数,则只能如下面中定义泛型方法:

public class Util {
  public static  boolean compare(Pair p1, Pair p2) {
      return p1.getKey().equals(p2.getKey()) &&
              p1.getValue().equals(p2.getValue());
  }
}

Pair p1 = new OrderedPair("Event", 8);
Pair p2 = new OrderedPair<>("hello", 9);       
boolean isEquals = Util.compare(p1, p2);

限定类型和多重限定

如果我们想限定类型参数T,只能是Number类型及其子类,改怎么限定呢?我们可以使用extend,表明数据类型的上限:如下实例

public class Box {

  private E element;

  public E getValue() {
      return element;
  }

  public  void inject(U u) {
      System.out.println("E: " + element.getClass().getName());
      System.out.println("U: " + u.getClass().getName());
  }
}

Box box = new Box();
box.inject(10);
// error   box.inject("10");

上面的泛型方向限定为Number及其子类(Integer、Float、Double等)。
当然泛型的一个参数类型也可以有多重限定,不过这个多重限定只能是一个类和多个接口,而且类限定并且放在第一位:

public class A { }
public interface B { }
public interface C { }


public class D

这就是多重限定,只能是限定一个类且并且放第一位。

继承与泛型

如果在已经确定T的数据类型后(如Number),后面在赋值T时可以是确定类型的子类(如Interger)。如下事例:

Box box = new Box<>();
box.setElement(new Integer(10));
box.setElement(new Double(10.1));

当然根据java的多态我们知道,父类也可以拥有子类的实例类,如果类之间有继承的关系,也可以有多态的关系,如

ArrayList->List->Collection

List list = new ArrayList<>();

ArrayList arrayList = new ArrayList<>();
List list1 = arrayList;

可以看到,只要泛型的数据类型一样,类依然可以有多态关系。
但是单单泛型的数据类型有继承关系,而类没有则不能构成多态关系,如下代码会报错:

Box box1 = new Box<>();
Box box2 = box1;  // 报错

遇到这种有没有办法解决,是有的,我们把下面的Number的数据类型改为? extends Number以下就可以赋值了:


Box box1 = new Box<>();
Box box2 = box1;

?通配符

?通配符并不是泛型,?不能像泛型T一样形成泛型类、泛型接口、泛型方法。?通配符只是一个实例T的一个参数,比如? extends NumberNumber是一样的,不过通配符可以更加灵活规定参数的上下限。

上限通配符
? extends即叫上限通配符,可以确定参数是其类型及其子类型。上限通配符是支持协变的,则可以使没有父子关系的类完成多态的赋值。以下是用List确定T参数是一个Number的上限通配符,完成元素的增加:

  public static double sumOfList(List list) {
      double sum = 0.0;
      for (Number number : list) {
          sum += number.doubleValue();
      }
      return sum;
  }


  List list2 = Arrays.asList(1, 2, 3);
  System.out.println("sum = " + sumOfList(list2));

上限通配符是支持协变的即:

Box box1 = new Box<>();
Box box2 = box1;

下限通配符
? spuer即叫下限通配符,可以确定参数的类型及其父类型。下限通配符则支持逆变,跟上边的协变是一个道理:

  public static void addNumbers(List list) {
      for (int i = 1; i <= 10; i++) {
          list.add(i);
      }
  }

  List list2 = Arrays.asList(1, 2, 3);
  addNumbers(list2);

  // 逆变
  Box box3 = new Box<>();
  Box box4 = box3;

通配符上下限使用时机

一般生产数据,有变化的数据用:super
用于阅读,使用的数据用:extends
比如实现一个copy方法:

  private void copy(List list, List list1) {
      list1.addAll(list);
  }

类型擦除

泛型只是java层定义的语法,在JVM中并没有泛型这一概念。所以泛型在编译的时候通过类型擦除变成JVM可以识别的数据类型。一般的类型擦除把泛型变成Object或限定类型,如:
T:Object
T extends Number: Number

Kotlin泛型

kotlin的泛型只是给java的泛型的概念换了一种写法,所以对java泛型不熟悉的可以看上面的文章

泛型类、泛型接口

通过泛型类OrderedPair去实现泛型接口:

interface Pair {
  var key: K

  var value: V

  fun generate(): K
}

class OrderedPair(override var key: K, override var value: V) : Pair {

  override fun generate(): K {
      return key
  }

}

OrderedPair实例类的写法:

val p1: OrderedPair = OrderedPair("Event", 8)
val p2 = OrderedPair("hello", "world")

泛型方法

如果在类和接口中没有定义想要的泛型参数,又想在方法中使用泛型参数,则只能如下面中定义泛型方法:

class Util {
  companion object {
      fun  compare(p1: Pair, p2: Pair): Boolean {
          return p1.key == p2.key
                  && p1.value == p2.value
      }
  }

}


val p1: OrderedPair = OrderedPair("Event", 8)
val p2 = OrderedPair("hello", 10)
var isEquals = Util.compare(p1, p2)

限定类型

如果我们想限定类型参数T,只能是Number类型及其子类,改怎么限定呢?在java中我们可以使用extend,在kotlin中使用

class Box {
  var element: E? = null

  fun  inject(u: U) {
      print("u= ${u}")
  }
}

var box: Box = Box()
box.inject(10)

out上限通配符

在java中? extends叫上限通配符,而在kotlin中out叫上限通配符,可以确定参数是其类型及其子类型。上限通配符是支持协变的,则可以使没有父子关系的类完成多态的赋值。以下是用List确定T参数是一个Number的上限通配符,完成元素的增加:

fun sumOfList(list: List): Double {
      var sum: Double = 0.0
      for (number in list) {
          sum += number.toDouble()
      }
      return sum
  }


val list = Arrays.asList(1, 2, 3)
val sum = sumOfList(list)

out上限通配符是支持协变的即:

val box3: Box = Box()
val box4: Box = box3 

kolint在out上的扩展,在java中通配符?:extends只能在确定类型参数的时候使用,并不可以在泛型类、泛型接口中使用,但out就可以直接在类、接口中直接声明该泛型为上下限:

interface List : Collection {
}

下限通配符

在java? spuer即叫下限通配符,在kotlinin即叫下限通配符,可以确定参数的类型及其父类型。下限通配符则支持逆变,跟上边的协变是一个道理:

fun printBox(box: Box) {
      println("number= ${box.element}")
  }

val box5: Box = Box()
box5.element = 10
printBox(box5)

你可能感兴趣的:(Java和Kotlin泛型)