Java/Scala 泛型快速入門教程

泛型(Generics)是強類型編程語言中常用的一種技術。不少框架的代碼中都會大量使用到泛型,好比在Java中咱們常常看到的:java

List<String> strList = new ArrayList<String>();
List<Double> doubleList = new LinkedList<Double>();
複製代碼

在這段代碼中,ArrayList就是一個泛型類,List就是一個泛型接口類,他們提供給開發者一個放置不一樣類型的集合容器,咱們能夠向這個集合容器中添加StringDouble以及其餘各種數據類型。不管內部存儲的是什麼類型,集合容器提供給開發者的功能都是相同的,好比添加addget等。有了泛型,咱們就不必建立StringArrayListDoubleArrayList等集合了,不然代碼量太大,維護起來成本極高。編程

二維碼

在Java中,泛型通常有三種使用方式:泛型類,泛型方法和泛型接口類。通常使用尖括號<>來接收泛型參數。markdown

Java泛型類

假如咱們本身定義一個支持泛型的MyArrayList,這個列表類能夠簡單支持初始化和數據寫入。只要在類名後面加上<T>就可讓這個類支持泛型,類內部的一些屬性和方法均可以使用泛型類型T。固然咱們給這個類也能夠添加多個泛型參數,好比<K,V>, <T,E,K>等。在類中設置泛型會做用到整個類上。框架

public class MyArrayList<T> {

    private int size;
    T[] elements;

    public MyArrayList(int capacity) {
        this.size = capacity;
        this.elements = (T[]) new Object[capacity];
    }

    public void set(T element, int position) {
        elements[position] = element;
    }

    @Override
    public String toString() {
        String result = "";
        for (int i = 0; i < size; i++) {
            result += elements[i].toString();
        }
        return result;
    }

    public static void main(String[] args){
        MyArrayList<String> strList = new MyArrayList<String>(2);
        strList.set("first", 0);
        strList.set("second", 1);

        System.out.println(strList.toString());
    }
}
複製代碼

咱們也能夠從父類中繼承並擴展泛型,好比Flink源碼中有這樣一個類定義,子類繼承了父類的T,同時本身增長了泛型KEY編程語言

public class KeyedStream<T, KEY> extends DataStream<T> {
  ...
}
複製代碼

Java泛型接口類

Java泛型接口類的定義和Java泛型類基本相同。下面的代碼展現了List接口中定義subList方法,該方法截取原來列表的一部分。ide

public interface List<E> {
    ...
    public List<E> subList(int fromIndex, int toIndex);
}
複製代碼

繼承並實現這個接口類的代碼以下:大數據

public class ArrayList<E> implements List<E> {
    ...
    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }
}
複製代碼

Java泛型方法

泛型方法能夠存在於泛型類(包括接口類)中,也能夠存在於普通的類中。this

public class MyArrayList<T> {
    ...
    // public關鍵字和返回值E之間的<E>代表這是一個泛型方法
    // 泛型方法中的類型E和泛型類中的類型T能夠不同
  	public <E> E processElement(E element) {
        ...
        return E;
    }
}
複製代碼

從上面的代碼示例能夠看出,publicprivate關鍵字和方法返回值之間的尖括號<E>表示這是一個泛型方法。泛型方法的類型E和泛型類中的T能夠不同,或者說,若是泛型方法是泛型類的一個成員,泛型方法既能夠繼續使用類中的T,也能夠本身定義新的類型E。spa

通配符

除了用 <T>表示泛型外,還有 <?>這種形式。<?> 被稱爲通配符,用來適應各類不一樣的泛型。scala

泛型小結

對Java的泛型總結下來發現,雖然它的語法有時候讓人有些眼花繚亂,其本質是爲了接受不一樣的數據類型,加強代碼的複用性。

咱們能夠在一個類裏使用多個泛型,每一個泛型通常使用大寫字母表示。Java爲此提供了一些大寫字母使用規範:

  1. T 表明通常的任何類。
  2. E 表明元素(Element)或異常(Exception)。
  3. K 表明鍵(Key)。
  4. V 表明值(Value),一般與K一塊兒配合使用,好比<K, V>。

Java的泛型給開發者提供了很多便利,尤爲是保證了底層代碼簡潔性,由於這些底層代碼一般被封裝爲一個框架,會有各類各樣的上層應用調用這些底層代碼進行特定的業務處理,每次調用均可能涉及泛型問題。好比,大數據框架Spark和Flink中都須要開發者基於泛型進行數據處理。

以上只對泛型作了一個簡單的介紹,實際上在具體使用時還有一些細節須要注意。

類型擦除

Java的泛型有一個遺留問題,那就是類型擦除(Type Erasure)。咱們先看一下下面的代碼:

Class<?> strListClass = new ArrayList<String>().getClass();
Class<?> intListClass = new ArrayList<Integer>().getClass();
// 輸出:class java.util.ArrayList
System.out.println(strListClass);
// 輸出:class java.util.ArrayList
System.out.println(intListClass);
// 輸出:true
System.out.println(strListClass.equals(intListClass));
複製代碼

雖然聲明時咱們分別使用了StringInteger,但運行時關於泛型的信息被擦除了,咱們沒法區別strListClassintListClass這兩個類型。這是由於,泛型信息只存在於代碼編譯階段,當程序運行到JVM上時,與泛型相關的信息會被擦除掉。類型擦除對於絕大多數應用系統開發者來講關係不太大,可是對於一些框架開發者來講,必需要注意。好比,Spark和Flink的開發者都使用了一些辦法來解決類型擦除問題,對於API調用者來講,受到的影響不大。

Scala中的泛型

對Java的泛型有了基本瞭解後,咱們接着來了解一下Scala中的泛型。相比而言,Scala的類型系統更復雜,本文只介紹一些簡單語法,幫助讀者可以讀懂一些源碼。

Scala中,泛型放在了中括號[]中。或者咱們能夠簡單地理解爲,原來Java的泛型類<T>,如今改成[T]便可。

咱們建立一個Stack[T]的泛型類,並實現了兩個簡單的方法,類中各成員和方法均可以使用泛型T。咱們也定義了泛型方法,形如isStackPeekEquals[T],方法中可使用泛型T。

object MyStackDemo {

  // Stack泛型類
  class Stack[T] {
   private var elements: List[T] = Nil
   def push(x: T) { elements = x :: elements }
   def peek: T = elements.head
  }

  // 泛型方法,檢查兩個Stack頂部是否相同
  def isStackPeekEquals[T](p: Stack[T], q: Stack[T]): Boolean = {
   p.peek == q.peek
  }

  def main(args: Array[String]): Unit = {
    val stack = new Stack[Int]
    stack.push(1)
    stack.push(2)
    println(stack.peek)

    val stack2 = new Stack[Int]
    stack2.push(2)
    val stack3 = new Stack[Int]
    stack3.push(3)
    println(isStackPeekEquals(stack, stack2))
    println(isStackPeekEquals(stack, stack3))
  }
}
複製代碼

總結

本文簡單介紹了Java/Scala的泛型,它容許數據類型是可變,提高了代碼的複用性,是不少框架都會採用的技術,開發者很是有必要了解泛型的基本用法。

相關文章
相關標籤/搜索