avatar

目录
Scala

引言:

  • Spark 就是使用 Scala 编写的。
  • Scala 是 Scalable Language 的简写,是一门多范式(编程的方式[面向对象编程,函数式编程])的编程语言

参考:

[toc]

Scala概述

介绍

Spark源码

.scala > .class > JVM虚拟机

环境安装-windows

Code
1
2
3
4
5
6
7
8
9
下载:http://www.scala-lang.org/    压缩包

配置JDK的环境变量 JAVA_HOME

配置Scala的环境变量SCALA_HOME

将Scala安装目录下的bin目录加入到PATH环境变量。在PATH变量中添加:%SCALA_HOME%\bin

在命令行窗口中输入“scala”命令打开scala解释器(REPL),安装完成

hello.scala

scala
1
2
3
4
5
object Hello {
def main(args: Array[String]): Unit = {
println(“Hello World”)
}
}

编译

Code
1
scalac Hello.scala

运行

Code
1
scala Hello

直接运行

Code
1
scala Hello.scala

没有在磁盘生成.class ,直接在内存里操作的。

环境安装-IDEA

插件安装

idea下的hello world

第一个Scala程序

不用再写分号了(太优秀了…java啥时候学学呀)

输出的三种方式

scala
1
2
3
4
5
6
7
8
9

// 字符串通过+号连接(类似java)
println("name=" + name + " age=" + age + " url=" + url)

// printf用法 (类似C语言)字符串通过 % 传值。(格式化输出)
printf("name=%s, age=%d, url=%s \n", name, age, url)

// 字符串插值:通过$引用(类似PHP)
println(s"name=$name, age=$age, url=$url")

反编译

scala字节码 > 反编译> java代码

java反编译工具:jd-gui.exe

注释

变量

变量说明

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
object C01 {

// 申明变量
var num1: Int = 10

// 类型可以省略(自动判断)
var num2 = 10

// 类型确定后,就不能修改
// num2 = "1"

// var 修饰的变量可改变,val 修饰的变量不可改
num2 = 20
val num3 = 10
// num3 = 20

// val修饰的对象属性在编译后,等同于加上final

// var 修饰的对象引用可以改变,val 修饰的则不可改变,但对象的状态(值)却是可以改变的。(比如: 自定义对象、数组、集合等等)
val dog = new Dog()
dog.name = "mimi"
// dog = new Dog()

// 变量声明时,必须有初始值
// var num

}

数据类型

image-20200118210840247
scala
1
2
3
4
5
6
7
8
9
10
// 在Scala中数据类型都是对象,也就是说scala没有java中的原生(基本)类型

// Scala数据类型分为两大类 AnyVal(值类型) 和 AnyRef(引用类型) ?
// 引用类型:集合、java类、scala类。Null在Scala中也是对象。user= Null

// Unit就是void

// 类型隐式转换

// 复杂多变的类型 --> 面向对象编程+函数式编程的融合

Unit、Null、Nothing

Unit 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。
Null null , Null 类型只有一个实例值 null
Nothing Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。 当一个函数,我们确定没有正常的返回值,可以用Nothing 来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性) def f1():Nothing = { throw new Exception() }
scala
1
2
3
4
5
6
// -----------------值类型转换------------------

// java
// int num = (int)2.5
// scala
var num : Int = 2.7.toInt

运算符

Scala不支持三目运算符 , 在Scala中使用 if – else的方式实现。

和java差不多

程序流程控制

顺序控制

Code
1
2
3
4
def main(args : Array[String]) : Unit = {
var num1 = 12
var num2 = num1 + 2
}

分支控制

Code
1
2
3
4
5
6
7
if(){

}else if(){

}else{

}

循环控制:for

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
println("案例1:to")
for(i <- 1 to 3){
print(i + " ")
}
println()
// 说明
//i 表示循环的变量, <- 规定好 to 规定
//i 将会从 1-3 循环, 前后闭合 (包括1 和 3)

println("案例2:until")
for(i <- 1 until 3) {
print(i + " ")
}
println()
// 说明:
//这种方式和前面的区别在于 i 是从1 到 (3-1)
//前闭合后开的范围

println("案例3: 循环守卫")
for(i <- 1 to 3 if i != 2) {
print(i + " ")
}
println()
// 说明
//循环守卫,即循环保护式(也称条件判断式,守卫)。保护式为true则进入循环体内部,为false则跳过,类似于continue

