Loading

1-Scala快速入门

Scala快速入门

算数运算

// 四则运算
1+(100-20)/4+5*2
// 求模运算
5%2
// 乘方运算
math.pow(2,3)
// 绝对值
math.abs(-1.0)
// 三角函数
math.cos(math.Pi)
// 对数运算
math.log(7)
// 指数函数
math.exp(1)

输入输出

  • 输出
    • println, print, printf
  • 输入
    • scala.io.StdIn
  • 写文件
    • java.io.PrintWriter
  • 读文件
    • scala.io.Source

输出

// println换行,print不换行,printf格式化字符串
println("Hello World")
print("Hello")
print("China \n")
printf("I'm %s, I'm %d, my weight is %.2f", "lotuslaw", 27, 70.0)

输入

// 以指定的UTF-8字符集读取文件,第一个参数可以是字符串或者是java.io.File
import scala.io.Source
val source = Source.fromFile("./HelloWorld.java", "UTF-8")
// 获取文件中的所有行
val lineIterator = source.getLines()
// 迭代打印所有行
lineIterator.foreach(println)
// 将所有行放入数组中
val source = Source.fromFile("./HelloWorld.java", "UTF-8")
val lines = source.getLines().toArray
for (item <- lines){
    println(item)
}
//将所有行读成一个字符串
val source = Source.fromFile("./HelloWorld.java", "UTF-8")
val contents = source.mkString
println(contents)
// 在使用完Source对象后,记得调用Close关闭流
source.close()
// 读取字符
val source = Source.fromFile("./HelloWorld.java", "UTF-8")
for (c <- source){
    println(c)
}
source.close()
// 如果你想查看某个字符但又不处理它的话,可以调用source对象的buffered方法。这样你就可以用head方法查看下一个字符,但同时又不把它当做已处理的字符。
import scala.io.Source
import scala.util.control.Breaks._
val source = Source.fromFile("./HelloWorld.java", "UTF-8")
val iter = source.buffered
while(iter.hasNext){
    println(iter.head)
    println(iter.getClass)
    println("p".getClass)
    if(String.valueOf(iter.head)=="p"){
        print("yes")
        break
    }else{
        println("no")
        break
    }
}
source.close()
// 读取词法单元
// 一种快而脏的方式来读取源文件中所有以空格隔开的词法单元
import scala.io.Source
val source = Source.fromFile("./HelloWorld.java", "UTF-8")
val tokens = source.mkString.split("\\s+")
for (str <- tokens){
    println(str)
}
source.close()
// 读取数字
import scala.io.Source
val source = Source.fromFile("./numbers.txt", "UTF-8")
val tokens = source.mkString.split("\\s+")
val numbers = tokens.map(_.toDouble)
println(numbers)
for (num <- numbers){
    println(num)
}
source.close()
// 从URL或其他源读取
mport scala.io.Source
val source1 = Source.fromURL("http://www.baidu.com", "UTF-8")
println(source1.mkString)
source1.close()
val source2 = Source.fromString("Hello world!")
println(source2)
println(source2.mkString)
//从标准输入读取???
/*
val source3 = Source.stdin
val line = Console.readLine()
*/
// 读取二进制文件
/*
new String(bytes, offset, length)
bytes为要解译的字符串;
offset为要解译的第一个索引,比如从0开始就是从字符串bytes的第一个字符开始;
length为要解译的字符串bytes的长度
*/
import java.io.{File, FileInputStream}
// 创建文件对象
val file = new File("./numbers.txt")
// 读取文件为字节流
val in = new FileInputStream(file)
// 创建字节数组
val bytes = new Array[Byte](file.length().toInt)
// 将字节流读入字节数组
val len = in.read(bytes)
// 把字节数组转成字符串输出
println(new String(bytes, 0, len))
// 关闭流
in.close()
// 写入文本文件
// Scala没有内建的对写入文件的支持。要写入文本文件,可使用java.io.PrintWriter
import java.io.PrintWriter
// 创建打印流对象
val out = new PrintWriter("./numbers.txt")
for (i <- 1 to 100){
    out.println(i)
}
// 关闭打印流
out.close()
// 访问目录
// 目前Scala并没有“正式的”用来访问某个目录中的所有文件,或者递归地遍历所有目录的类。不过可以使用一些替代方案。???
// 遍历某目录下所有子目录的函数:
import java.io.File
def subdirs(dir: File): Iterator[File] = {
    val children = dir.listFiles.filter(_.isDirectory)
    children.toIterator ++ children.toIterator.flatMap(subdirs _)
}
for (d <- subdirs(new File("/Users/lotuslaw/Desktop/JAVA"))){
    println(d.getAbsolutePath)
}
// 打印所有子目录的函数
import java.nio.file._
import java.nio.file.attribute._
var dir = new File("/Users/lotuslaw/Desktop/JAVA")
implicit def makeFileVisitor(f: (Path) => Unit) = new SimpleFileVisitor[Path]{
    override def visitFile(p: Path, attrs: BasicFileAttributes): FileVisitResult = {
        f(p)
        FileVisitResult.CONTINUE
    }
}
Files.walkFileTree((dir.toPath), (f: Path) => println(f))
// 序列化????
/*
// 在Java中,我们用序列化来将对象传输到其他虚拟机中,或临时存储。
// Scala中声明一个可被序列化的类。
// @SerialVersionUID(42L)  class Person extends Serializable
// Serializable特质定义在scala包,因此不需要显示引入。可以按照常规的方式对对象进行序列化和反序列化:
class Person extends Serializable
// 创建Person对象lotuslaw
val lotuslaw = new Person("lotuslaw")
import java.io._
// 指定写入的文件的路径文件名,创建对象写入流
val out = new ObjectOutputStream(new FileOutputStream("./lotuslaw.obj"))
// 写入序列化对象
out.writeObject(lotuslaw)
// 关闭写入流
out.close()
// 创建对象读出流
val in = new ObjectInputStream(new FileInputStream("./lotuslaw.obj"))
// 读出序列化对象
val saveLotuslaw = in.readObject().asInstanceOf[Person]
// 打印读出的Person信息
println(saveLotuslaw)
*/

