一、Scala 简介
1、概述
scala 是一门多范式(multi-paradigm)的编程语言,设计初衷是要继承面向对象编程和函数式编程的各种特性。
scala 运行在 JVM 虚拟机上,并可以调用现有的 Java 类型。
2、环境安装
# 解压安装包
# 下载地址:https://www.scala-lang.org/download/2.11.12.html
tar -zxvf scala-2.11.12.tgz -C ~/training/
# 建立软链接
ln -s /root/training/scala-2.11.12/ /root/training/scala
# 配置环境变量
vi ~/.bash_profile
SCALA_HOME=/root/training/scala
export SCALA_HOME
PATH=$PATH:$SCALA_HOME/bin
export PATH
# 测试环境是否生效
scala -version
# 打开 shell
scala
3、代码规范
scala 的单行多行注释跟 Java 一样
/*
进行多行注释
多行注释的代码不会被执行
println("Hello Scala 1")
*/
// 进行单行注释
// 双斜线进行单行注释
println("Hello Scala 2")
一般情况下,类和变量对象的命名遵循驼峰标识,缩进使用空格或者tab(见仁见智,团队内保持一致即可)。
4、包的定义和引入
scala 的包是一个命名的代码模块,用于管理大型程序。和 Java 中包的命名空间的目的是相同的。
要想在一个包中引用其他包的类,scala 的语法如下:
// 直接在代码中引入
object DemoImportPkg {
def main(args:Array[String]) : Unit = {
import pkg._
...
}
}
// 在class或object的前面引入
import pkg._
object DemoImportPkg {
def main(args:Array[String]) : Unit = {
...
}
}
// 注意:跟java引入包中所有类的语法:import pkg.* 略有差异
// scala 是可以执行定义一个 object 的,而不是一定要基于 class 来 new 一个 object
二、基础语法
1、变量、常量
// 定义一个变量
var a = 1
// 定义一个常量
val b = 2
b = 3 // 报错,因为b是常量不允许被修改
2、数据类型
类型 | 具体类型 |
---|---|
数字类型 | Short、Int、Long |
浮点型 | Float、Double |
字节 | Byte |
字符、字符串 | Char、String |
布尔型 | Boolean |
无值 | Unit |
空 | Null |
所有类型的子类型,等同于void | Nothing |
示例代码:
// 演示常用数据类型
// Byte型
val a : Byte = 2
a
// 定义 Char
val c : Char = 'X'
c
// 定义 String
val s : String = " a String"
s
// Unit类型, 可行想象成Java里的Void类型,没有类型
// 我们没有明确指明u的类型,scala自动判断为Unit
val u = {}
u
// Nothing 类型
// 可以用于函数当中判断函数是否产生异常
def myFunc() = throw new IllegalArgumentException
// Null
val n = null
3、字符串操作
示例代码:
// 演示字符串常用操作
val s = "String"
// 字符串的长度
s.length
// 字符串的连接
s.concat(" another string")
s + "xxx"
// 创建格式化字符串
val floatVal = 1.23
val intVal = 1000
val stringVal = "Simon"
printf("浮点型变量为 %.3f, 整型变量为 %d, 字符串为 %s", floatVal, intVal, stringVal)
// 字符串插值
printf("这是 ${s}")
// 其他的字符串常用方法
// 1 - endWith - 测试此字符串是否以指定的后缀结束
val ew = "Hello Scala"
ew.endsWith("Scala")
// 2 - equals - 将此字符串与指定的对象比较
val ew2 = "Hello Scala"
ew2.equals(ew)
ew2.equals(s)
// 3 - compare - 比较两个字符串
val str1 : String = "Strings"
val str2 : String = "Strings"
val str3 : String = "Strings123"
var result : Int = str1.compareTo(str2)
result = str2.compareTo(str3)
result = str3.compareTo(str1)
// 4 - compareToIgnoreCase - 比较2个字符串,不考虑大小写
val ew3 = "hello scala"
ew3.compareToIgnoreCase(ew2)
ew3.compareTo(ew2)
// 5 - indexOf - 返回指定子字符串在此字符串中第一次出现的索引
ew.indexOf("o")
ew.indexOf("l",5)
// 6 - matches - 告知此字符串是否匹配给定的正则表达式
str1.matches("^S.+")
str3.matches("^S.*\\d+$")
// 7 - replace - 用newChar替换oldChar,得到新的字符串
ew.replace("Scala","Simon")
ew
// 8 - split - 根据给定的正则表达式,匹配拆分此字符串
ew.split(" ")
ew.split("")
ew.split("S")
4、正则表达式
scala 提供了 Regex 对象使得使用正则表达式更加方便,通过 String.r 可以得到一个 Regex 对象
// 演示 - 正则表达式的使用
// 0 - 回顾 - str.matches
val str = "Abc123"
str.matches("^A.*\\d+$")
// 1 - 用 String 类的r方法,构建一个 Regex 对象
val numPattern = "[0-9]+".r
val srcString = "Abc!@#456QWE789()_"
// 2 - findAllIn - Regex对象的findAllIn方法,返回一个迭代器,用于遍历所有匹配项
// 2.1 for 循环做遍历
for (each <- numPattern.findAllIn(srcString)) {
println(each)
}
// 2.2 可以将迭代器转化为数组
numPattern.findAllIn(srcString).toArray
// 3 - findFirstIn - 找到字符串中首个匹配项
// Note - 得到的结果是一个Option[String],后面章节介绍
numPattern.findFirstIn(srcString)
// 4 - replaceFirstIn - 将第一个匹配项替换为指定的内容
numPattern.replaceFirstIn(srcString, "888")
// 5 - replaceSomeIn
numPattern.replaceSomeIn(srcString ,
m => if (m.matched.toInt %2 == 0) Some("999") else None)
// 6 - 首字母可以大写S或小写s
val pattern = new scala.util.matching.Regex("(S|s)cala")
val str2 = "Scala is scalable and cool"
pattern.findAllIn(str2).mkString(",")
val b = Array("a","b")
b.mkString(",")
// 7 - 正则表达式组
val numItemPattern = "([0-9]+) ([a-z]+)".r
val src2 = "10 cups, 20 cups"
// 这里注意 findAllIn 跟 findAllMatchIn 的区别
for (m <- numItemPattern.findAllIn(src2)) {
println(m)
}
// 10 cups
// group(1)是匹配到的 ([0-9]+) 的 10
// group(2)是匹配到的 ([a-z]+) 的 cups
for(m <-numItemPattern.findAllMatchIn(src2)){
println(m.group(1) + " / " + m.group(2))
}
5、文件读写
scala 在 scala.io._ 包中提供了 io 操作的类,包括文件读写。
使用 java.io.PrintWriter 写入文件
使用 scala.io.Source 读取文件
使用 Paths 访问目录
import java.io.{File, PrintWriter}
import java.nio.file.Paths
import scala.io.Source
// 演示 - 文件的读写操作
// 用 java.io.PrintWriter 来写入文件
val writer = new PrintWriter(new File("/tmp/test.txt"))
writer.write("Line 1 content in test.txt \n")
writer.write("Line 2 content in test.txt \n")
writer.close()
// 读文本文件
val src1 = Source.fromFile("/tmp/test.txt", "UTF-8")
for (l <- src1.getLines()) {
println(l)
}
src1.mkString
src1.close()
// 访问目录
println("#" *10)
val dir = "/tmp"
val path = Paths.get(dir)
println(path.toString)
println(path.getParent.toString)
三、运算符
包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符。
/**
* 演示 - 算数运算符
*/
object Arithmetic {
def main(args:Array[String]) : Unit = {
val a = 20
val b = 30
val c = 75
println("a + b = " + (a+b))
println("a - b = " + (a-b))
println("a * b = " + (a*b))
println("a / b = " + (a/b))
println("b / a = " + (b/a))
println("a % b = " + (a%b))
println("b % a = " + (b%a))
println("c % a = " + (c%a))
}
}
/**
* 演示 - 关系运算符
*/
object Relational {
def main(args: Array[String]): Unit = {
val a = 3
val b = 9
println("a == b : " + (a==b))
println("a == b : " + (a == b))
println("a != b : " + (a != b))
println("a > b : " + (a > b))
println("a < b : " + (a < b))
println("a >= b : " + (a >= b))
println("a <= b : " + (a <= b))
}
}
/**
* 演示 - 逻辑运算符
*/
object Logical {
def main(args: Array[String]): Unit = {
val a = true
val b = false
println("a && b = : " + (a && b))
println("a || b = : " + (a || b))
println("!(a && b) = : " + (!(a && b)))
}
}
/**
* 演示 - 位运算符
*/
object Bit {
def main(args: Array[String]): Unit = {
val a = 4
val b = 9
var c = 0
// 数字的二进制表示的字符串
println(s"a=${a}, bin=${a.toBinaryString}")
println(s"b=${b}, bin=${b.toBinaryString}")
println(s"c=${c}, bin=${c.toBinaryString}")
println("#"*20)
c = a & b
println(s"a & b = ${c}, bin=${c.toBinaryString}")
println("#"*20)
c = a | b
println(s"a | b = ${c}, bin=${c.toBinaryString}")
println("#"*20)
c = a ^ b
println(s"a ^ b = ${c}, bin=${c.toBinaryString}")
println("#"*20)
c = ~a
println("a = " + a)
println(s"~a = ${c}, bin=${c.toBinaryString}, len=${c.toBinaryString.length}")
println("#"*20)
// 有符号左移、右移 (如果是正数,高位补0;负数,高位补1)
c = a << 2
println(s"a << 2 = ${c}, bin=${c.toBinaryString}, len=${c.toBinaryString.length}")
println("#"*20)
c = ~a >> 2
println(s"~a >> 2 = ${c}, bin=${c.toBinaryString}, len=${c.toBinaryString.length}")
println("#"*20)
// 无符号右移 用>>> (不论高位正负,高位补0)
println("#"*20)
c = ~a >>> 2
println(s"~a >>> 2 = ${c}, bin=${c.toBinaryString}, len=${c.toBinaryString.length}")
println("#"*20)
}
}
/**
* 演示 - 赋值运算符
*/
object Assign {
def main(args: Array[String]): Unit = {
var a = 3
val b = 13
var c = 0
c = a + b
println("c = a + b = " + c)
c +=a
println("c +=a = " +c)
c -=a
println("c -=a = " +c)
c *=a
println("c *=a = " +c)
a = 10
c = 15
c %= a
println("c %= a = " + c)
println(s"c=${c},bin=${c.toBinaryString}")
c <<= 2
println(s"c=${c},bin=${c.toBinaryString}")
println("c <<= 2 = " + c )
}
}
四、程序控制结构
在一般的编程语言中,将表达式和语句看成两种不一样的东西,表达式有值,而语句用于执行动作。而scala 跟其他语言之间一个重要的差别是几乎所有构造出来的语法结构都是有值的,这使得程序更加精简,也更易读。
条件表达式
在 scala 中 if/else 是表达式,有值,这个值就是跟在 if/else 之后的表达的值。
// 演示 条件表达式 if/ else
// 条件表达式 是有值, 最终的值取决于{}内的表达式的值
val test = if (true) {
"state1"
} else {
"state2"
}
val test1 = if (false) "xxx1" else "xxx2"
// else if 示例
val a = 13
if (a < 10){
println("<10")
} else if ( 10 <= a && a <20) {
println("[10,20)")
} else {
println("others")
}
// 程序执行结果
test: String = state1
test1: String = xxx2
a: Int = 13
[10,20)
循环结构
scala 中有跟 Java、C++ 相同的 while 和 for 循环,但是没有 for(i = 0; i < 10; i++) 这种结构,要实现循环有两种选择:
- 使用 while 循环
- 使用 for(i <- 1 to n) 的语句
// 演示 循环结构 for while
val l = List ("a","b","c")
// 1、使用 for 循环遍历
for (x <- l ) {
println(x)
}
// 2、循环之前可以先进行判断,决定要不要执行循环体内的语句
// for 后面可以用括号也可以用花括号
for (
x <- l
if (x == "b")
) {
println(x)
}
// 等同于 java 代码中的
for (object x : iterator) {
if (x == "b") {
println(x)
}
}
// 3、循环加判断的另一种写法(刚接触觉得会很混乱,熟了还好)
for (x <- l if x == "b") { println(x) }
// 4、for循环的结果赋值
// 使用 1 to 10 表示一个元素集合,可被用来迭代,不需要变量赋值
for (i <- 1 to 10) println(i)
// 对迭代的每个元素将其值对3取模,再赋值给一个变量
val result = for (i <- 1 to 10 ) yield i %3
// 5、实现类似 Java 代码双重循环的效果
// ##################### Java ######################
for (int i = 1; i<= 3; i++) {
for (int j = 2; j <= 4; j++) {
if (i != j)
System.out.println(i + "," + j);
}
}
// ##################### Scala ######################
for (i <- 1 to 3; j <- 2 to 4 if i != j) {
println((i, j))
}
// 6、while 循环
var i = 0
while ( i < l.length) {
println(l(i))
i += 1
}
五、函数
- scala 有函数和方法,二者在语义上区别很小
- 方法是类的一部分,而函数是一个对象,可以赋值给一个变量
- 函数体最后一行表示返回值,不用 return 关键字
1、函数的声明
用 def 关键字自定义函数(返回值类型可以省略)
<img src="drawing\3.png" style="zoom:40%;" />
// 使用 scala 的内置函数,需要导入对应的包
import scala.math._
max(2,7)
sqrt(9)
// 自定义函数
// ##################### Java ######################
String helloScala(String name) {
return "Hello scala, this's " + name;
}
// ##################### Scala ######################
// 函数名为 helloScala, 入参类型是 String,参数名为 name,返回值类型为 String
def helloScala(name : String) : String = {
"Hello scala, this's ${name}"
}
// 调用自定义函数
helloScala("Tom")
// 将函数返回值赋值给变量
val simon = helloScala("Simon")
// 定义一个无返回值的函数
// ##################### Java ######################
void procedure(String name) {
System.out.println("Hello scala, this's " + name);
}
// ##################### Scala ######################
val empty = procedure("test unit func")
2、函数的参数
-
默认参数
在调用某些函数时并不显式地给出所有的参数,这些函数可以使用默认参数
-
带名参数
- 在使用函数时,带名参数可以让函数更加可读
- 也可以混用未命名参数和带名参数(混用时未命名参数需要排在前面)
-
变长参数
有时候实现一个可以接受可变长度参数列表的函数会更方便
// 1、简单的指定传入参数的函数,部分参数指定了默认值
def testParam(name:String, age:Int, city:String = "Beijing") : String = {
s"Name: ${name}, Age: ${age}, City: ${city}"
}
// 不指定city参数,输出默认值
testParam("Simon", 18)
// 传参也可指定参数名,这样就不要求传入参数的顺序跟函数定义参数列表相同
testParam(age = 21, name = "Tom")
// 不建议的做法:混用不带名参数和带名参数,要求顺序与定义一致
testParam("Tony", 28, city = "HongKong")
// 报错,不带名参数应放在带名的前面
//testParam("Tony", city = "HongKong", 28)
// 2、变长参数
// ##################### Java ######################
int sum(int... args) {
int res = 0;
for (int i : args) {
res += i;
}
return res;
}
// ##################### Scala ######################
def sum (args: Int*) : Int = {
var res = 0
for (i <- args) res += i
res
}
sum(1,2,3,4)
sum(9,10)
sum(10)
sum(1 to 100 : _*) // 1 to 100: _* 被当成 Int*
// 3、递归函数
def recSum(args: Int*) : Int = {
if (args.length == 0) {
0
} else {
// 取第一个元素 + 后面的所有元素
args.head + recSum( args.tail: _* )
}
}
recSum(1,2,3,4)
recSum(1 to 100: _*)
3、Lazy 值
当 val 被声明为 lazy 时,初始化将被延迟,直到首次对它进行取值
// 延迟加载
// 当我们定义testFile时,并没有真正去执行
// 直到我们首次调用testFile才会触发底层的执行逻辑,
// 进而引发异常捕获 -- 参见demo04
lazy val testFile = Source.fromFile("/tmp/xxx")
try {
testFile.mkString
} catch {
case _:Exception => {
println("Lazy test")
}
}
4、异常
- scala 的异常处理和其他语言比如 Java 类似
- 抛出的异常对象必须是 java.lang.Throwable 的子类
- 判断函数是否抛出异常,通过返回值是否为 Nothing 判断
import scala.io.Source
// 演示 关于异常处理
// 异常的抛出,使用的是 throw
// 判断函数的执行是否产生了异常,通过判断【返回值】是否为Nothing
def func(): Nothing = {
// processing
throw new IllegalArgumentException
}
// try catch / finally 捕获处理异常
try {
val test = Source.fromFile("/tmp/xxx").mkString
} catch {
case ex: java.lang.IllegalArgumentException => {
println("IllegalArgumentException, say sth...")
ex.printStackTrace()
}
case nf: java.io.FileNotFoundException => {
println("XXX file not found")
}
case _: Exception => {
println("Other Exception")
}
} finally {
println("No matter what happend, do this line")
}
六、面向对象
1、基础
(1)类
类的定义
具有相同特性(数据元素)和行为(功能)的对象的抽象就是类
-
类是对象的抽象,而对象是类的具体实例
-
类是抽象的,不占用内存,而对象是具体的,占用存储空间
scala 中的类不声明为 public,一个 scala 源文件中可定义多个
面向对象的三大特征
- 封装:类定义将其说明(用户可见的外部接口)与实现(用户不可见的内部实现)显式地分开,其内部实现按其具体定义的作用域提供保护。
- 继承:继承性是子类自动共享父类的数据结构和方法的机制,这是类之间的一种关系。
- 多态:多态性是指相同的操作或函数、过程可作用于多种类型的对象上并获得不同的结果。
// 类的定义
class DemoDefineClass {
val a = 0
def testFunc() : Int = {
1
}
}
// 类的使用
object DemoDefineClass {
def main(args : Array[String]) : Unit = {
val obj = new DemoDefineClass
println(obj.a)
println(obj.testFunc())
}
}
(2)对象
单例对象
- 单例对象,就是一个类只允许有一个对象
- 在 scala 中,没有静态方法和静态字段,但是可以通过单例模式的实现方法,那就是使用关键字 object;
- scala 的 object 对象下定义的所有成员都是静态的
- scala 中使用单例模式时,除了定义的类之外,还要定义一个同名的 object 对象,它和类的区别是,object 对象不能带参数
伴生对象
- 当单例对象与某个类共享同一个名称时,它被称作是这个类的伴生对象
- 类被称为是这个单例对象的伴生类:companion class
- 类和它的伴生对象可以互相访问其私有成员
- 伴生对象和伴生类必须存在于同一个源文件中
/**
* 演示 在伴生类内调用私有属性
*/
class DemoCompanion {
// 公共属性
var sth = "Java"
// 定义一个私有属性
private val p = "private"
def sayHello() = {
println(s"Hello ${this.sth}")
}
def setSth(s :String ) {
this.sth = s"this.sth = ${s}"
}
}
object DemoCompanion {
var sth = "Scala"
def sayHello() = {
println(s"Companion object, hello ${this.sth}")
}
def main(args: Array[String]): Unit = {
println(this.sth)
this.sayHello()
val testObj = new DemoCompanion
testObj.sayHello()
println("getter: " + testObj.sth)
testObj.sth = "Golang"
println("after setter: " + testObj.sth)
testObj.setSth("Spark")
println(testObj.sth)
// 我们使用伴生对象 - object 和 class名 相同的时候, 私有属性是能被访问的
// 如果将object DemoCompanion替换为其他的object名称,.p就不能被访问了
println(s"testObj.p = ${testObj.p}")
}
}
应用程序对象
- 每个 scala 程序都必须从一个 main 函数开始
- 除了每次都提供自己的main方法,你还可以扩展 APP 应用程序
- 在用 main 方法时,使用传进来的 args 参数获取命令行参数
- 在使用 App 时,可以直接使用 args 获取命令行的参数
注意:获取命令行参数时,使用的是小括号而不是中括号(Python使用的是中括号)。
/**
* 演示 - App应用对象
*/
object DemoObjectApp extends App {
println("DemoObjectApp")
if (args.length <= 0) {
println("no args")
println(args.length)
} else {
println(args(0))
}
}
对象的 apply 方法
Array 对象定义了 apply 方法,让我们可以不使用 new 关键字的表达式来创建数组:
- Array(100),这个表达式会调用 apply 方法
- Array(100) 中的 100 表示数组中的一个元素,而不是数组的元素个数
如果使用 new Array(100) - 则调用构造器 this(100)
- new Array(100) 表示创建一个大小为 100 个元素的数组,数组中的每个元素都是 null
// scala 类的构造器中的参数默认为其成员,而不需要在类中显式定义成员
// 下面的 Java 代码和 Scala 代码达到相同的效果
// ##################### Java ######################
public class User {
String name;
int age;
User(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
User user = new User("tom", 29);
System.out.println("name: " + user.name + ", age: " + user.age);
}
}
// ##################### Scala ######################
class SUser(val name: String, val age: Int) {
println("This is scala class SUser")
}
object SUser {
def main(args: Array[String]): Unit = {
val user = new SUser("Tom", 29);
println(s"name: ${user.name}, age: ${user.age}")
}
}
下面代码演示了使用在伴生对象中定义 apply 及其使用,不使用 new 来创建对象
class DemoApplyObject(val id : Int, val name : String) {
println("This is class DemoApplyObject.")
}
object DemoApplyObject {
// 相当于构造器的效果,不适用new创建对象时用到
def apply(id: Int, name: String): DemoApplyObject = {
println("Applied in object DemoApplyObject")
new DemoApplyObject(id, name)
}
def main(args: Array[String]): Unit = {
// new
val demo02 = new DemoApplyObject(1, "Simon")
println(s"id: ${demo02.id}, name: ${demo02.name}")
println("#" * 20)
// apply , no new
val demoApply = DemoApplyObject(2, "applied")
println(s"id: ${demoApply.id}, name: ${demoApply.name}")
}
}
私有类成员的封装
/**
* 演示 私有成员
*/
class DemoPrivate {
private var p = "private" // 定义了私有属性
/**
* 定义了公有的成员方法
* - 调用私有成员
*/
def getSth() = {
this.p
}
/**
* 定义了公有成员方法,来修改私有成员
* @param s - 接收的参数,用于修改私有成员的值
*/
def setSth(s : String) = {
this.p = s
}
}
object Test {
def main(args: Array[String]): Unit = {
val testObj = new DemoPrivate
println(testObj.getSth())
testObj.setSth("public xxx")
println(testObj.getSth())
// .p是class DemoPrivate的私有成员,无法在Object Test里获取
// testObj.p
}
}
(3)构造器
scala 的构造器有主构造器和辅助构造器之分,主构造器跟类的声明结合在一起
- 一个类只能有一个主构造器
- 主构造器的参数直接放置在类名之后
- 主构造器会执行类定义中的所有语句
class ClassName(val p1: String, val p2: int)
对于辅助构造器
- 一个类可以有多个辅助构造器
- 辅助构造器通过 this 关键字实现
- 每个辅助构造器必须以一个对先前已定义的其他辅助构造器或者主构造器的调用开始
/**
* 演示 主构造器 辅助构造器
* - 主构造器,接收2个参数
* @param a
* @param b
*/
class DemoThis(val a : String, val b : Int) {
println(s"主构造器被调用,其中 a=${a}, b=${b}")
/**
* 辅助构造器
* @param c - 辅助构造器的局部变量
*/
def this(c : Int) {
this("a", c)
println(s"辅助构造器被调用,其中 a=${a}, b=${b}")
}
}
object Demo03 {
def main(args: Array[String]): Unit = {
// 对象的成员变量以主构造器的参数名为主
val obj01 = new DemoThis("d", 2)
println(s"主构造器: a = ${obj01.a}, b = ${obj01.b}")
println("#" * 20)
val obj02 = new DemoThis(3)
println(s"辅助构造器: a = ${obj02.a}, b = ${obj02.b} ")
}
}
上面代码用 Java 实现如下:
public class JDemoThis {
String a;
int b;
JDemoThis(String a, int b) {
this.a = a;
this.b = b;
System.out.println("主构造器被调用,其中a=" + a + ", b=" + b);
}
JDemoThis(int b) {
this("a", b);
System.out.println("辅助构造器被调用,其中a=" + a + ", b=" + b);
}
public static void main(String[] args) {
JDemoThis demo = new JDemoThis("d", 2);
System.out.println("主构造器,其中a=" + demo.a + ", b=" + demo.b);
JDemoThis demo2 = new JDemoThis(2);
System.out.println("辅助构造器,其中a=" + demo2.a + ", b=" + demo2.b);
}
}
(4)继承
extends 关键字
scala 继承一个基类跟 Java 相似,使用 extends 关键字
重写
重写一个非抽象方法必须使用 override 修饰符
子类在主构造器中定义私有属性时,需要在前面加上 override 关键字,以重写父类中的私有属性
子类和超类
用 super 关键字在子类中调用超类的方法
用 isInstanceOf 方法测试某个对象是否属于给定的类及其子类
// 演示 继承 和 重写
// ###### case1
class Teacher {
// 基类的sayHi方法
def sayHi() = {
"I'm a teacher"
}
}
/**
* ScalaTeacher 继承了基类 Teacher
*/
class ScalaTeacher extends Teacher {
/**
* 子类定义了一个新方法teach
* @return
*/
def teach() = "Scala"
}
// ###### case2
class Human(val id : Int, val name : String) {
def sayHi() = s"Howdy, I'm ${name}, my ID is ${id}."
}
class Student(override val id : Int, override val name: String, val score : Int) extends Human(id, name) {
def myScore() = s"My Score is ${score}"
// 重写父类方法
//override def sayHi(): String = super.sayHi() + myScore()
}
object DemoExtends {
def main(args: Array[String]): Unit = {
// ###### case1
val simon = new Teacher
println(s"Simon says: ${simon.sayHi()}")
println(simon.isInstanceOf[ScalaTeacher]) // 不属于子类
val tony = new ScalaTeacher
println(s"Tony says: ${tony.sayHi()}")
println("Tony teaches: " + tony.teach())
println(tony.isInstanceOf[ScalaTeacher])
// ###### case2
val jerry = new Human(2, "Jerry")
println(jerry.sayHi())
println(jerry.isInstanceOf[Human])
println(jerry.isInstanceOf[Student])
val tom = new Student(1, "Tom", 90)
println(tom.sayHi())
println(tom.isInstanceOf[Human])
println(tom.isInstanceOf[Student])
}
}
(5)匿名子类
匿名子类表示这个类是没有名字的,通常包含带有定义或重写的代码块的方式创建一个匿名子类
/**
* 演示 - 匿名子类
*/
class Person(val name : String) {
def sayHi = s"Hi everyone, I'm ${name}"
}
object DemoPerson {
def main(args: Array[String]): Unit = {
val tom = new Person("Tom")
println(tom.sayHi)
println("#" * 20)
// 定义一个匿名子类
val jerry = new Person("Jerry") {
override def sayHi: String = "Hey there. "
def greetings = s"Greeting everybody, I'm ${name}. "
}
println(jerry.sayHi)
println(jerry.greetings)
println(jerry.isInstanceOf[Person])
println("#" * 20)
// 要传入的参数是一个带 sayHi、greetings 方法并且返回值都是字符串的 Person 匿名子类
def testFunc(p: Person{def sayHi:String; def greetings:String}): Unit = {
println(s"SayHi:${p.sayHi}, greetings: ${p.greetings}")
}
testFunc(jerry)
// testFunc(tom) // 不能执行,type mismatched,因为tom中没有定义greetings方法
}
}
(6)抽象
抽象类
- 和 Java 一样,用 abstract 关键字来标记不能被实例化的类
- 抽象类只能被继承不能被实例化
- 通常这是因为这个抽象类的某个方法没有被完整定义
- 无需对抽象方法使用 abstract 关键字,只是省去其方法体
- 在子类中重写超类的抽象方法时,不需要使用 override 关键字
抽象字段
- 除了抽象方法外,类还可以有抽象字段
- 抽象字段是一个没有初始值的字段
- 和方法一样,在子类中重写超类的抽象字段时,不需要 override 关键字
- 可以随时使用匿名类型来定制抽象字段
// 演示 Abstract - 抽象类 抽象方法 抽象字段
// ###### case1
/**
* 抽象类
* - 抽象类可以被继承,但是不能被实例化
* @param name
*/
abstract class School(val name: String) {
/**
* 抽象方法id:
* - 方法名叫id,没有具体的函数体内容,就是一个抽象方法;
* - 抽象方法不用abstract关键字
* @return
*/
def id() : Int
}
class University(name : String) extends School(name) {
/**
* 在子类重写超类的抽象方法时,可以不用override关键字
* @return Int 型
*/
def id(): Int = name.hashCode.abs
}
// ###### case2
abstract class Car {
val brand : String // 抽象字段
}
class Audi extends Car {
val brand: String = "Audi" // 在子类中重写超类的抽象字段时,可以不用override修饰符
}
abstract class BMW extends Car {
brand: String
}
object DemoAbstract {
def main(args: Array[String]): Unit = {
// #1
// val test = new School("Test") // class 'School' is abstract; cannot be instantiated
val peking = new University("Peking")
println(s"ID: ${peking.id}, name: ${peking.name}")
// #2
val q7 = new Audi
println(q7.brand)
// 定义匿名子类并且用其创建一个对象
val z4 = new BMW {
val brand: String = "BMW"
}
println(z4.brand)
println(z4.isInstanceOf[Car])
println(z4.isInstanceOf[Audi])
println(z4.isInstanceOf[BMW])
}
}
2、高级
(1)trait
- scala 和 Java 都不支持从多个超类继承
- scala 提供了 trait 特质,trait 支持多重继承
- trait 在定义字段或方法时不提供初始值和方法的具体实现,留给子类完成
- trait 支持多重继承,采用如下形式:
extends trait1 with trait2
代码示例:
// 演示 trait 特质
trait furniture {
val name : String = "furniture" // 这里是具体的实现,后续的继承需要override关键字
def checkType() = name
}
trait feature {
val func : String
def showFeature() : String
}
class Chair() extends furniture with feature {
override val name: String = "Chair"
val func: String = "Can be seat" // 同样是继承的成员,这里不需要使用override关键字
def showFeature(): String = {
s"${name} ${func}"
}
}
object DemoTrait {
def main(args: Array[String]): Unit = {
val c = new Chair
println(c.checkType())
println(c.showFeature())
println(s"func = ${c.func}, name = ${c.name}")
}
}
(2)隐式转换
隐式转换函数(implict conversion function)
- 以 implict 关键字声明的带单个参数的函数
- 隐式转换函数不需要被显式地调用,而自动被应用,将值从一种类型转换为另外一种形式
- 命名规则:可以任意命名,但一般建议采用 source2Target 的命名方式
// 演示隐式转换函数
class Car(name : String) {
def getName() = name
}
class UFO(car : Car) {
def fly(): Unit = {
println(s"${car.getName()} if flying ...")
}
}
object DemoImplicitFunc {
def main(args: Array[String]): Unit = {
// 实例化一个Car对象
var car : Car = new Car("car")
// 利用隐式转换函数,将其转换为UFO
// 先将 car 转换为 ufo 对象,再调用 ufo 对象的 fly()
car.fly()
}
implicit def car2Ufo(car : Car): UFO = {
new UFO(car)
}
}
隐式类(implict class)
隐式类必须有一个单入参数的主构造器,不能是顶层类,可将它放在使用类型转换的类中,或者在另一个对象或类中,然后引入
隐式类就是不需要定义一个类型转换的隐式函数了,直接在主构造器中定义一个成员,类型为需要得到增强功能的类型。
// 演示 隐式类
object DemoImplicitClass {
def main(args: Array[String]): Unit = {
// 默认情况下Int是没有 add 方法供我们使用的
// Scala 可以通过隐式类扩展这种功能
println(1.add(2))
println(1.addd(2)) // 温习: 隐式转换函数
}
/**
* 1. 不会有IntAdd的对象被创建
* 2. 隐式类必须有一个单入参数的主构造器
* @param x
*/
implicit class IntAdd(val x : Int) {
def add(y : Int) : Int = x + y;
}
implicit def int2Addd(n: Int) :Addd = {
new Addd(n)
}
}
class Addd(x : Int) {
def addd(y : Int) : String = {
s"[${x + y}]"
}
}
隐式参数
函数或方法可以带上一个标记为 implict 的参数列表。在这种情况下,编译器将会查找默认值,供本次函数调用。编译器将在如下两处查找:
- 在当前作用域所有可以用单个标识符指代的满足类型要求的 val 和 def
- 目标类型的伴生对象
// 演示 - 隐式参数
object DemoImplicitParam {
/**
* 常规的带参数的函数,在调用时必须显式给入参数值
* @param value
*/
def commonFunc(value : String): Unit = {
println(s"The value is ${value}")
}
/**
* 带隐式参数的函数
* @param value
*/
def demoFunc(implicit value : String): Unit = {
println(s"The lession is ${value}")
}
def main(args: Array[String]): Unit = {
// commonFunc() // 常规的带参数的函数,在调用时必须显式给入参数值
//implicit val value : String = "scala learning" // 常量/变量的名称,不必非要和demoFunc需要的隐式参数名称一致,比如用x也可以被找到
implicit val value2 : String = "scala learning2"
// 调用函数未传入参数,则寻找隐式参数作为默认值
demoFunc
// 调用函数传入参数,不使用隐式参数
demoFunc("xx")
}
}
七、数据结构
1、基础
(1)数组 Array
- ① scala 中数组分为定长数组 Array 和变长数组 ArrayBuffer,并且可互相转换;
- ② 因为两个类都在伴生对象中实现了 apply 方法,因此提供初始值时可以省略使用 new 关键字;
- ③ 使用(index) 来访问元素;
- ④ 用 for 循环来遍历元素;用 yield 来将原来的数组变为新数组;
- ⑤ Scala 和 Java 的数组可以互相转换;
import scala.collection.mutable.ArrayBuffer
// 演示 scala 数组 Array
// 定长数组
val nums = new Array[Int](10) // 10个整数的数组,所有元素初始化为0
val strs = new Array[String](10) // 10个字符串的数组,所有元素初始化为null
// apply方法
// 直接给定初始值,不使用new关键字,并且自动推断元素的类型
val s = Array("a", "b", "c", "d")
// 使用arr(index)取值
s(1)
// for循环遍历数组
for (x <- s) {
println(x)
}
// 定长数组是不可变的,如果声明从 val s 改成 var s 则可以
// s += "a"
// 变长数组
val d = ArrayBuffer[Int]()
d += 1
d
d += (2, 3, 4) // 使用 += (xxx, yyy, ...) 的形式追加多个元素
d ++= Array(5, 6, 7) // 使用 ++= 来追加其他的collection
d.trimEnd(3) // trimEnd从尾部原地修建3个元素
d // 1, 2, 3, 4
d.insert(2, 9) // 在位置2(从0开始算)插入9
d // 1, 2, 9, 3, 4
d.remove(1)
d // 1, 9, 3, 4
// Array 和 ArrayBuffer 之间的相互转换
val s1 = s.toBuffer
s1
val d1 = d.toArray
d1
// 使用yied将原来的数组做一定处理后,变为新的数组
s
val b = for ( x <- s ) yield x.toUpperCase()
b.foreach(println) // 高阶函数,接入的是函数名, 实现Array的遍历
// 内置函数处理数组
val a1 = Array(1, 4, 2, 6, 8, 7, 0, 1, 2)
a1.max
a1.min
a1.sum
a1.sortWith(_<_)
// 多维数组
val matrix = Array.ofDim[Int](3, 4)
matrix(2)(3) = 7
matrix(2)(3)
matrix(1)(2)
val m2 = new Array[Array[Int]](10)
// Scala数组 和Java的List的相互转换
import scala.collection.JavaConversions._
val b1 = ArrayBuffer("a", "b", "c")
val pBuilder = new ProcessBuilder(b1)
pBuilder.command() // 返回一个Java List
b1
println("#" * 20)
import scala.collection.JavaConverters._
val b2 = ArrayBuffer("a", "b", "c")
val jList = b2.asJava
jList.asScala
(2)元组 Tuple
数组是有类型限定的,即数据中的元素必须是同个类型;而元组是不同类型的值的聚集。
元组通过将单个的值包含在圆括号中构成,元组的下标是从1开始,不是从0开始。
// 演示 Tuple 元组
// 元组是不同类型的值的聚集
val t1 = (1, "a", 3.14)
val a1 = Array(1, "a", 3.14) // 将元素转化为Any类型
val l1 = List(1, "a", 3.14)
// tuple和array及list的下标取值方式不同,是从1开始取下标
t1._2
t1._3
a1(2)
l1(2)
//t1._0
// 遍历元组
for ( x <- t1.productIterator) {
println(x)
}
t1.productIterator.foreach(println)
(3)列表 List
在 Scala 中列表
- 要么是 Nil 空表
- 要么是一个 head 元素加上一个 tail
- tail 又是另外一个表
在定义变量声明类型时
- 使用中括号[]
- 赋值和取下标使用小括号()
分类:不可变的 List、可变的 LinkedList
使用右操作::进行结合:右侧必须是List,左侧可以是常用的数据类型
// 演示 列表 List
// 不可变列表
val s = List ("a", "b", "c")
val i = List (1,4,2,3,0)
val nullList = List()
val dim = List ( List(1,2,3), List(2,3,4) )
// list 的操作
// 判断list是否为空
nullList.isEmpty
s.isEmpty
s.head // head 输出第一个值
s.tail // head 之外的元素组成的list
// 相当于插入一个元素到head
3 :: i // 右操作, 必须右侧是List,左侧可以是常用的数据类型
6 :: (7 :: ( 8 :: i))
i.sum // 将数组中的所有元素加起来求和
i.drop(4) // 不是原地操作,是有返回值的操作,将原list头部去掉4个元素,返回新的List
// (1,4,2,3,0) 去掉头部前4个元素,剩下(0)
i // 会看到,原始list并未改变
//i(1) = 22 // List为不可变列表,无法进行原地的替换操作
// 可变列表LinkedList
import scala.collection.mutable.LinkedList
val lk = LinkedList(1, 2, 3)
lk(1) = 22
lk
(4)集合 Set
- set 集合是没有重复的对象集合,元素都是唯一的
- set 不保留原始插入的顺序,需要使用 LinkedHashSet
- 默认集合是不可变的,可变集合使用 scala.collection.mutable.Set
// 演示 Set 集合
// 不可变Set
// set 元素的唯一性
val a = Set (1,1,1,1,12,2,2,2,3,0)
for (x <- a ) println(x)
val b = Set(1,2,3,4,5,6)
for (x <- b ) println(x)
// 可变的mutable.set
val mSet = scala.collection.mutable.Set(2,0,1)
mSet += 1
mSet += 3
mSet
// 使用 LinkedHashSet 可以记住插入的顺序
val lSet = scala.collection.mutable.LinkedHashSet(1,2,3,4,5,6)
lSet.foreach(println)
2、高级
(1)Map
映射是一种可迭代的键值对(key/value)结构,Map 中的键都是唯一的,所有的值都可以通过键来获取。
Map 也叫哈希表(Hash Tables),有两种类型:可变与不变。区别在于可变对象可修改它,而不可变对象不可,默认情况下 Scala 使用不可变 Map。可变 Map 使用 scala.collection.mutable.Map。
// 演示 Map 映射
// 不可变的Map
val scores = Map("Tom" -> 90, "Jerry" -> 75, "Mickey" -> 100)
scores("Tom")
val opt = scores.get("Jerry")
scores.getOrElse("Mickey", 60)
scores.getOrElse("foo", 60)
// 可变的Map
import scala.collection.mutable.{Map => mMap}
val ms = mMap("Tom" -> 90, "Jerry" -> 75, "Mickey" -> 100)
ms("Tom") = 60
ms("Tom")
ms
(2)zip 拉链操作
将两个数据集,对应下标位置的元素结合在一起叫做拉链操作。zip 方法可以将2个数据集组成一个个对偶的列表,这个方法之所以叫 “拉链(zip)”,是因为它就像拉链的齿状结构一样,将2个集合结合在一起。
// 演示 Zip 拉链
// 假设2个数据集, 类型为list
// note : 无论谁zip谁,list2最后的8并没有组合起来
// 说明,结果中的对偶数量,和较短的那个数据集合保持一致
val list1 = List(5.3, 3.2, 2.1, 6.0, 7.9)
val list2 = List( 23, 34, 3, 5, 6, 8)
list1.zip(list2)
list2.zip(list1)
// zipAll 可以让你指定列表的默认值
list1.zipAll(list2, 4.0, 100)
list2.zipAll(list1, 4, 100.0)
// zipWithIndex 返回对偶列表
// - 其中每个对偶中,第二个组成部分是每个元素的下标
// note: 这个方法在spark开发做自定义repartition时可能会用到
"scala".zipWithIndex
val array = "zyxwvu".split("")
array.zipWithIndex
array.zipWithIndex.max // 这里返回的res6类型是元组,所以用元组方式访问
array.zipWithIndex.max._2
(3)迭代器 Iterator
对于完整构造需要很大开销(内存)的集合而言,迭代器很有用。
每次只将一小部分数据加载到内存中读取,避免一次性将所有数据加载消耗大量内存。eg: Source.fromFile 产生了一个迭代器,因为将整个文件都读取到内存可能并不是很高效的做法
// 演示迭代器
// 使用next和hasNext来遍历迭代中的元素
val data = List(3, 5, 24, 4, 73).iterator
while (data.hasNext) {
val res = data.next()
println(res)
}
// 遍历后data变成 empty iterator
data
data.isEmpty
// 使用groupded方法,可以对迭代器进行分组
// 扩展: 比如某个api接口,只允许一次接受post请求的行数为200
val message = List(
"line1",
"line2",
"line3",
"line4",
"line5").toIterator
val gm = message.grouped(2) // 每个分组最多两条数据
while (gm.hasNext) {
val res = gm.next()
println(res)
}
message
// 使用duplicate可以将迭代器复制成2个一样的迭代器,可以互补影响的分布去遍历
// 扩展: 如果在实现写对象存储的功能时, 比如S3, Oss等,可以用于实现写重试的功能
val lines = List(
"line1",
"line2",
"line3",
"line4",
"line5").toIterator
lines.isEmpty
val dp = lines.duplicate
val its = dp.productIterator
for (it <- dp.productIterator) {
println(it)
}
val itr = dp._1
val itrRetry = dp._2
println("### 1st time ...")
itr.foreach(println)
itr.isEmpty
lines.isEmpty
itrRetry.foreach(println)
itrRetry.isEmpty
lines.isEmpty
(4)并行集合
编写正确的并发程序不容易,但是如今为了更好地利用计算机的多处理器,支持并发通常是必须的。Scala 用于操作大型数据集合的解决方案十分诱人,这些任务通常可以很好地并行操作。
/**
* 演示 - 并行集合
*/
object DemoPar {
def main(args: Array[String]): Unit = {
val data = 1 to 100
println(data.par.sum)
for (i <- data.par) {
println(s"${i}@Thread-${Thread.currentThread().getId%8}")
}
println("### 对比普通的遍历方式,单线程,顺序执行:")
for (i <- data) {
println(s"${i}@Thread-${Thread.currentThread().getId}")
}
}
}
网友评论