println("案例4:引入变量")
for(i <- 1 to 3; j = 4 - i) {
print(j + " ")
}
println()

println("案例5:嵌套循环")
for(i <- 1 to 3; j <- 1 to 3) {
println(" i =" + i + " j = " + j)
}
println()

println("案例6:循环返回值")
val res = for(i <- 1 to 10) yield i * 2
println(res)
// 说明
//将遍历过程中处理的结果返回到一个新Vector集合中,使用yield关键字
println()

println("案例6:使用花括号{}代替小括号()")
for{i <- 1 to 3; j = i * 2} {
println(" i= " + i + " j= " + j)
}
println()
// 说明
//{}和()对于for表达式来说都可以
//for 推导式有一个不成文的约定:当for 推导式仅包含单一表达式时使用圆括号,当其包含多个表达式时使用大括号
//当使用{}i 来换行写表达式时,分号就不用写了

结果:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
案例1:to
1 2 3
案例2:until
1 2
案例3: 循环守卫
1 3
案例4:引入变量
3 2 1
案例5:嵌套循环
i =1 j = 1
i =1 j = 2
i =1 j = 3
i =2 j = 1
i =2 j = 2
i =2 j = 3
i =3 j = 1
i =3 j = 2
i =3 j = 3

案例6:循环返回值
Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)

案例6:使用花括号{}代替小括号()
i= 1 j= 2
i= 2 j= 4
i= 3 j= 6

循环控制:while

scala
1
2
3
4
5
6
7
8
9
10
11
循环变量初始化 //循环的四个要素
while (循环条件) {
循环体(语句)
循环变量迭代
}

循环变量初始化;
do{
循环体(语句)
循环变量迭代
} while(循环条件)

推荐使用for

循环控制:循环中断

去掉了break和continue。

scala
1
2
3
4
5
6
7
println("案例: 循环中断")
for(i <- 1 to 10){
print(i + " ")
if(i==5){
break()
}
}

函数式编程-基础

在scala中函数式编程和面向对象编程融合在一起了

函数/方法

函数/方法定义

Code
1
2
3
4
5
6
7
8
def 函数名 ([参数名: 参数类型], ...)[[: 返回值类型] =] {
语句... //完成某个功能
return 返回值
}
返回值形式1: // def 函数名(参数列表) : 数据类型 = {函数体} // 返回值确定,清晰
返回值形式2: // def 函数名(参数列表) = {函数体} // 有返回值, 类型是推断出来的
返回值形式3: // def 函数名(参数列表) {函数体} // 无返回值 Unit
如果没有return ,默认以执行到最后一行的结果作为返回值

demo

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 函数/方法

// --案例1:调用
def sum(n1: Int, n2: Int): Int = {
return n1 + n2
}

// --案例2:可变参数
//支持0到多个参数
// def sum(args: Int*) : Int = { }

//支持1到多个参数
// def sum(n1: Int, args: Int*) : Int = { }
// 说明:
//args 是集合, 通过 for循环 可以访问到各个值。
//可变参数需要写在形参列表的最后。

// --案例3:方法转为函数
def f1(): Int = {100} // 定义方法
println(f1) //100
println(f1()) // 100
var f2 = f1
var f3 = f1 _ // 方法转为函数
println(f2) //100
println(f3) // 打印的是100吗? <function0>
// 如果要打印f3函数的返回值
println(f3()) // 100

// --案例4:匿名函数
val f1 = () => "abc"
println(f1()) // abc

// 完整的写法:
val f2: (String, Double) => Int = (a: String, b : Double) => a.toInt + b.toInt
println(f2("2",1.5)) // 3
// 简化版1, 后面的函数写法省略全部类型
val f3: (String, Double) => Int = (a, b) => a.toInt + b.toInt
println(f3("2",1.5)) // 3
// (String, Double) => Int 是一个数据类型(函数签名)
// 简化版2 , 省略函数签名
var f4 = (a: String, b:Double) => a.toInt + b.toInt
println(f4("2",1.5)) //3


// 案例5:传值调用与传名调用
var money = 100
def buy(): Int = {
money -= 10
money
}

// 传值:不会改变外部传参money值
def test1(a: Int) = {
println(a)
println(a)
}
// 传名:会改变外部传参money值
def test2(a: => Int) = {
println(a)
println(a)
}

test1(buy) // 90 90
test2(buy) // 80 70

高阶函数

scala
1
2
3
4
5
6
7
8
9
10
11
12
// 高阶函数:将其他函数作为参数或返回值为一个函数的函数函数(higher-order function)