写文件

import java.io._
val out = new PrintWriter("./data.txt")
for (i <- 1 to 5) {
    out.println(i)
}
out.close
import java.io._
val writer = new PrintWriter(new File("./data/data2.txt"))
writer.write("Hello World\n")
writer.write("Hello lotuslaw")
writer.close()

读文件

import scala.io.Source
val f = Source.fromFile("./data/data.txt")
f.foreach(print)
f.close()
import scala.io.Source
val source = Source.fromFile("./data/data2.txt")
for (line <- source.getLines){
    println(line)
}
import scala.io.Source
val f = Source.fromFile("./data/data.txt")
println(f.mkString)
f.close()
import scala.io.Source
val f = Source.fromFile("./data/data2.txt")
val s = f.getLines().toList
println(s)

导入package

  • Scala有以下一些常见的导入package的方式
    • 引入全部对象(import scala.io._)
    • 引入某个对象(import scala.io.Source)
    • 引入某些对象(import scala.io.{Source, BufferedSource})
    • 引入后重命名(import scala.io.{Source => Src})
    • 隐藏某些对象(imprort scala.io.Source => _)
    • 隐式引入(java.lang_, scala._, Predef._默认被引入)
// 不引入,直接使用全名
val f = scala.io.Source.fromFile("./data/data.txt")
f.getLines.foreach(println)
// 引入全部
import scala.io._
println(Source.fromFile("./data/data2.txt").mkString)
println(Source.fromString("Hello world").mkString)
// 引入某些类
import scala.io.{Source, BufferedSource}
println(Source.fromString("Hello World").mkString)
// 引入后重命名
import scala.io.{Source => Src}
println(Src.fromString("Hello lotuslaw").mkString)
// 隐式引入
// java.lang包,scala包以及Predef对象是所有scala程序默认导入的
// 其中Predef对象println,断言assert以及一些隐式类型转换方法 
Predef.println("Hello lotuslaw")
12 + "lll"

语法规则

标识符

  • 标识符由字母和数字组成,遵循驼峰命名规则。
  • 类和对象的名称以大写字母开头
  • 方法的名称以小写字母开头
  • 可以被用作字母,但要慎重使用,因为有些特殊符号如+的内部表示为可以被用作字母,但要慎重使用,因为有些特殊符号如+的内部表示为plus
  • 当存在标识符和关键字冲突时,可以加上``来避免冲突
1+2
1.$plus(2) 
// yield为生成器关键字,加上``避免冲突
val vec = for (i <- 1 to 3)yield i*i
val `yield` = 100

注释

  • 多行注释用/开头,以/结尾。
  • 单行注释用//开头。
/*
multiline comment
some description
more description
*/
def hello() {
    // online comment
    println("HELLO LOTUSLAW")
}
hello()

数据类型

  • Scala中的数据类型有:Byte,Short,Int,Long,Float,Double,Char,String,Boolean.
  • Unit(表示无值与C语言void等同,用作无返回值的方法的返回类型),
  • Null(AnyRef的子类,null是它的唯一对象),
  • Nothing(所有类型的子类,它没有对象),
  • Any(所有其他类的超类),
  • AnyRef(所有引用类reference class的超类)……
// 数值类型
123
12L
1.1F
1.2D
// 字符和字符串
val char = "a"
val str = "HELLO"
val longStr = """
Hello World
Hello Lotuslaw
"""
println(longStr)
// 判断数据类型
str.getClass == classOf[String]
str.getClass.getSimpleName
str.isInstanceOf[Any]
// 强制类型转换
// 可以用句点符号或者空格调用方法
1.toString
"123".toInt
1 toString
// 自动类型转换
2.3 + 5
1 + "HELLO WORLD"

变量

  • Scala支持两种类型的变量,即常量val和变量var。
  • 常量在程序中不可以被重新指向,变量可以被重新指向新的对象。
  • 声明变量时可以指定类型,也可以由解释器根据初始值自动推断。
