From e48e55c581bd9414e226e207e1479e58d2bd5768 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=BD=97=E7=A5=A5?= <1366971433@qq.com>
Date: Mon, 13 May 2019 17:16:02 +0800
Subject: [PATCH] =?UTF-8?q?scala=E7=B1=BB=E5=9E=8B=E5=8F=82=E6=95=B0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
notes/Scala类型参数.md | 554 ++++++++++++++++++++--------
pictures/scala-ordered-ordering.png | Bin 0 -> 7002 bytes
2 files changed, 401 insertions(+), 153 deletions(-)
create mode 100644 pictures/scala-ordered-ordering.png
diff --git a/notes/Scala类型参数.md b/notes/Scala类型参数.md
index 173cdb8..04e7522 100644
--- a/notes/Scala类型参数.md
+++ b/notes/Scala类型参数.md
@@ -1,154 +1,402 @@
-# 类型参数
+# 类型参数
+
-## 一、泛型
-
-Scala支持类型参数化,使得我们能够编写泛型程序。
-
-### 1.1 泛型类
-
-Java中使用`<>`符号来定义类型参数,Scala中使用`[]`来定义类型参数。
-
-```scala
-class Pair[T, S](val first: T, val second: S) {
- override def toString: String = first + ":" + second
-}
-```
-
-```scala
-object ScalaApp extends App {
-
- // 使用时候你直接指定参数类型,也可以不指定,由程序自动推断
- val pair01 = new Pair("heibai01", 22)
- val pair02 = new Pair[String,Int]("heibai02", 33)
-
- println(pair01)
- println(pair02)
-}
-```
-
-### 1.2 泛型方法
-
-函数和方法也支持类型参数。
-
-```scala
-object Utils {
- def getHalf[T](a: Array[T]): Int = a.length / 2
-}
-```
-
-## 二、类型限定
-
-### 2.1 类型上界
-
-对于对象之间进行大小比较,Scala和Java一样,都要求比较的对象需要实现`java.lang.Comparable`接口。
-
-所以如果想对泛型进行比较,需要限定类型上界,语法为` S <: T`,代表S必须是类型T的子类或其本身。示例如下:
-
-```scala
-// 使用 <: 符号,限定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
-}
-```
-
-```scala
-// 测试代码
-val pair = new Pair("abc", "abcd")
-println(pair.smaller) // 输出 abc
-```
-
->注:如果你想要在Java中实现类型变量限定,需要使用关键字extends来实现,对于的Java代码如下:
->
->```java
->public class Pair> {
-> 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;
-> }
->}
->```
-
-### 2.2 视图界定 & 类型约束
-
-#### 1.视图界定
-
-在上面的例子中,如果你使用Int类型或者Double等类型进行测试,点击运行后,你会发现程序根本无法通过编译:
-
-```scala
-val pair1 = new Pair(10, 12)
-val pair2 = new Pair(10.0, 12.0)
-```
-
-之所以出现这样的问题,是因为在Scala中Int并没有实现Comparable,真正实现Comparable接口的是RichInt。在日常的编程中之所以你能够执行`3>2`这样的判断操作,是因为程序执行了隐式转换,将Int转换为RichInt。
-
-
-
-直接继承Java中Comparable接口的是特质Ordered,RichInt混入了该特质,Ordered源码如下:
-
-```scala
-// 除了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)
-}
-```
-
-所以要想在泛型中解决这个问题,需要使用视图界定:
-
-```scala
-// 视图界定符号 <%
-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,所以也可以使用如下的视图界定:
->
-> ```scala
-> class Pair[T <% Ordered[T]](val first: T, val second: T) {
->
-> def smaller: T = if (first.compareTo(second) < 0) first else second
->
-> }
-> ```
-
-#### 2. 类型约束
-
-如果你用的Scala是2.11+,则视图界定已经不推荐使用,官方推荐使用类型约束(type constraint)来实现同样的功能:
-
-```scala
-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.3 上下文界定
-
-上下文界定的形式为`T:M`,它要求必须存在一个类型为M[T]的隐式值,当你声明一个使用隐式值的方法时,需要添加一个隐式参数。上面的程序也可以使用上下文界定进行如下改写:
-
-```scala
-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
-
-}
-```
-
-### 2.4 类型下界
-
-
-
-## 三、类型通配符
\ No newline at end of file
+## 一、泛型
+
+Scala支持类型参数化,使得我们能够编写泛型程序。
+
+### 1.1 泛型类
+
+Java中使用`<>`符号来包含定义的类型参数,Scala则使用`[]`。
+
+```scala
+class Pair[T, S](val first: T, val second: S) {
+ override def toString: String = first + ":" + second
+}
+```
+
+```scala
+object ScalaApp extends App {
+
+ // 使用时候你直接指定参数类型,也可以不指定,由程序自动推断
+ val pair01 = new Pair("heibai01", 22)
+ val pair02 = new Pair[String,Int]("heibai02", 33)
+
+ println(pair01)
+ println(pair02)
+}
+```
+
+### 1.2 泛型方法
+
+函数和方法也支持类型参数。
+
+```scala
+object Utils {
+ def getHalf[T](a: Array[T]): Int = a.length / 2
+}
+```
+
+## 二、类型限定
+
+### 2.1 类型上界限定
+
+Scala和Java一样,对于对象之间进行大小比较,要求被比较的对象实现`java.lang.Comparable`接口。
+
+所以如果想对泛型进行比较,需要限定类型上界为`java.lang.Comparable`,语法为` S <: T`,代表类型S是类型T的子类或其本身。示例如下:
+
+```scala
+// 使用 <: 符号,限定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
+}
+```
+
+```scala
+// 测试代码
+val pair = new Pair("abc", "abcd")
+println(pair.smaller) // 输出 abc
+```
+
+>扩展:如果你想要在Java中实现类型变量限定,需要使用关键字extends来实现,等价的Java代码如下:
+>
+>```java
+>public class Pair> {
+> 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;
+> }
+>}
+>```
+
+### 2.2 视图界定
+
+在上面的例子中,如果你使用Int类型或者Double等类型进行测试,点击运行后,你会发现程序根本无法通过编译:
+
+```scala
+val pair1 = new Pair(10, 12)
+val pair2 = new Pair(10.0, 12.0)
+```
+
+之所以出现这样的问题,是因为Scala中的Int类并没有实现Comparable接口。在Scala中直接继承Comparable接口的是特质Ordered,它在继承compareTo方法的基础上,额外定义了关系符方法,源码如下:
+
+```scala
+// 除了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特质,所以能够进行比较。
+
+```scala
+// Predef.scala
+@inline implicit def intWrapper(x: Int) = new runtime.RichInt(x)
+```
+
+
+
+要想解决传入数值无法进行比较的问题,可以使用视图界定。语法为`T <% U`,代表T能够通过隐式转换转为U,即允许Int型参数在无法进行比较的时候转换为RichInt类型。示例如下:
+
+```scala
+// 视图界定符号 <%
+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,所以如下的视图界定和上面是等效的:
+>
+> ```scala
+> // 隐式转换为Ordered[T]
+> class Pair[T <% Ordered[T]](val first: T, val second: T) {
+> def smaller: T = if (first.compareTo(second) < 0) first else second
+> }
+> ```
+
+### 2.3 类型约束
+
+如果你用的Scala是2.11+,会发现IDEA会提示视图界定已被标识为废弃。官方推荐使用类型约束(type constraint)来实现同样的功能,其本质是使用隐式参数进行隐式转换,示例如下:
+
+```scala
+ // 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
+}
+```
+
+当然,隐式参数转换也可以运用在具体的方法上:
+
+```scala
+object PairUtils{
+ def smaller[T](a: T, b: T)(implicit order: T => Ordered[T]) = if (a < b) a else b
+}
+```
+
+### 2.4 上下文界定
+
+上下文界定的形式为`T:M`,其中M是一个泛型,它要求必须存在一个类型为M[T]的隐式值,当你声明一个带隐式参数的方法时,需要定义一个隐式默认值。所以上面的程序也可以使用上下文界定进行改写:
+
+```scala
+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]这个隐式值。为了更好的说明上下文界定,下面给出一个自定义类型的比较示例:
+
+```scala
+// 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
+}
+```
+
+### 2.5 类型下界限定
+
+2.1小节介绍了类型上界的限定,Scala同时也支持下界的限定,语法为:`U >: T`,即U必须是类型T的超类或本身。
+
+```scala
+// 首席执行官
+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)
+}
+```
+
+### 2.6 多重界定
+
++ 类型变量可以同时有上界和下界。 写法为 :`T > : Lower <: Upper`;
+
++ 不能同时有多个上界或多个下界 。但可以要求一个类型实现多个特质,写法为 :
+
+ `T < : Comparable[T] with Serializable with Cloneable`;
+
++ 你可以有多个上下文界定,写法为`T : Ordering : ClassTag` 。
+
+
+
+## 三、Ordering & Ordered
+
+上文中使用到Ordering和Ordered特质,它们最主要的区别在于分别继承自不同的Java接口:Comparable和Comparator:
+
++ **Comparable**:可以理解为内置的比较器,实现此接口的对象可以与自身进行比较;
++ **Comparator**:可以理解为外置的比较器;当对象自身并没有定义比较规则的时候,可以传入外部比较器进行比较。
+
+为什么Java中要同时给出这两个比较接口,这是因为你要比较的对象不一定实现了Comparable接口,而你又想对其进行比较,这时候当然你可以修改代码实现Comparable,但是如果这个类你无法修改(如源码中的类),这时候就可以使用外置的比较器。同样的问题在Scala中当然也会出现,所以Scala分别使用了Ordering和Ordered来继承它们。
+
+
+
+
+
+下面分别给出Java中Comparable和Comparator接口的使用示例:
+
+### 3.1 Comparable
+
+```scala
+import java.util.Arrays;
+// 实现Comparable接口
+public class Person implements Comparable {
+
+ 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
+```
+
+### 3.2 Comparator
+
+```scala
+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() {
+ @Override
+ public int compare(Person o1, Person o2) {
+ return o1.age-o2.age;
+ }
+ });
+ Arrays.stream(peoples).forEach(System.out::println);
+ }
+}
+```
+
+使用外置比较器还有一个好处,就是你可以随时定义其排序规则:
+
+```scala
+// 按照年龄大小排序
+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);
+```
+
+### 3.3 上下文界定的优点
+
+这里再次给出上下文界定中的示例代码作为回顾:
+
+```scala
+// 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代码一样,在同一个上下文中传入两个外置比较器,即下面的代码是无法通过编译的。但是你可以在不同的上下文中引入不同的隐式默认值,即使用不同的外置比较器。
+
+```scala
+implicit val ImpPersonOrdering = new PersonOrdering
+println(pair.smaller)
+implicit val ImpPersonOrdering2 = new PersonOrdering
+println(pair.smaller)
+```
+
+
+
+## 参考资料
+
+1. Martin Odersky . Scala编程(第3版)[M] . 电子工业出版社 . 2018-1-1
+2. 凯.S.霍斯特曼 . 快学Scala(第2版)[M] . 电子工业出版社 . 2017-7
+
diff --git a/pictures/scala-ordered-ordering.png b/pictures/scala-ordered-ordering.png
new file mode 100644
index 0000000000000000000000000000000000000000..5854d2e49e473b1946a7d2eb7bcf7bad5217dcc8
GIT binary patch
literal 7002
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