- 变量初始化可以用用
_
作占位符,赋值为默认值,字符串 null,Float、Int、Double 等为 0
var s:String = _
-
lazy
修饰的变量在真正使用时才被赋值
lazy var v2 = "test"
- 3个双引号
"""
包裹的字符串为原始字符串,无需任何转义 - 无符号左移
<<<
,无符号右移>>>
- Scala 都是基于内容的比较,
equals
等同于==
,而eq
则是引用相等的判断1==1.0 // 返回值为 true y.eq(x)
- 通过对元组的内容提取初始化变量
val (first, second, third) = tuple3
-
符号类型是
'xxx
形式的变量,主要起标识作用,常用于模式匹配和判断中val s = ‘symbol1 if(status == s) ... // 作用类似于字符串标识
-
1 to 5
生成 1-5 的 Range,1 until 5
生成 1-4 的Range
for(i <- 1 until(100,3)) // 生成带步长的 Range
- Scala 中无
break, continue
关键字,可引入scala.util.control.Breaks.break
实现 - 带条件判断的 for 循环
for(i <- 1 to 10 if(i %2 == 1); if(i > 3)) { ... }
-
++
可用作数组、列表合并运算 -
List
的::
操作符是从右向左追加元素,Nil
为空List
val nums = 1::2::3::4::Nil // 得到List(1,2,3,4)
- 递归调用的函数必须指定返回值
-
值函数只能通过类型推断来确定返回类型,可以对值函数进行
lazy
修饰val sum = (x: Int, y: Int) => x + y // 不能写成 (x: Int, y: Int): Int => x + y val inc = (_:Int) + 1 val inc:(int)=>Int = 1+ _
-
值函数常作为高阶函数的参数,以下写法等效
arrInt.map((x: Int) => x + 1) arrInt.map((x) => x + 1) // 类型推断,x 为 Int 型 arrInt.map( x => x + 1) // 只有一个参数,括号可以省略 arrInt.map( _ + 1) // 参数值只用一次可以用占位符,同时进行了类型推断
-
闭包可理解为函数和上下文(高阶函数可视为闭包的一种)
val i = 15 val f = (x: Int) => x + i // 执行此处时,i 已经确定,函数编程封闭
-
柯里化函数,必须同时传入所有参数
def curryFun(x: Int)(y: Int) = x * y
- 可以传入部分参数,让柯里化函数成为部分应用函数,对普通函数同样适用
val partialFun1 = curryFun(10) _ // 传入一个参数,得到接受一个参数的部分应用函数 val partialFun2 = curryFun _ // 不传入任何参数,得到接受两个参数的部分应用函数 val f = (x:Int, y: Int, z: Int) val f1 = f(_: Int, 2, 3) //一个参数的部分应用函数 val f2 = f(_: Int, _: Int, 3) //两个参数的部分应用函数 val f3 = f(_: Int, _: Int, _:Int) //三个参数的部分应用函数 val f3 = f _ //三个参数的部分应用函数
-
偏函数只能对部分范围的传入参数进行处理,对不能处理的范围会抛出异常,偏函数和模式匹配完美结合
trait PartialFunction[-A, +B] extends(A => B) // A为入参,B为返回值 val isEven: PartialFunction[Int, String] = { case x if x % 2 == 0 => x + " is even" } isEven(11) //传入奇数会抛出异常
- 类变量未初始化时,需声明为抽象类
-
p.name_=
为成员变量的setter
方法,成员变量加上@BeanProperty
注解后会生成Java风格的getter,setter
- 单例对象混入
App
特质得到应用程序对象,对象内的代码等效于在main
中执行 - 单例对象和其对应的类,称为伴生对象和伴生类,伴生类型的对象和伴生对象内部可以相互访问私有成员,另外还有
apply, unapply
特性方法 - 一个类只能有一个主构造函数,对
val、var
定义的参数会生成相应成员变量和getter、setter
方法也可以提供默认值class Person(var name: String, var age: Int=18) {...} class Person private(var name: String, var age: Int) {...} // 私有主构造函数 class Person { private var name: String = null private var age: Int = 18 def this(name: String, age: Int) { // 辅助构造函数,中必须先调用主构造函数,使用时 new Person("atom") this() this.name = name this.age = age } def this(name="jack", age=10) { // 辅助构造函数和主构造函数调用签名一致优先选择主构造函数 // 主构造函数私有则调用辅助构造函数 this() this.name = name this.age = age } }
- 用
extends
继承时,子类的主构造方法入参不写var、val
则表示继承父类的变量,写了则表示为子类新定义的成员变量,构造方法的调用顺序也是先父类再子类,重写,多态与Java相同
class Student(name: String, age: Int, var studentNo: String) extends Person(name, age) {...}
-
成员变量访问控制
- 默认访问控制:类、子类及外部都可以访问,可生成 Java 或 Scala 风格
getter、setter
- protected:类、子类可以访问,可生成 Java 或 Scala 风格
getter、setter
- private:只能在本类及伴生对象想访问,且不能用
@BeanProperty
注解,仅可生成 Scala 风格getter、setter
- private[this]:只能在本类中访问,伴生对象无法访问
- 默认访问控制:类、子类及外部都可以访问,可生成 Java 或 Scala 风格
- 抽象类中可以有抽象方法,也可以有抽象成员变量
- 伴生类和伴生对象内都可以定义内部类和内部对象,内部类按照
new outerObject.InnerClass()
方式创建内部类对象,内部对象按照outerObject.InnerObject
方式访问 - 通过
extends、with
关键字来混入特质trait;如果需要继承其他类,则只能使用with
来混入特质;特质中可以包括执行语句,抽象方法,具体方法,抽象成员,具体成员,特质中的方法并非必须是抽象的
- 抽象方法的特质,等效于Java 接口中定义的方法
- 抽象成员等效于Java接口中定义的
getter、setter
- 具体成员变量和方法等效于抽象类中的成员变量和方法
特质可以继承
extends
特质和类,混入特质的类构造函数的调用顺序:
超类构造函数=>子类混入特质(从左到右,先父后子)=>子类构造函数-
对于多重继承的情况,采用最右深度优先遍历进行访问,
super
调用时也是如此线性化。在下面的示例中,C
对象调用重载方法时,是调用B2
的方法,super
方法的调用顺序是B2=>B1=>A
trait B1 extends A trait B2 extends A class C extends B1 with B2
-
自身类型要求混入定义了自身类型特质的具体类,必须确保该类的类型符合特质的自身类型,通常用于将大类拆分成若干特质
trait B /* 定义了自身类型B,混入A特质的类对象需同时混入B特质 这样A就能访问B中的内容,A的方法中通过this.xxx可访问B特质的方法或成员 */ trait A { this: B => } class C extends A with B
private[X],protected[X]
将访问控制限定到X
(包,类或者单例对象);private[this]
是私有成员,限定只有该类的对象才能访问,即使是伴生对象也不能访问;protected[this]
在该类及子类对象中可以访问,即使是伴生对象也不能访问-
包对象主要用于对常量和工具函数的封装,直接通过包名引用
package object Math { val PI = 3.1415 } class Computation { def computeArea(r: Double) = Math.PI * r * r }
Scala默认会引入
java.lang._
和scala.Predef._
中的所有类和方法,即隐式引入。import java.util.{ HashMap => JavaHashMap }
实现引入重命名
import java.util.{ HashMap => _, _ }
引入包下除HashMap
外的所有类,实现类隐藏-
Scala的模式匹配中可以使用表达式
case x if (x%2 ==0) => println(s"xxxxx")
;还可以作为函数体,模式匹配结果作为函数的返回值def patternMatching(x: Int) = x match { case 5 => "five" case _ => "not five" } println(patternMatching(5))
-
模式匹配的七大类型
- 常量模式
- 变量模式,
case
后接变量或条件判断表达式(守卫) - 构造函数模式,会按构造函数析取各成员,只析取部分时可以用通配符,原理是
unapply
def patternMatching(x: AnyRef) = x match { case Dog(name, age) => println(s"Dog name=$name and age=$age") case Dog(_, age) => println(s"Dog is no name and age=$age") case _ => }
- 序列模式,用于对序列中的元素内容进行析取,原理是
unapplySeq
def patternMatching(x: AnyRef) = x match { case Array(first, second) => println(s"array first=$first and second=$second") case Array(first, _, three, _*) => println(s"array first=$first and third=$third") case _ => }
- 元组模式,用于对元组中的内容进行析取,模式中不能用
_*
def patternMatching(x: AnyRef) = x match { case (first, second) => println(s"tuple first=$first and second=$second") case Array(first, _, three, _) => println(s"tuple first=$first and third=$third") case _ => }
- 类型模式,用于判断变量的具体类型
def patternMatching(x: AnyRef) = x match { case x: String => println("String") case x: B => println("B") case _ => }
- 变量绑定模式,用于匹配符合析取模式的对象
def patternMatching(x: AnyRef) = x match { case x: d@Dog(_, _) => println("the matching obj is "+ d) // 匹配两个参数的Dog构造函数的Dog类型对象 case _ => } val list = List(List(1,2,3), List(4,5,6,7) def patternMatching(x: AnyRef) = x match { case e1@List(_, e2@List(4, _*)) => println("e1="+e1+"\n, e2="+e2) case _ =>
-
字符串的
.r
方法直接将字符串转换成正则表达式对象,三引号可避免转义,提取模式的分组值,原理同样是unapplySeq
val dateRegx = """(\d\d\d\d)-(\d\d)-(\d\d)""".r val text = "2015-12-31 2016-02-20" for (date <- dateRegx.findAllIn(text)) { date match { case dateRegx(a,b,c) => println(s"a=$a, b=$b, c=$c") case _ => } } dateRegx.findFirstMatchIn(text) match { case Some(dateRegx(year, month,day)) =>println(s"year=$year, month=$month, day=$day") case None => println("nothing") }
for
循环中同样可以使用模式匹配,支持变量模式、常量模式、变量绑定 、类型匹配模式、构造函数模式、序列模式-
封闭类或封闭特质的样例子类进行模式匹配时,必须列出所有样例子类的匹配情况,在编译阶段就可以避免匹配出错;对于没有定义类成员的情况,可使用样例对象。样例对象和样例类的差异如下
- 进行模式匹配时,case class 需要先创建对象,而 case object 可直接使用
- case class 类会生成两个字节码文件,而 case object 会生成一个字节码文件
- case class 生成的伴生对象会自动实现 apply 及 unapply, 而 case object 不会
sealed trait DeployMessage case class RegisterWorker(id: String, name: String) extends DeployMessage case class UnRegisterWorker(id: String, name: String) extends DeployMessage case object RequestState extends DeployMessage def handleMessage(msg: DeployMessage) = msg match { case RegisterWorker(id, host)=>s"reg:$id, $host" case UnRegisterWorker(id, host)=>s"unreg:$id, $host" case RequestState=>"Request State" } println(handleMessage(RegisterWorker("reg","name"))) println(handleMessage(RegisterWorker("unreg","name"))) println(handleMessage(RequestState))
-
隐式转换函数的歧义只与函数签名有关,与函数名无关,用于将一个类型转换成另外一个类型;隐式类通过在类定义的有且仅有一个参数的构造函数之前加上
implicit
实现类型转换,原理与隐式转换函数相同,隐式类藏得更深;隐式对象是在XXXXXXXXXXXXXXXXXX;隐式参数是在当前作用域内查找匹配类型的隐式对象作为函数的参数;隐式值也称隐式变量,是通过implicit
关键字修饰,当函数参数缺失时会寻找作用域内的隐式值作为入参implicit def float2int(x:Float) = x.toInt // 隐式转换函数 implicit class Dog(val name: String) { def bark=println(s"$name is barking") } // 隐式类 // 隐式对象 def multiply[T: Multiplicable](x: T) (implicit ev:Multiplicable[t]) = { ev.multiply(x) } // 隐式参数 implicit val x: Double = 3.14 // 隐式值
-
隐式参数使用常见问题
- 当函数没有柯里化时,
implicit
关键字会作用于函数参数列表中的所有参数,例中,x,y都为 implicit,而且因为类型同为Double,则使用时为同一隐式值/隐式对象
def product(implicit x:Double, y:Double) = x * y
- 隐式参数使用时要么全指定,要么全不指定,不能只指定部分
product(3.0) //出错 product //正确 product(3.0, 2.0) //正确
- 同类型的隐式值在当前作用域内只能出现一次
implicit val d=3.0 implicit val b=4.0 // 出错,已定义 Double 类型隐式值
- 定义参数时,
implicit
关键字只能出现在参数的开头,可通过柯里化达到,而且柯里化的函数implicit
关键字只能用于最后一个参数;implicit
关键字在隐式参数中只能出现一次def product(x:Double, implicit y:Double) = x*y // 出错 def product(x:Double)(implicit y:Double) = x*y // 柯里化 def product(implicit x:Double)(y:Double) = x*y // 出错 def product(implicit x:Double)(implicit y:Double) = x*y //出错
- 匿名函数不能使用隐式参数
val product=(implicit x:Double, y:Double)=> x*y // 出错
- 柯里化函数如果有隐式参数,则不能使用其偏应用函数
def product(x:Double)(y:Double) = x*y val p2 = product _ // 两个参数的偏应用函数 val p1 = product(3.0) _ // 一个参数的偏应用函数 def produce(x:Double)(implicit y:Double) = x*y val p1 = product _ // 出错
- 当函数没有柯里化时,
-
类型是比类更具体,
typeOf
获取类型信息,classOf
获取类信息;存在类型的作用类似于泛型通配符,Scala 中的语法糖为_
,可以传入任何泛型参数的类型def printAll(x: Array[T] forSome { type T }) = { println(x.mkString(",")) } // 原始写法 def printAll(x: Array[_]) = { println(x.mkString(",")) } // 语法糖
-
方法参数的类型以及泛型类型都可以通过类型变量界定来设定类型参数的上下界,
[L <: R]
L 的上界是 R,即 L 必须是 R 的子类;[R >: L]
R 的下界是 L,即 R 必须是 L 的超类;视图界定的范围相对宽泛可以是子类,也可以是经过隐式转换后的类S <% Comparable[S]
,即 S 实现了 Comparable ,或者 S 可以隐式转换为实现了 Comparable 的类型(可以跨越类的层次结构,can be seen as 关系)case class Student[S, T <: AnyVal](var name: S, var hight: T) // 泛型类型 def compare[T <: Comparable[T]](first:T, second:T) // 方法参数类型 case class Student[T,S <% Comparable[S]] (var name:T, var height: S) // 视图界定,S需继承 Comparable 或者能隐式转换为继承了 Comparable 的
-
上下文界定描述 has a 的关系,
T: M
其中 M 是一个泛型,要求在作用域中存在一个 M[T] 类型的隐式转换class Pair[T: Ordering](val first: T, val second: T) { def smaller(implicit ord: Ordering[T]) = { if(ord.compare(first, second) > 0) first else second // 隐式值在内部方法中可直接使用 } } case class Person(val name:String) { println("initing ..." + name) } class PersonOrdering extends Ordering[Person] { ... } // 定义类 implicit val p1 = new PersonOrdering // 定义隐式值 val p = new Pair(Person("123"), Person("456")) println(p.smaller) // 通过上下文界定,调用 ord.compare
-
import Ordered.orderingToOrdered
可以让编译器看到Ordering
的实现类的比较方式是小于号<
时进行隐式转换为Ordered
,并调用<
方法class Pair[T: Ordering](val first: T, val second: T) { import Ordered.orderingToOrdered def smaller = { if(first < second) first else second // 隐式值在内部方法中可直接使用 } }
-
多重界定具有多种形式
-
T:M:K
作用域中必须存在M[T] K[T]
类型的隐式值 -
T<%M<%K
作用域中必须存在 T 到 M、T 到 K 的隐式转换 -
K>:T<:M
意味着 M 是 T 类型的超类,K 也是 T 类型的超类
class A[T] class B[T] implicit val a=new A[String] // 隐式值 implicit val b=new B[String] // 隐式值 def test[T:A:B](x:T)=println(x) test("测试") implicit def t2A[T](x:T)=new A[T] // 隐式转换 implicit def t2B[T](x:T)=new B[T] // 隐式转换 def test2[T<%A[T]<%B[T]](x: T)=println(x) test2("测试2")
-
非变是指,如果 A 是 B 的子类,则
List[A]
不是List[B]
的子类;协变是指,如果 A 是 B 的子类,则List[A]
是List[B]
的子类,其中List[+T]
的泛型方法中参数U
必须为泛型的超类U>:T
;逆变是指,如果 A 是 B 的子类,则List[A]
是List[B]
超类,其中List[-T]
的泛型方法中参数U
必须为泛型的子类U<:T
逆变参考博文单例类型即普通类对象和单例对象的类型,使用
scala.reflect.runtime.universe.typeOf(XXX)
返回未泛型擦除的类型,单例类型在链式调用中比较常用,返回类型为this.type
,返回值为this
,如果未限定返回类型为this.type
则调用父类方法后将返回父类类型对象
object Dog
typeOf[Dog.type] // Dog.type 为 Dog 的单例类型
class Dog
val dog1 = new Dog
typeOf[dog1.type] // dog1.type 为 Dog 类的子类且它有唯一的对象实例 dog1
-
类型投影是作用于内部类,Scala 中不同外部类对象对应的内部类属于不同的类型(即 o1.Inner 与 o2.Inner 不同),通过类型投影,不同外部对象的内部类同为
Outter#Inner
的子类
class Outter {
val x: Int = 0
def test(i: Outter#Inner)=i
class Inner
}
val o1 = new Outter
val 02 = new outter
val inner2 = new o2.Inner // o2.Inner 为 Outer#Inner 子类
val inner1 = new o1.Inner // o1.Inner 为 Outer#Inner 子类
o1.test(inner1)
o2.test(inner2)
-
类型别名使用
type
关键字可以给类取别名type JavaHashMap=java.util.HashMap[String,String]
作用等效于import java.util.{HashMap=>JavaHashMap}
,但是引入重命名不能带泛型参数而类型别名必须指定泛型参数 - 抽象类型是指在父类中类型不明确,而在子类中才能确定的类型,通常父类中定义抽象类型,而子类中需要给抽象类型赋值
abstract class Food
class Rice extends Food { override def toString="rice" }
class Meat extends Food { override def toString="meat" }
class Animal {
type FoodType // 定义抽象类型
def eat(f: FoodType) = f
}
class Human extends Animal {
type FoodType = Food // 确定具体类型
override def eat(f: FoodType) = f
}
class Tiger extends Animal {
type FoodType = Meat // 确定具体类型
override def eat(f: FoodType) = f
}
- 在
class TestB extends TestA with Cloneable
中TestA with Cloneable
为复合类型,type CompoundType = TestA with Cloneable
为符合类型取别名,通过该别名可以定义其他对象
20171121