val a = 5
val b:Int = 10
// a = 17 报错
var x = "aba"
x += "ccc"
// 同时对多个变量进行声明
val m,n = 1

标点括号

  • 小括号()用来表示优先级,传入函数参数序列,以及传入容器的下标或key。
  • 中括号[]用来表示容器的元素的数值类型。
  • 大括号{}用来划分作用域,{}的返回值为最后一个语句的值。
  • 句点符号.表示方法,可以用空格代替。
  • 冒号:用来说明变量的数据类型。
  • =>用来表示匿名函数的映射关系。
  • ->用来指定映射Map中的键值关系。
  • <-用来指定for表达式的迭代器。
  • 下划线_在Scala中被用作占位符表示匿名函数参数或作为引入package的通配符。

编译执行

  • 进入scala解释器交互式执行。
  • 保存成HelloWorld.scala的脚本。然后在cmd中输入 scala HelloWorld.scala 执行。
  • 使用scalac进行编译然后执行。scalac HelloWorld.scala,生成HelloWorld.$class和HelloWorld.class的JVM文件,再用 scala -classpath . HelloWorld 执行。
  • 使用sbt或者maven等项目管理工具将项目及其依赖编译成jar包,再通过java -jar HelloWorld.jar执行。
//对于方式三,脚本中必须要定义object单例对象。并且在object对象中实现main方法作为程序入口。
object HelloWorld {
    def main(args: Array[String]):Unit={
        println("Hello World!")
    }
}

Scala数据结构概述

  • Scala中最常用的数据结构为数组Array以及Collection包中的各种容器类。
  • 按照两个角度进行划分,容器类可以分为可变或者不可变类型,有序或者无序类型。
    • 有序的容器派生类封装在 scala.collection.mutable包中。
    • 无序的容器派生类封装在 scala.collection.immutable包中。
  • 常用的数据结构有以下一些:
    • Array 定长数组: 有序,可变类型,长度不可变。
    • ArrayBuffer 不定长数组:有序,可变类型,长度可以扩展。
    • List 列表:有序,不可变类型。
    • Set 集合:无序,不可变类型。
    • Map 映射:无序,不可变类型。
    • Tuple 元组:有序,不可变类型,可以存放不同数据类型元素。
    • Option 选项:表示有可能包含值的容器,也可能不包含值。
    • Iterator 迭代器:不属于容器,但是提供了遍历容器的方法。
    • 除了Array和ArrayBuffer默认引入的是可变类型外,其它数据结构默认都是不可变的,可以显式地从scala.collection.mutable引入对应可变容器。
// Array可变
val arr = Array(1, 2, 3)
arr(2) = 0
// List不可变
val list = List(1, 2, 3)
list.isInstanceOf[Immutable]
import scala.collection.mutable.ListBuffer
val l = ListBuffer(1, 2, 3)
l.isInstanceOf[Mutable]

字符串String

  • Scala的字符串是一种有序且不可变的基本数据类型,直接使用的Java中定义好的java.lang.String

创建字符串

val s1 = "Hello World"
val s2 = "Hello Lotuslaw"
val s3 = """
Hello World!
Hello Lotuslaw!
"""

常用字符串操作

// +号字符串连接
s1 + "\n" + s2
// concat字符串连接
s1.concat("Hi")
// 字符串长度
s1.length
// 取某个字符
s1(1)
// 字符串比较
s1 > s2
// 字符串替换
s1.replace(" ", "_")
// 字符串分割
s1.split(" ")
// s插值器
val name = "lotuslaw"
val age = 18
val weight = 70
val s_str = s"I'm $name, I'm $age years old, my weight is $weight"
// f插值器
// 格式控制符m.n,m表示输出数据宽度,n表示数据精度
// 小数点前的数字必须大于小数点后的数字。小数点前的数值规定了打印的数字的总宽度。如果忽略了(也就是.2f),总宽度无限制。
val f_str = f"$name%s's weight is $weight%2.2f"
// raw插值器
val raw_str = raw"Hello\s\n"

数组Array

  • 数组Array是一种可变的有序数据结构,但其长度创建后是不可变的,如果要使用长度可变的数组,可以引入ArrayBuffer。

创建数组

// 使用new进行初始化后赋值
val a:Array[Any] = new Array[Any](3)
a(0) = 1
a(1) = "Hello"
a(2) = "LOTUSLAW"
// 隐式调用apply方法进行初始化
val a = Array("Hello", "World", "Hello", "Lotuslaw")
// 使用range方法
// 左闭右开
val a = Array.range(0, 10, 2)
// 使用tabulate方法
val a = Array.tabulate(6)(i => i+5)
// 多维数组
val b = Array.ofDim[String](2, 2)
for (i <- 0 to 1){
    for (j <- 0 to 1){
        b(i)(j) = (i + j).toString
    }
}
// 多维数组
val c = Array(Array(1, 2), Array(3, 4))
// 多维数组
val a = Array.fill(2, 2)(3)
// 多维数组,使用tabulate方法
val a = Array.tabulate(2, 2)(_+_)

