泛型(Generics)是强类型编程语言中常用的一种技术。不少框架的代码中都会大量使用到泛型,好比在Java中咱们常常看到的:java
List<String> strList = new ArrayList<String>(); List<Double> doubleList = new LinkedList<Double>(); 复制代码
在这段代码中,ArrayList
就是一个泛型类,List
就是一个泛型接口类,他们提供给开发者一个放置不一样类型的集合容器,咱们能够向这个集合容器中添加String
、Double
以及其余各种数据类型。不管内部存储的是什么类型,集合容器提供给开发者的功能都是相同的,好比添加add
,get
等。有了泛型,咱们就不必建立StringArrayList
、DoubleArrayList
等集合了,不然代码量太大,维护起来成本极高。编程
在Java中,泛型通常有三种使用方式:泛型类,泛型方法和泛型接口类。通常使用尖括号<>
来接收泛型参数。markdown
假如咱们本身定义一个支持泛型的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泛型类基本相同。下面的代码展现了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); } } 复制代码
泛型方法能够存在于泛型类(包括接口类)中,也能够存在于普通的类中。this
public class MyArrayList<T> { ... // public关键字和返回值E之间的<E>代表这是一个泛型方法 // 泛型方法中的类型E和泛型类中的类型T能够不同 public <E> E processElement(E element) { ... return E; } } 复制代码
从上面的代码示例能够看出,public
或private
关键字和方法返回值之间的尖括号<E>
表示这是一个泛型方法。泛型方法的类型E和泛型类中的T能够不同,或者说,若是泛型方法是泛型类的一个成员,泛型方法既能够继续使用类中的T,也能够本身定义新的类型E。spa
除了用 <T>
表示泛型外,还有 <?>
这种形式。<?>
被称为通配符,用来适应各类不一样的泛型。scala
对Java的泛型总结下来发现,虽然它的语法有时候让人有些眼花缭乱,其本质是为了接受不一样的数据类型,加强代码的复用性。
咱们能够在一个类里使用多个泛型,每一个泛型通常使用大写字母表示。Java为此提供了一些大写字母使用规范:
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)); 复制代码
虽然声明时咱们分别使用了String
和Integer
,但运行时关于泛型的信息被擦除了,咱们没法区别strListClass
和intListClass
这两个类型。这是由于,泛型信息只存在于代码编译阶段,当程序运行到JVM上时,与泛型相关的信息会被擦除掉。类型擦除对于绝大多数应用系统开发者来讲关系不太大,可是对于一些框架开发者来讲,必需要注意。好比,Spark和Flink的开发者都使用了一些办法来解决类型擦除问题,对于API调用者来讲,受到的影响不大。
对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的泛型,它容许数据类型是可变,提高了代码的复用性,是不少框架都会采用的技术,开发者很是有必要了解泛型的基本用法。