// 函数的第一个参数类型是另一个函数
def apply(f: Int => String, v: Int) = f(v)
def fmtInt(n: Int) : String = "[整数值{" + n + "}]"
println(apply(fmtInt, 1200))

// 函数的返回值是一个函数
def addBy(n: Int) = {
(d : Double) => n + d
}
println(addBy(50)(80.223))

函数柯里化

scala
1
2
3
4
5
6
7
8
9
10
// 柯里化指的是将原来接受多个参数的函数变成新的接受一个参数的函数的过程
// 原始函数, 有3个参数的函数
// def addMulti(a: Int, b: Int, c: Int) = (a + b) * c

// 函数A的返回值是一个函数B, 函数B的返回值是函数C
def addMulti(a: Int) = {
(b: Int) => (c: Int) => (a + b) * c
}

println(addMulti(50)(80)(20))

参数(类型)判断

Code
1
2
3
1、参数类型是可以推断时,可以省略参数类型
2、当传入的函数,只有单个参数时,可以省去括号
3、如果变量只在=>右边只出现一次,可以用_来代替

demo

scala
1
2
3
4
5
val list = List(1, 2, 3, 4)
println(list.map((x:Int)=>x + 1))
println(list.map((x)=>x + 1))
println(list.map(x=>x + 1))
println(list.map(_ + 1))

惰性函数

类似懒加载,将耗时的计算推迟到绝对需要的时候。

比如java使用单例模式懒汉式实现该思路

定义:当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数

scala
1
2
3
4
5
6
7
def sum(n1 : Int, n2 : Int): Int = {
println("sum() 执行了..")
return n1 + n2
}
lazy val res = sum(10, 20) // 不会执行
println("-----------------")
println("res=" + res) // 在真正使用时才执行

异常

类似java

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// demo1: 异常
try {
val r = 10 / 0
} catch {
// catch子句是按次序捕捉的
case ex: ArithmeticException=> println("捕获了除数为零的算术异常")
case ex: Exception => println("捕获了异常") // 不会打印
} finally {
// 最终要执行的代码
println("scala finally...")
}

// demo2: throw关键字
def test(): Nothing = {
throw new Exception("不对")
}
val res = test()
// throw表达式是有类型的,就是Nothing,因为Nothing是所有类型的子类型,所以throw表达式可以用在需要类型的地方


// demo3: 可以使用throws注释来声明异常
@throws(classOf[NumberFormatException])
def f11() = {
"abc".toInt
}
f11()
println("---")

面向对象编程

类与对象

与java对比

  1. scala语法中,类并不声明为public,所有这些类都具有公有可见性(即默认就是public)
  2. 一个Scala源文件可以包含多个类.

定义类

scala
1
2
3
4
5
6
7
8
9
10
class Person {
// Scala中声明一个属性,必须显示的初始化,然后根据初始化数据的类型自动推断,属性类型可以省略
var age : Int = 10
var sal = 8090.9
var name = null
var address : String = null // 如果赋值为null,则一定要加类型,因为不加类型, 那么该属性的类型就是Null类型

// 如果在定义属性时,暂时不赋值,也可以使用符号_(下划线),让系统分配默认值.
var a : Double = _
}

创建对象

scala
1
2
3
4
val | var 对象名 [:类型]  = new 类型()

1、如果我们不希望改变对象的引用(即:内存地址), 应该声明为val 性质的,否则声明为var, scala设计者推荐使用val ,因为一般来说,在程序中,我们只是改变对象属性的值,而不是改变对象的引用。
2、类型声明可以省略,但当类型和后面new 对象类型有继承关系即多态时,就必须写了

访问属性

Code
1
对象名.属性名

类和对象的内存分配机制

scala
1
2
3
4
5
6
7
val p1 = new Person
p1.name = "jack"
p1.age = 30
val p2 = p1 //内存布局?
p2.age = 18
println("p2.age=" + p2.age) //18
println("p1.age="+ p1.age) //18

