Scala REPL:Read(读)、Evaluate(执行)、Print(打印)、Loop(循环)
第2章 处理数据:字面量、值、变量和类型
Scala编译器将从赋值判断这个值的类型,这个过程称为类型推导
命名:Scala中的名字可以使用字母、数字和一些特殊的操作符字符
Scala不准许从高等级类型转换成低等级类型,但支持低等级类型转换成高等级,可以选择toType手动转换。
要对正则表达式完成更高级的操作,需要调用它的r操作符将字符串转换为正则表达式类型,返回Regex实例。
val input ="Enjoying this apple 3.141596 times today"
val pattern = """.* apple ([\d.]+) times.*""".r
val pattern(result)=input
// 模式 (输出的结果) = 输入
val res=result.toDouble
1 val定义常量
2 var定义变量
3 lazy val定义懒变量,对应的变量第一次使用时才会计算对应的变量值作用域保护:private[x] 或 protected[x]
如果参数声明不带val或var,相当于private[this]
Scala类型层次体系:
- Any、AnyVal、AnyRef类型是scala类层次体系的根
- AnyVal是值类型,表示数据的核心值,在堆中分配内存,或者可以作为JVM基本类型在栈中分配内存
- AnyRef是所有其他类型的根,只能作为对象在堆中分配内存
- Nothing是所有类型的子类型,它的存在提供一个兼容的返回类型。
- NULL是所有AnyRef类型的子类型,为关键字null提供一个类型。
- Char是一个文本单位,使用时用单引号(区分String)
- Unit类型相当于void,通常用来定义函数/表达式
元组是一个包含两个或多个值的有序容器,所有这些值可以有不同的类型,元组的作用只是作为多个值的容器。
val info = (5,"zhang",true)//创建1
val red = "red"->"like"//创建2
val name = info._2
val reversed =red._2->red._1
第3章 表达式和条件式
"hello"
val amount = {val x=5*20;x+10}
{val a = 1;{val b = a*2; {val c = b+4; c}}}//返回值是c
val result = if(false)"what does this return?"
result:Any=()
结果值没有指定,编译器只能推测确定最合适的类型,可能返回一个String或Unit,所以选择了根类Any
(String和Unit的共同根类)
在Scala中不需要三元式,因为if else可以紧凑写在一行上
val max = x > y match {
case true => x
case false => y
}
match 匹配表达式可以存在其他操作
val status = 500
val message = status match {
case 200 => "ok"
case 400 => { println("ERROR-1")
"error"}
case 500 => { println("ERROR-2")
"error"}
}
val kind = day match {
case "MON"|"TUE"|"WED"|"THU"|"FRI" => "weekday"
case "SAT"|"SUN" =>"weekend"
}
def matchTest(x: Int): String = x match {
case 1 => "one"
case 2 => "two"
case _ => "many"
}
case pattern if <Boolean expression> => <one or more expression>
case s if s!=null => println("hello world")
(4) 指定模式变量:对传入的值的类型进行判断
import scala.util.matching.Regex
object Test {
def main(args: Array[String]) {
val pattern = new Regex("abl[ae]\\d+")
val str = "ablaw is able1 and cool"
println((pattern findAllIn str).mkString(","))
}
}
for循环
for(x <- 1 to 7){println(s"Day $x:")}
yield返回一个表达式,表达式中的集合可以用其他for循环中作为迭代器:
for(x<- 1 to 7) yield {s"Day $x:"}
res
for(day <- res) print(day+",")
迭代器哨兵
for{
t<-quate.split(",")
if t!=null
if t.size>0
}{println(t)}
for(x<-1 to 2 y<-1 to 3){println(s"($x,$y)")}
val power = for(i<- 0 to 8; pow = 1<<i) yield pow
var name:String = _ //会生成getter和setter方法
val age = 10 //只会生成getter方法
方法属性重写:
override val school = "......"
在Scala中函数是可重用的命名表达方式,函数可以参数化,可以返回一个值。
def multiplier(x:Int,y:Int):Int = { x*y }
过程是没有返回值的函数,Scala翻译器就会导出这个函数的返回值类型为Unit
def hi():String ="hi"
def formatEuro(amt:Double) = f"$amt%.2f"//调用函数
formatEuro(3.4645)
为了避免递归过程中不适用额外的栈空间,Scala编译器可以使用尾递归优化一些递归函数。利用尾递归优化函数,递归调用不会创建新的栈空间,而是使用当前函数的栈空间。
需要在函数定义前或前一行增加文本@annotation.tailrec标志尾递归优化。
@annotation.tailrec
def power(x:Int,n:Int,t:Int =1):Int = {
if(n<1) t
else power(x,n-1,x*t)
}
power(2,8)
Int = 256
def max(a:Int, b:Int, c:Int) = {
def max(x:Int,y:Int) = if(x>y) x else y //嵌套函数
max(a,max(b,c)) //max(42,181,19) --> 181
}
调用参数:greet(name="Brown",prefix="Mr")
函数参数默认值:
def greet(name:String, prefix:String = "")=s"$prefix$name"
Scala支持vararg参数,可以定义输入参数个数可变的函数
要标志一个参数匹配一个或多个输入实参,在函数定义中需要该参数类型后面增加一个星号
def sum(items:Int*):Int = {
var total = 0
for(i<-items) total+=i
total
}
sum(10,20,30)
def max(x:Int)(y:Int) = if (x>y) x else y
val larger=max(20)(39)
def identity[A](a:A):A = a
val s:String = identity[String]("Hello")
val d:Double = identity[Double](2.717)
第5章 首类函数
高阶函数:map() 、 reduce()、filter()
高阶函数优点:具体处理细节,留给高阶函数框架万恒,调用者可以指定做什么,让高阶函数处理具体的逻辑流。
def double(x:Int):Int = x*2
val mDouble = double_ //相当于函数指针
val amount=mDouble(20)
def max(a:Int,b:Int) = if (a>b) a else b
val maximize:(Int,Int) => Int = max
// 输入 => 返回 = 调用函数
maximize(50,30)
// 参数为函数
def safeStringOp(s:String, f:String => String) ={
if(s!=null) f(s) else s
}
// 传入函数
safeStringOp(null,(s:String) => s.reverse)
如果保留一些参数,可以部分应用这个函数,使用通配符替代其中的一个函
def factorOf(x:Int)(y:Int) = y%x ==0
val isEven = factorOf(2)_
val z = isEven(32)
Scala的解释器在解析函数参数(function arguments)时有两种方式:
- 传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部;
- 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部
传名函数 :
object Test {
def main(args: Array[String]) {
delayed(time());
}
def time() = {
println("获取时间,单位为纳秒")
System.nanoTime
}
def delayed( t: => Long ) = {
println("在 delayed 方法内")
println("参数: " + t)
t
}
}
偏函数:调用一个偏函数时,如果所使用的数据不能满足其中至少一个case模式,就导致Scala错误
def safeStringOp(s:String,f:String => String) = {
if(s!=null) f(s) else s
}
val timedUUID = safeStringOp(uuid, s => {
val timed = s.take(24)
timed.toUpperCase
})
scala中flatmap和map的区别
https://blog.csdn.net/qq_40625030/article/details/80648120
map的形式:
List(List(朋友1, 朋友2, 朋友3), List(朋友3, 朋友4, 朋友5))
flatmap函数有一般的功能和map函数函数一样,就是经过map函数之后多了一个扁平化的过程
flatmap的形式 :
List(朋友1, 朋友2, 朋友3, 朋友3, 朋友4, 朋友5)
第6章 常用集合
val colors = List("red","green","blue")
colors.head
colors.tail
colors(1)
colors(2)
colors.foreach((c:String) => println(c))
val sizes = colors.map((c:String) => c.size)
val total = numbers.reduce((a:Int,b:Int) => a+b )
List[List[Int]] = List(List(1,3,5),List(2,4,6))
Nil:所有列表都有一个Nil实例作为终结点,迭代器可以通过比较当前元素Nil检查是否达到列表末尾
while(i!=Nil){print(i.head+",");i=i.tail}
val l:List[Int] = List()
l==Nil
m.tail == Nil
Cons操作符:Cons操作符::绑定元素,可以构建一个列表
val f = List(23,8,14,21) filter (_ > 18)
val p = List(1,2,3,4,5) partition (_ < 3)
val s = List("apple","to") sortBy (_.size)
List是一个链表,由于在列表末尾增加项会改变这个列表,需要复制整个列表,并返回得到这个列表。
List(0,1,0) collect {case 1 => "ok"}//偏函数
List("milk,tea") flatMap(_.split(','))//使用一个给定的函数转换各个元素
List("milk","tea") map(_.toUpperCase)//转换各个元素
List(41,59,26).max
List(41,59,26).min
List(1,2,3).product
List(1,4,5).sum
val validations = List(true,false,true)
validations forall (_ == true)
======================================
val unique = Set(10,20,30,20,20,10)
val sum = unique.reduce((a:Int,b:Int) => a+b)
val colorMap = Map("red" ->123,"green"->456)
val hashWhite = colorMap.contains("white")
for(pairs <- colorMap){println(pairs)}
======================================
import collection.JavaConverters._
asJava List(12,29).asJava
asScala new Java.util.ArrayList(5).asScala
第7章 更多集合
不可变类型 | 可变类型 |
collection.immutable.List | collection.mutable.Buffer |
collection.immutable.Set | collection.mutable.Set |
collection.immutable.Map | collection.mutable.Map |
val m = Map("APPL"->597,"MSFT"->40)
val n = m - "APPL" + ("GOOC"->521)//删除一个元素,并增加
不可变集合List、Map和Set都可以用toBuffer方法转换为collection.mutable.Buffer类型,最后可以通过toList,和toMap变为不可变函数
newBuilder指定集合元素的类型,调用构建器的result转换为集合
val b = Set.newBuilder[Char]
b+='h'
b++= List('e','l','l','o')
val helloSet = b.result
Array类型实际上只是Java数组类型的一个包装器,另外提供了一个高级特性:隐含类
val colors = Array("red","green","blue")
colors(0)
colors
val files= new java.io.File(".").listFiles
val scala = files map (_.getName) filter(_ endsWith "scala")
多维数组:
var myMatrix = ofDim[Int](3,3)
for (i <- 0 to 2) {
for ( j <- 0 to 2) {
print(" " + myMatrix(i)(j));
}
println();
}
scala> Array.tabulate(3)(a => a + 5)
res0: Array[Int] = Array(5, 6, 7)
Seq和序列
val hi = "Hello," ++ "worldly" take 12 replaceAll ("w","W")
Stream类型是一个懒集合,由一个或多个初始元素和一个递归函数生成,第一次访问元素时才会把这个元素增加到集合中
def inc(i:Int):Stream[Int] = Stream.cons(i,inc(i+1))//1.使用cons会生成流
val s = inc(1)
def inc(head:Int):Stream[Int] = head #:: inc(head+1)//2.使用#::也会生成流
def to(head:Char,end:Char):Stream[Char] = (head > end) match {
case true => Stream.empty
case false => head #::to((head+1).toChar,end)
}
val hexChars = to('A','F').take(20).toList
一元集合
Option可以安全替代null值,Option类型本身没有实现,而是依赖两个子类型提供的具体实现:
var x:String ="Indeed"
var a = Option(x)//Some
x=null
var b = Option(x)//None
a.isDefined
b.isEmpty
//检测分母是否为0
def divide(amt:Double,divisor:Double):Option[Double] = {
if(divisor == 0) None
else Option(amt / divisor)//提供一种安全方法检测函数结果
}
util.Try集合将错误处理转变为集合管理,它提供了一种机制来捕获给定函数中的错误
val t = util.Try(...)//使用方法
val input="123"
val result = Util.Try(input.toInt) orElse util.Try(input.trim.toInt)
result foreach {r =>println(s"Parsed '$input' to $r!")}
val x = result match {
case util.Success(x) => Some(x)
case util.Failure(ex) =>{
println(s"Couldn't parse input '$input'")
None
}
}
import concurrent.ExecutionContext.Implicits.global
val f = concurrent.Future {println("hi")}
def nextFtr(i:Int = 0) = Future{
def rand(x:Int) = util.Random.nextInt(x)
Thread.sleep(rand(5000))
if(rand(3) > 0) (i+1) else throw new Exception
}
concurrent.Await.result()
第8章 类
class User
val u = new User
val isAnyRef = u.isInstanceOf[AnyRef]
class User(n:String){
val name:String = n
}
val u = new User("zhangsan")
嵌套类
类定义中嵌套类,嵌套类除了可以访问自己的字段和方法,还可以访问其父类的字段和方法
class Singular[A](element:A) extends Traversable[A]{
def foreach[B](f:A=>B) = f(element)
}
val p = new Singular("Planes")
抽象类生命但不定义字段和方法。如果一个类扩展了抽象类,但这个类没有标志抽象类,就必须提供这些字段和方法的实现。
abstract class Car{
val year:Int
val automatic:Boolean = true
def color:String
}
class RedMini(val year:Int) extends Car{
def color="Red"
}
val m:Car = new RedMini(2005)
apply方法有时是指它作为一个默认方法或一个注入方法,可以直接调用而不需要方法名
class Multiplier(factor:Int){
def apply(input:Int)=input * factor
}
val tripleMe = new Multiplier(3)
val tripled = treipledMe.apply(10)
如果要确保时间或性能敏感操作在类的生命期中只执行一次,懒值是一个很好的方法:
import collection.mutable._
import colection.mutable.{Queue,ArrayBuffer}//批量导入
为了防止发生冲突,可以使用一个导入的别名,在局部命名空间中对某个类型重命名
import collection.mutable.{Map => MuMap}
第9章 对象、Case类和Trait
对象会在首次访问时在当前运行的JVM中自动实例化(在访问之前,不会实例化)
会返回完全由其输入计算得到的结果,而没有任何副作用,而且在引用方面是透明的
伴生对象是与类同名的一个对象,与类在同一个文件中定义。伴生对象和类可以认为是一个单个单元,所以它们可以互相访问私有和保护字段及方法。
:paste
class Multiplier(val x:Int){def product(y:Int) = x*y}
object Multiplier{def apply(x:Int) = new Multiplier(x)}//伴生对象模式,默认构造方法
:paste
object DBConnection {
private val db_url ="jdbc://localhost"
private val db_user="franken"
private val val_db_pass="berry"
def apply()=new DBConnection
}
class DBConnection {
private val props = Map(
"url" -> DBConnection.db_url,
"user" -> DBConnection.db_user,
"pass" -> DBConnection.db_pass
)
println(s"Created new connection for" + props("url"))
}
REPL的粘贴模式还有一个好处:对象和类会同时编译。除了对私有字段的特殊伴生访问
Date.scala
object Date{
def main(args:Array[String]){
println(new java.util.Date)
}
}
scalac Date.scala //编译
scala Date //使用
Case类是不可实例化的类,包含多个自动生成的方法。它包括一个自动生成的伴生对象,这个对象也有其自己的自动生成的方法。Case类对数据传输对象很适用,这些类主要用于存储数据。
不用new就可以直接产生对象(只要在object提供一个apply方法)
扩展第一个trait使用extends,扩展第二个trait使用with
class Page(val s:String) extends SageStringUtils with HtmlUtils{...}
编译器会创建多个trait的副本,形成一个“很高”的单列层次体系
编译到.class二进制文件时,实际上会扩展一个类,这个类又扩展另一个类,后者进一步扩展下一个类
是trait注解,向一个类增加这个trait时,要求这个类必须有一个特定的类型或子类型
class A{def hi="hi"}
trait B{ self:A => override def toString = "B:"+ hi}//增强方法
class C extends A with B
new C()
第10章 高级类型
val t1:(Int,Char) = (1,'a')
val t2:(Int,Char) = Tuple2[Int,Char](1,'a')
val f1:Int => Int = _+2
val f2:Int => Int = new Function1[Int,Int]{def apply(x:Int) = x*2 }
提供一种类型安全的方法:"monkey-pathc", 动态修改现有的代码,为现有类增加新方法和字段
//隐含类
object ImplicitClasses{
implicit class Hello(s:String){ def hello = s"Hello,$s"}//隐含类
def test = {
println("world".hello)
}
}
//隐含值,隐含参数
object ImplicitParams{
def greet(name:String)(implicit greeting:String) = s"$greeting, $name"
implicit val hi="Hello"
def test = {
println(greet("Developers"))
}
}
ImplicitParams.test
为现有的类型创建一个别名,类型别名只能在对象、类或trait中定义
object TypeFun{
//Int 类型别名
type Whole = Int
val x:Whole =5
//Tuple类型别名
type UserInfo = Tuple[Int,String]
val u:UserInfo = new UserInfo(123,"zhangsan")
}
trait Factory[A]{def create:A}
trait UserFactory extends Factory[User]{def create = new User("")}
def check[A<:BaseUser](u:A){ if(u.name.isEmpty) println("Fail")}//不能高于BaseUser
A>:Customer //不能低于
case class Item[+A](a:A){ def get:A = a }
case class Check[-A] { def check(a:A) = { } }
Scala隐式转换:https://liam-blog.ml/2019/09/28/scala-implicit/
Java动态代理,Scala隐式转换
- 隐式转换函数
- 转换期望类型:一旦编译器看到X,但需要Y,就会检查从X到Y的隐式转换函数
- 转换方法的调用者:如果obj.f(),如果obj对象没有f方法,则尝试将obj转换为拥有f方法的类型
object ImpFunction extends App {
class Dog(val name: String) {
def bark(): Unit = println(s"$name say: Wang !")
}
implicit def double2int(d: Double): Int = d.toInt
implicit def string2Dog(s: String): Dog = new Dog(s)
val f: Int = 1.1 //转换为期望类型,1.1通过double2int转成了Int类型
println(f)
"Teddy".bark() // 转换方法的调用者,字符串通过string2Dog转成了Dog, 于是有了bark方法
}
val f: Int = 1.1
因为类型不匹配,这段本来是无法通过编译的,但是编译器发现存在一个Double至Int的隐式转换函数,所以进行了隐式转换。
"Teddy".bark()
String类型本来是没有bark方法的,但是编译器发现了隐式转换string2Dog可以使得String转成一种拥有bark方法的类型,相当于进行了这样的转换:string2Dog("Teddy").bark()
编译器只关心隐式转换函数的输入输出类型,不关心函数名。同一个作用域中不能有输入输出类型相同的隐式转换函数
- 隐式类
Scala 2.10引入了一种叫做隐式类的新特性。隐式类指的是用implicit关键字修饰的类。使用情况与隐式转换函数类似,可以看做将类的构造函数定义为隐式转换函数,返回类型就是这个类。
object ImpClass extends App {
implicit class Dog(val name: String) {
def bark(): Unit = println(s"$name say: Wang !")
}
"Teddy".bark()
}
- 隐式参数 & 隐式值
参数加上implicit就成了隐式参数,需要与隐式值(变量定义加上implicit)搭配使用
implicit关键字会作用于函数列表中的的所有参数,如def test(implicit x:Int, y: Double)
这样定义函数,x和y就都成了隐式函数。但是通常我们只希望部分参数为隐式参数,就好比通常会给部分参数提供默认值而不是全部都指定默认值,于是隐式参数常常与柯里化函数一起使用,这样可以使得只有最后一个参数为隐式参数,例如def test(x: Int)(implicit y: Double)
object ImpParamWithCurry extends App {
def bark(name: String)(implicit word: String): Unit = println(s"$name say: $word !")
implicit val w: String = "Wang"
bark("Hot Dog")
}
- 1)当函数没有柯里化时,implicit关键字会作用于函数列表中的的所有参数。
- 2)隐式参数使用时要么全部不指定,要么全不指定,不能只指定部分。
- 3)同类型的隐式值只能在作用域内出现一次,即不能在同一个作用域中定义多个相同类型的隐式值。
- 4)在指定隐式参数时,implicit 关键字只能出现在参数开头。
- 5)如果想要实现参数的部分隐式参数,只能使用函数的柯里化,
如要实现这种形式的函数,def test(x:Int, implicit y: Double)的形式,必须使用柯里化实现:def test(x: Int)(implicit y: Double).
- 6)柯里化的函数, implicit 关键字只能作用于最后一个参数。否则,不合法。
- 7)implicit 关键字在隐式参数中只能出现一次,柯里化的函数也不例外!
- 隐式对象
隐式对象跟上面的隐式值非常相似,只是类型特殊而已
object ImpObject extends App {
//定义一个`排序器`接口,能够比较两个相同类型的值的大小
trait Ordering[T] {
//如果x<y返回-1,x>y返回1,x==y则返回0.
def compare(x: T, y: T): Int
}
//实现一个Int类型的排序器
implicit object IntOrdering extends Ordering[Int] {
override def compare(x: Int, y: Int): Int = {
if (x < y) -1
else if (x == y) 0
else 1
}
}
//实现一个String类型的排序器
implicit object StringOrdering extends Ordering[String] {
override def compare(x: String, y: String): Int = x.compareTo(y)
}
//一个通用的max函数
def max[T](x: T, y: T)(implicit ord: Ordering[T]): T = {
if (ord.compare(x, y) >= 0) x else y
}
println(max(1, 2))
println(max("a", "b"))
}
转换时机
- 当类型与目标类型不一致时
- 当对象调用类中不存在的方法或成员时
- 缺少隐式参数时