Scala编程
scala 基础知识
scala是一种多范式编程语言,支持面向对象编程和函数式编程
基本数据类型和变量
所有的类型都是类:scala.Int,只有String类型是java直接引入的:java.lang.string
数据类型 | 取值范围 |
---|---|
Byte | 8位有符号补码整数( − 2 7 → 2 7 − 1 -2^7\to2^7-1 −27→27−1) |
Short | 16位有符号补码整数( − 2 15 → 2 15 − 1 -2^{15}\to2^{15}-1 −215→215−1) |
Int | 32位有符号补码整数( − 2 31 → 2 31 − 1 -2^{31}\to2^{31}-1 −231→231−1) |
Long | 64位有符号补码整数( − 2 63 → 2 63 − 1 -2^{63}\to2^{63}-1 −263→263−1) |
Char | 16位无符号补码整数( 0 → 2 16 − 1 0\to2^{16}-1 0→216−1) |
String | 字符序列 |
Float | 32位IEEE754单精度浮点数 |
Double | 64位IEEE754单精度浮点数 |
Boolean | true 或 false |
字面量
val a = 123//123是整型字面量
运算符:优先级(算术>关系>逻辑>赋值)
操作符类型 | 操作符 |
---|---|
算术运算符 | +、-、*、/、% |
关系运算符 | <、>、==、!=、>=、<= |
逻辑运算符 | &&、|、! |
位运算符 | &、|、^、~ |
赋值运算符 | +=、%= |
//每个操作符都是对象的方法
val a = 5 + 3//实际上调用了5.+(3)
富包装类
每种数据类型都定义了一些基本操作方法(+、-、*、/),并没有定义一些复杂的操作
3 max 5//RichInt整型富包装类,隐式装换当前对象,3从Int转为RichInt
变量
- 不可变(val):声明时必须初始化,不能再赋值
- 可变(var) :声明时需要初始化,可以再赋值
val a:int = 3
类型自动推断机制
val myStr = "hello world!"//String
输入输出
- 从控制台输入输出
import io.StdIn._
//readInt
//readFloat
//readBoolean
......
//启动时默认导入scala.Predef
val i = 345
print("i=");print(i)
println()
printf("i am %d years old and weight %.1f Kg",i,f)//支持C语言风格格式化字符串
//s字符串
val i = 10
val f = 3.5
val s = "hello"
println(s"$s:i=$i,f=$f")//"hello:i=10,f=3.5"
println(f"$s:i=$i,f=$f%.1f")//"hello:i=10,f=3.5"
- 读写文件
import java.io.PrintWriter
import scala.io.Source
val outFile = new PrintWriter("test.txt")
outFile.println("hello world")
outFile.close()
val inputFile = Source.fromFile("output.txt")
val lines = inputFile .getLines
for(line <- lines)println(line)
控制结构
val s = 3
if(s>0){
println("positive num!")
}
else{
println("negative num!")}
//scala中if表达式可以有值
val x = 6
val a = if(x>0) 1 else -1
// for循环
for(i<- 1 to 5 by 2)println(i)
//守卫条件
for(i<- 1 to 5 if(i%2==0))println(i)
//多重生成器
for(i<- 1 to 5;j<- 1 to 3)println(i*j)
//for推导式
val r = for(i<- Array(1,2,3,4,5) if i %2 == 0) yield{println(i);i}
//r=Array(2,4)
//scala中没有break、continue
//Breaks类scala.util.control
import util.control.Breaks._
val arr = Array(1,3,10,5,4)
breakable{
for(i<- arr){
if(i>5)break
println(i)//输出1,3
}
}
for(i<-arr){
breakable{
if(i>5)break
println(i)//输出1,3,5,4
}
}
异常处理
-
受检异常
编译时要考虑的异常 -
不受检异常
运行时抛出的异常
scala中所有异常都视为不受检异常
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException
try{
val f = new FileReader("input.txt")//文件操作
}
catch{
case ex:FileNotFoundException=>//文件不存在时的操作
case ex:IOException=>//发生I/O错误的操作
}
finally{
f.close()
}
容器(collection)
序列、集合、映射
scala.collection封装了可变容器和不可变容器的超类,定义了一些可变容器和不可变容器的一些通用操作,trait特质继承
- Seq :按0,1,2索引,LinearSeq:高效的取头取尾操作;IndexedSeq:高效的随机存取操作
列表:具体的容器类,共享相同类型的不可变序列,继承scala.collection.immutable- Map:按键索引,键是唯一的
- Set:无序 通过哈希方法快速找到某个具体值
scala.collection.mutable
scala.collection.immutable
//构造列表
val otherList = "Apache"::strList
val intList = List(1,2,3)
val intList = 1::2::3::Nil//右结合,从右向左结合
//构造向量
val vec1 = Vector(1,2)
val vec2 = 3+:4+:vec1
val vec3 = vec2:+5//Vector(3,4,1,2,5)
Range:是一种特殊的带索引的不可变数字等差序列
val r = new Range(1,5,1)
//1 to 5
//1.to(5)
//1 until 5 不包含区间终点
//Set
//集合默认为不可变集immutable
var mySet = Set("Hadoop","Spark")
mySet += "Scala"//生成一个新的不可变集
import scala.collection.mutable.Set
val myMutableSet = Set("Database","BigData")
myMutableSet += "Cloud Computing"
//Map
val university = Map("XMU"->"xiamen university","PKU"->"peking university")
println(if(university("XMU")) university("XMU") else 0)
import scala.collection.mutable.Map
val university2 = Map("XMU"->"xiamen university","PKU"->"peking university")
university2 +=("TJU"->"tianjin university")
面向对象编程
类
//定义类
class Counter{
var value = 0
//方法,不使用val或var定义参数
def increment(step:Int):Unit={
value += step
}
//方法无参数时可以省略括号,省略后调用时不可以加括号,方法名后参数体可以用大括号或者圆括号
def current():Int={
value
}//只有一条语句,大括号可以省略掉
}
//实例化
val myCounter = new Counter
myCounter.value = 5
myCounter.increment(3)
println(myCounter.current)
可见性: scala默认所有成员都是公有的
private:本类型可见,嵌套类型可见
protected:本类型可见,其他继承类型可见
建议不直接暴露public 字段,设置get,set方法
方法名称 value_ = ()
class Counter{
private var privateValue = 0
def value = privateValue //get方法
//set方法
def value_ = (newValue:Int){
if(newValue>0) privateValue = newValue
}
def increment(step:Int):Unit={
value += step
}//:Unit=可以省略
def current():Int={
value
}
}
val myCounter = new Counter
myCounter.value_=(3)//为privateValue设置新值myCounter.value=3
println(myCounter.value)//访问privateValue当前值
//中缀表示法
val c = 3.+5//3+5
val c = new Counter
c increment 5//c.increment(5)
构造器
- 主构造器:类名称(参数列表)
参数要给出关键字val或var修饰
放在主构造器参数列表里的参数自动变成它的类内部的成员字段
不加val或var修饰时,就不会生成get,set方法
class Counter(var name:String)
var myCounter = new Counter("Runner")
myCounter.name_ = ("Timer")//调用写方法
myCounter.name = "Timer"//更直观的调用写方法,和上句等效
println(myCounter.name)
- 类参数列表中的具名参数和缺省参数使我们可以按照多种方式构造对象,一个类定义可以包含一个主构造器和若干个辅助构造器
每个辅助构造器要调用主构造器或前面已定义的辅助构造器
this(参数列表)
class Counter{
private var name = ""
private var value = 0
private var step = 1
println("the main constructor")
def this(name:String):Unit={
this()//调用主构造器
this.name = name
printf("the first auxiliary constructor,name:%s\n",name)
}
def this(name:String,step:Int){
this(name)//调用前一个辅助构造器
this.step = step
printf("the second auxiliary constructor,name:%s,step:%d\n",name,step)
}
def increment(step:Int):Unit={value+=step}
def current():Int = {value}
}
//调用
val c = new Counter//the main constructor
val c1 = new Counter("the 1st Counter")//the main constructor,the first auxiliary constructor,name:the 1st Counter
val c2 = new Counter("the 2nd Counter",2)//the main constructor,the first auxiliary constructor,name:the 1st Counter,the second auxiliary constructor,name:the 2nd Counter,step:2
单例对象
单例对象提供与java静态成员同样的功能,不需要实例化就可以使用对象的成员字段
//用object定义
object Person{
private var lastId = 0 //静态变量
//静态方法
def newPersonId()={
lastId += 1
laseId
}
}
printf("the first person id:%d.\n",Person.newPersonId())
//output:the first person id:1
- 伴生对象
定义类和单例对象同名,且在同一代码文件中,两个对象可以互相访问成员变量
class A{}
object A{}
- 孤立对象
同一文件中没有与单例对象同名的类
apply 方法
scala中独有,通过apply方法不断生成对象(工厂对象),实现面向对象编程和函数式编程的统一
apply方法调用约定
用括号传递给类实例或单例对象名一个或多个参数时,scala会在相应的类或对象中查找方法名为apply,且参数列表与传入的参数一致的方法,并用传入的参数来调用该apply方法
工厂对象
一个class一个类
给类定义一个伴生对象
类的构造方法以apply方法的形式写在伴生对象中
伴生对象的apply方法会被自动调用
调用会自动生成类对象
val myStrArr = Array("BigData","Hadoop","Spark")//不需要new一个Array对象,scala自动调用Array这个类的伴生对象的apply方法
//测试apply方法
class TestApplyClass{
def apply(param:String){
println("apply method called:"+param)
}
}
val myObject = new TestApplyClass
myObject("Hello Apply")
//output:apply method called:Hello Apply
//工厂对象实例
class Car(name:String){
def info(){
println("Car name is "+name)
}
}
object Car{
def apply(name:String)=new Car(name)//调用伴生类Car的构造方法,生成Car类实例
}
object MyTestApply{
def main(args:Array[String]){
val myCar = Car("BMW")
myCar.info()//输出Car name is BMW
}
}
//编程方法统一实例
def add=(x:Int,y:Int)=>x+y//add是一个函数
add(4,5)//函数式调用,输出9
add.apply(4,5)//对象.方法调用,输出9
update 方法
update方法遵循相应的调用约定
当对带有括号并包括一到若干参数的对象进行赋值时,编译器将调用对象的update方法,并将括号里的参数和等号右边的值一起作为update方法的输入参数来执行调用
val myStrArr = new Array[String](3)
myStrArr(0) = "BigData"//实际上,调用了伴生类Array中的upate方法,执行myStrArr.update(0,"BigData")
unapply 方法
apply方法的反向操作,方便模式匹配和参数提取
class Car(brand:String,price:Int){
def info(){
println("Car brand is "+brand+" and price is "+price)
}
}
object Car{
def apply(brand:String,price:Int)={
println("Debug:calling apply...")
new Car(brand,price)
}
def unapply(c:Car):Option[(String,Int)]={
println("Debug:calling unapply...")
Some(c.brand,c.price)
}
}
object TestUnapply{
def main(args:Array[String]){
//等号自动调用apply方法,等号左边自动调用unapply方法
var Car(carbrand,carprice)=Car("BMW",500000)
println("brand:"+carbrand+" and price:"+carprice)
}
}
继承
- 抽象类
类中有没有被实现的成员,用关键字abstract修饰 class,抽象类中的抽象方法不需要使用关键字abstract修饰,抽象类中的字段,如果没有给初始值就是抽象字段,但必须给出类型生命
abstract class Car(val name:String){
val carBrand:String//字段没有初始化值,就是一个抽象字段
def info()//抽象方法
def greeting(){
println("Welcome to my car!")
}
}
- 扩展类
重载父类对象中的抽象成员时,关键字override是可选的,如果重载非抽象成员时,override是必须要加的,只能重载val类型字段
abstract class Car{
val carBrand:String//抽象字段
def info()//抽象方法
def greeting(){println("welcome to my car!")}}
class BMWCar extends Car{
override val carBrand = "BMW"
def info(){printf("this is a %s car,it is expensive.\n",carBrand)}
override def greeting(){println("Welcome to my BMW car!")
}
}
class BYDCar extends Car{
override val carBrand = "BYD"
def info(){println("this is a %s car,it is cheap.\n",carBrand)
override def greeting(){println("welcome to my BYD car")}
}
}
object MyCar{
def main(args:Array[String]){
val myCar1 = new BMWCar()
val myCar2 = new BYDCar()
myCar1.greeting()
myCar1.info()
myCar2.greeting()
myCar2.info()
}
}
AnyVal值类型,保存在寄存器中
AnyRef引用类型,对象被实例化后保存在堆中
Nothing 没有实例,只用于异常处理或错误处理
Option类:scala不建议用null,不确定是否有返回值是用Option[Some(有值),None(无值)]
case class Book(val name:String,val price:Double)//两个成员字段,系统自动封装case class 的apply方法
val books = Map("hadoop"->Book("Hadoop",35.5),"spark"->Book("Spark",55.5),"hbase"->Book("Hbase",26.0))//定义一个书名到书对象的映射
books.get("hadoop")//返回Option[Book]=Some(Book(Hadoop,35.5))
books.get("hadoop").get//返回BooK=Book(Hadoop,35.5)
books.get("hive")//返回Option[Books]=None
books.get("hive").get//None对象的get方法会抛出异常java.util.NoSuchElementException:None.get
books.get("hive").getOrElse(Book("Unknown name",0))//返回Book = Book(Unknown name,0.0)
特质
scala特有的概念,类似java中的接口
scala中一个类只可以继承一个超类,但可以混入多个特质,重用特质中的方法、字段
特质中定义抽象字段或方法也不需要加abstract关键字
特质可以继承其他特质[extends with]
trait Flyable{
var maxFlyHeight:Int//抽象字段
def fly()//抽象方法
def breathe(){
println("i can breathe")
}
}
trait Haslegs{
val leg:Int
def move(){printf("i can walk with %d legs.",leg)}}
class Animal(val category:String){
def info(){println("this is a "+category:String)}
}
//单继承
class Bird(flyHeight:Int) extends Flyable{
var maxFlyHeight:Int = flyHeight//重载特质的抽象字段
def fly(){//重载特质的抽象方法
printf("i can fly at the height of %d.",maxFlyHeight)
}
}
val b = new Bird(100)
b.fly()//i can fly at the height of 100
b.breathe()//i can breathe
//特质混入继承
class Bird2(flyHeight:Int) extends Animal("Bird") with Flyable with Haslegs{
var maxFlyHeight:Int = flyHeight
val leg = 2
def fly(){
printf("i can fly at the height of %d",maxFlyHeight)
}
def move(){
printf("i can walk with %d legs",leg)}
}
val b2 = new Bird2(108)
b2.info()//this is a Bird
b2.fly()//i can fly at the height of 108
b2.move()//i can walk with 2 legs
模式匹配
match语句和case类
包
包解决程序当中的命名冲突
包可以嵌套定义
package xmu{
package xiamen{
package abc{
class Test{}
}
}
}
import xmu.xiamen.abc
class test2{
val a = new Test()
}
函数式编程
函数的定义与使用
函数像整型值一样可以在不同方法调用中操作/传递
def counter(value:Int):Int={value += 1}
- 函数类型
函数的输入参数的类型以及返回值的类型一起构成函数类型
(Int)=>Int
- 函数值
(value)=>{value += 1}
val num:Int = 5//定义整型
val counter:Int=>Int = {(value => value += 1)}//定义函数
- 函数字面量
{(value => value += 1)}
- 匿名函数(Lambda表达式)
(参数)=>表达式
(num:Int)=>num*2
val myNumFunc:Int=>Int = (num:Int)=>num*2
println(myNumFunc(3))//输出6
val myNumFunc = (num:Int)=>num*2//scala自动推断函数类型
println(myNumFunc(3))//输出6
- “”占位符
某个参数只在函数字面量里出现一次时,可用“”表示
val counter = (_:Int)+1//有类型时,括号不能省略,等效于“x:Int=>x+1”
val add = (_:Int)+(_:Int)//等效于(a:Int,b:Int)=>a+b
val m1 = List(1,2,3)
val m2 = m1.map(_*2)//map接受一个函数作为参数,相当于m1.map(x=>x*2)
高阶函数
函数的参数仍是函数,该函数就叫高阶函数
连加和,平方和,2的幂次和
//根据传入的不同的函数,实现高阶函数的不同效果
def sum(x=>x,1,5)//1+2+3+4+5
def sum(x=>x,1,5)//1**2+2**2+3**2+4**2+5**2
针对容器的操作
- 遍历操作
val list = List(1,2,3)
val f=(i:Int)=>println(i)
list.foreach(f)
//1
//2
//3
val university = Map("XMU"->"Xiamen University","THU"->"Tsinghua Univrsity","PKU"->"Peking University")
university.foreach{kv=>println(kv._1+kv._2)}
university foreach{x=>x match{case(k,v)=>println(k+":"+v)}}//中缀写法,case后元组结构
university foreach{case(k,v)=>println(k+":"+v)}
- 映射操作
map
val books = List("hadoop","hive","hdfs")
books.map(s=>s.toUpperCase)//List("HADOOP","HIVE","HDFS")
books.map(s=>s.length)
flatmap一对多的映射
val books = List("hadoop","hive","hdfs")
books.flatMap(s=>s.toList)//List(h,a,d,o,o,p,h,i,v,e,h,d,f,s)
//常用于词频统计
- 过滤操作
filter
val university = Map("XMU"->"Xiamen University","THU"->"Tsinghua Univrsity","PKU"->"Peking University")
university.filter(kv=>kv._2 contains "Xiamen")//Map("XMU"->"Xiamen University")
val a = List(1,2,3,4,5,6) filter {_%2 == 0}//List(2,4,6)
- 规约操作
reduce
val list = List(1,2,3,4,5)
list.reduce(_+_)//(a,b)=>a+b;(((1+2)+3)+4)+5
list.reduceLeft(_-_)//-13
list.reduceRight(_-_)//-5;((((5-4)-3)-2)-1)
fold 需提供初始值
val list = List(1,2,3,4,5)
list.fold(0)(_+_)//15
list.foldLeft(10)(_-_)//-5
list.foldRight(10)(_-_)//-7;(1-(2-(3-(4-(5-10)))))
函数式编程实例WordCount
import java.io.File
import scala.io.Source
import collection.mutable.Map
object WordCount{
def main(args:Array[String])}:Unit={
val dirfile = new File("testfiles")
val files = dirfile.listFiles
val results = Map.empty[String,Int]
for(file<-files){
val data = Source.fromFile(file)
val strs = data.getLines.flatMap{s=>s.split(" ")}
strs.foreach{word=>
if(results.contains(word))
results(word)+=1 else results(word)=1
}
}
results.foreach{case(k,v)=>println(s"$k:$v")}
}
}