Scala
数据类型
Scala是一门纯粹的面向对象的语言,每个值都是对象。
数据类型
数据类型 | 描述 |
---|---|
Unit | 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null | null , Null 类型只有一个实例值null,只可以赋值给AnyRef类或者其子类 |
Nothing | Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。 当一个函数,我们确定没有正常的返回值, 可以用Nothing来指定返回类型,这样有一个好处,就是我们可以把返回的值(异常)赋给其它的函数或者变量(兼容性) |
类型转换
# 判断obj是否是T类型
obj.isInstanceOf[T]
# 强转类型
obj.asInstantceOf[T]
# 获取类对象
classOf[ClassName] 获取对象的类名
Scala还提供了一个Unit类型,类似Java中的void类型,表示“什么都不是”。
每种基本类型都有一个对应的富包装类。当对一个基本数据类型的对象调用其富包装类提供的方法时,Scala会自动通过隐式转换,将该对象转换为对应的富包装类型,然后再调用相应的方法。例如,执行语句3 max 5时,Scala检测到基本类型Int没有提供max方法,但是Int的富包装类RichInt具有max方法,这时,Scala会自动将3这个对象转换为RichInt类型,然后调用RichInt的max方法,并将5作为参数传给该方法,最后返回的结果是Int型的5。
定义时指定类型
var i:Double = 1
文件IO
// 向文件中写数据
val outputFIle = new PrintWriter("test.txt")
outputFIle.println("Hello world")
outputFIle.println("Spark is good")
outputFIle.close()
// 从文件中读数据
val inputFile = Source.fromFile("test.txt")
for (line <- inputFile.getLines()) {
print(line)
}
inputFile.close()
分支
if (6>0) 1 else -1
相当于if(6 > 0) ? 1 : -1
循环
for(i <- 1 to 3) println(i)
for(i <- Array(3,5,6)) println(i)
// for中加判断
for(i <- 1 to 100 if i % 2 == 0) println(i)
// 多层循环
for(i <- 1 to 5; j <- -1 to 3) println(i * j)
// 带返回值的循环,yield的最后一个表达式的值作为每次循环的返回值
val nums = for(i <- Array(1,2,3,4,5) if i % 2 == 0) yield {println(i);i}
// 对循环的控制
val array = Array(1,3,10,5,4)
breakable {
for(i<- array){
//跳出breakable,终止for循环,相当于Java中的break
if(i>5){
break
}
println(i)
}
}
字符串输出方式
var name: String = "jinlian"
var age: Int = 18
//(1)字符串,通过+号连接
println(name + " " + age)
//(2)printf用法字符串,通过%传值。
printf("name=%s age=%d\n", name, age)
//(3)字符串,通过$引用
println(s"name=$name age=$age")
基础语法
-
break和continue
break:breakable放在循环外
continue:breakable放在循环内// 相当于break breakable { while (n < 10) { println("n=" + n) n += 1 if (n == 5) { break() } } } //相当于continue while (n < 10) { breakable { n += 1 if (n % 2 != 0) { println(n) } else { println("continue") break() } } }
-
if可以有返回值
var res = if(age > 18) { "您以成人" }else{ "小屁孩一个" }
-
循环可以有返回值
val res = for(i <- 1 to 10 if(i % 2 == 0)) yield i println(res) // 返回一个Vector(2, 4, 6, 8, 10)
-
包命名
命名规范
一般是小写字母+小圆点
com.公司名.项目名.业务模块名
例如
com.atguigu.oa.model
com.atguigu.oa.controller
com.sohu.bank.order
数据结构
-
数组
// 定义 val intValueArr = new Array[Int](3) val myStrArr = Array("BigData","Hadoop","Spark") // 访问,通过小括号 println(myStrArr(1)) //输出Hadoop // 二维或三维数组 val myMatrix = Array.ofDim[Int](3,4) val myCube = Array.ofDim[String](3,2,4)
-
元组(当函数需要返回多个值)
// 定义 val tuple = ("BigData",2015,45.0) // 取值,可同时取出三个值 val (t1, t2, t3) = tuple
-
序列(List,Vector,Range, ListBuffer)
// List定义 val nums_1 = List[Double](1,3,4) val nums_2 = 3.0::nums_1 //等于List(3.0, 1.0, 3.0, 4.0) val nums_3 = 1.0::3.0::4.0::Nil //空列表对象Nil nums_1.head //第一个值,1.0 nums_1.tail //除第一个外的值构成的新列表,List(3.0, 4.0) // Vector定义 val vec_1 = Vector(1,2) val vec_2 = 3 +: 4 +: vec_1 //等于Vector(3,4,1,2) val vec_3 = vec_2 :+ 5 //等于Vector(3,4,1,2,5) // ListBuffer定义 val mutableL1 = ListBuffer(10,20,30) mutableL1 += 40 //在列表结尾增加一个元素40 val mutableL2 = mutableL1:+50 //在列表尾部增加一个元素50,并返回这个新列表,原列表保持不变 mutableL1.insert(2, 60,40) //从第2个索引位置开始,插入60和40 mutableL1 -= 40 //在数组中删除值为40的第一个元素 var temp=mutableL1.remove(2)//移除索引为2的元素,并将其返回 // Range定义 val r=new Range(1,5,1) // 1 to 5 和 1 until 5 和 1 to 5 by 2
-
集合(Set)
// Set定义 var mySet = Set("Hadoop","Spark") mySet += "Scala"
-
映射(Map)
// Map定义 val university = Map("XMU" ->"Xiamen University", "THU" ->"Tsinghua University","PKU"->"Peking University") university2("FZU") = "Fuzhou University" university2 += ("TJU"->"Tianjin University") println(university get "XMU") //输出 Some(Xiamen University) println(university get "XMU" get) //输出 Xiamen University println(university get "XMUU") //输出 None println(university get "XMUU" get) //报异常 java.util.NoSuchElementException: None.get println(university("XMU")) //输出 Xiamen University println(university("XMUU")) //报异常 java.util.NoSuchElementException: key not found: XMUU
-
迭代器
// 该操作执行结束后,迭代器会移动到末尾,就不能再使用了,如果继续执行一次println(iter.next),就会报错。 val iter = Iterator("Hadoop","Spark","Scala") while (iter.hasNext) { println(iter.next()) }
-
针对数据结构的操作
遍历//中规中矩 val list = List(1,2,3) val f = (i:Int) => println(i) list.foreach(f) //推荐 list foreach (i => println(i)) list foreach println val university = Map("XMU" -> "Xiamen University", "THU" -> "Tsinghua University", "PKU" -> "Peking University") university foreach { kv => println(kv._1 + ":" + kv._2) } university foreach { x => x match { case (k, v) => println(k + ":" + v) } university foreach { case (k, v) => println(k + ":" + v) } for (i <- list) println(i) // 由于Map的每个元素实质上是一个二元组,因此,可以使用“_1”和“_2”得到它的第一个元素和第二个元素,即键和值。 for (kv <- university) println(kv._1 + ":" + kv._2) for ((k, v) <- university) println(k + ":" + v) //与上一句的效果一样
映射
// 映射操作是针对容器的典型变换操作。映射是指通过对容器中的元素进行某些运算来生成一个新的容器。 // map方法 // map方法将某个函数应用到集合中的每个元素,映射得到一个新的元素, // 因此,map 方法会返回一个与原容器类型大小都相同的新容器,只不过元素的类型可能不同。 val books = List("Hadoop","Hive","HDFS") books.map(s => s.toUpperCase) //将字符串中的每一个字母都变成大写字母, List(HADOOP, HIVE, HDFS) books.map(s => s.length) //将字符串映射为每个元素的长度, List(6, 4, 4) // flatMap方法 // flatMap方法稍有不同,它将某个函数应用到容器中的元素时,对每个元素都会返回一个容器(而不是一个元素), // 然后,flatMap把生成的多个容器“拍扁”成为一个容器并返回。返回的容器与原容器类型相同,但大小可能不同,其中元素的类型也可能不同。 books flatMap (s => s.toList) //List(H, a, d, o, o, p, H, i, v, e, H, D, F, S)
过滤
// filter方法,它接受一个返回布尔值的函数f作为参数,并将f作用到每个元素上,将f返回真值的元素组成一个新容器返回。 val university = Map("XMU" ->"Xiamen University", "THU" ->"Tsinghua University","PKU"->"Peking University","XMUT"->"XiamenUniversity of Technology") val xmus = university filter {kv => kv._2 contains "Xiamen"} // 过滤出值中包含“Xiamen”的元素,contains为String的方法 val l=List(1,2,3,4,5,6) filter {_ % 2 == 0} val t=List("Spark","Hadoop","Hbase") t exists {_ startsWith "H"} //startsWith为String的函数,筛选出以H开头的字符串
规约
// 规约操作是对容器的元素进行两两运算,将其“规约”为一个值。 // 最常见的规约方法是 reduce方法,它接受一个二元函数f作为参数, // 首先将f作用在某两个元素上并返回一个值,然后再将f作用在上一个返回值和容器的下一个元素上, // 再返回一个值,依此类推,最后容器中的所有值会被规约为一个值。 // 有两个与reduce相关的方法,reduceLeft和reduceRight val list =List(1,2,3,4,5) val result = list.reduce(_ + _) //将列表元素累加,使用了占位符语法,result = 15 // reduce方法非常类似的一个方法是fold方法。fold方法是一个双参数列表的函数, // 第一个参数列表接受一个规约的初始值,第二个参数列表接受与reduce中一样的二元函数参数。 // 两个方法唯一的差别是, reduce是从容器的两个元素开始规约,而fold则是从提供的初始值开始规约。 val list =List(1,2,3,4,5) list.fold(10)(_*_) //结果为1200 (list fold 10)(_*_) //中缀调用发,结果也为1200
拆分
// 拆分操作是把一个容器里的元素按一定的规则分割成多个子容器。常用的拆分方法有partition、groupBy、grouped和sliding。 // partition val xs = List(1,2,3,4,5) val part = xs.partition(_ < 3) //返回 part: (List[Int], List[Int]) = (List(1, 2),List(3, 4, 5)) // groupBy val gby = xs.groupBy(x => x % 3) //按被3整除的余数进行划分 gby(2) //获取键值为2(余数为2)的子容器,List[Int] = List(2, 5) // grouped val ged = xs.grouped(3) //拆分为大小为3个子容器 ged.next //第一个子容器,List(1,2,3) ged.next //第二个子容器,List(4,5) ged.hasNext //返回false // sliding val sl = xs.sliding(3)//滑动拆分为大小为3个子容器 sl.next //第一个子容器,List(1,2,3) sl.next //第二个子容器,List(2,3,4)
对比
- List和Array
- List一旦创建,其值不能被修改;而Array可以。
Scala集合三大类
- 序列(Seq
- 集合(Set
- 映射(Map
Scala对这些集合都提供了不可变集合和可变集合
- 不可变集合:
scala.collection.immutable
- 可变集合:
scala.collection.mutable
不可变集合继承图
可变集合继承图
IndexSeq和LinearSeq的区别
- IndexSeq 可以通过索引来查找和定位,所以速度比较快。比如String就是一个IndexSeq,可通过索引进行定位。
- LinearSeq 是线性的,即有头和尾的概念,一般通过遍历来查找。
面向对象编程
// 类的定义
class Counter {
var value = 0
//方法的最后一个表达式的值就是方法的返回值,且只有一条语句是可以省略大括号
def increment(step:Int):Unit = {
value += step
}
def current():Int = {
value
}
}
// 内部类
class Top(name:String,subname:String){ //顶层类
case class Nested(name:String) //嵌套类
def show{
val c = new Nested(subname)
printf("Top %s includes a Nested %s\n",name,c.name)
}
}
val t = new Top("A","B")
t.show
// 私有化属性
class Counter {
private var privateValue = 0
// 下面两个相当于getter方法和setter方法
def value = privateValue
def value_=(newValue: Int){
if (newValue > 0) privateValue = newValue
}
def increment(step: Int): Unit = {value += step}
def current():Int = {value}}
}
// Scala语法中有如下规范,当编译器看到以value和value_=这种成对形式出现的方法时,它允许用户去掉下划线“_”,而采用类似赋值表达式的形式
myCounter.value_=(3) //为privateValue设置新的值,也可以写成myCounter.value= 3
println(myCounter.value)//访问privateValue的当前值
// 操作类
val myCounter = new Counter
myCounter.value = 5
myCounter.add(3)
println(myCounter.current) //调用无参数方法时,可以省略方法名后的括号
函数式编程
-
普通函数
// 函数的定义与使用 // 类型为 (Int) => Int,两种方式等价,下面一种称为匿名函数 val counter: (Int) => Int = {value => value + 1} val counter = (value:Int) => value + 1 val add = (a:Int, b:Int) => a + b val show = (s:String) => println(s) val javaHome= () => System.getProperty("java.home") // 当函数的每个参数在函数字面量内仅出现一次,可以省略“=>”并用下划线作为参数的占位符来简化函数字面量的表示, // 第一个下划线代表第一个参数,第二个下划线代表第二个参数,依此类推。 val counter = (_:Int) + 1 //等价于val counter = (value:Int) => value + 1 val add = (_:Int) + (_:Int) //等价于val add = (a:Int, b:Int) => a + b
-
高阶函数
// 当一个函数包含其他函数作为其参数或者返回结果为一个函数时,该函数被称为高阶函数 def sum(f: Int => Int, a: Int, b: Int): Int = { if (a > b) 0 else f(a) + sum(f, a + 1, b) } // 1 + 2 + 3 + 4 + 5 sum(x => x, 1, 5) // 1 + 2^2 + 3^2 + 4^2 + 5^2 sum (x => x * x, 1, 5)
-
闭包
// 当函数的执行依赖于声明在函数外部的一个或多个变量时,则称这个函数为闭包。 // 闭包可以捕获闭包之外对自由变量的变化,反过来,闭包对捕获变量做出的改变在闭包之外也可见 var more = 10 val addMore =(x:Int)=> x + more println(addMore(5)) //输出15 more = 20 println(addMore(5)) //输出25 var sum=0 val accumulator = (x:Int) => sum += x //包含外部变量sum的闭包 accumulator(5) println(sum) //输出5
-
偏应用函数
// 这种只保留了函数部分参数的函数表达式,称为偏应用函数。 def sum(a:Int, b:Int, c:Int) = a + b + c val a = sum(1, _:Int, _:Int) //a的类型为 (Int, Int) => Int,等价于 val a = (x:Int, y:Int) => x + y + 1
-
Curry化
// 下面第1条语句中的multiplier有两个参数列表,每个参数列表里面都只包含1个参数, // 因此,这里的multiplier函数也称为Curry化的函数。 // 随后第2条语句采用偏应用函数的形式转化成了只带有一个参数的函数byTwo。 def multiplier(factor:Int)(x:Int) = x * factor val byTwo=multiplier(2)_ println(multiplier(2)(5)) //输出10 println(byTow(5)) //输出10 // 通过Curry化过程,将一个多参数的普通函数转化为Curry化的函数。 def plainMultiplier(x:Int,y:Int) = x * y val curriedMultiplier = (plainMultiplier _).curried plainMultiplier(2,5) curriedMultiplier(2)(5) //两者等价
-
函数和方法的区别
-
函数是完成某一个功能的程序指令。定义在类中的函数称为方法,定义在函数内的函数还是称为函数。
-
函数没有重载和重写的概念,而方法有
object TestFunction { // (2)方法可以进行重载和重写,程序可以执行 def main(): Unit = {} def main(args: Array[String]): Unit = { // (1)Scala语言的语法非常灵活,可以在任何的语法结构中声明任何的语法 import java.util.Date new Date() // (2)函数没有重载和重写的概念,程序报错 def test(): Unit ={ println("无参,无返回值") } test() def test(name:String):Unit={ println() } //(3)scala中函数可以嵌套定义 def test2(): Unit ={ def test3(name:String):Unit={ println("函数可以嵌套定义") } } } }
-
隐式转换
/*
(1)首先会在当前代码作用域下查找隐式实体(隐式方法、隐式类、隐式对象)。(一般是这种情况)
(2)如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生对象以及该类型所在包的包对象。
*/
//(2)如果第一条规则查找隐式实体失败,会继续在隐式参数的类型的作用域里查找。类型的作用域是指与该类型相关联的全部伴生模块,
object TestTransform extends PersonTrait {
def main(args: Array[String]): Unit = {
//(1)首先会在当前代码作用域下查找隐式实体
val teacher = new Teacher()
teacher.eat()
teacher.say()
}
class Teacher {
def eat(): Unit = {
println("eat...")
}
}
}
trait PersonTrait {
}
object PersonTrait {
import com.TestTransform.Teacher
// 隐式类 : 类型1 => 类型2
implicit class Person5(user:Teacher) {
def say(): Unit = {
println("say...")
}
}
}
例子
-
WordCount
import java.io.File import scala.io.Source import collection.mutable.Map object WordCount { def main(args: Array[String]) { val dirfile = new File("C:\\Users\\halfOfGame\\IdeaProjects\\TestScala\\src\\main\\resources\\testfiles.txt") // 调用 File 对象的 listFiles 方法,得到其下所有文件对象构成的数组,files 的类型为Array[java.io.File]; val files = dirfile.listFiles // 建立一个可变的空的映射(Map)对象 results,保存统计结果。映射中的条目都是一个(ky,value)键值对,其中,key是单词,value是单词出现的次数; val results = Map.empty[String, Int] for (file <- files) { val data = Source.fromFile(file) // getLines方法返回文件各行构成的迭代器对象,类型为Iterator[String],flatMap进一步将每一行字符串拆分成单词,再返回所有这些单词构成的新字符串迭代器; val strs = data.getLines.flatMap {s => s.split(" ") } // 对上述的字符串迭代器进行遍历,在匿名函数中,对于当前遍历到的某个单词,如果这个单词以前已经统计过, // 就把映射results中以该单词为key的映射条目的value增加1。如果以前没有被统计过,则为这个单词新创建一个映射条目, // 只需要直接对相应的 key 进行赋值,就实现了添加新的映射条目; strs foreach { word => if (results.contains(word)) results(word) += 1 else results(word) = 1 } } // 对Map对象results进行遍历,输出统计结果。 results foreach { case (k, v) => println(s"$k:$v") } } }