特别说明,本文主要参考 尚硅谷大数据之韩顺平Scala视频及对应资料,本文是笔者对应的学习笔记。
1. Scala 概述
Scala 有些类是对 Java 类的封装,所以 Scala 依赖 JDK,使用 Scala,需要先装 JDK。Scala 编译成 Java 字节码,运行在 JVM 之上。
可去官网下载对应操作系统及对应版本的 JDK 和 Scala,安装/解压,并配置环境变量。一般在 Windows 开发,在 Linux 部署。一般使用 IDEA 开发(需要安装 Scala 插件)。
第一个 Scala 程序
方式一:Notepad++ 写程序
// HelloScala.scala
// 第一个 Scala 程序
object HelloScala {
def main(args: Array[String]): Unit = {
println("Hello, Scala!")
}
}
// 编译+运行
scalac HelloScala.scala
scala HelloScala
方式二:IDEA 写程序
- 建立 Maven 项目
- 在 main 目录下建立 scala 文件夹并右键标记成 Sources Root
- 在项目上右键 Add Framework Support 添加 Scala 支持
- 书写 Scala 程序
输出 Demo
package com.example.chapter01
object PrintDemo {
def main(args: Array[String]): Unit = {
val str1: String = "Hello"; val str2: String = "Scala" // 同一行多条语句需要分号隔开
println(str1 + ", " + str2)val name: String = "Tom"
val age: Int = 29
val salary: Float = 2.9f
printf("name: %s\tage: %d\tsalary: %.2f\n", name, age, salary) // 格式化输出
println(s"name: $name\tage: $age\tsalary: $salary")
println(s"name: $name\tage: ${age+10}\tsalary: ${salary*2}")
println((10.0 / 3).formatted("%.2f"))
}
}
输入 Demo
package com.example.chapter01
import scala.io.StdIn
object InputDemo {
def main(args: Array[String]): Unit = {
println("Input your name:")
val name: String = StdIn.readLine
println("Input your age:")
val age: Int = StdIn.readInt
println("Input your salary:")
val salary: Double = StdIn.readDouble
println(s"name=$name")
println(s"age=$age")
println(s"salary=$salary")
}
}
关联源码
本文使用的是 2.11.12 版本,官方下载源码,IDEA 上点击关联源码的解压文件。
2. 变量
val/var
package com.example.chapter02.vars
object VarDemo01 {
def main(args: Array[String]): Unit = {
var num = 10 // 类型推断
println(num.isInstanceOf[Int]) // true
println(num.isInstanceOf[Double]) // false
//类型确定后,就不能修改,说明 Scala 是强数据类型语言
// num = 2.3 // 编译错误
num = 10 // var 可变
val num2 = 1
// num2 = 2 // 编译错误,val 不可变
}
}
数据类型
- Scala 与 Java 有着相同的数据类型,在 Scala 中数据类型都是对象, Scala 没有 java 中的原生类型。
- Unit 等同于 Java 的 void,只有一个实例值:()。
- 在开发中通常可将 Nothing 类型的值返回给任意变量或函数,抛异常用的多。
- Nothing 类型没有实例。它对于泛型结构是有用处的。比如,空列表 Nil 的类型是 List[Nothing],它是 List[T]的子类型,T 可以是任何类。
- Null 类型的唯一实例就是 null 对象。可以将 null 赋值给任何引用,但不能赋值给值类型的变量。
3. 运算符
略(同 Java)
4. 程序流程控制
for
package com.example.chapter04
object ForDemo01 {
def main(args: Array[String]): Unit = {
for(i <- 1 to 5) {
// 闭区间
print(i + " ")
} // 1 2 3 4 5
println("\n--------------------")
for(i <- 1 until 5) {
// 左闭右开
print(i + " ")
} // 1 2 3 4
println("\n--------------------")
val list = List("hello", 1, 2, "hah")
for(item <- list) {
print(item + " ")
}// hello 1 2 hah
println("\n--------------------")
/**
* 等价代码:
* for(i <- 1 to 5) {
* if(i % 2 == 0) {
* print(i + " ")
* }
* }
*/
for(i <- 1 to 5 if i % 2 == 0) {
print(i + " ")
}// 2 4
println("\n--------------------")
/**
* 等价代码:
* for(i <- 1 to 5) {
* val j = 5 - i
* print(j + " ")
* }
*
* 等价代码:
* for{
* i <- 1 to 5
* j = 5 - i
* }{
*
* }
* */
for(i <- 1 to 5; j = 5 - i) {
print(j + " ")
}// 4 3 2 1 0
println("\n--------------------")
/**
* 等价代码:
* for(i <- 1 to 5) {
* for(j <- 1 to 5) {
*
* }
* }
*/
for(i <- 1 to 5; j <- 1 to 5) {
}
println("\n--------------------")
val res = for(i <- 1 to 5) yield {
if(i % 2 == 0) {
i
} else {
"odd"
}
}
println(res)
println("\n--------------------")
for(i <- Range(1, 5, 2)) {
// 控制步长
print(i + " ")
}// 1 3
println("\n--------------------")
}
}
实现类似 break/continue 的功能
Scala 内置控制结构特地去掉了 break 和 和 continue。
package com.example.chapter04
import scala.util.control.Breaks._
object BreakAndContinue {
def main(args: Array[String]): Unit = {
/**
* 类似代码:
* for(i <- 1 to 5) {
* if(i != 3) {
* print(i + " ")
* }
* }
*/
for(i <- 1 to 5 if(i != 3)) {
// 实现 Java 的 continue 功能
print(i + " ")
}
println("\n--------------------------")
/**
* def breakable(op: => Unit)
*
* breakable 高阶函数,函数的参数就是一个函数 op,op 无参且返回值为 Unit
* breakable 函数对 op 这段代码块捕获异常,如果不是 breakException 就抛出,否则不处理异常,中断当前程序执行
*/
breakable {
// 传入的参数是代码块时,可把 () 改成 {}
for (i <- 1 to 5) {
if (i == 3) {
break() // def break(): Nothing = { throw breakException }
}
print(i + " ")
}
}
println("\nbroken")
}
}
5. 函数式编程(基础)
第一个函数
package com.example.chapter05.fun
object FunDemo01 {
def main(args: Array[String]): Unit = {
val n1 = 10
val n2 = 20
println(getRes(n1, n2, '+')) // 30
println(getRes(n1, n2, '-')) // -20
println(getRes(n1, n2, '*')) // null
}
def getRes(n1: Int, n2: Int, oper: Char) = {
if(oper == '+') {
n1 + n2
} else if (oper == '-') {
n1 - n2
} else {
null
}
}
}
注意事项
注意事项
//如果写了 return ,返回值类型就不能省略
def getSum(n1: Int, n2: Int): Int = {
return n1 + n2
}
// 如果没有指定实参,则会使用默认值。如果指定了实参,则实参会覆盖默认值
def sayOk(name : String = "jack"): String = {
return name + " ok! "
}
// 递归函数未执行之前无法推断出来结果类型,在使用时必须有明确的返回值类型
def f(n: Int): Int = {
if(n <= 1) {
1
} else {
f(n-1) + f(n -2 )
}
}
//支持 0 到多个参数
def sum(args :Int*) : Int = {
}
//支持 1 到多个参数
def sum(n1: Int, args: Int*) : Int = {
}
// 返回类型为 Unit 的函数称为过程(procedure),无返回值,可写成:
// def f(name: String) {
// println(name + " hello")
// }
def f(name: String): Unit = {
println(name + " hello")
}
// 未声明返回类型,但有等号,该函数实际有返回值(对最后一行代码类型推断),不是过程
def f(name: String) = {
1
}
注意事项(函数多个参数并且有参数具备默认值)
package com.example.chapter05.fun
/**
* 如果函数存在多个参数,每一个参数都可以设定默认值,那么这个时候,传递的参数到底是覆
* 盖默认值,还是赋值给没有默认值的参数,就不确定了(默认按照声明顺序[从左到右])。在
* 这种情况下,可以采用带名参数
*/
object FunParameterDetail {
def main(args: Array[String]): Unit = {
mysqlCon()
mysqlCon("127.0.0.1", 7777) //从左到右覆盖
mysqlCon(user = "tom", pwd = "123")
// f6("v2") // 错误!从左到右覆盖,p1 是 "v2",但 p2 未被赋值
f6(p2="v2") // (?)
}
def mysqlCon(add:String = "localhost",port : Int = 3306,
user: String = "root", pwd : String = "root"): Unit = {
println("add=" + add)
println("port=" + port)
println("user=" + user)
println("pwd=" + pwd)
}
def f6 ( p1 : String = "v1", p2 : String ) {
println(p1 + p2);
}
}
lazy
package com.example.chapter05.fun
object LazyDemo01 {
def main(args: Array[String]): Unit = {
// lazy 不能修饰 var 类型的变量
lazy val res = sum(1, 2) // 此句代码不会执行 sum 函数
// println(res) // 用到 res 才会执行 sum 函数
// 变量值得分配也会推迟到首次用的时候,比如 lazy val i = 10
}
def sum(n1: Int, n2: Int): Int = {
println("sum(n1: Int, n2: Int): Int 执行了...")
n1 + n2
}
}
异常
package com.example.chapter05.fun
object ExceptionDemo {
def main(args: Array[String]): Unit = {
try {
val r = 1 / 0
} catch {
// 在 scala 中只有一个 catch
case ex: ArithmeticException => {
// 小异常写在大异常前面,否则虽然不会报错,但这不是好的编程风格
println("ArithmeticException: divided by 0")
}
case ex: Exception => {
println("Exception")
}
} finally {
println("finally")
}
println("continue...")
try {
// 捕获方法调用可能遇到的异常,避免程序异常终止
f
} catch {
case ex: NumberFormatException => {
println("To deal with NumberFormatException...")
}
case ex: Exception => {
println("Exception")
}
}
println("continue...")
}
// 声明可能出现的异常,有助于该方法的调用者捕获异常,避免程序异常终止
@throws(classOf[NumberFormatException])
def f {
"abc".toInt
}
}
6. 面向对象编程(基础)
可以利用 jd-gui 等反编译工具把 Scala 代码编译成的 .class 文件反编译成 Java 代码,利用 Java 代码读懂 Scala 在背后干了啥。(如果 jd-gui 执行提示需要下载 jdk 1.7,那就根据对应指引下载安装即可)
第一个面向对象程序
package com.example.chapter06.oop
object CatDemo {
def main(args: Array[String]): Unit = {
val cat = new Cat
cat.name = "小白" // 实际调用了对应的方法(见下面反编译代码)
cat.age = 3 // 实际调用了对应的方法(见下面反编译代码)
cat.color = "白色" // 实际调用了对应的方法(见下面反编译代码)
println(s"Cat[name=${cat.name}, age=${cat.age}, color=${cat.color}]")
}
}
// 类默认就是 public,但不能写 public
class Cat {
var name: String = ""
var age: Int = _ // _ 表示给 age 默认值,Int 对应的默认值是 0 (Byte/Short/Int/Long 默认值是 0,Float/Double 默认值是 0.0)
var color: String = _ // _ 表示给 color 默认值,String 对应的默认值是 null (引用类型默认是 null)
}
// 使用 jd-gui 反编译 Cat.class 得到下面 Java 代码:
/*
package com.example.chapter06.oop;
import scala.reflect.ScalaSignature;
@ScalaSignature(bytes="\006\001}2A!\001\002\001\027\t\0311)\031;\013\005\r!\021aA8pa*\021QAB\001\nG\"\f\007\017^3saYR!a\002\005\002\017\025D\030-\0349mK*\t\021\"A\002d_6\034\001a\005\002\001\031A\021Q\002E\007\002\035)\tq\"A\003tG\006d\027-\003\002\022\035\t1\021I\\=SK\032DQa\005\001\005\002Q\ta\001P5oSRtD#A\013\021\005Y\001Q\"\001\002\t\017a\001\001\031!C\0013\005!a.Y7f+\005Q\002CA\016\037\035\tiA$\003\002\036\035\0051\001K]3eK\032L!a\b\021\003\rM#(/\0338h\025\tib\002C\004#\001\001\007I\021A\022\002\0219\fW.Z0%KF$\"\001J\024\021\0055)\023B\001\024\017\005\021)f.\033;\t\017!\n\023\021!a\0015\005\031\001\020J\031\t\r)\002\001\025)\003\033\003\025q\027-\\3!\021%a\003\0011AA\002\023\005Q&A\002bO\026,\022A\f\t\003\033=J!\001\r\b\003\007%sG\017C\0053\001\001\007\t\031!C\001g\0059\021mZ3`I\025\fHC\001\0235\021\035A\023'!AA\0029BaA\016\001!B\023q\023\001B1hK\002B\021\002\017\001A\002\003\007I\021A\r\002\013\r|Gn\034:\t\023i\002\001\031!a\001\n\003Y\024!C2pY>\024x\fJ3r)\t!C\bC\004)s\005\005\t\031\001\016\t\ry\002\001\025)\003\033\003\031\031w\016\\8sA\001")
public class Cat
{
public String name()
{
return this.name;
}
public void name_$eq(String x$1)
{
this.name = x$1;
}
private String name = "";
private int age;
private String color;
public int age()
{
return this.age;
}
public void age_$eq(int x$1)
{
this.age = x$1;
}
public String color()
{
return this.color;
}
public void color_$eq(String x$1)
{
this.color = x$1;
}
}
*/
构造器
class 类名(形参列表) {
// 主构造器,会执行类定义中的所有语句
// 类体
def this(形参列表) {
// 辅助构造器,
}
def this(形参列表) {
//辅助构造器可以有多个,重载
}
}
class Person {
// 主构造器无参,可省略(),定义私有/保护主构造器可 class Person private/protected (Scala 中 protected 只有子类可访问,同包不可访问,与 Java 不同)
var name: String = _
var age: Int = _
def this(name: String) {
this() // 辅助构造器一定得先调用主构造器
this.name = name
}
def this(age: Int) {
this("hah") // 通过调用 def this(name: String) 间接调用主构造器
this.age = age
}
def this(name: String, age: Int) {
this()
this.name = name
this.age = age
}
}
主构造器参数
1) Scala 类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量。
2) 如果参数使用 val 关键字声明,Scala 会将参数作为类的私有的只读属性使用(有 get 方法,无 set 方法)。
3) 如果参数使用 var 关键字声明,那么那么 Scala 会将参数作为类的成员属性使用(有 get 方法,也有 set 方法)。
7. 面向对象编程(中级)
-
Scala 为了简化开发,当声明属性 var 时,本身就自动提供了对应 setter/getter 方法,如果属性声明为 private 的,那么自动生成的 setter/getter 方法是 private 的,如果属性省略访问权限修饰符,那么自动生成的 setter/getter 方法是 public 的。
-
子类继承了所有的属性,只是私有的属性不能直接访问。
包的引入
// 了解下包,Scala 里的包挺复杂,还有包对象,一个文件都能定义多重包。。暂且不深入研究
//这里我们增加一个包访问权限,下面 private[visit]:
// 1,仍然是 private
// 2. 在 visit 包(包括子包)下也可以使用 name ,相当于扩大访问范围
protected[visit] val name = "jack"
-
在 Scala 中,import 语句可以出现在任何地方,并不仅限于文件顶部。import 语句的作用一直延伸到包含该语句的块末尾。这种语法的 好处是,在需要时在引入包, 缩小 import 包的作用范围,提高效率。
class User { import scala.beans.BeanProperty // @BeanProperty 修饰后,可像 Java 一样用 setName/getName 访问属性,仅仅是提供和 Java 的形式一致性 @BeanProperty var name : String = "" }
-
Java 中如果想要导入包中所有的类,可以通过通配符*,Scala 中采用_。
-
如果不想要某个包中全部的类,而是其中的几个类,可以采用大括号。
def test(): Unit = { //可以使用选择器,选择引入包的内容,这里,我们只引入 HashMap, HashSet import scala.collection.mutable.{ HashMap, HashSet} var map = new HashMap() var set = new HashSet() }
-
如果引入的多个包中含有相同的类,那么可以将不需要的类进行重命名进行区分。
def test2(): Unit = { //下面的含义是 将 java.util.HashMap 重命名为 JavaHashMap import java.util.{ HashMap=>JavaHashMap, List} import scala.collection.mutable._ var map = new HashMap() // 此时的 HashMap 指向的是 scala 中的 HashMap var map1 = new JavaHashMap() // 此时使用的 java 中 HashMap 的别名 }
-
如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉。
import java.util.{ HashMap=>_, _} // 含义为引入 java.util 包的所有类,但是忽略 HahsMap 类. var map = new HashMap() // 此时的 HashMap 指向的是 scala 中的 HashMap
Scala 中类型检查和转换
classOf[String] 就如同 Java 的 String.class
obj.isInstanceOf[T] 就如同 Java 的 obj instanceof T 判断 obj 是不是 T 类型
obj.asInstanceOf[T] 就如同 Java 的 (T)obj 将 obj 强转成 T 类型
println(classOf[String]) // class java.lang.String
println("Scala".getClass.getName) // java.lang.String (反射)
package com.example.chapter07
object TypeConvertCase {
def main(args: Array[String]): Unit = {
val student = new Student
val employee = new Employee
test(student)
test(employee)
test(null)
test(new Person)
}
def test(p: Person): Unit = {
if(p == null) {
return
}
if(p.isInstanceOf[Student]) {
p.asInstanceOf[Student].info
} else if (p.isInstanceOf[Employee]) {
p.asInstanceOf[Employee].info
} else {
p.info
}
}
}
class Person {
def info = println("Person")
}
class Student extends Person {
override def info = println("Student")
}
class Employee extends Person {
override def info = println("Employee")
}
Scala 中对象的构造过程
只有主构造器可以调用父类的构造器,辅助构造器不能直接调用父类的构造器。
package com.example.chapter07
object ObjectMadeProcess {
def main(args: Array[String]): Unit = {
val employee1 = new Employee66
println("------------------------")
val employee2 = new Employee66("Tom")
}
}
class Person66(pName:String) {
var name = pName
println("父类主构造器...")
def this() {
this("父类辅助构造器...")
println("父类辅助构造器...")
}
}
class Employee66 extends Person66 {
println("子类主构造器....")
def this(name: String) {
this // 必须调用主构造器
this.name = name
println("子类辅助构造器...")
}
}
/*
父类主构造器...
父类辅助构造器...
子类主构造器....
------------------------
父类主构造器...
父类辅助构造器...
子类主构造器....
子类辅助构造器...
*/
package com.example.chapter07
object ObjectMadeProcess02 {
def main(args: Array[String]): Unit = {
val employee1 = new Employee67("Tom")
println("------------------------")
val employee2 = new Employee67("Jack", 99)
employee2.info
}
}
class Person67(pName:String) {
var name = pName
println("父类主构造器...")
def this() {
this("父类辅助构造器...")
println("父类辅助构造器...")
}
}
class Employee67(eName: String, age: Int) extends Person67(eName) {
println("子类主构造器....")
def this(name: String) {
this(name, 10) // 必须调用主构造器
this.name = name
println("子类辅助构造器...")
}
def info: Unit = {
println(s"雇员的名字是${this.name}")
}
}
/*
父类主构造器...
子类主构造器....
子类辅助构造器...
------------------------
父类主构造器...
子类主构造器....
雇员的名字是Jack
*/
字段重写
Java 中只有方法重写,没有字段重写。
package test;
/**
* Java 动态绑定机制
* 1.如果调用的是方法,则 JVM 会将该方法和对象的内存地址绑定
* 2.如果调用的是一个属性,则没有动态绑定机制,在哪里调用,就返回对应值
*/
public class DynamicBind {
public static void main(String[] args) {
AA obj = new BB();
System.out.println(obj.sum()); //40 //? 30
System.out.println(obj.sum1()); //30 //? 20
}
}
class AA {
public int i = 10;
public int sum() {
// 取决于调用的是 AA 的 getI 还是 BB 的 getI,分别对应 10+10 和 20+10
return getI() + 10;
}
public int sum1() {
// 20
return i + 10;
}
public int getI() {
// 10
return i;
}
}
class BB extends AA {
public int i = 20; // 父类的 i 可用 super 获取
// public int sum() { // 40
// return i + 20;
// }
public int getI() {
// 20
return i;
}
// public int sum1() { // 30
// return i + 10;
// }
}
Scala 覆盖/重写方法/字段
package com.example.chapter07
object ScalaFieldOverride01 {
def main(args: Array[String]): Unit = {
val a:A = new B
val b: B = new B
println(s"a.n=${a.n}\tb.n=${b.n}") // a.n=11 b.n=11
println(s"a.f=${a.f}\tb.f=${b.f}") // a.f=22 b.f=22
println(s"a.f=${a.fun}\tb.f=${b.fun}") // a.fun=33 b.fun=33
}
}
class A {
val n: Int = 1
def f(): Int = {
2
}
def fun(): Int = {
3
}
}
class B extends A {
override val n: Int = 11 // val 覆盖 val(val 只能覆盖 val 或 无参 def)
override val f: Int = 22 // val 覆盖无参 def(val 只能覆盖 val 或 无参 def)
override def fun: Int = 33 // def 只能覆盖 def
}
/**
* 反编译,下面的 class C 的 name 实际生成了两个抽象方法(set/get)
* public abstract class C {
* public abstract String name();
* public abstract void name_$eq(String paramString);
* }
*/
abstract class C {
var name: String // 抽象字段(未初始化的字段)
}
/**
* public class D extends C {
* public void name_$eq(String x$1) {
* this.name = x$1;
* }
* public String name() {
* return this.name;
* }
* private String name = "hah";
* }
*/
class D extends C {
// var 只能重写/覆盖抽象的 var
override var name: String = "hah" // override 可省略,本质是对抽象方法的实现,非覆盖/重写
}
抽象类
package com.example.chapter07
/**
* 1. 抽象类不能实例化
* 2. 抽象类可以没有抽象方法
* 3. 一旦类包含了抽象方法或者抽象字段,这个类必须声明为 abstract
* 4. 抽象方法不能有主体,不允许使用 abstract 修饰
* 5. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法和抽象字段,除非它自己也声明为 abstract 类
* 6. 抽象方法和抽象字段不能使用 private、final 来修饰,因为这些关键字都是和重写/实现相违背的
* 7. 抽象类中可以有普通方法
* 8. 子类重写(实现)抽象方法不需要 override,写上也不会错
*/
object AbstractClassDemo {
def main(args: Array[String]): Unit = {
// 抽象类的匿名实现
new Animal {
override var name: String = "Dog Shi"
override var age: Int = 3
override def say(): Unit = {
println(s"$name, $age years old, is saying...")
}
}.say()
}
}
//抽象类
abstract class Animal{
var name : String //抽象字段
var age : Int // 抽象字段
var color : String = "black" //普通属性
def say() //抽象方法,不要标记 abstract
}
8. 面向对象编程(高级)
伴生类/伴生对象
package com.example.chapter08
object AccompanyObject {
def main(args: Array[String]): Unit = {
println(ScalaPerson.sex)
ScalaPerson.hi()
}
}
/**
* 1. 同一个文件中,有 class ScalaPerson 和 object ScalaPerson
* 2. class ScalaPerson 是伴生类,object ScalaPerson 是伴生对象
* 3. 伴生对象中是静态内容,伴生类中是非静态内容
* 4. 伴生类编译成 ScalaPerson.class
* public class ScalaPerson {
* private String name;
*
* public void name_$eq(String x$1) {
* this.name = x$1;
* }
*
* public String name() {
* return this.name;
* }
*
* public static boolean sex() {
* return ScalaPerson..MODULE$.sex();
* }
*
* public static void sex_$eq(boolean paramBoolean) {
* ScalaPerson..MODULE$.sex_$eq(paramBoolean);
* }
*
* public static void hi() {
* ScalaPerson..MODULE$.hi();
* }
* }
* 5. 伴生对象编译成 ScalaPerson$.class
* public final class ScalaPerson$ {
* public static final MODULE$;
* private boolean sex;
*
* public boolean sex() {
* return this.sex;
* }
*
* public void sex_$eq(boolean x$1) {
* this.sex = x$1;
* }
*
* public void hi() {
* Predef..MODULE$.println("Hi...");
* }
*
* private ScalaPerson$() {
* MODULE$ = this;this.sex = true;
* }
*
* static {
* new ();
* }
* }
*/
class ScalaPerson {
var name: String = _
}
object ScalaPerson {
var sex: Boolean = true
def hi() = {
println("Hi...")
}
}
package com.example.chapter08
object ApplyDemo {
def main(args: Array[String]): Unit = {
val pig = new Pig("Pig")
// 使用 apply 方法创建对象
val pig11 = Pig() // () 不可省略
val pig22 = Pig("Pig22")
println(s"pig.name=${pig.name}")
println(s"pig11.name=${pig11.name}")
println(s"pig22.name=${pig22.name}")
}
}
class Pig(pName: String) {
var name: String = pName
}
object Pig {
def apply(pName: String): Pig = new Pig(pName)
def apply(): Pig = new Pig("Anonymous")
}
trait
- 理解 trait 等价于 Java 的 interface + abstract class。
- 在 scala 中,java 的接口都可以当做 trait 来使用。
- 所有混入某 trait (B)的类(A),会自动成为 B 所继承的父类(C)的子类,
class A extends D with B
,此时,A 继承了 D 和 C,要求 D 是 C 的子类,否则 A 就多继承了。
1) 没有父类
class 类名 extends 特质 1 with 特质 2 with 特质 3 ..
2) 有父类
class 类名 extends 父类 with 特质 1 with 特质 2 with 特质 3
混入 trait
package com.example.chapter08
object MixInDemo {
def main(args: Array[String]): Unit = {
val oracleDB = new OracleDB with Operate // 混入 trait
oracleDB.insert(100)
val mySQLDB = new MySQLDB with Operate // 混入 trait
mySQLDB.insert(200)
val mySQLDB_ = new MySQLDB_ with Operate {
// 混入 trait
override def say(): Unit = {
println("say...")
}
}
mySQLDB_.insert(999)
mySQLDB_.say()
}
}
trait Operate {
def insert(id: Int): Unit = {
println(s"insert $id")
}
}
class OracleDB
abstract class MySQLDB
abstract class MySQLDB_ {
def say()
}
继承多个 trait
package com.example.chapter08
object AddTraits {
def main(args: Array[String]): Unit = {
// 1. 构建动态混入的对象时,从左到右执行
// 2. 执行动态混入对象的方法时,从后面的 trait 执行(即从右到左)
// 3. 当执行到 super 时,指的是其左边的 trait,如果左边没有 trait,则 super 就是父 trait
// 本例中,File4 中调用 super.insert 会执行左边 DB4 的 insert,DB4 左边没 trait,就执行 Data4 的 insert
val mysql = new MySQL4 with DB4 with File4
println(mysql)
mysql.insert(666)
}
}
trait Operate4 {
println("Operate4...")
def insert(id: Int)
}
trait Data4 extends Operate4 {
println("Data4...")
override def insert(id: Int): Unit = {
println(s"insert $id")
}
}
trait DB4 extends Data4 {
println("DB4")
override def insert(id: Int): Unit = {
println("into database")
super.insert(id)
}
}
trait File4 extends Data4 {
println("File4")
override def insert(id: Int): Unit = {
println("into file")
super.insert(id)
}
}
class MySQL4
trait 构造顺序
package com.example.chapter08
object MixInSeq {
def main(args: Array[String]): Unit = {
// 此方式实际是构建类对象, 在混入特质时,该对象还没有创建。
new FF
println("----------------------")
// 此方式实际是构造匿名子类,可以理解成在混入特质时,对象已经创建了。
new KK with CC with DD
}
}
trait AA {
println("AA")
}
trait BB extends AA {
println("BB")
}
trait CC extends BB {
println("CC")
}
trait DD extends CC {
println("DD")
}
class EE {
println("EE")
}
class FF extends EE with CC with DD {
println("FF")
}
class KK extends EE {
println("KK")
}
/*
EE
AA
BB
CC
DD
FF
----------------------
EE
KK
AA
BB
CC
DD
*/
嵌套类
略
9. 隐式转换和隐式值
隐式函数
package com.example.chapter09
object ImplicitDemo01 {
def main(args: Array[String]): Unit = {
/**
* 1. 隐式转换函数的函数名可以是任意的, 隐式转换与函数名称无关,只与函数参数类型和返回值类型有关。
* 2. 隐式函数可以有多个,但是需要保证在当前环境下,只有一个隐式函数能被识别。
*/
implicit def Double2Int1(d: Double): Int = {
d.toInt
}
// 如果下面再定义一个参数是 Double 返回值是 Int 的函数,后面的 3.5 无法完成隐式转换,不知道选哪个隐式函数!
// implicit def Double2Int2(d: Double): Int = {
// 1
// }
val num: Int = 3.5 // 隐式函数 Double2Int 自动把 3.5 转成 3
println(num)
}
}
用隐式函数丰富类库
package com.example.chapter09
object ImplicitDemo02 {
def main(args: Array[String]): Unit = {
val mysql: MySQL = new MySQL
mysql.insert
// 搞个 MySQL 类型的,就能自动生成一个 DB,就具备了 DB 的 delete 方法和 update 方法
implicit def enhanceMySQL(mysql: MySQL): DB = {
new DB
}
mysql.delete
mysql.update
}
}
class MySQL {
def insert(): Unit = {
println("insert")
}
}
class DB {
def delete(): Unit = {
println("delete")
}
def update(): Unit = {
println("update")
}
}
隐式值
入门示例
package com.example.chapter09
object ImplicitValDemo03 {
def main(args: Array[String]): Unit = {
implicit val str1: String = "Tom" //这个就是隐式值
// 调用时,若不传参数就使用隐式值 str1
def hello(implicit name: String): Unit = {
println(s"Hello, $name!")
}
hello
}
}
一个案例说明隐式值 ,默认值,传值的优先级
- 当程序中同时有隐式值,默认值,传值
- 编译器的优先级为传值 > 隐式值 > 默认值
- 在隐式值匹配时,不能有二义性
- 如果隐式值,默认值,传值都没有,报错
package com.example.chapter09
object ImplicitValDemo02 {
def main(args: Array[String]): Unit = {
// 隐式变量(值)
implicit val name: String = "Scala"
implicit val number: Int = 10
def hello1(implicit content: String = "Tom"): Unit = {
println(s"Hello1, $content")
}
// 1. 优先使用传值
hello1("Marry") // Hello1, Marry
// 2. 没有传值,就优先使用默认值
hello1 // Hello1, Scala
def hello2(implicit content: Int): Unit = {
println(s"Hello2, $content")
}
// 3. 没有传值,没有默认值,那就使用隐式值
hello2 // Hello2, 10
// 4. 没有隐式值,没有默认值,又没有传值,就会报错
def hello3(implicit content: Double ): Unit = {
println(s"Hello3, $content")
}
// hello3 // error
}
}
隐式类
- 其所带的构造参数有且只能有一个。
- 隐式类必须被定义在“ 类 ” 或 “ 伴生对象 ” 或 “ 包对象”里。
- 隐式类不能是 case class。
- 作用域内不能有与之相同名称的标识符。
package com.example.chapter09
object ImplicitClassDemo {
def main(args: Array[String]): Unit = {
implicit class DB66(val m: MySQL6) {
def addSuffix(): String = {
s"${m}.scala"
}
}
val mysql = new MySQL6
// println(mysql)
mysql.say
println(mysql.addSuffix)
}
}
class DB6
class MySQL6 {
def say(): Unit = {
println("say")
}
}
隐式的转换时机
- 当方法中的参数的类型与目标类型不一致时, 或者是赋值时。
- 当对象调用其对应类不存在的方法或成员时,编译器会自动将对象进行隐式转换。
10. 集合
Scala 默认采用不可变集合,对于几乎所有的集合类,Scala 都同时提供了可变(mutable)和不可变(immutable)的版本。
图片来源
scala.collection.immutable
scala.collection.mutable
数组
定长数组:Array
变长数组:ArrayBuffer
// 多维数组示例
package com.example.chapter10
object ArrayDemo01 {
def main(args: Array[String]): Unit = {
val arr = Array.ofDim[Int](3, 4)
for(i <- 0 until arr.length) {
for(j <- 0 until arr(i).length) {
arr(i)(j) = i + j
}
}
for(items <- arr) {
for(item <- items) {
print(s"$item\t")
}
println
}
}
}
元组
元组可以理解为一个容器,可以存放各种相同或不同类型的数据。说的简单点,就是将多个无关的数据封装为一个整体,称为元组,。最大的特点是灵活,对数据没有过多的约束。注意:元组中最大只能有 22 个元素。
package com.example.chapter10
object TupleDemo01 {
def main(args: Array[String]): Unit = {
val tuple = (1, 1.0, true, "a")
println(tuple)
println(tuple._3)
for(item <- tuple.productIterator) {
print(s"$item ")
}
println
}
}
列表 List(不可变)
package com.example.chapter10
object ListDemo01 {
def main(args: Array[String]): Unit = {
val list1 = List(1, 2, 3, "aaa") // List(1, 2, 3, aaa)
val list2 = list1 :+ 4 // List(1, 2, 3, aaa, 4)
val list3 = 10 +:list2 // List(10, 1, 2, 3, aaa, 4)
println(list1)
println(list2)
println(list3)
println("----------------------")
// 符号 :: 表示向集合中添加元素,集合对象一定要放置在最右边,运算规则是从右向左。
val list4 = List(11, 22, 33, "xxx")
val list5 = 4 :: 5 :: 6 :: list4 :: Nil
println(list5) // List(4, 5, 6, List(11, 22, 33, xxx))
// ::: 运算符是将集合中的每一个元素加入到集合中
val list6 = 4 :: 5 :: 6 :: list4 ::: Nil
println(list6) // List(4, 5, 6, 11, 22, 33, xxx)
}
}
列表 ListBuffer(可变)
package com.example.chapter10
import scala.collection.mutable.ListBuffer
object ListBufferDemo01 {
def main(args: Array[String]): Unit = {
val listBuffer1 = ListBuffer[Int](1, 2, 3)
for(item <- listBuffer1) {
print(s"$item ")
}
println("\n--------------------")
val listBuffer2 = new ListBuffer[Int]
listBuffer2 += 4
listBuffer2.append(5)
listBuffer1 ++= listBuffer2
println(listBuffer1)
println("\n--------------------")
val listBuffer3 = listBuffer1 ++ listBuffer2
println(listBuffer3)
println("\n--------------------")
val listBuffer4 = listBuffer1 :+ 5
println(listBuffer4)
println("\n--------------------")
listBuffer1.remove(1)
println(listBuffer1)
}
}
Queue
常用可变的 Queue
Map
Scala 中不可变的 Map 是有序的,可变的 Map 是无序的。
package com.example.chapter10
import scala.collection.mutable
object MapDemo01 {
def main(args: Array[String]): Unit = {
val map1 = Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> "北京")
println(map1)
// 注意比较打印顺序
val map2 = mutable.Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> "北京")
println(map2)
val map3 = new scala.collection.mutable.HashMap[String, Int]
println(map3)
val map4 = mutable.Map(("Alice" , 10), ("Bob" , 20), ("Kotlin" , "北京"))
println("map4=" + map4)
println(map4("Bob")) // 20
// println(map4("xxx")) // key 不存在就抛异常:NoSuchElementException
// 映射.get(键) 这样的调用返回一个 Option 对象,要么是 Some,要么是 None
println(map4.get("Bob").get) // 20
// println(map4.get("xxx").get) // key 不存在就抛异常:NoSuchElementException
println(map4.getOrElse("xxx", "hah")) // hah
map4 += ("D" -> 4)
map4 += ("D" -> 6)
println(map4)
val map5 = map4 + ("E"->7, "F"->9)
println(map5)
map5 -= ("E", "P") // key 不存在不报错
println(map5)
// 遍历
for((k, v) <- map5) print(s"$k->$v " ); println
for(k <- map5.keys) print(s"$k "); println
for(v <- map5.values) print(s"$v "); println
for(kv <- map5) print(s"$kv "); println // Tuple2
}
}
Set
11. 集合操作
高阶函数
def myPrint(): Unit = {
println("hello,world!")
}
val f1 = myPrint()
f1
// val f1 = myPrint _
// f1()
def sum2(d: Double): Double = {
d + d
}
def test(f: Double => Double, d: Double) = {
f(d)
}
val res1 = test(sum2 _, 3.5)
val res2 = test(d => d + d, 3.5)
val res3 = test(sum2, 3.5)
println(res1)
println(res2)
println(res3)
val f2 = sum2 _
println(f2) // <function1>
val f3 = sum2(3.5)
println(f3) // 7.0
val f4 = sum2 _
println(f4(3.5)) // 7.0
map/flatMap/filter 操作
package com.example.com.example.chapter11
object MapOperateDemo01 {
def main(args: Array[String]): Unit = {
// map (把列表各个元素都扩大为原来的 2 倍)
val list = List(1, 2, 3, 4)
val list2 = list.map(x => x + x)
println(list2) // List(2, 4, 6, 8)
def process(x: Int): Int = {
x + x
}
val list3 = list.map(process) // 其实就是传入一个函数,同简化代码 x=>x+x
println(list3) // List(2, 4, 6, 8)
// flatMap
val list4 = List(List(1,2,3), List(4,5,6))
val list5 = list4.flatMap(x => x) // 不再单独定义个函数传进来了
println(list5) // List(1, 2, 3, 4, 5, 6)
// filter
val list6 = List("Bob", "Bad", "Yes")
val list7 = list6.filter(s => s.startsWith("B")) // 不再单独定义函数传进来了
println(list7) // List(Bob, Bad)
}
}
reduce/fold/scan
package com.example.com.example.chapter11
object ReduceDemo01 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5)
val res1 = list.reduceLeft((x, y) => x + y) // ((((1 + 2) + 3) + 4) + 5) = 15
println(res1)
val res2 = list.reduceRight((x, y) => x - y)
println(res2) // 3
val res3 = list.reduceRight((x, y) => y - x)
println(res3) // -5
val res4 = list.reduce((x, y) => x + y)
println(res4) // 15
val min = list.reduce((x, y) => if(x < y) x else y)
println(min)
// fold (类似于 reduce,只是可设定初始值)
val res5 = list.fold(0)((x, y) => x + y) // (0 /: list)((x, y) => x + y)
println(res5) // 15
// 扫描(把 fold 操作的中间结果保留下来)
val res6 = list.scan(0)((x, y) => x + y)
println(res6) // List(0, 1, 3, 6, 10, 15)
}
}
zip/iterator/stream
package com.example.chapter11
object ZipDemo01 {
def main(args: Array[String]): Unit = {
// zip
val list1 = List(1, 2, 3)
val list2 = List(11, 22, 33, 44)
val list3 = list1.zip(list2)
println(list3) // List((1,11), (2,22), (3,33))
for(item <- list3) {
print(s"(${item._1},${item._2}) ") // (1,11) (2,22) (3,33)
}
println
// iterator
val iterator = List(1,2,3,4,5,6).iterator
// while (iterator.hasNext) { // 迭代方式一
// print(s"${iterator.next} ")
// }
// println
for(item <- iterator) {
// 迭代方式二
print(s"$item ")
}
println
// stream (自定义一个函数,第一个元素是 n,后续不断生成 n+1,遵循 lazy)
def numsForm(n: BigInt) : Stream[BigInt] = n #:: numsForm(n + 1)
val stream1 = numsForm(1)
println(stream1) // Stream(1, ?)
println("head=" + stream1.head) // 取出第一个元素
println(stream1.tail.head) // 2 执行 tail 时,会生成一个新数据.
println(stream1) // Stream(1, 2, ?)
println(stream1.tail.tail.head) // 3 执行 tail 时,会生成一个新数据.
println(stream1) // Stream(1, 2, 3, ?)
}
}
view
package com.example.chapter11
object ViewDemo {
def main(args: Array[String]): Unit = {
//如果这个数,逆序后和原来数相等,就返回 true,否则返回 false
def eq(i: Int): Boolean = {
println("eq 被调用..")
i.toString.equals(i.toString.reverse)
}
// 常规方式(不用 view)
val viewSquares1 = (1 to 100).filter(eq)
println(viewSquares1)
// 使用 view 来完成这个问题,程序中,对集合进行 map,filter,reduce,fold...
// 你并不希望立即执行,而是在使用到结果才执行,则可以使用 view 来进行优化(lazy)
val viewSquares2 = (1 to 100).view.filter(eq)
// println(viewSquares2)
// //遍历
// for (item <- viewSquares2) {
// println("item=" + item)
// }
}
}
模式匹配
入门示例
package com.example.chapter12
object MatchDemo01 {
def main(args: Array[String]): Unit = {
for (char <- "+-3a0") {
//是对"+-3!" 遍历
var sign = 0
var digit = 0
char match {
case '+' => sign = 1
case '-' => sign = -1
// 如果 case 后有条件守卫即 if,那么这时的 _ 不是表示默认匹配,表示忽略传入的 ch
case _ if char.toString.equals("3") => digit = 3
case _ if (char > 48) => println("ch > 48 ")
case _ => sign = 2
}
//分析
// + 1 0
// - -1 0
// 3 0 3
// ! 2 0
println(s"char=$char,sign=$sign,digit=$digit")
}
}
}
匹配变量
package com.example.chapter12
// 如果在 case 关键字后跟变量名,那么 match 前表达式的值会赋给那个变量
object MatchVar {
def main(args: Array[String]): Unit = {
val ch = 'a'
ch match {
case 'k' => println("ok")
case myChar => println(s"ok, ${myChar}")
case _ => println ("ok?")
}
// match 是一个表达式,因此可以有返回值
val res = ch match {
case 'a' => "ok, a"
case _ => println("ok?") // println 返回值为 (),默认匹配将会有 res = ()
}
println("res=" + res)
val (x, y, z) = (1, 2, "hello")
println("x=" + x)
val (q, r) = BigInt(10) /% 3 // q = BigInt(10) / 3 r = BigInt(10) % 3
val arr =Array(1, 7, 2, 9)
val Array(first, second, _*) = arr // 提出 arr 的前两个元素
println(first, second)
}
}
匹配类型
package com.example.chapter12
object MatchType {
def main(args: Array[String]): Unit = {
val a = 8
// obj 实例的类型 根据 a 的值来返回
val obj1 = if (a == 1) 1
else if (a == 2) "2"
else if (a == 3) BigInt(3)
else if (a == 4) Map("aa" -> 1) // Map[String, Int] 和 Map[Int, String]是两种不同的类型
else if (a == 5) Map(1 -> "aa")
else if (a == 6) Array(1, 2, 3)
else if (a == 7) Array("aa", 1)
else if (a == 8) Array("aa")
// 根据 obj 的类型来匹配返回值
val res1 = obj1 match {
case a: Int => a
case b: Map[String, Int] => "对象是一个字符串-数字的 Map 集合"
case c: Map[Int, String] => "对象是一个数字-字符串的 Map 集合"
case d: Array[String] => d // [Ljava.lang.String;@140d5f0
case e: Array[Int] => "对象是一个数字数组"
case f: BigInt => Int.MaxValue
case _ => "啥也不是"
}
println(res1)
val obj2 = 10
val res2 = obj2 match {
case a: Int => a // a = obj1,再判断类型
// case b: Map[String, Int] => "Map" // 编译出错,required: Int
// case c: Long => c // 编译出错,required: Int
// case _: Int => 1 // _ 出现在 match 中间某个位置,不表示默认匹配,而是隐藏变量名
case _ => "nothing"
}
println(res2) // 10
}
}
匹配数组
package com.example.chapter12
import scala.collection.mutable.ArrayBuffer
/**
* 匹配数组
* 1) Array(0) 匹配只有一个元素且为 0 的数组
* 2) Array(x,y) 匹配数组有两个元素,并将两个元素赋值为 x 和 y。当然可以依次类推 Array(x,y,z) 匹
* 配数组有 3 个元素的等等...
* 3) Array(0,_*) 匹配以 0 开始的数组
*/
object MatchArr {
def main(args: Array[String]): Unit = {
val arrs =Array(Array(0), Array(1, 0), Array(0, 1, 0), Array(1, 1, 0), Array(1, 1, 0, 1))
for (arr <- arrs) {
val result = arr match {
case Array(0) => "0"
case Array(x, y) => x + "=" + y
case Array(0, _*) => "以 0 开头和数组"
case _ => "什么集合都不是"
}
println("result = " + result)
// result = 0
// result = 1=0
// result = 以 0 开头和数组
// result = 什么集合都不是
// result = 什么集合都不是
}
}
}
匹配列表
package com.example.chapter12
object MatchList {
def main(args: Array[String]): Unit = {
for (list <- Array(List(0), List(1, 0), List(88), List(0, 0, 0), List(1, 0, 0))) {
val result = list match {
case 0 :: Nil => "0" //
case x :: y :: Nil => x + " " + y //
case 0 :: tail => "0 ..." //
case x :: Nil => x
case _ => "something else"
}
println(result)
// 0
// 1 0
// 88
// 0 ...
}
}
}
匹配元组
package com.example.chapter12
object MatchTuple {
def main(args: Array[String]): Unit = {
for (tuple <-Array((0, 1), (0, 1, 1), (1, 0), (10, 30), (1, 1), (1, 0, 2))) {
val result = tuple match {
case (0, _) => "0 ..."
case (y, 0) => y
case (x, y) => (y, x)
case _ => "other"
}
println(result)
// 0 ...
// other
// 1
// (30,10)
// (1,1)
// other
}
}
}
匹配对象
package com.example.chapter12
object MatchObjectDemo01 {
def main(args: Array[String]): Unit = {
val number: Double = Square(5.0)
number match {
//说明 case Square(n) 的运行的机制
//1. 当匹配到 case Square(n)
//2. 调用 Square 的 unapply(z: Double),z 的值就是 number
//3. 如果对象提取器 unapply(z: Double) 返回的是 Some(5.0) ,则表示匹配成功,同时
// 将 5.0 赋给 Square(n) 的 n
//4. 如果对象提取器 unapply(z: Double) 返回的是 None ,则表示匹配不成功
case Square(n) => println("匹配成功 n=" + n)
case _ => println("nothing matched")
}
}
}
object Square {
//说明
//1. unapply 方法是对象提取器
//2. 接收 z:Double 类型
//3. 返回类型是 Option[Double]
//4. 返回的值是 Some(math.sqrt(z)) 返回 z 的开平方的值,并放入到 Some(x)
def unapply(z: Double): Option[Double] = {
println("Square.unapply 被调用,z=" + z)
Some(math.sqrt(z))
// None
}
def apply(z: Double): Double = z * z
}
for 中的匹配
package com.example.chapter12
object MatchFor {
def main(args: Array[String]): Unit = {
val map = Map("A" -> 1, "B" -> 0, "C" -> 3)
for ((k, v) <- map) {
println(k + " -> " + v) // 出来三个 key-value ("A"->1), ("B"->0), ("C"->3)
}
//说明 : 只遍历出 value =0 的 key-value ,其它的过滤掉
println("--------------(k, 0) <- map-------------------")
for ((k, 0) <- map) {
println(k + " --> " + 0)
}
//说明, 这个就是上面代码的另外写法, 只是下面的用法灵活和强大
println("--------------(k, v) <- map if v >= 1-------------------")
for ((k, v) <- map if v >= 1) {
println(k + " ---> " + v)
}
}
}
样例类
package com.example.chapter12
object CaseClassDemo01 {
def main(args: Array[String]): Unit = {
for (amt <- Array(Dollar2(1000.0), Currency2(1000.0, "RMB"), NoAmount2)) {
val result = amt match {
case Dollar2(v) => "$" + v // $1000.0
case Currency2(v, u) => v + " " + u // 1000.0 RMB
case NoAmount2 => ""
}
println(amt + ": " + result)
}
}
}
abstract class Amount2
case class Dollar2(value: Double) extends Amount2 //样例类
case class Currency2(value: Double, unit: String) extends Amount2 //样例类
case object NoAmount2 extends Amount2 //样例类
// case class 构造器中的每一个参数都是 val,除非显式地声明为 var(不建议这样做)
13. 函数式编程(高级)
偏函数
给你一个集合 val list = List(1, 2, 3, 4, “abc”) ,请完成如下要求:
- 将集合 list 中的所有数字+1,并返回一个新的集合。
- 要求忽略掉非数字元素,即返回的新的集合为 (2, 3, 4, 5)。
package com.example.chapter13
object PartialFunDemo01 {
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, "Hello")
// 方法1
// 下面等价:val res1 = list.filter(x => x.isInstanceOf[Int]).map(x => x.asInstanceOf[Int]+1)
val res1 = list.filter(f1).map(f2)
println(res1)
// 方法2,偏函数
//1. PartialFunction[Any,Int] 表示偏函数接收的参数类型是 Any,返回类型是 Int
//2. isDefinedAt(x:Any) 如果返回 true ,就会去调用 apply 构建对象实例,如果是 false,过滤
//3. apply 构造器 ,对传入的值 + 1,并返回(新的集合)
val partialFun = new PartialFunction[Any,Int] {
override def isDefinedAt(x:Any) = {
x.isInstanceOf[Int]
}
override def apply(v1:Any) = {
v1.asInstanceOf[Int] + 1
}
}
val res2 = list.collect(partialFun)
println(res2)
// 偏函数简写
def partialFun2: PartialFunction[Any,Int] = {
case i:Int => i + 1
case j:Double => (j * 2).toInt
}
val res3 = list.collect(partialFun)
println(res3)
// 偏函数简写
val res4 = list.collect{
case i: Int => i+1
case d: Double => (d*2).toInt
}
println(res4)
}
def f1(x: Any): Boolean = {
x.isInstanceOf[Int]
}
def f2(x: Any): Int = {
x.asInstanceOf[Int] + 1
}
}
高阶函数
能够接受函数作为参数的函数,叫做高阶函数 (higher-order function)。
package com.example.chapter13
object HigherOrderFunctionDemo01{
def main(args: Array[String]): Unit = {
def test(f1: Double => Double, f2: Double =>Int , n1: Double) = {
f1(f2(n1))
}
def sum(d: Double): Double = {
d + d
}
def mod(d:Double): Int = {
d.toInt % 2
}
val res = test(sum, mod, 5.0)
println("res=" + res) // 2.0
}
}
函数返回类型是函数
package com.example.chapter13
object HigherOrderFunctionDemo02{
def main(args: Array[String]) = {
def minusxy(x: Int) = {
(y: Int) => x - y // minusxy 返回类型是函数 Int => Int
}
println(minusxy(9)(1)) // 8
val f = minusxy(9) // 等价:val f = (y: Int) => 9 - y
println(f) // <function1>
println(f(1)) // 8
}
}
闭包
// minusxy 返回一个匿名函数,因为该函数引用到到函数外的 x,该函数和 x 形成一个闭包
// 例如,val f = minusxy(9) 的 f 函数就是闭包
def minusxy(x: Int) = (y: Int) => x - y
编写一个函数 makeSuffix(suffix: String) 可以接收一个文件后缀名(比如.jpg), 并返回一个闭包。调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返回文件名.jpg , 如果已经有.jpg 后缀,则返回原文件名。
package com.example.chapter13
object ClosureDemo {
def main(args: Array[String]): Unit = {
def makeSuffix(suffix: String) = {
(fileName: String) => {
if(fileName.endsWith(suffix)) {
fileName
} else {
fileName + suffix
}
}
}
println(makeSuffix(".jpg")("xxx"))
println(makeSuffix(".png")("file.png"))
}
}
函数柯里化(curry)
函数编程中,接受多个参数的函数都可以转化为接受单个参数的函数,这个转化过程就叫柯里化。
// 常规方式
def mul(x: Int, y: Int) = x * y
println(mul(10, 10))
// 闭包
def mulCurry(x: Int) = (y: Int) => x * y
println(mulCurry(10)(9))
// 柯里化
def mulCurry2(x: Int)(y:Int) = x * y
println(mulCurry2(10)(8))
package com.example.chapter13
object CurryDemo {
def main(args: Array[String]): Unit = {
def eq(s1: String, s2: String): Boolean = {
s1.equals(s2)
}
// 隐式类
implicit class TestEq(s: String) {
def checkEq(ss: String)(f: (String, String) => Boolean): Boolean = {
f(s.toLowerCase, ss.toLowerCase)
}
}
val str1 = "hello"
println(str1.checkEq("HeLLO")(eq))
// 简写形式
println(str1.checkEq("HeLLO")(_.equals(_)))
}
}
16. 并发编程模型 Akka
- Akka 是 JAVA 虚拟机 JVM 平台上构建高并发、分布式和容错应用的工具包和运行时,你可以理解成 Akka 是编写并发程序的框架。
- Akka 用 Scala 语言写成,同时提供了 Scala 和 JAVA 的开发接口。
- Akka 主要解决的问题是:可以轻松的写出高效稳定的并发程序,程序员不再过多的考虑线程、锁和资源竞争等细节。
Actor 模型
-
Akka 处理并发的方法基于 Actor 模型。
-
在基于 Actor 的系统里,所有的事物都是 Actor,就好像在面向对象设计里面所有的事物都是对象一样。
-
Actor 模型是作为一个并发模型设计和架构的。Actor 与 Actor 之间只能通过消息通信,如图的信封。
-
Actor 与 Actor 之间只能用消息进行通信,当一个 Actor 给另外一个 Actor 发消息,消息是有顺序的(消息队列),只需要将消息投寄的相应的邮箱即可。
-
怎么处理消息是由接收消息的 Actor 决定的,发送消息 Actor 可以等待回复,也可以异步处理。
-
ActorSystem 的职责是负责创建并管理其创建的 Actor, ActorSystem 是单例的(可以理解 ActorSystem 是一个工厂,专门创建 Actor),一个 JVM 进程中有一个即可,而 Acotr 是可以有多个的。
-
Actor 模型是对并发模型进行了更高的抽象。
-
Actor 模型是异步 、非阻塞、 高性能的事件驱动编程模型。
-
Actor 模型是轻量级事件处理(1GB 内存可容纳百万级别个 Actor),因此处理高并发性能好。
-
ActorySystem 创建 Actor。
-
ActorRef:可以理解成是Actor的代理或者引用。消息是通过ActorRef来发送,而不能通过Actor 发送消息,通过哪个 ActorRef 发消息,就表示把该消息发给哪个 Actor。
-
消息发送到 Dispatcher Message (消息分发器),它得到消息后,会将消息进行分发到对应的 MailBox。(注: Dispatcher Message 可以理解成是一个线程池, MailBox 可以理解成是消息队列,可以缓冲多个消息,遵守 FIFO)。
-
Actor 可以通过 receive 方法来获取消息,然后进行处理。
Actor 模型快速入门
- 建立 Maven 项目,在 /src/main/ 和 /src/test/ 下均建立 scala 文件夹并标记为 Source Root。
- 注意在 File-Settings-Build,Execution,Deployment-Build Tools-Maven,勾选 Alaways update snapshots(如果 Maven 无法加载配置的包,笔者没遇到这种情况)
- Maven 加载不进来包,可以执行
mvn clean
/mvn package
命令,重启 IDEA 试试。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>Akka</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- 定义一下常量 -->
<properties>
<encoding>UTF-8</encoding>
<scala.version>2.11.12</scala.version>
<scala.compat.version>2.11</scala.compat.version>
<akka.version>2.4.17</akka.version>
</properties>
<dependencies>
<!-- 添加scala的依赖 -->
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${
scala.version}</version>
</dependency>
<!-- 添加akka的actor依赖 -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-actor_${
scala.compat.version}</artifactId>
<version>${
akka.version}</version>
</dependency>
<!-- 多进程之间的Actor同学 -->
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-remote_${
scala.compat.version}</artifactId>
<version>${
akka.version}</version>
</dependency>
</dependencies>
<!-- 指定插件-->
<build>
<!-- 指定源码包和测试包的位置 -->
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<!-- 指定编译scala的插件 -->
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.2.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${
project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
<!-- maven打包的插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>reference.conf</resource>
</transformer>
<!-- 指定maven方法 -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass></mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
package org.example.akka.actor
import akka.actor.{
Actor, ActorRef, ActorSystem, Props}
// Actor 自己跟自己玩
class HelloActor extends Actor{
// 查看源码 type Receive = scala.PartialFunction[scala.Any, scala.Unit]
override def receive: Receive = {
// PartialFunction 简写
case "hello" => println("hello, too (respond hello)")
case "ok" => println("ok, too (respond ok)")
case "exit" => {
println("Exiting...(respond exit)")
context.stop(self) // to stop ActorRef
context.system.terminate() // to terminate ActorSystem
}
case _ => println("match nothing")
}
}
object HelloActorDemo {
private val actorFactory = ActorSystem("actorFactory")
// Props[HelloActor] 反射一个 HelloActor,也可 Props(new HelloActor)
private val helloActorRef: ActorRef = actorFactory.actorOf(Props[HelloActor], "helloActor")
def main(args: Array[String]): Unit = {
helloActorRef ! "hello"
helloActorRef ! "ok"
helloActorRef ! "hah"
helloActorRef ! "exit"
}
}
Spark Master/Worker 进程通讯
- Worker 注册到 Master,Master 完成注册,并回复 Worker 注册成功
- Worker 定时发送心跳,Master 接收到 Worker 心跳后,更新该 Worker 的最近一次发送心跳的时间
- 给 Master 启动定时任务,定时检测注册的 Worker 有哪些没有更新心跳(死了),并将其从 hashmap 中删除
IDEA,Run-Edit Configurations,可对同一个程序配置不同参数分别运行,模仿多个 Worker。
SparkMaster.scala
package org.example.akka.sparkmasterworker.master
import java.util.concurrent.TimeUnit
import akka.actor.{
Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import org.example.akka.sparkmasterworker.common.{
HeartBeat, RegisterWorkerInfo, RegisteredWorkerInfo, RemoveTimeOutWorker, SendHeartBeat, StartTimeOutWorker, WorkerInfo}
import scala.collection.mutable
import scala.concurrent.duration.FiniteDuration
class SparkMaster extends Actor{
val workers = mutable.Map[String, WorkerInfo]()
override def receive: Receive = {
case "start" => {
println("Master 启动了...")
self ! StartTimeOutWorker
}
case StartTimeOutWorker => {
println("master 开始定时检测 worker 心跳,看看有没有 worker 死了...")
import context.dispatcher
//说明
//1. 0 不延时,立即执行定时器
//2. 9000 表示每隔 9 秒执行一次
//3. self: 表示发给自己
//4. SendHeartBeat 发送的内容
context.system.scheduler.schedule(new FiniteDuration(0, TimeUnit.MILLISECONDS), new FiniteDuration(9000, TimeUnit.MILLISECONDS), self, RemoveTimeOutWorker)
}
//检测哪些 worker 心跳超时(now - lastHeartBeat > 6000),并从 workers 中删除
case RemoveTimeOutWorker => {
val workerInfos = workers.values
val nowTime = System.currentTimeMillis()
workerInfos.filter(workerInfo => (nowTime - workerInfo.lastHeartBeat) > 6000)
.foreach(workerInfo => workers.remove(workerInfo.id))
println(s"SparkMaster has ${workers.size} workers alive.")
}
case RegisterWorkerInfo(id, cpu, ram) => {
// 接到 SparkWorker 的注册信息,注册其信息 WorkerInfo 到 hashMap 并给其返回注册成功信息 RegisteredWorkerInfo
if(!workers.contains(id)) {
val workerInfo = new WorkerInfo(id, cpu, ram)
workers += ((id, workerInfo))
println(s"SparkMaster has ${workers.size} workers.")
sender ! RegisteredWorkerInfo
}
}
case HeartBeat(id) => {
// 更新对应的 Worker 的心跳时间
val workerInfo = workers(id)
workerInfo.lastHeartBeat = System.currentTimeMillis()
println("Master 更新了 Worker " + id + " 的心跳时间...")
}
}
}
object SparkMaster {
def main(args: Array[String]): Unit = {
if(args.length != 3) {
println("Please input the arguments in the form of \"masterIP masterPort masterActorName\"!")
System.exit(-1)
}
val masterIP = args(0)
val masterPort = args(1)
val masterActorName = args(2)
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$masterIP
|akka.remote.netty.tcp.port=$masterPort
""".stripMargin)
val sparkMasterSystem = ActorSystem("SparkMaster", config)
val sparkMasterRef = sparkMasterSystem.actorOf(Props[SparkMaster], masterActorName)
sparkMasterRef ! "start"
}
}
SparkWorker.scala
package org.example.akka.sparkmasterworker.worker
import java.util.UUID
import java.util.concurrent.TimeUnit
import akka.actor.{
Actor, ActorSelection, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import org.example.akka.sparkmasterworker.common.{
HeartBeat, RegisterWorkerInfo, RegisteredWorkerInfo, SendHeartBeat}
import scala.concurrent.duration.FiniteDuration
class SparkWorker(masterIP: String, masterPort: Int, masterActorName: String) extends Actor{
// sparkMasterPorxy 是 SparkMaster 的代理/引用 ref
var sparkMasterPorxy :ActorSelection = _
val id = UUID.randomUUID().toString // SparkWorkder ID
override def preStart(): Unit = {
println("调用 preStart(): Unit,初始化 sparkMasterPorxy...")
sparkMasterPorxy = context.actorSelection(s"akka.tcp://SparkMaster@${masterIP}:${masterPort}/user/${masterActorName}")
println("sparkMasterPorxy=" + sparkMasterPorxy)
}
override def receive: Receive = {
case "start" => {
println("Worker 启动了...")
// SparkWorker 启动后,就注册到 SparkMaster
sparkMasterPorxy ! RegisterWorkerInfo(id, 16, 16*1024)
}
case RegisteredWorkerInfo => {
println(s"Worker $id 注册成功!")
// 当注册成功后,就定义一个定时器,每隔一定时间,发送 SendHeartBeat 给自己
import context.dispatcher
//说明
//1. 0 不延时,立即执行定时器
//2. 3000 表示每隔 3 秒执行一次
//3. self: 表示发给自己
//4. SendHeartBeat 发送的内容
context.system.scheduler.schedule(new FiniteDuration(0, TimeUnit.MILLISECONDS), new FiniteDuration(3000, TimeUnit.MILLISECONDS), self, SendHeartBeat)
}
case SendHeartBeat =>{
println("Worker = " + id + "给 Master 发送心跳...")
sparkMasterPorxy ! HeartBeat(id)
}
}
}
object SparkWorker {
def main(args: Array[String]): Unit = {
if(args.length != 6) {
println("Please input the arguments in the form of \"workerIP workerPort workerActorName masterIP masterPort masterActorName\"!")
System.exit(-1)
}
val workerIP = args(0)
val workerPort = args(1)
val workerActorName = args(2)
val masterIP = args(3)
val masterPort = args(4)
val masterActorName = args(5)
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$workerIP
|akka.remote.netty.tcp.port=$workerPort
""".stripMargin)
val sparkWorkerSystem = ActorSystem("SparkWorker", config)
val sparkWorkerRef = sparkWorkerSystem.actorOf(Props(new SparkWorker(masterIP, masterPort.toInt, masterActorName)), workerActorName)
sparkWorkerRef ! "start"
}
}
MessageProtocol.scala
package org.example.akka.sparkmasterworker.common
// worker 注册信息
case class RegisterWorkerInfo(id: String, cpu: Int, ram: Int)
// 这个是 WorkerInfo, 这个信息将来是保存到 master 的 hm(该 hashmap 是用于管理 worker)
// 将来这个 WorkerInfo 会扩展(比如增加 worker 上一次的心跳时间)
class WorkerInfo(val id: String, val cpu: Int, val ram: Int) {
var lastHeartBeat: Long = System.currentTimeMillis
}
// 当 worker 注册成功,服务器返回一个 RegisteredWorkerInfo 对象
case object RegisteredWorkerInfo
// worker 每隔一定时间由定时器发给自己的一个消息
case object SendHeartBeat
// worker 每隔一定时间收到定时器发的 SendHeartBeat 后,向 master 发送 HeartBeat 心跳
case class HeartBeat(id: String)
// master 给自己发消息,触发检查超时 worker 的任务
case object StartTimeOutWorker
// master 定时检查 worker 超时的任务给 master 自己发消息 RemoveTimeOutWorker,开始检测超时 worker 并删除
case object RemoveTimeOutWorker
参考资料
[1] 尚硅谷大数据之韩顺平Scala视频及对应笔记
[2] Scala 官方文档
[3] Scala 官方入门教程