构造器

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 构造器
// Scala类的构造器包括: 主构造器(一个) 和 辅助构造器(多个)
// 可以体会到Scala的函数式编程和面向对象编程融合在一起,即:构造器也是方法(函数)
// 如果主构造器无参数,小括号可省略,构建对象时调用的构造方法的小括号也可以省略
// 如果想让主构造器变成私有的,可以在()之前加上private,这样用户不能直接通过主构造器来构造对象了 class Person private()
/*
class 类名(形参列表) { // 主构造器
// 类体
def this(形参列表) { // 辅助构造器
}
def this(形参列表) { //辅助构造器可以有多个...
}
}
*/
class Person() {
var name: String = _
var age: Int = _
def this(name : String) {
//辅助构造器无论是直接或间接,最终都一定要调用主构造器,执行主构造器的逻辑
//而且需要放在辅助构造器的第一行[这点和java一样,java中一个构造器要调用同类的其它构造器,也需要放在第一行]
this() //直接调用主构造器
this.name = name
}
def this(name : String, age : Int) {
this() //直接调用主构造器
this.name = name
this.age = age
}
def this(age : Int) {
this("匿名") //简介调用主构造器,因为 def this(name : String) 中调用了主构造器!
this.age = age
}
def showInfo(): Unit = {
println("person信息如下:")
println("name=" + this.name)
println("age=" + this.age)
}
}

构造器参数

Code
1
2
3
1、Scala类的主构造器的形参未用任何修饰符修饰,那么这个参数是局部变量。
2、如果参数使用val关键字声明,那么Scala会将参数作为类的私有的只读属性使用
3、如果参数使用var关键字声明,那么Scala会将参数作为类的成员属性使用,并会提供属性对应的xxx()[类似getter]/xxx_$eq()[类似setter]方法,即这时的成员属性是私有的,但是可读写。

Bean属性

scala
1
2
3
4
5
6
// 给某个属性加入@BeanPropetry注解后,会生成getXXX和setXXX的方法
// 并且对原来底层自动生成类似xxx(),xxx_$eq()方法,没有冲突,二者可以共存。
import scala.beans.BeanProperty
class Car {
@BeanProperty var name: String = null
}

Scala对象创建对象流程分析

  1. 加载类的信息(属性信息和方法信息), 如果父类也没有加载, 则由父到子加载父类
  2. 在内存中(堆)给对象开辟空间
  3. 使用父类的构造器(主构造器/辅助构造器)完成父类的初始化 (多个父类)
  4. 使用本类的主构造器完成初始化
  5. 使用本类的辅助构造器继续初始化
  6. 将对象在内存中的地址赋给 p 这个引用

基本

1、Scala中包名和源码所在的系统文件目录结构要可以不一致,但是编译后的字节码文件路径和包名会保持一致(这个工作由编译器完成)。
2、Scala会自动引入的常用包

Code
1
2
3
java.lang.*
scala包
Predef包

3、使用嵌套形式打包:好处是:程序员可以在同一个文件中,将类(class / object)、trait 创建在不同的包中
4、作用域原则:可以直接向上访问。即: Scala中子包中直接访问父包中的内容, 大括号体现作用域。
5、父包要访问子包的内容时,需要import对应的类等
6、可以在同一个.scala文件中,声明多个并列的package(建议嵌套的pakage不要超过3层)
7、包名可以相对路径也可以绝对路径,比如,访问BeanProperty的绝对路径是:root. scala.beans.BeanProperty ,在一般情况下:我们使用相对路径来引入包,只有当包名冲突时,使用绝对路径来处理。

scala
1
2
3
4
5
6
//第一种形式
//@BeanProperty var age: Int = _
//第二种形式, 和第一种一样,都是相对路径引入
//@scala.beans.BeanProperty var age: Int = _scala
//第三种形式, 是绝对路径引入,可以解决包名冲突
@_root_. scala.beans.BeanProperty var age: Int = _

demo

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.mxx{
//这个类就是在com.mxx包下
class User{
}
//这个类对象就是在Monster$ , 也在com.mxx包下
object Monster {
}
class Dog {
}
package scala {
//这个类就是在com.mxx.scala包下
class User{
}
//这个Test 类对象
object Test {
def main(args: Array[String]): Unit = {
//子包可以直接访问父包的内容
var dog = new Dog()
println("dog=" + dog)
//在子包和父包 类重名时,默认采用就近原则.
var u = new User()
println("u=" + u)
//在子包和父包 类重名时,如果希望指定使用某个类,则带上包路径
var u2 = new com.mxx.User()
println("u2=" + u2)
}
}
}
}