数组常用操作

// 修改元素值
val x = Array(1, 2, 3, 5, 7, 9, 3)
x(0) = 10
x.update(1, 10)
// 切片
x.slice(2, 9)
// 最大值、最小值、排序
x.max
x.min
val sortedx = x.sorted
// 转换成ArrayBuffer
import scala.collection.mutable.ArrayBuffer
val x_buffer = Array(1, 2, 3).toBuffer
// 增加元素
x_buffer.append(4)
x_buffer.appendAll(Array(5, 6))
x_buffer
// 删除元素
val x_buffer_drop = x_buffer.dropRight(3)
// 插入元素
x_buffer.insert(1, 0)
println(x_buffer)
// 多维数组遍历
// c的元素arr,arr的元素打印
val c = Array(Array(1, 2), Array(3, 4))
c.foreach(arr => arr.foreach(println))
c.map(arr => arr.map(_+1))

列表List

  • 列表和数组相似,都是有序的结构,但列表中的元素是不可变的。并且列表的存储结构为递推的链表结构,和数组不同。

创建列表

// 指定递推关系创建
val l = List.iterate(5, 6)(x => x+2)
val l = List(0.01, 0.02, 0.03)
// Nil为空列表
val l:List[Double] = 0.02::0.04::0.09::Nil
val l = List.tabulate(2, 2)(_+_)
val l = List.range(0, 10, 2)

列表常用操作

val list = List(1, 2, 3)
list.length
// 在列表前拼接某个元素
4::list
// 列表头
list.head
// 列表其余部分
list.tail
// 列表连接
val list_new = list:::list
// 提取前四个元素和后四个元素
list.take(4)
list.takeRight(4)
// 列表反转
list.reverse
val list2 = list:+3
val list3 = 3+:list
// 去除重复元素
list3.distinct
// 过滤
list3.filter(_>5)
// 测试是否每个元素都满足条件,返回Boolean
list2.forall(_>0)

集合

  • 集合是一种不可变的类型,并且是无顺序的,适合查找某个元素是否在集合中。
var s = Set(1, 2, 3)
//虽然集合不可变,但是s可变,添加元素5得到新的集合并将s指向它
s += 5
s
//删除元素
s - 5
// 包含
s.contains(3)
// 并集
s.union(Set(3, 4, 5))
// 交集
s.intersect(Set(2, 3, 4))
// 交集
s & Set(2, 3, 4)
// 并集
s | Set(2, 3, 4)

映射Map

  • 映射和Python中的字典很像,但是Scala中的Map是一种不可变类型。如果需要使用可变的Map,需要从scala.collection.mutable引入。

创建Map

// 创建空列表,尽管Map不可变,但city是可变的
// 添加元素得到新的Map并指向它
var city:Map[String,String] = Map[String,String]()
city += ("WuHan"->"WH")
city += ("XiaMen"->"XM")
// 隐式使用apply
var city = Map("WuHan" -> "WH", "XiaMen" -> "XM")

Map常用操作

var city = Map("WuHan" -> "WH", "XiaMen" -> "XM")
city("WuHan")
// 查找
val result = if(city.contains("ShangHai")){
    city("WuHan")
}else{
    "NOTFOUND"
}
// 用get查找
var result = city.get("BeiJing").getOrElse("NOTFOUND")
// 遍历
for ((k, v) <- city){
    println(k, v)
}
// 导入可变对象后,可以增加和修改键值对
import scala.collection.mutable.Map

val city = Map("WuHan" -> "WH")
city("WuHan") = "wh"
city("ShangHai") = "SH"
println(city)

元组Tuple

  • 元组也是一种不可变的数据结构,其特点是可以存储类型不同的对象。默认情况下元组的最长长度为22。使用圆括号括号括起来的几个对象就构成了元组。
val t = (123, "hello", 123.2f, true)
val t = new Tuple3(1, 2.22, "s")
val t = (123, "hello", 123.2f, true)
t._1
t._2
t._3

迭代器Iterator

  • 迭代器不是一种容器,但是它提供了一种访问容器的方法。迭代器主要有hasNext和next两个常用方法。

创建Iterator

// 直接创建迭代器
val iter = Iterator("baidu", "ali", "tencent", "westyle")
// 容器转换成迭代器
val set = Set("hello", 123, true, 3.12)
val iter = set.toIterator
// 容器通过分组和滑动窗口得到迭代器
val group = set.grouped(3)
val slide = set.sliding(2)

使用Iterator

val iter = Iterator("baidu", "ali", "tencent", "westyle")
println(iter.next)
println(iter.next)
println(iter.next)
println(iter.next)
iter.hasNext
// for表达式遍历Iterator
val set = Set("hello", 123, true, 3.12)
val iter = set.toIterator
for (x <- iter){
    println(x)
}
// while语句遍历Iterator
val set = Set("hello", 123, true, 3.12)
val iter = set.toIterator
while (iter.hasNext){
    println(iter.next)
}
// 迭代器合并
val iter1 = Iterator(1, 2, 3)
val iter2 = Iterator ("Hello", "Lotuslaw")
val iter = iter1 ++ iter2
for (x <- iter){
    println(x)
}

