0. 今日大纲
-
模式匹配
-
类型参数
-
隐式转换
-
线程通信AKKA
1. 模式匹配
1、模式匹配是Scala中非常强大的一种功能。模式匹配,其实类似于Java中的switch case语法,即对一个值进行条件判断,然后针对不同的输入条件,进行结果处理。
2、Scala的模式匹配的功能比Java的switch case语法的功能要强大地多,Java的switch case语法只能对值进行匹配。但是Scala的模式匹配除了可以对值进行匹配之外,还可以对类型进行匹配、对Array和List的元素情况进行匹配、对case class进行匹配、甚至对有值或没值(Option)进行匹配。
模式匹配常见的语法结构如下:
变量 match {
case 可能性1 => 操作1
。。。
case 可能性N => 操作N
case _ => 默认/其它操作
}
1.1. 可以用到Switch语句中
Scala强大的模式匹配机制,可以应用在switch语句、类型检查以及“析构”等场合。
//使用模式匹配完成switch的操作
var sign = 0
val ch = '+'
ch match {
case '-' => sign = -1
case '*' => sign = -2
case '+' => sign = 0
case _ => sign = 1
}
/*
* switch(ch) {
* case '+' :
* sign = 0;
* break;
* case '-' :
* sign = -1;
* break;
* case '*' :
* sign = -2;
* break;
* default:
* sign = 1;
* break;
* }
*/
println("sign=" + sign)
//scala中的每一个表达式都有返回值,默认最后一句话就是返回值
sign = ch match {
case '-' => -1
case '*' => -2
case '+' => 0
case _ => 1
}
println("sign=" + sign)
1.2. 守卫
def caseOps2: Unit = {
var sign = 0
val ch = '-'
val ret = ch match {
case '-' => sign = -1
case '*' => sign = -2
case _ if Character.isDigit(ch) => 3
case _ => sign = 1
}
println("ret = " + ret)
}
1.3. 模式中的变量和类型模式
模式匹配中的变量,需要从语法结构说起:
变量 match {
case 可能性1 => 操作1
。。。
case 可能性N => 操作N
case _ => 默认/其它操作
}
启动如果是匹配的类型,可以将可能性x书写为==> 变量:类型,可能性以就成为=>变量1:类型1,这样做的好处是什么呢?就是直接进行类型转换,可以方便操作其特有的api。
如果类型时确定的,可以将:后面的类型省略掉。
-
变量
def caseOps3: Unit = { "Hello world~".foreach(c => println( c match { case ' ' => "space" case ch => "Char: " + ch } )) }
-
类型
val input:Any = StdIn.readLine("请输入内容:") // input.isInstanceOf // input.asInstanceOf input match { case i:Int => println("int i = " + i) case s:String => println("String s = " + s) case ch:Char => println("char ch = " + ch) case _ => println(input) }
1.4. 匹配数组、列表和元组
val array = Array(0, -5)
array match {
//匹配数组有2个元素,分别将两个元素赋值给x和y
case Array(x, y) => println(s"x=$x, y=$y")
//匹配数组以0开头
case Array(0, _*) => println("Array(0, _*)")
//默认的匹配,如果匹配不到,scala.MatchError
case _ => println("default")
}
1.5. 样例类
所谓样例类,就是case class,是scala中一种专门用来携带数据的class,类似于java中的javabean,
一般的定义方式需要定义该case class的主构造器,其所有的字段都在主构造器中完成声明,
case class自动会提供所谓getter和setter方法。
case class的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)
Scala自动为case class定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象
def caseOps6: Unit = {
abstract class Expr //抽象类 表达式
case class Var(name:String) extends Expr
case class UnOp(expr:Expr, operator:String) extends Expr
case class Number(num:Double) extends Expr
case class BinOp(left:Expr, operator:String, right:Expr) extends Expr
def test(expr:Expr) = {
expr match {
case Var(name) => println("Var: " + name)
case Number(num) => println("Number: " + num)
case UnOp(Var(name), "+") => println(name + "+")
case BinOp(Number(num1), "+", Number(num2)) => println(num1 + num2)
case BinOp(Number(num1), "-", Number(num2)) => println(num1 - num2)
case BinOp(Number(num1), "*", Number(num2)) => println(num1 * num2)
case _ => println(expr)
}
}
test(BinOp(Number(3.0), "-", Number(5.2)))
}
1.6. 模拟枚举
-
枚举的一般定义方式
object _02EnumrationOps { def main(args: Array[String]): Unit = { def accrossRoad(light: TrafficLight.Value): Unit = { light match { case TrafficLight.GREEN => println("畅行无阻,及时通过") case TrafficLight.YELLOW => println("hurry up") case TrafficLight.RED => println("行车不规范,亲人泪两行") case _ => println("翻车了") } accrossRoad(TrafficLight.GREEN) } } //普通的枚举的定义 object TrafficLight extends Enumeration { val GREEN, YELLOW, RED = Value }
-
枚举的模式匹配方式
scala中的枚举操作
scala中没有想java中的enum这个结构来定义枚举,但是有一个工具类Enumeration来辅助枚举
枚举的结尾必须要用Value来进行赋值
所以scala中枚举的类型就是Xxx.Valuesealed class密封类,一个类的所有的子类型都是已知的,都必须要在定义期间给出 当然一般这个sealed class密封类通常会被声明为抽象,使用子类来完成具体构建 密封类的所有子类都必须在与该密封类相同的文件中定义。 如果某个类是密封的,那么在编译期所有的子类是可知的,因而可以检查模式语句的完整性。 让所有同一组的样例类都扩展某个密封的类或特质是个好的做法。
sealed abstract class TrafficLight(color:String) case class RED(red:String) extends TrafficLight(red) case class GREEN(green:String) extends TrafficLight(green) case class YELLOW(yellow:String) extends TrafficLight(yellow) val light:TrafficLight = RED("酒后来驾车,亲人泪两行") light match { case RED(red) => println(red) case GREEN(green) => println(green) case YELLOW(yellow) => println(yellow) }
1.7. Option
Option是scala中有值(Some)或者没有值(None)的类型,scala也可以对应Option进行模式匹配进行处理。
val map = Map[String, String](
"China" -> "BJ",
"India" -> "XDL",
"Japan" -> "Tokyo"
)
map.get("India") match {
case Some(capital) => println("India's capital is " + capital)
case None => println("所查国家不存在")
}
2. 类型参数
所谓类型参数,其实就是java中的泛型,java中的泛型定义可以使任意的标识符,
可以是一个字符,两个等等,但是通常都用一个大写的字母来表示,泛型就是用来表示某一种类型,
所谓广泛的类型,同时泛型在定义的时候,需要使用<>来包裹,泛型可以定义在类上面,方法上面。
额外说明一点,成员变量上不可以定义泛型,只能沿用类上声明的泛型。
scala中的泛型和java中的泛型高度相似,也可以定义在类,方法上,成员变量上的泛型也需要沿用类的声明,
定义的是一般也是用一个大写字母,但是泛型定义时需要使用[]包裹。
最后需要说明一点的是,scala中的泛型要比java中的泛型更加的灵活和强大。
2.1. 泛型类
所谓泛型类,就是在类上定义一个泛型。
object _01GenericOps {
def main(args: Array[String]): Unit = {
val stu1 = new Student1("first", "second")
stu1.smaller()
val stu2 = new Student2(3, 4)
stu2.smaller()
val stu3 = new Student3('a', 'A')
stu3.smaller()
val stu = new Student[String, String]("aaa", "bbb")
val stu4 = new Student("aaa", 5)//因为有类型的推断,泛型可以省略掉
}
class Student1(first:String, second:String) {
def smaller(): Unit = {
if(first.compareTo(second) < 0) {
println("samller is " + first)
} else {
println("samller is " + second)
}
}
}
class Student2(first:Int, second:Int) {
def smaller(): Unit = {
if(first.compareTo(second) < 0) {
println("samller is " + first)
} else {
println("samller is " + second)
}
}
}
class Student3(first:Char, second:Char) {
def smaller(): Unit = {
if(first.compareTo(second) < 0) {
println("samller is " + first)
} else {
println("samller is " + second)
}
}
}
class Student[T, S](first: T, second: S) {
def smaller(): Unit = {
println("类上的泛型")
}
}
}
2.2. 方法上的泛型、泛型限定、视图界定
-
java版
public class GenericMethodOps { public static void main(String[] args) { int[] arr = { 3, -1, 0, 5, -5, 7, 6}; Float[] floatArr = { 3f, -1f, 0f, 5f, -5f, 7f, 6f}; String[] strArr = { "abc", "acC", "ccc", "a", "shit"}; System.out.println("排序前的数组:" + Arrays.toString(strArr)); // insertSort(arr); insertSort(strArr); System.out.println("排序后的数组:" + Arrays.toString(strArr)); } /** * 所谓的插入排序,指的是,将一个元素试图插入到一个有序的集合中。 * 首先,只有一个元素的集合,是否有序?有序。 * 从第二个元素开始进行处理 * 第一次:-1 * 3, -1, 0, 5, -5, 7, 6 * --》 -1, 3, 0, 5, -5, 7, 6 * 第二次:0 * -1, 3, 0, 5, -5, 7, 6 * ---> -1, 0, 3, 5, -5, 7, 6 * .... * * -5, -1, 0, 3, 5, 6, 7 * 通过上述的操作,发现需要两层循环,外循环用于控制每次插入的元素,内循环用于控制每次的比较 * * 泛型的限定 * T extends Comparable<T> */ private static <T extends Comparable<T>> void insertSort(T[] arr) { for(int x = 1; x < arr.length; x++) { for(int y = x; y > 0 ; y--) { if(arr[y].compareTo(arr[y-1]) < 0) { swap(arr, y, y-1); }else { break; } } } } private static void swap(int arr[], int y, int x) { //^是异或运算符,异或规则是转换成二进制比较相同为0 不同为1 arr[y] = arr[y] ^ arr[x]; arr[x] = arr[y] ^ arr[x]; arr[y] = arr[y] ^ arr[x]; } private static <T> void swap(T arr[], int y, int x) { T temp = arr[y]; arr[y] = arr[x]; arr[x] = temp; } }
两个知识点:一个是方法上声明泛型,还有一个是泛型的上限限定。
-
scala版
//scala版本的方法泛型 object _02GenericOps { def main(args: Array[String]): Unit = { val arr = Array[Integer](3, -1, 0, 5, -5, 7, 6) println("排序前的数组:" + arr.mkString("[", ", ", "]")) insertSort(arr) println("排序后的数组:" + arr.mkString("[", ", ", "]")) } /* scala中的泛型的限定语法格式 T <: 类型 java中的泛型限定语法格式 T extends 类型 */ def insertSort[T <: Comparable[T]](array: Array[T]): Unit = { for(x <- 1 until array.length) { for(y <- 1 to x reverse) { if(array(y).compareTo(array(y - 1)) < 0) { swap(array, y, y - 1) } } } } private def swap[T](arr: Array[T], y: Int, x: Int): Unit = { val temp = arr(y) arr(y) = arr(x) arr(x) = temp } }
我们将java的版本修正为scala的版本,传入Integer类型的数组,完美解决问题,但是如果传递的是一个Int类型的数组,编译就会报错:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XZgxVJXr-1575337956275)(assets/1572925381551.png)]
这个错误说的是Int类型,不具备比较性,观察Int的源码:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VyQM7L0n-1575337956277)(assets/1572925413164.png)]
确实没有体现出Comparable的特性,所以是不能直接进行比较,但是Int类型的数据,在scala中确确实实可以进行加减乘除等等运算,比较也是可以的,所以我们想要让上述代码也能执行成功,就需要将泛型定义的修改为:
[T <: Comparable[T]] ==> [T <%: Comparable[T]]
其中T <% Comparable[T]的操作,其实是对T做了类型的内部转换或者功能的增强,把这种操作我们称之为scala类型参数(泛型)的视图界定,其本质是通过下面要学习的隐式转换函数来实现的。
2.3. 泛型的协变和逆变
首先需要明确的是,在java中等号左右两侧的泛型必须是要一直的,不可以出现泛型之间继承关系,纵然类型可以出现继承。
class Person{
}
class Student extends Person{
}
List<Person> list = new ArrayList<Person>(); //ok
不可以
List<Person> list = new ArrayList<Student>();
or
List<Student> list = new ArrayList<Person>();
scala是一个非常灵活的语言,java办不到的往往scala可以轻而易举的搞定。
当然默认情况下,scala也只能支持=左右两侧泛型是一直的,但是为了程序更加的灵活和通用,scala也支持泛型出现继承关系,所谓协变和逆变。
//泛型的协变和逆变
object _03GenericOps {
def main(args: Array[String]): Unit = {
val myList1:MyList[Person] = new MyList[Person]()
val myList2:MyList[Person] = new MyList[Student]()//泛型的协变
val myList3:MyList1[Student] = new MyList1[Person]()//泛型的逆变
}
}
class MyList[+T] {//泛型协变的定义
}
class MyList1[-T] {//泛型逆变的定义
}
class Person {}
class Student extends Person{}
3. 隐式转换
3.0 隐式转换
-
说明
scala提供的能够将一种类型根据需要,自动转化成其他类型的操作方式,进而可以让原有的类型具备其没有的功能,丰富现有api,而不会对原有的代码造成过多的污染。
这一点是scala程序非常优秀的一点设计,是java等其它语言所不具备的能力。其实在前面的视图界定案例中,视图界定就采用的是隐式转换将Int类型转化成为了RichInt,而RichInt扩展了Comparable接口,自然可以进行比较了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vMPRPeLp-1575337956277)(assets/1572934320555.png)]
这背后是通过一个叫做隐式转换函数的结构来时实现的。
-
隐式转换函数
现在有这样一个需求:
val x:Int = 3.5
要求这个赋值的变量,编译通过,显然正常情况下是不可以的,但是在scala中一切皆有可能,就需要用到隐式转换,本质是隐式转换函数。
隐式转换函数就是在普通的函数前添加一个关键字implicit,即可完成。一般的结构为
implicit def source2Target(source:Source):Target = { source ---> target 操作 }
这样,变量source就会在自己的作用域范围内自动的搜索是否有这样的隐式转换,如果有,则自动完成类型转换或者加强。
implicit def double2Int(d:Double):Int = d.toInt val a:Int = 3.5 println("a=" + a)
3.1. 利用隐士转换丰富现有类库的功能
利用隐士转换丰富现有类库的功能,举例说明,通过java.io.File来读取一个文件中的内容,但是File没有read的功能,如何能做到让File也具备流的读写功能?
在scala中的答案就是,隐式转换!
def main(args: Array[String]): Unit = {
val file = new File("E:/data/hello.txt")
file.read().foreach(println)
}
implicit def file2RichFile(file:File):RichFile = new RichFile(file)
//将file最后要转化为RichFile,才能具备read的功能
class RichFile(file:File) {
def read() = Source.fromFile(file).getLines()
}
3.2. 引入隐士转换(在不同包下的话可以在主方法中导入包)
当开发出一个隐式转换函数时,该如何使用呢?
最简单的其实就是,只要在要转换的变量的作用域内能够访问的到,即可进行自动转换,不需要人工干预。
在同一个文件中定义的隐式转换,不需要做任何的干预,但是如果要导入其它类中定义的隐式转换,要想在本类中使用,就必须要向导包一样导入。只要在其作用域内导入,即可。同时这个导入的隐式转换因为和导包一样,也不存在对原有类的结构发生入侵。
我们在导入的时候,我们倾向于导入到最靠近变量的部分。越靠近变量,其作用域范围越小,影响越小。
object _03ImplicitOps {
def main(args: Array[String]): Unit = {
import _02ImplicitOps._
val file = new File("E:/data/hello.txt")
file.read().foreach(println)
}
}
3.3. 隐式转换参数
一个函数的参数被implicit修饰,称该参数为隐式转换参数,这个隐式转换参数在传递的时候,可以传,有时候也可以不用传。
def main(args: Array[String]): Unit = {
val list = List(3, -3, 0, -8, 45, 4)
list.foreach(println)
println("---------------")
implicit val ord = new Ordering[Int](){
override def compare(x: Int, y: Int) = {
y.compareTo(x)
}
}
//显示的传递隐式转换参数
list.sorted(ord).foreach(println)
println("-------隐式传递---------")
//隐式传递隐式转换参数
list.sorted.foreach(println)
}
说明:隐式转换参数,可以显示的指定,也可以隐式的指定,如果是隐式指定,就需要让该变量在其作用域内访问到被implicit关键字修饰的相关变量,自动进行赋值。
4. RPC和AKKA
不管是RPC还是AKKA,都是大数据体系中不同框架中所采用的通信框架。比如RPC就是大名鼎鼎的Hadoop中Hdfs里面NameNode,dataNode等等通信所采用的框架,AKKA在早起的Spark版本中(1.6一下)所采用的通信框架,1.6以上也开始采用基于Netty的RPC框架(其实自己基于RPC,模仿AKKA手写一个通信的框架)。
这些通信框架都是干嘛?通信!主要指的是进程间的消息的传递与处理。
RPC和AKKA便是这些通信框架中的两个典型代表。除此以外,还有非常多的进程间的通信框架。
eg:
WebService、Thirft、RMI等等。
WebService:在传统的j2ee中使用还是比较广泛。数据传输基于xml,客户端读取到请求的响应之后,就需要解析xml。比如进行天气情况的显示、航班信息的显示等等。
Thirft: 是apache的一个顶级项目,是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。
RMI: remote method invocation.远程方法调用,是jdk中自带的一个进程间通信的框架,在java.rmi包下面。
4.1. RPC
RPC是Remote process call(远程过程调用),是IPC(Inter-Process Communication进程间调用)中的一种,IPC分为了两种,一种便是RPC,还有一种LPC(Local process call),LPC相当于RPC要简单的多,在大多数分布式系统中,多采用RPC来完成消息的传递。
RPC的工作原理是这样的。首先RPC底层通过TCP/IP进行消息的传递,采用客户端和服务端通信的方式。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lLgjavZJ-1575337956278)(assets/1572940241822.png)]
rpc的使用需要用到hadoop相关的依赖,导入maven依赖
<dependency>