包对象

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/*
包对象:包可以包含类、对象和特质trait,但不能包含函数/方法或变量的定义。
这是Java虚拟机的局限。为了弥补这一点不足,scala提供了包对象的概念来解决这个问题。

*/
package com.mxx {
//每个包都可以有一个包对象。你需要在父包(com.mxx)中定义它,且名称与子包一样。
package object scala {
var name = "jack"
def sayOk(): Unit = {
println("package object sayOk!")
}
}
package scala {
class Test {
def test() : Unit ={
//这里的name就是包对象scala中声明的name
println(name)
sayOk()//这个sayOk 就是包对象scala中声明的sayOk
}
}
object TestObj {
def main(args: Array[String]): Unit = {
val t = new Test()
t.test()
//因为TestObje和scala这个包对象在同一包,因此也可以使用
println("name=" + name)
}
}
}
}

/*
包对象的底层实现机制分析
包的可见性
默认为public访问权限
在scala中没有public关键字,即不能用public显式的修饰属性和方法。
scala设计者将访问的方式分成三大类(1) 处处可以访问public (2) 子类和伴生对象能访问protected (3) 本类和伴生对象访问 private

*/

包的引入

1、在Scala中,import语句可以出现在任何地方,并不仅限于文件顶部,import语句的作用一直延伸到包含该语句的块末尾。这种语法的好处是:在需要时在引入包,缩小import 包的作用范围,提高效率。
2、Java中如果想要导入包中所有的类,可以通过通配符*,Scala中采用下划线_
3、如果不想要某个包中全部的类,而是其中的几个类,可以采用选取器(大括号)

scala
1
import scala.collection.mutable.{HashMap, HashSet}

4、如果引入的多个包中含有相同的类,那么可以将类进行重命名进行区分,这个就是重命名。

scala
1
2
3
4
import java.util.{ HashMap=>JavaHashMap, List}
import scala.collection.mutable._
var map = new HashMap()
var map1 = new JavaHashMap();

5、如果某个冲突的类根本就不会用到,那么这个类可以直接隐藏掉。

scala
1
2
import java.util.{ HashMap=>_, _}
var map = new HashMap()

封装

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
1、Scala中为了简化代码的开发,当声明属性时,本身就自动提供了对应setter/getter方法,如果属性声明为private的,那么自动生成的setter/getter方法也是private的,如果属性省略访问权限修饰符,那么自动生成的setter/getter方法是public的
2、因此我们如果只是对一个属性进行简单的set和get ,只要声明一下该属性(属性使用默认访问修饰符) 不用写专门的getset,默认会创建,访问时,直接对象.变量。这样也是为了保持访问一致性
3、从形式上看 dog.food 直接访问属性,其实底层仍然是访问的方法, 看一下反编译的代码就明白
*/
class Cat(inAge:Int) {
private var age: Int = 0
var name: String = ""
private val age2: Int = 0
val name2: String = ""
}

object C08 {
def main(args: Array[String]): Unit = {
val cat = new Cat(10)
cat.name = "喵"
println(cat.name)
}
}

继承

1、基本语法

scala
1
2
3
4
5
class Student extends Person {
def studying(): Unit = {
println(this.name + "学习 scala中....")
}
}

2、子类继承了所有的属性,只是私有的属性不能直接访问,需要通过从父类继承的公共的方法去访问

3、scala明确规定,重写一个非抽象方法需要用override关键字修饰,调用超类的方法使用super关键字

scala
1
2
3
4
5
6
class Emp extends Person {
override def printName() {
println("Emp printName() " + name)
super.printName()
}
}

4、类型检查和转换

scala
1
2
3
4
5
6
7
8
9
10
// 要测试某个对象是否属于某个给定的类,可以用isInstanceOf方法。用asInstanceOf方法将引用转换为子类的引用。classOf获取对象的类名。
object C10{
def main(args: Array[String]): Unit = {
println(classOf[String])
val s = "zhangsan"
println(s.getClass.getName) //这种是Java中反射方式得到类型
println(s.isInstanceOf[String])
println(s.asInstanceOf[String]) //将s 显示转换成String
}
}

5、超类的构造

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1)类有一个主构器和任意数量的辅助构造器,而每个辅助构造器都必须先调用主构造器(也可以是间接调用)
// 2)只有主构造器可以调用父类的构造器。辅助构造器不能直接调用父类的构造器。在Scala的构造器中,你不能调用super(params)

class Person(name: String) {

}
class Emp (name: String) extends Person(name) {

// super(name)

def this() {
//super("abc")
}
}