选项Option

  • Option表示有可能包含值,也可能不包含值的容器。它有两个子类,一个是Some,一个是对象None。它的主要方法是getOrElse 和isEmpty。
val option1:Option[String] = Some("Hello")
val option2:Option[String] = None
option1.getClass
option2.getClass
val d = Map("WuHan" -> "WH")
val opt = d.get("BeiJing")
opt.getOrElse("NOTFOUND")
opt.isEmpty

选择结构

  • Scala的选择结构主要通过if语句以及match语句实现。match 语句相当于多分支结构,可以使用模式匹配。

if语句

// if...else...语句
val a = 2
val b = 3
var m = a
if (a>b){
    m=a
}else{
    m=b
}
// 单行if语句
val a = 2
val b = 3
var m = if (a>b) a else b
// if...else if...else if...else...
val a = 2
val b = 3
val c = 7
var m = 0
if (a>b&&a<c){
    m = a
}else if(a<b&&a<c){
    m = b
}else{
    m=c
}
// 逻辑运算符
true || false
true && false
!true

match语句

// match多分支结构
val a = 1
val x = a match{
    case 1 => "one"
    case 2 => "two"
    case _ => "many"
}
println(x)
// match类型匹配
// 注意将选择器声明为any类型
val elem:Any = "Hello"
val x = elem match{
    case i:Int => i + "is a int value"
    case d:Double => d + "is a double value"
    case "spark" => "spark is found"
    case s:String => s + " is a string value"
}
// match添加guard语句
for (elem <- List(1, 2, 3, 4)){
    elem match{
        case _ if (elem%2==0) => println(elem + " is even")
        case _ => println(elem + " is odd")
    }
}
// match使用case类进行模式匹配
// case类是一种特殊的类,经过了优化用于模式匹配
// 声明case类
case class Person(name: String, age: Int)
val lotuslaw = new Person("lotuslaw", 27)
val bob = new Person("bob", 11)
val xiaopi = new Person("xiaopi", 8)
for (person <- List(lotuslaw, bob, xiaopi)){
    person match {
        case Person("lotuslaw", 27) => println("hi lotuslaw")
        case p:Person if (p.name == "bob") => println(s"Hi ${p.name}!")
        case Person(name, age) => println("age:" + age + "years old, name:" + name + "?")
    }
}

循环结构

  • Scala循环结构主要是 for循环和while循环,此外还可以使用for推导式。

for循环

// 求一个列表中的最大元素
val l = List(1, 2, 3, 8)
var l_max = l(0)
for (i <- l){
    //if (i >= l_max) l_max = i
    l_max = if (i < l_max) l_max else i
}
println(l_max)

while循环

// 1到100求和
var s = 0
var i = 1
while (i <= 100){
    s += i
    i += 1
}
println(s, i)

循环控制

// scala中默认没有breaks和continue关键字
// 可以用return和带if的循环生成器实现对应功能
// 单引号表示:char字符
// 双引号表示:string字符
val s = "Hello World"
//replace continue by if guard
for (i <- s if i!=' '){
    print(i)
}
print("\n")
// replace break by return
def loop():Unit = {
    for (i<-s){
        if (i==' ') return
        print(i)
    }
}
loop()

for表达式的高级用法

  • 在Scala里,一个for表达式可以包含1个或多个「生成器」(Generator)。
  • 其中,每个生成器可以包含0个或多个if「守卫」(Guard)。以及0个或多个「定义」(Definition)。以及一个可选的yield子句。
  • 不带yield子句的for表达式叫做for循环。带有yield子句的for表达式叫做for推导式。
// 双重for循环带一个if守卫
for (i <- 1 to 3; j <- i to 3 if i != j) println(i + j)
// 双重for循环带一个if守卫带一个定义
for (i <- 1 to 3; a =  i + 2; j <- a to 10 if i != j) print(i*j + " ")
// for推导式,1个生成器
val vec = for (i <- Array.range(1, 5)) yield i%3
// for推导式,2个生成器
val boys = Array("xiaopi1", "xiaopi2")
val girls = Array("xiaohua1", "xiaohua2")
val pairs = for (b <- boys; g <- girls; pair = (b, g)) yield pair
// for推导式,可以将生成器放在花括号中
val boys = Array("xiaopi1", "xiaopi2")
val girls = Array("xiaohua1", "xiaohua2")
val pairs = for {
    b <- boys
    g <- girls
    pair = (b, g)
}
yield pair

异常捕获

  • 异常捕获的语句是 try...catch...finally...此外还可以用throw抛出异常。
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException

object Test {
    def main() {
        try {
            val f = new FileReader("./data/t.txt")
        } catch {
            case ex: FileNotFoundException => {
                println("Missing file exception")
            }
            case ex: IOException => {
                println("IO Exception")
            }
        } finally {
            println("Exiting Finally")
        }
    }
}
Test.main()

