Scala 支持类型参数化,使得咱们可以编写泛型程序。java
Java 中使用 <>
符号来包含定义的类型参数,Scala 则使用 []
。git
class Pair[T, S](val first: T, val second: S) { override def toString: String = first + ":" + second }
object ScalaApp extends App { // 使用时候你直接指定参数类型,也能够不指定,由程序自动推断 val pair01 = new Pair("heibai01", 22) val pair02 = new Pair[String,Int]("heibai02", 33) println(pair01) println(pair02) }
函数和方法也支持类型参数。github
object Utils { def getHalf[T](a: Array[T]): Int = a.length / 2 }
Scala 和 Java 同样,对于对象之间进行大小比较,要求被比较的对象实现 java.lang.Comparable
接口。因此若是想对泛型进行比较,须要限定类型上界为 java.lang.Comparable
,语法为 S <: T
,表明类型 S 是类型 T 的子类或其自己。示例以下:编程
// 使用 <: 符号,限定 T 必须是 Comparable[T]的子类型 class Pair[T <: Comparable[T]](val first: T, val second: T) { // 返回较小的值 def smaller: T = if (first.compareTo(second) < 0) first else second }
// 测试代码 val pair = new Pair("abc", "abcd") println(pair.smaller) // 输出 abc
扩展:若是你想要在 Java 中实现类型变量限定,须要使用关键字 extends 来实现,等价的 Java 代码以下:数组
public class Pair<T extends Comparable<T>> { private T first; private T second; Pair(T first, T second) { this.first = first; this.second = second; } public T smaller() { return first.compareTo(second) < 0 ? first : second; } }
在上面的例子中,若是你使用 Int 类型或者 Double 等类型进行测试,点击运行后,你会发现程序根本没法经过编译:app
val pair1 = new Pair(10, 12) val pair2 = new Pair(10.0, 12.0)
之因此出现这样的问题,是由于 Scala 中的 Int 类并无实现 Comparable 接口。在 Scala 中直接继承 Comparable 接口的是特质 Ordered,它在继承 compareTo 方法的基础上,额外定义了关系符方法,源码以下:ide
// 除了 compareTo 方法外,还提供了额外的关系符方法 trait Ordered[A] extends Any with java.lang.Comparable[A] { def compare(that: A): Int def < (that: A): Boolean = (this compare that) < 0 def > (that: A): Boolean = (this compare that) > 0 def <= (that: A): Boolean = (this compare that) <= 0 def >= (that: A): Boolean = (this compare that) >= 0 def compareTo(that: A): Int = compare(that) }
之因此在平常的编程中之因此你可以执行 3>2
这样的判断操做,是由于程序执行了定义在 Predef
中的隐式转换方法 intWrapper(x: Int)
,将 Int 类型转换为 RichInt 类型,而 RichInt 间接混入了 Ordered 特质,因此可以进行比较。函数
// Predef.scala @inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
要想解决传入数值没法进行比较的问题,可使用视图界定。语法为 T <% U
,表明 T 可以经过隐式转换转为 U,即容许 Int 型参数在没法进行比较的时候转换为 RichInt 类型。示例以下:测试
// 视图界定符号 <% class Pair[T <% Comparable[T]](val first: T, val second: T) { // 返回较小的值 def smaller: T = if (first.compareTo(second) < 0) first else second }
注:因为直接继承 Java 中 Comparable 接口的是特质 Ordered,因此以下的视图界定和上面是等效的:大数据
// 隐式转换为 Ordered[T] class Pair[T <% Ordered[T]](val first: T, val second: T) { def smaller: T = if (first.compareTo(second) < 0) first else second }
若是你用的 Scala 是 2.11+,会发现视图界定已被标识为废弃。官方推荐使用类型约束 (type constraint) 来实现一样的功能,其本质是使用隐式参数进行隐式转换,示例以下:
// 1.使用隐式参数隐式转换为 Comparable[T] class Pair[T](val first: T, val second: T)(implicit ev: T => Comparable[T]) def smaller: T = if (first.compareTo(second) < 0) first else second } // 2.因为直接继承 Java 中 Comparable 接口的是特质 Ordered,因此也能够隐式转换为 Ordered[T] class Pair[T](val first: T, val second: T)(implicit ev: T => Ordered[T]) { def smaller: T = if (first.compareTo(second) < 0) first else second }
固然,隐式参数转换也能够运用在具体的方法上:
object PairUtils{ def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b }
上下文界定的形式为 T:M
,其中 M 是一个泛型,它要求必须存在一个类型为 M[T]的隐式值,当你声明一个带隐式参数的方法时,须要定义一个隐式默认值。因此上面的程序也可使用上下文界定进行改写:
class Pair[T](val first: T, val second: T) { // 请注意 这个地方用的是 Ordering[T],而上面视图界定和类型约束,用的是 Ordered[T],二者的区别会在后文给出解释 def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second } // 测试 val pair= new Pair(88, 66) println(pair.smaller) //输出:66
在上面的示例中,咱们无需手动添加隐式默认值就能够完成转换,这是由于 Scala 自动引入了 Ordering[Int]这个隐式值。为了更好的说明上下文界定,下面给出一个自定义类型的比较示例:
// 1.定义一我的员类 class Person(val name: String, val age: Int) { override def toString: String = name + ":" + age } // 2.继承 Ordering[T],实现自定义比较器,按照本身的规则重写比较方法 class PersonOrdering extends Ordering[Person] { override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1 } class Pair[T](val first: T, val second: T) { def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second } object ScalaApp extends App { val pair = new Pair(new Person("hei", 88), new Person("bai", 66)) // 3.定义隐式默认值,若是不定义,则下一行代码没法经过编译 implicit val ImpPersonOrdering = new PersonOrdering println(pair.smaller) //输出: bai:66 }
这里先看一个例子:下面这段代码,没有任何语法错误,可是在运行时会抛出异常:Error: cannot find class tag for element type T
, 这是因为 Scala 和 Java 同样,都存在类型擦除,即泛型信息只存在于代码编译阶段,在进入 JVM 以前,与泛型相关的信息会被擦除掉。对于下面的代码,在运行阶段建立 Array 时,你必须明确指明其类型,可是此时泛型信息已经被擦除,致使出现找不到类型的异常。
object ScalaApp extends App { def makePair[T](first: T, second: T) = { // 建立以一个数组 并赋值 val r = new Array[T](2); r(0) = first; r(1) = second; r } }
Scala 针对这个问题,提供了 ClassTag 上下文界定,即把泛型的信息存储在 ClassTag 中,这样在运行阶段须要时,只须要从 ClassTag 中进行获取便可。其语法为 T : ClassTag
,示例以下:
import scala.reflect._ object ScalaApp extends App { def makePair[T : ClassTag](first: T, second: T) = { val r = new Array[T](2); r(0) = first; r(1) = second; r } }
2.1 小节介绍了类型上界的限定,Scala 同时也支持下界的限定,语法为:U >: T
,即 U 必须是类型 T 的超类或自己。
// 首席执行官 class CEO // 部门经理 class Manager extends CEO // 本公司普通员工 class Employee extends Manager // 其余公司人员 class OtherCompany object ScalaApp extends App { // 限定:只有本公司部门经理以上人员才能获取权限 def Check[T >: Manager](t: T): T = { println("得到审核权限") t } // 错误写法: 省略泛型参数后,如下全部人都能得到权限,显然这是不正确的 Check(new CEO) Check(new Manager) Check(new Employee) Check(new OtherCompany) // 正确写法,传入泛型参数 Check[CEO](new CEO) Check[Manager](new Manager) /* * 如下两条语句没法经过编译,异常信息为: * do not conform to method Check's type parameter bounds(不符合方法 Check 的类型参数边界) * 这种状况就完成了下界限制,即只有本公司经理及以上的人员才能得到审核权限 */ Check[Employee](new Employee) Check[OtherCompany](new OtherCompany) }
类型变量能够同时有上界和下界。 写法为 :T > : Lower <: Upper
;
不能同时有多个上界或多个下界 。但能够要求一个类型实现多个特质,写法为 :
T < : Comparable[T] with Serializable with Cloneable
;
你能够有多个上下文界定,写法为 T : Ordering : ClassTag
。
上文中使用到 Ordering 和 Ordered 特质,它们最主要的区别在于分别继承自不一样的 Java 接口:Comparable 和 Comparator:
为何 Java 中要同时给出这两个比较接口,这是由于你要比较的对象不必定实现了 Comparable 接口,而你又想对其进行比较,这时候固然你能够修改代码实现 Comparable,可是若是这个类你没法修改 (如源码中的类),这时候就可使用外置的比较器。一样的问题在 Scala 中固然也会出现,因此 Scala 分别使用了 Ordering 和 Ordered 来继承它们。
下面分别给出 Java 中 Comparable 和 Comparator 接口的使用示例:
import java.util.Arrays; // 实现 Comparable 接口 public class Person implements Comparable<Person> { private String name; private int age; Person(String name,int age) {this.name=name;this.age=age;} @Override public String toString() { return name+":"+age; } // 核心的方法是重写比较规则,按照年龄进行排序 @Override public int compareTo(Person person) { return this.age - person.age; } public static void main(String[] args) { Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)}; Arrays.sort(peoples); Arrays.stream(peoples).forEach(System.out::println); } } 输出: bai:55 hei:66 ying:77
import java.util.Arrays; import java.util.Comparator; public class Person { private String name; private int age; Person(String name,int age) {this.name=name;this.age=age;} @Override public String toString() { return name+":"+age; } public static void main(String[] args) { Person[] peoples= {new Person("hei", 66), new Person("bai", 55), new Person("ying", 77)}; // 这里为了直观直接使用匿名内部类,实现 Comparator 接口 //若是是 Java8 你也能够写成 Arrays.sort(peoples, Comparator.comparingInt(o -> o.age)); Arrays.sort(peoples, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.age-o2.age; } }); Arrays.stream(peoples).forEach(System.out::println); } }
使用外置比较器还有一个好处,就是你能够随时定义其排序规则:
// 按照年龄大小排序 Arrays.sort(peoples, Comparator.comparingInt(o -> o.age)); Arrays.stream(peoples).forEach(System.out::println); // 按照名字长度倒序排列 Arrays.sort(peoples, Comparator.comparingInt(o -> -o.name.length())); Arrays.stream(peoples).forEach(System.out::println);
这里再次给出上下文界定中的示例代码做为回顾:
// 1.定义一我的员类 class Person(val name: String, val age: Int) { override def toString: String = name + ":" + age } // 2.继承 Ordering[T],实现自定义比较器,这个比较器就是一个外置比较器 class PersonOrdering extends Ordering[Person] { override def compare(x: Person, y: Person): Int = if (x.age > y.age) 1 else -1 } class Pair[T](val first: T, val second: T) { def smaller(implicit ord: Ordering[T]): T = if (ord.compare(first, second) < 0) first else second } object ScalaApp extends App { val pair = new Pair(new Person("hei", 88), new Person("bai", 66)) // 3.在当前上下文定义隐式默认值,这就至关于传入了外置比较器 implicit val ImpPersonOrdering = new PersonOrdering println(pair.smaller) //输出: bai:66 }
使用上下文界定和 Ordering 带来的好处是:传入 Pair
中的参数不必定须要可比较,只要在比较时传入外置比较器便可。
须要注意的是因为隐式默认值二义性的限制,你不能像上面 Java 代码同样,在同一个上下文做用域中传入两个外置比较器,即下面的代码是没法经过编译的。可是你能够在不一样的上下文做用域中引入不一样的隐式默认值,即便用不一样的外置比较器。
implicit val ImpPersonOrdering = new PersonOrdering println(pair.smaller) implicit val ImpPersonOrdering2 = new PersonOrdering println(pair.smaller)
在实际编码中,一般须要把泛型限定在某个范围内,好比限定为某个类及其子类。所以 Scala 和 Java 同样引入了通配符这个概念,用于限定泛型的范围。不一样的是 Java 使用 ?
表示通配符,Scala 使用 _
表示通配符。
class Ceo(val name: String) { override def toString: String = name } class Manager(name: String) extends Ceo(name) class Employee(name: String) extends Manager(name) class Pair[T](val first: T, val second: T) { override def toString: String = "first:" + first + ", second: " + second } object ScalaApp extends App { // 限定部门经理及如下的人才能够组队 def makePair(p: Pair[_ <: Manager]): Unit = {println(p)} makePair(new Pair(new Employee("heibai"), new Manager("ying"))) }
目前 Scala 中的通配符在某些复杂状况下还不完善,以下面的语句在 Scala 2.12 中并不能经过编译:
def min[T <: Comparable[_ >: T]](p: Pair[T]) ={}
可使用如下语法代替:
type SuperComparable[T] = Comparable[_ >: T] def min[T <: SuperComparable[T]](p: Pair[T]) = {}
更多大数据系列文章能够参见 GitHub 开源项目: 大数据入门指南