6、覆写字段

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
- 在Scala中,子类改写父类的字段,我们称为覆写/重写字段。覆写字段需使用 override修饰。
- 抽象属性:声明未初始化的变量就是抽象的属性,抽象属性在抽象类
- 如果是覆写一个父类的抽象属性,那么override 关键字可省略(本质上是实现)
*/
class A {
val age : Int = 10
}
class B extends A {
override val age : Int = 20
}
object C10 {
def main(args: Array[String]): Unit = {
val obj1 : A = new B()
val obj2 : B = new B()

println(obj1.age) //20
println(obj2.age) // 20

}
}

7、抽象类

scala
1
2
3
4
5
6
7
8
9
10
11
/*
- 方法不用标记abstract,只要省掉方法体即可。
- 抽象字段/属性就是没有初始值的字段
- 抽象类的价值更多是在于设计,是设计者设计好后,让子类继抽象类。抽象类本质上就是一个模板设计
*/
abstract class Animal {
var name : String //抽象的属性
var age : Int // 抽象的属性
var color : String = "black" // 普通属性
def cry() // 抽象方法
}

8、匿名子类

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Monster{
var name : String
def cry()
}
object C10 {
def main(args: Array[String]): Unit = {
var monster = new Monster {
override var name: String = "牛魔王"
override def cry(): Unit = {
println("牛魔王哼哼叫唤..")
}
}
}
}

9、继承层级

image-20200205171238470

1、在scala中,所有其他类都是AnyRef的子类,类似Java的Object。
2、AnyVal和AnyRef都扩展自Any类。Any类是根节点/根类型
3、Any中定义了isInstanceOf、asInstanceOf方法,以及哈希方法等。
4、Null类型的唯一实例就是null对象。可以将null赋值给任何引用,但不能赋值给值类型的变量[案例演示]。
5、Nothing类型没有实例。它对于泛型结构是有用处的,举例:空列表Nil的类型是List[Nothing],它是List[T]的子类型,T可以是任何类。

静态属性和静态方法

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
Scala中静态的概念-伴生对象
- Scala语言是完全面向对象,没有静态的概念,就产生了一种特殊的对象来模拟类对象,我们称之为类的伴生对象。这个类的所有静态内容都可以放置在它的伴生对象中声明和调用。
- Scala中伴生对象采用object关键字声明,伴生对象中声明的全是 "静态"内容,可以通过伴生对象名称直接调用。
- 伴生对象对应的类称之为伴生类,伴生对象的名称应该和伴生类名一致。
- 伴生对象中的属性和方法都可以通过伴生对象名直接调用访问
- 从语法角度来讲,所谓的伴生对象其实就是类的静态方法和静态变量的集合
- 从技术角度来讲,scala还是没有生成静态的内容,只不过是将伴生对象生成了一个新的类,实现属性和方法的调用。
- 如果 class A 独立存在,那么A就是一个类, 如果 object A 独立存在,那么A就是一个"静态"性质的对象[即类对象]
- 伴生对象-apply方法
在伴生对象中定义apply方法,可以实现: 类名(参数) 方式来创建对象实例.

*/
// class ScalaPerson 是伴生类
class ScalaPerson {
var name : String = _
}
// object ScalaPerson 是伴生对象
object ScalaPerson {
var sex : Boolean = true

def main(args: Array[String]): Unit = {
println(ScalaPerson.sex)
// println(ScalaPerson.name) 不行
}
}

单例模式

Code
1
2
3
采用类对象(即伴生对象)方式构建单例对象
- 懒汉式
- 饿汉式

TODO

接口 & 特质(trait)

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
- 接口并不属于面向对象的范畴,Scala是纯面向对象的语言,在Scala中,没有接口, 也没有implements关键字
- Scala语言中,采用trait(特质,特征)来代替接口的概念
- 在scala中,java中的接口可以当做特质使用
- 使用
没有父类
class 类名 extends 特质1 with 特质2 with 特质3 ..
有父类
class 类名 extends 父类 with 特质1 with 特质2 with 特质3

- 特质可以同时拥有抽象方法和具体方法
- 和Java中的接口不太一样的是特质中的方法并不一定是抽象的,也可以有非抽象方法(即:实现了的方法)。

*/
trait Trait1 {
def getConnect(user: String, pwd: String): Unit //声明方法,抽象的.
def test(n1:Int)
}
class A {}
class B extends A {}
class C extends A with Trait1 {
override def getConnect(user: String, pwd: String): Unit = {
println("c连接mysql")
}
}
class D {}
class E extends D with Trait1 {
def getConnect(user: String, pwd: String): Unit = {
println("e连接oracle")
}
}