函数定义

  • Scala中的函数可以通过关键字def定义或者使用匿名函数。此处介绍def定义函数的语法。 Scala def functionName(args list) :[return type] = { function body }
  • 当函数的输出类型可以推断时,可以省去:[return type]=。
// 完整定义的函数
def my_abs(arg: Double): Double = {
    if (arg > 0) arg else -arg
}
my_abs(-2.0)
// 当函数的输出类可以推断时,可以省去”:[return type]=“
def my_abs(arg: Double) = {
    if (arg > 0) arg else -arg
}
my_abs(-2.0)
// 没有返回值的函数可以用Unit作为返回值
def printMe(): Unit = {
    println("hello lotuslaw")
}
printMe()
// 使用默认参数
def addInt(a:Int=5, b:Int=7): Int = {
    var sum:Int = a + b
    return sum
}
addInt()
// 使用可变参数
def printStrings(args: String*) {
    for (str <- args) println(str)
}
printStrings("Hello", "lotuslaw")
// 调用函数时指定参数名
object Test {
    def main(){
        printInt(a=5, b=7)
    }
    def printInt(a:Int, b:Int) = {
        println("Value of a: " + a)
        println("Value of b: " + b)
    }
}
Test.main()
// 递归函数
def factorial(n:BigInt):BigInt = {
    if (n<=1) 1 else n * factorial(n-1) 
}
factorial(20)

匿名函数

  • Scala中的函数是一等公民,可以像变量一样定义和使用。和变量一样,函数具有类型和值。
  • 函数的类型是函数的参数和返回值的类型映射关系,如 Int => Unit , (Array[Int],String) => Int 。
  • 函数的值是函数的参数和返回值的取值映射关系,如 x => x+1 x,y => x+y 。使用这种方式声明的函数叫做匿名函数。
  • 此外,当函数表达式中引用了非参数的变量时,这种函数叫做闭包。闭包的特性是每次调用它时都会将外部的开放的变量封闭成局部值。闭包的返回值受外部变量取值变化的影响。
// 完整定义
// val f:(Int=>Int) = ((x:Int)=>x+1)
val f:Int=>Int = (x:Int)=>(x+1)
f(2)
// 可以推断函数的类型,予以省略
val f = (x:Int) => x+1
f(2)
// 可以省略函数名,匿名函数
((_:Int) + 1)(2)
// 每个参数依次填充占位符位置
((_:Int) + (_:Int))(2, 2)
// 闭包示范
var a = 1
val f = (x:Int) => x+a
println(f(2))
a = 2
println(f(2))

高阶函数

  • 高阶函数即可以传入函数作为其参数的函数。Scala支持非常强大的函数式编程风格。
  • 函数式编程风格的特点不显式使用循环,而是利用高阶函数调用普通函数在数据上进行遍历操作。
  • Scala的Array和容器中的各种数据结构内置有非常丰富的高阶函数。
// map遍历作用,代替for推导式
val arr = Array(1, 2, 3)
arr.map(x=>x*2)
// flatMap遍历作用并压平
val s = List("Hello World", "Hello lotuslaw")
s.flatMap(x=>x.split(" "))
// filter过滤
val s = Set("Hello World", "Hello lotuslaw")
s.filter(x=>x.contains("s"))
// foreach 无返回值,代替for循环
val d = Map("ShenZhen"->"SZ", "ShangHai"->"SH")
d.foreach(println)
d.foreach(t=>println(t._1))
// forall测试是否全部满足条件
val vec = -1 to 5
vec.forall(_>0)
// reduce归约
val arr = Array(1, 2, 3, 4, 5)
arr.reduce(_+_)
arr.reduceLeft(_+_)
// fold和reduce相似,只要传入第一个参数
// fold需要从一个初始值开始,并以该值作为上下文
val arr = Array(1, 2, 3, 4, 5)
arr.fold(10)(_*_)

类的定义

  • Scala中用关键字class定义普通类,用abstract class定义抽象类,用case class定义样例类, 用object定义单例对象,用trait定义特征。
  • 类的定义中可以用private声明为私有属性和私有方法,只允许在类的作用域访问,不允许在类的外部访问。
  • 可以用protected声明为受保护的属性和方法,只允许在类作用域及其子类作用域中访问。
  • 其余属性和方法默认为公有属性和公有方法,可以在类的作用域外访问。
  • 此外还可以在private或protected后面用方括号加上作用域保护,表示方括号中的类和对象不受访问限制。
  • Scala有3中定义类的风格,java风格,简写风格,和case类风格。
  • 简写风格可以在类声明的参数前加上val即表示为类的属性,省去属性的绑定。
  • case类本来设计用来进行模式匹配,自带apply和unapply方法,实例化时可以不用new关键字。除了做了优化用于模式匹配,其它方面和普通类没有什么区别。

java风格

// 一个计数器类范例
class Counter{
    private var value = 0
    def increment(){value += 1}
    def current(){value}
}
val myCounter = new Counter
myCounter.increment()
myCounter.current()
// 一个dog范例类
class Dog(n:String, w:Double, b:String, a:Int){
    var name = n
    var breed = b
    private var weight = w
    private var age = a
    
    def run(){
        println(name + " is running...")
    }
    def bark(){
        println("wangwangwang, wangwangwang...")
    }
    def sleep(){
        println("Znn,Znn,Znn...")
    }
    def eat(food:String){
        printf("%s is eating %s\n", name, food)
    }
    private def think(){
        println("I feel myself a hero and very handsome!")
    }
    def speaking(words:String=""){
        think()
        println(words)
    }
}
val snoopy = new Dog("Snoopy", 12.6, "HuSky", 6)
snoopy.name
snoopy.run()
snoopy.eat("fish")
snoopy.speaking("hahaha")
// 获取对象信息
snoopy.getClass()
snoopy.getClass().getSimpleName()
snoopy.isInstanceOf[Dog]

简写风格

class Cat(val name:String, private val age:Int){
    def call() = {println("miao,miao,miao...")}
}
val Kitty = new Cat("Kitty", 2)
Kitty.name
Kitty.call()

case类风格

// case类风格,最简洁
case class Cat(name:String, weight:Double, private val age:Int){
    def call() = {println("miao,miao,miao...")}
}
val Kitty = new Cat("Kitty", 12.0, 3)
Kitty.call()
Kitty.name
Kitty.weight

getter和setter

  • 私有属性可以通过getter和setter方法比较安全地访问和修改。
// getter方法通过定义无参数的方法实现
// setter方法通过定义带参数的命名中有下划线的方法实现
class Counter{
    private var privatevalue = 0
    def value = {privatevalue}
    def value_(newValue:Int){
        if (newValue>0) privatevalue = newValue
    }
    def increment(){privatevalue += 1}
}
val myCounter = new Counter
myCounter.increment()
myCounter.value
myCounter.value_(-1)
myCounter.value
myCounter.value_(4)
myCounter.value

构造器

  • Scala的类包括一个主构造器和若干个(0个或多个)辅助构造器。
  • 主构造器即定义类时传参并用来初始化对象属性的构造器,它是隐含的。
  • 辅助构造器的名称为this,每个辅助构造器都必须调用一个此前已经定义好的主构造器或辅助构造器。
// 构造器范例
class Counter(){
    private var value = 0
    var name = "None"
    var mode = 1
    def this(name:String){
        this()
        this.name = name
    }
    def this(name:String, mode:Int){
        this(name)
        this.mode = mode
    }
    def increment(){value += 1}
    def current(){value}
}
val myCounter1 = new Counter
println(myCounter1.name)
val myCounter2 = new Counter(name="lotuslaw")
println(myCounter2.name)
val myCounter3 = new Counter(name="lotuslaw", mode=1)
println(myCounter3.name)
println(myCounter3.mode)

单例对象和伴生对象

  • object定义的对象为单例对象,可以直接使用无需实例化。
  • 如果在一个文件有一个object和一个class是同名的,那么这个object称为这个class的伴生对象,这个class称为这个object的伴生类。
  • 伴生对象和伴生类信息可以共享,它们的属性和方法对彼此都是透明的,实际上在编译的时候,会把它们编译成一个Java类,伴生对象定义了这个Java类的静态属性和静态方法。
// 单例对象
object Hello{
    var name = "World"
    def main(){
        println("Hello " + name)
    }
}
Hello.main()
Hello.name = "China"
Hello.main()
// 伴生对象和伴生类
class Person{
    private var Id = Person.newPersonId()
    private var name =""
    def this(name:String){
        this()
        this.name = name
    }
    def info(){
        printf("The Id of %s is %d\n", name, Id)
    }
}

object Person{
    private var lastId:Int = 0
    private def newPersonId():Int = {
        lastId += 1
        lastId
    }
    def main(){
        val person1 = new Person("lotuslaw")
        val person2 = new Person("lotuslaw2")
        person1.info()
        person2.info()
    }
}
Person.main()

继承和特征

  • Scala可以通过extends关键字指定从某个超类(父类)进行继承。
  • 只有子类的主构造器可以调用超类的主构造器。子类可以使用super引用超类的某个属性和方法。
  • 子类如果要重写超类的某个属性和方法,需要使用override关键字。除非超类的该属性和该方法为抽象方法,只有声明没有定义。
  • 如果某个类定义时被abstract声明为抽象类时,它可以被继承但是不能直接被实例化。
  • 和Python语言不同,Scala每个类只能继承一个超类。为了实现多继承的功能,在指定一个超类的同时可以指定若干个trait特征进行继承。