TODO

type关键字

使用type关键字可以定义新的数据类型名称。本质上就是类型的一个别名

scala
1
2
3
type S = String
var v : S = "abc"
def test() : S = "xyz"

嵌套类

在Scala中,你几乎可以在任何语法结构中内嵌任何语法结构。如在类中可以再定义一个类,这样的类是嵌套类,其他语法结构也是一样。

嵌套类类似于Java中的内部类。

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ScalaOuterClass {
class ScalaInnerClass { //成员内部类
}
}
object ScalaOuterClass { //伴生对象
class ScalaStaticInnerClass { //静态内部类
}
}

val outer1 : ScalaOuterClass = new ScalaOuterClass();
val outer2 : ScalaOuterClass = new ScalaOuterClass();
// Scala创建内部类的方式和Java不一样,将new关键字放置在前,使用 对象.内部类 的方式创建
val inner1 = new outer1.ScalaInnerClass()
val inner2 = new outer2.ScalaInnerClass()
//创建静态内部类对象
val staticInner = new ScalaOuterClass.ScalaStaticInnerClass()
println(staticInner)

TODO

隐式转换和隐式参数

todo

集合

可变与不可变

可变集合

collections

不可变集合

collections.immutable

String是一种集合类型

数组

元组 Tuple

可以存放各种相同或不同类型的数据 。

注意:元组中最大只能有 22 个元素

scala
1
2
3
4
5
6
7
8
9
10
11
12
//创建元组
val tuple = (1, 2, "hello")
//访问元组
//1. 使用 _顺序号
println(tuple._3) // "hello"
//2. 使用
println(tuple.productElement(2)) //下标是从 0 开始计算

//遍历元组[通过迭代器来遍历]
for (i <- tuple.productIterator) {
println("i=" + i)
}

列表 List

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 创建
// 默认的scala.collection.immutable.List
val list01 = List(1, 2, 3) //创建时,直接分配元素 apply
println(list01)
val list02 = Nil //空集合
println(list02)

// 访问
val value1 = list1(1) // 1 是索引,表示取出第 2 个元素.
println(value1)

// 追加
var list1 = List(1, 2, 3, "abc")

// :+运算符表示在列表的最后增加数据
// 说明 1. :+ 符号 : 前是集合 + 后是元素
val list2 = list1 :+ 4
println(list1) //list1 没有变化
println(list2) //新的列表结果是 [1, 2, 3, "abc", 4]

//给 list 向前面追加
//说明 1. +: 符号 : 后是集合 + 前是元素
val list3 = 100 +: list1
println("list3=" + list3)

// :: 使用
// 符号::表示向集合中 新建集合添加元素。
// 运算规则, 从右向左。
val list4 = List(1, 2, 3, "abc")
// 操作步骤
// 1. ()
// 2. (List(1, 2, 3, "abc"))
// 3. (6, List(1, 2, 3, "abc"))
// 4. (5, 6, List(1, 2, 3, "abc"))
// 5. (4, 5, 6, List(1, 2, 3, "abc"))
val list5 = 4 :: 5 :: 6 :: list4 :: Nil
println("list5=" + list5)

// ::: 的使用
// ::: 运算符是将集合中的每一个元素加入到空集合中去, ::: 左右两边需要时集合
//下面等价 4 :: 5 :: 6 :: list1
var list6 = List(1, 2, 3, "abc")
// 步骤
// 1. ()
// 2. (1, 2, 3, "abc")
// 3. (6, 1, 2, 3, "abc")
// 4. (5, 6, 1, 2, 3, "abc")
// 5. (4, 5, 6, 1, 2, 3, "abc")
val list7 = 4 :: 5 :: 6 :: list6 ::: Nil
println("list7=" + list7)

列表 ListBuffer

队列 Queue

映射 Map

Scala 中不可变的 Map 是有序的,可变的 Map 是无序的

创建

scala
1
2
3
4
5
6
7
8
9
10
11
12
// 构造不可变映射
val map1 = Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> "北京")

// 构造可变映射
val map2 = scala.collection.mutable.Map("Alice" -> 10, "Bob" -> 20, "Kotlin" -> 30)

// 创建空的映射
val map3 = new scala.collection.mutable.HashMap[String, Int]

// 对偶元组
// 即创建包含键值对的二元组, 和第一种方式等价
val map4 = mutable.Map( ("A", 1), ("B", 2), ("C", 3),("D", 30) )

取值

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 1. 使用 map(key)
// 如果 key 不存在,则抛出异常[java.util.NoSuchElementException]
val value1 = map2("Alice")

// 2. 使用 contains 方法检查是否存在 key,返回 Boolean
map4.contains("B")
// 使用 containts 先判断在取值,可以防止异常

// 3. 使用 map.get(key).get 取值
var map4 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) )
println(map4.get("A")) //Some
println(map4.get("A").get) //得到 Some 再取出

// 4. 使用 map4.getOrElse()取值
// 如果 key 不存在,返回默认值。
val map4 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) )
println(map4.getOrElse("A","默认"))

//如何选择取值方式建议
/*
1. 如果我们确定 key 是存在的,应该使用 map("key") ,速度快.
2. 如果我们不确定 key 是否存在, 而且在不存在时,有业务逻辑处理就是用 map.contains() 配合 map("key")
3. 如果只是简单的希望返回一个值,就使用 getOrElse()
*/

对 map 修改、添加和删除

scala
1
2
3
4
5
6
7
8
9
10
11
12
// 更新 map 的元素
// map 是可变的,才能修改
// 如果 key 存在:则修改对应的值,key 不存在,等价于添加一个 key-val
val map4 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) )
map4("A") = 20 //修改和增加

// 添加 map 元素
map+=("A"->1,"B"->2)

// 删除 map 元素
map-=("A","B")
map4.remove(key)

对 map 遍历

scala
1
2
3
4
5
val map1 = mutable.Map( ("A", 1), ("B", "北京"), ("C", 3) )
for ((k, v) <- map1) println(k + " is mapped to " + v)
for (v <- map1.keys) println(v)
for (v <- map1.values) println(v)
for(v <- map1) println(v) //v 是 Tuple2

集 Set

集合应用操作

map 映射操作

集合(List,Array..) [数据] ==> 过滤(函数) ==> 操作(函数计算任务)[map] ==> 得到新的集合

案例:请将 List(3,5,7) 中的所有元素都 * 2 ,将其结果放到一个新的集合中返回

在 Scala 中可以通过 map 映射操作来解决:将集合中的每一个元素通过指定功能(函数)映射(转换)成新的

结果集合这里其实就是所谓的将函数作为参数传递给另外一个函数,这是函数式编程的特点

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
val list = List(3, 5, 7)

/*
- 这个就是 map 映射函数,集合类型都有
- map 是一个高阶函数,接收的函数
- 这里我们传的是方法,但是会转成函数 val f2 = f1 _
- 通过 map 的操作,返回一个新的集合 list2
- 底层会遍历 list 的所有元素,并传递给 f1,将 f1 的结果封装成新的集合并返回
- list 没有变化
*/
val list2 = list.map(f1)


//方法
def f1(n:Int): Int = {
println("hello...")
n * 2
}

// 可以直接定义一个函数
val f3 = (n1: Int,n2: Int) => {
n1 + n2
}
println(f3(1,2))

flatmap 映射

flatmap: flat 即压扁,压平,扁平化,效果就是将集合中的每个元素的子元素映射到某个函数并返回新的集合。

scala
1
2
3
4
5
6
7
8
9
val names = List("Alice", "Bob", "Nick")
def upper( s : String ) : String = {
s. toUpperCase
}
// 每个字符串也是 char 集合
println(names.flatMap(upper)) // List(A, L, I, C, E, B, O, B, N, I, C, K)

// (hello world, hello scala) -> hello,world,hello,scala
val res1 = list.flatMap(_.split(" "))

过滤-filter

模式匹配

函数式编程-高阶

泛型 上下界 视图界定 上下文界定 协变逆变不变

AKKA

scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
object C10 {
def main(args: Array[String]): Unit = {
val sub = new Sub()
sub.sayOk()
}
}
class Base {
var n1: Int = 1
protected var n2: Int = 2
private var n3: Int = 3
def test100(): Unit = {
println("base 100")
}
protected def test200(): Unit = {
println("base 200")
}
private def test300(): Unit = {
println("base 300")
}
}
class Sub extends Base {
def sayOk(): Unit = {
this.n1 = 20
this.n2 = 40
// this.n3 访问不到
println("范围" + this.n1 + this.n2)
}
}
文章作者: Machine
文章链接: https://machine4869.gitee.io/2020/01/18/20200118180040984/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 哑舍
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论