// 抽象类
abstract class Animal(n:String){
    val name:String = n
    val age:Int
    val home = "Earth"
    def eat(food:String)  // abstract method
    def sleep(){
        println("Znn, Znn, Znn...")
    }
}
// 继承自上述抽象类
class Cat(n:String, a:Int) extends Animal(n){
    override val age = a
    override def eat(food:String){
        printf("%s is eating %s...\n", name, food)
    }
    override def sleep(){
        printf("%s is sleeping ...\n", name)
        super.sleep()
    }
}
val kitty = new Cat("Kitty", 10)
println(kitty.name)
println(kitty.home)
kitty.eat("fish")
kitty.sleep()
// 特征
trait Flyable{
    val fly_height:Int
    def fly(){
        println(" is flying....")
    }
}
// 同时继承超类和特征,如果有多个特征,用多个with连接
class Parrot(n:String, a:Int, h:Int) extends Animal(n)
with Flyable{
    override val age = a
    override val fly_height = h
    override def eat(food:String){
        printf("%s is eating %s...\n", name, food)
    }
    override def fly(){
        print(name + " ")
        super.fly()
    }
}

val poly = new Parrot("Poly", 3, 3)
println(poly.name)
poly.sleep()
poly.eat("bug")
poly.fly()
println(poly.fly_height)

apply, unapply和update

  • 当把对一个对象当做函数使用时,会自动调用它的apply方法。
  • 实践中我们一般用apply方法来构造对象,而无需用new声明一个对象,从而相当于一个语法糖。
  • unapply方法是apply方法的逆方法,我们一般用它来从对象中反推得到其构造参数。
  • unapply方法通常在模式匹配中会自动被使用。
  • case类内部实现了apply方法和unapply方法。
  • 当把一个对象当做容器取其某个元素赋值时,会自动调用它的update方法。

内部范例

// new一个对象
val arr = new Array[Int](3)
// 隐式调用apply方法构造一个对象,工厂方法
val arr = Array(1, 2, 3)
// 显示调用apply方法构造一个对象
val arr = Array.apply(1, 2, 3)
// 示范case类的apply方法和unapply方法
case class Person(name:String, age:Int)
// val person = Person.apply("lotuslaw", 27)
val person = Person("lotuslaw", 27)
println(Person.unapply(person))
person match{
    case Person(name, age) => println("name:", name, " age:", age)
}
// 隐式调用update方法
arr(0) = 2
arr
// 显式调用update方法
arr.update(2, 5)
arr

apply使用演示

class Money(val value:Double, val country:String){
    def apply(){
        println(country + " " + value)
    }
}

object Money{
    def apply(value:Double, country:String):Money = {
        new Money(value, country)
    }
}

val money1 = new Money(12.0, "RMB")
println(money1.country)
val money2 = Money(80.0, "USD")
println(money2.value)
money1()
money2()

unapply使用演示

class Money(val value:Double, val country:String){}
object Money{
    def apply(value:Double, country:String):Money = {
        new Money(value, country)
    }
    def unapply(money:Money):Option[(Double, String)] = {
        if (money==null){None}
        else {
            Some(money.value, money.country)
        }
    }
}
// 测试unapply方法在模式匹配时被调用,case类内部已经实现
val money = Money(10.1, "RMB")
money match {
    case Money(num, "RMB") => println("RMB" + num)
    case _ => println("NOT RMB")
}

Scala语言的设计哲学

  • 一切皆对象
    • 从整数,字符串,函数,类到各种数据结构,Scala中一切皆为对象,Any是它们的超类。
  • 一切皆表达式
    • Scala中书写的每条语句都可以看成是一条表达式。表达式的基本格式是 name:type =
    • name是对象标识符,type是它的类型,{}括起来的作用域部分都是它的值。
    • 从变量的定义,函数的定义,判断语句,循环语句到类的定义,都可以看成是这个格式省去某些部分的特例或语法糖等价书写形式。
  • 简洁而富有表现力
    • 同样的功能,Scala的代码量可能不到Java的五分之一。
    • 并且Scala的许多特性设计非常有表现力。
    • 简洁范例:强大的自动类型推断,隐含类型转换,匿名函数,case类,字符串插值器。
    • 表现力范例:集合的&和|运算,函数定义的=>符号,for循环<-的符号,Map的 ->符号,以及生成range的 1 to 100等表达。
  • 函数式编程
    • 函数的特点是操作无副作用,唯一的作用的生成函数值。
    • 把一个函数作用到一些参数上,不会对输入参数造成改变。
    • 为了逼近这个目标,scala设计的默认数据结构绝大部分是不可变的。
    • 并且在一个良好风格的scala程序中,只需要使用val不可变变量而无需使用var可变变量。
    • 显式的for或者while循环是不可取的,让我们用更多的高阶函数吧。
  • 多范式编程
    • 尽管函数式编程是Scala的推荐编程范式,但Scala同时混合了强大的命令式编程的功能。
    • 你可以使用强大的for循环,for推导式,使用可变的变量和数据类型实现命令式编程。
    • 你还可以使用强大的模式匹配,基于模式匹配完成复杂的变换操作,实现模式化编程。
  • 最后,正如同它的名字的蕴意,Scala是一门可以伸缩的语言。通过编写扩展类和对象,或继承各种Trait生成新数据结构,Scala可以很容易地成为某个领域的"专业语言"。新增加的那些特性就好像是Scala语法本身的一部分。
posted @ 2021-05-16 23:51  lotuslaw  阅读(203)  评论(0编辑  收藏  举报