@spiritnotes
2016-03-22T13:48:18.000000Z
字数 12173
阅读 2305
Scala
Scala中使用方法而不是强制类型转换来做数值类型之间的转换
没有++,--因为Int类是不可变的
a 方法 b 与 a.方法(b) 一样
数学函数位于 scala.math 中,可以像函数一样使用,实际上应该是伴生对象的方法
一般情况下,没有参数并不改变当前对象的方法不带圆括号
条件表达式是有值的,可以分支的类型不一样,其结果类型为分支的公共超类型。
:paste 用以REPL中粘贴多行代码,避免短视
if (x>0) 1 else -1
if (x>0) 1 // if (x>0) 1 else ()
跨行:可以使用括号,未结束的操作符
块语句是指位于 {} 中的语句序列,包含一系列表达式,结果也是表达式,最后一个表达式的值就是语句块的值
赋值本身是没有值的,其值是 Unit 类型的 () 值
输入输出:print println printf, readLine readInt
while / do while 循环:
while (n>0){...}
for 循环
for (i <- 1 to 6) ..
for (i <- 1 to 6; from = 4-i;j <- from until 9
if i!=j ;)
for (...) yield {}
对于递归函数,必须指定返回类型
默认参数,带名参数与Python接近
变长参数: def sum(args:Int*) ...
使用: sum(1 ot 5:_*)
函数体包含在花括号中但没有等号的返回类型是Unit,称为过程,不返回值,调用它只是为了副作用
当val被声明为lazy时,初始化将被推迟,直到首次对它取值
懒值也有开销,访问时都有线程安全方式检查该值是否已经被初始化
val words = //当时初始化
lazy val wrods = //第一次使用时初始化
def words = //每次使用时均求值
异常表达式的类型是Nothing,在if/else中,则返回类型是非Nothing分支的类型
异常捕获使用模式匹配
tray catch finally
若长度固定使用Array,可能有变化使用ArrayBuffer
val nums = new Array[Int](10)
val nums2 = Array(1,2,3);nums(0)=4
import scala.collection.mutable.ArrayBuffer
val b = ArrayBuffer[Int]() / new ArrayBuffer[Int]
b += 1
b += (1,3,4)
b ++= Array(4,5,6)
b.trimEnd(5)
b.insert(2, x)/insert(2, x,y,z)
b.remove(2)/remove(2,3)
b.toArray / a.toBuffer
提供初始值时不要使用new
用()来访问元素
用for (elem <- arr) 来遍历数组
(0 until (a.length,2)).reverse
用 for (elem <- arr if ..) yield ...来将数组转变为新数组
集合类型与原集合相同,Array to Array,Buffer to Buffer
可以使用 .filter().map() 替换
var first = true
val indexes = for (i <- 0 until array.length if first || array(i)>=0) yield {if (array(i)<0) first=false;i}
for (i <- 0 until indexes.length) {a(i)=a(indexs(i))}
a.trimEnd(a.length - indexs.length)
常见函数
sum /max /min /b.sorted(_ < _)(返回排序后的)
scala.util.Sorting.quickSort(a) (不能使用在ArrayBuffer)
a.mkString( str )/ .mkString(a,b,c)
多维数组:数组的数组来实现
val m = Array.ofDimInt
m(3)(4) = 7
val t = new ArrayArray[Int]
for (i <- 0 to 9){t(i)=new ArrayInt}
Scala数组和Java数组可以互操作;用ArrayBuffer,使用scala.collection.JavaConversions中的转换函数
对偶是n=2的元组
创建、查询和遍历映射
val m = Map('a'->3, 'b'->2, 'c'->4) //->构建对偶
val m = Map(('a',3),('b',2), ('c',4))
val m = scala.collection.mutable.Map(...)
val m = new scala.collection.mutable.HashMap[String,Int]
m('a')
if (m.contains('a')) m('a') else 0
m.getOrElse('a', 0)
m.get('a') //返回一个Option对象,值或者None
m += ('c'->4, 'e'->7)
m -= 'a'
val m3 = m1 + ('c'->3...) //新旧结构会共享
for ((k,v) <- m) ....
for (k <- m.keySet) ...
for (v <- m.values) ...
for ((k,v) <- m) yield (v,k)
scala.collection.immutable.SortedMap //树Map
scala.collection.mutable.LinkedHashMap //按插入顺序处理
需要从可变和不可变的映射中作出选择
默认情况下获得哈希映射,可以指明要树形映射
可以很容易在scala与java映射之间切换
import scala.collection.JavaConversions.mapAsScalaMap
元组可以用来聚集值
元组是通过将单个的值包含在圆括号中构成的,可用于返回不止一个值的情况
val t = new Tuple3[Int,Int,String]
t._1, t._2, t._3
val (first, sencond, _) = t//匹配模式获得值
zip操作
zip将Array合并成元组的Array
keys.zip(values).toMap
所有类都具有公有可见性
带getter和setter的属性
getter和setter分别叫做 x 和 x_=,可以自己重定义
def age = privateAge
def age_=(age: Int) {privateAge = age}
统一访问原则:某个模块提供的所有服务都应该是通过统一的表示法访问到,不管其是存储还是通过计算实现的
方法可以访问该类的所有对象的私有变量,包括其他实例
使用 private[this] ... 则只能访问本实例this的字段
将字段标注为 @BeanProperty 可生成Java的默认访问方法getXX, setXX
import scala.reflect.BeanProperty
class Person{
@BeanProperty var name: String = _
}
辅助构造器的名字为 this;每个辅助构造器都必须以一个先前已经定义的其他辅助构造器或主构造器的调用开始。没有显式定义主构造器则自动拥有一个无参数的主构造器
def this(name: String){
this()
this.name = name
}
主构造器:与类定义交织在一起,参数直接放在类名之后。参数被编译成字段,其值被初始化成构造时传入的参数。主构造器会执行类定义中的所有语句
class Person(val name:String,private val age:Int){
...
}
不带val或var的参数如果在类中至少被一个方法使用,则其升级为字段,相当于private[this] val。否则就仅是一个普通参数,供主构造器中代码访问。
可以在任何语法结构中内嵌任何语法结构。类中定义类。
每个实例都有它自己的内嵌类(泛型导致,实例有可能是不一样类型,其内嵌类也有可能不一样),进行操作时需要注意。需要公用,则可将其移入伴生对象中。或者使用类型投影。 外部类.this 访问外部类的 this 引用。
class NetWork(val name:Int){ outer => //指向 NetWork.this
class Member(){
... // use outer.name
}
}
用对象作为单例或存放工具方法/常量或高效共享不可变实例
使用object创建单例,对象的构造器在其首次调用时指向。如果从未被使用,则其构造器不会被执行。其本质上可拥有类的所有特性,除了不能提供构造器参数。
类可以拥有一个同名的伴生对象
类与其伴生对象可以相互访问私有特性,必须存在同一个源文件中。
对象可以扩展类或者特质
其结果是一个扩展了指定类以及特质的类的对象,同时拥有在对象定义中给出的所有特性。
对象的apply方法通常用来构造伴生类的新实例
通常会定义和使用对象的apply方法,遇到如下形式的表达式,apply方法会被调用。返回的是伴生类的对象。可以省略使用 new
object(para1, ... paran)
如果不想显式定义main方法,可以扩展App特质的对象
执行程序必须从某个对象的main方法开始
object Hello{
def main(args:Array[String]){
...
}
}
object Hello extends App{
if (args.length ...)...
}
可以通过扩展Enumeration对象来实现枚举
object TrafficLightColor extends Enumeration{
val Red, Yellow, ... = Value
val Red = Value(0, "stop")
val Red = Value(10) //类型为TrafficLightColor.Value
val Red = Value("Stop") //每次调用Value都返回新实例
type TrafficLightColor = Value //类型别名
}
import TrafficLightColor._
for (c <- TrafficLightColor.values) ...
TrafficLightColor(0)
TrafficLightColor.withName("Stop")
包可以像内部类那样嵌套
源文件的目标与包之间没有强制的关联关系。可以在一个文档中定义多个包中的内容。
包路径不是绝对路径
包支持嵌套,可以访问上层作用域中的名称。如果需要访问非直接嵌套域中的包,则可以使用绝对包名 root. ....
包声明链x.y.z并不自动将中间包x和x.y变成可见
package com.horstman.xx.xx{
// com._, horstman_, xx_ 是不可见的
}
位于文件顶部不带花括号的包声明在整个文件范围内有效
package x.y.z
package m
//相当于
package x.y.z{
package m{
...
}
}
包对象可以持有函数和变量
包可以包含类、对象和特质,但不能包含函数或变量的定义。Java虚拟机的限制。每个包可以有一个包对象,需要在父包中定义,且名字与子包一样
package x.y.x
package object m{
val xname =...
} //Java虚拟机将其编译成带有静态方法和字段的JVM类,名为package.class
package m{
class P{
var name = xname
}
}
可以通过private[packageName]来设置可见性
引入语句的目的是使用更短的名称而不是较长的名称
引入语句可以引入包、类和对象
引入语句可以出现在任何位置,将其放入需要的地方,可以减少可能的名称冲突
引入语句可以重命名和隐藏特定成员
选取器 .{A, B}
重命名 .{A=>Z, C}
隐藏 .{A=>_, _}
java.lang、scala、Predef总是被引入,每个文件默认以以下代码开头
import java.lang._
import scala._ //可覆盖前面一个,与用户引入不同
import Predef._
使用extends
可以将类声明为final,这样它就不会被扩展
重写非抽象方法必须使用override修饰符
调用超类方法与Java一样,使用super
使用isInstanceOf方法测试类型
if (p.isInstanceOf[Boss]) {
val s = p.asInstanceOf[Boss]
}
判断一个对象只是某类而非子类
if (p.getClass == classOf[Boss])
使用模式匹配
p match{
case s:Boss =>
case _ =>
}
将字段或则方法声明为protected,成员可以被任何子类访问
还提供了protected[this],访问权限制在当前对象
只有主构造器才可以调用超类的构造器
class A(name:string, age:Int) extends B(name, age)
可以用同名的val字段重写一个val(或不带参数的def)
var alien = new Person("Fred"){
def greeting = "....."
}
//创建一个结构类型的对象,新类型为 Person{def greeting:String}
省区方法体的方法,抽象方法
只要存在一个抽象方法,就必须声明为abstract
重写超类的抽象方法时,不需要使用override方法
抽象字段是指没有初始值的字段
var id: Int //生成了抽象的getter/setter方法,不带字段
class B(var id:Int) extends AbstractClass {...}
子类重写超类构造时所需的变量,则可以出现异常。
构造器中不应该依赖val的值
class Ant extends{override val range=2} with Creature
C++中构造函数虚表先是基类然后才是子类,因此无法改变基类行为,Java允许在超类的构造方法中调用子类的方法。
Any AnyVal Unit/Int/Float...
Any AnyRef ....
Null 类型唯一实例 null
Nothing 类型无实例, Nil的类型是List[Nothing]
AnyRef的eq方法比较两个引用是否指向同一个对象,equals调用eq。确保方法参数为Any。
source = scala.io.Source.fromFile(...)
iters = source.getLines //迭代器
iters.toArray/ toBuffer
source.mkString
for (c <- source)
iter = source.buffered
while (iter.hasNext){
if (iter.head)... iter.next()
}
val tokens = source.mkString.split("\\S+")
val numbers = for (w <- tokens) yield w.toDoubel
val numbers = tokens.map(_.toDouble)
ReadInt
Source.fromURL(....)
Source.fromString(....)
Source.stdin
通过Java库读取
val file = new File(filename)
val in = new FileInputStream(file)
val bytes = new Array[Byte](file.length.toInt)
in.read(bytes)
in.close()
val out = new PrintWriter(...)
for (i <- 1 to 100) out.println(i)
out.close()
使用printf需要将其转为AnyRef,可以使用string的format方法
import java.io.File
def subdirs(dir: File):Iterator[File] = {
val children = dir.listFiles.filter(_.isDirectory)
children.toInterator ++ children.toInterator.flagmap(subdirs _)
}
import sys.process._
"ls -al .."!
sus.process包含一个从字符串到ProcessBuilder对象的隐式转换,!就是执行的该对象。!!使用字符串的形式返回,#|作为管道。#>重定向 #>> #<
使用字符串.r方法构造Regex类对象
.findAllIn方法返回所有匹配项的迭代器,可以在for循环中使用
.findAllIn.tpArray
.findFirstIn
.findPrefixOf
.replaceFirstIn
.replaceAllIn
val pattern = "(..) (..)".r
pattern(x, b) = "...."
for (pattern(x,b) <- pattern.findAllIn(....)
多基类中共同的元素
虚拟基类
特质中未被实现的方法默认就是抽象的,需要特质不止一个,可以使用with关键字来添加额外的特质
trait Logger{
def log(msg:String) //抽象方法
}
特质中的方法也可以是具体的
如果特质中有具体的方法,当特质发生改变时,所有混入该特质的类都必须重写编译
可以在类定义时添加特质
而在对象中混入具体实现
class X extends Y with Logged{...}
val x = new X with BetterLogged
可以为类或对象添加多个互相调用的特质,从最后一个开始
写的时候往往会调用父类的同名函数,因为不知道实际调用时的次序,如果父类是抽象方法,则编译会出错
abstract override def log(msg:String){
super.log(new .....)
}
在特质中使用具体和抽象方法十分普遍
特质中的字段可以是具体的也可以是抽象的,混入该特质的类自动获得一个字段与之对应。该字段不是继承,只是简单加入子类中。由于Java限定; 超类只有一个,因此特质的字段,就相当于处于子类的。
1)可以将抽象字段定义到类中
2)也可以将其定义到具体的对象中
不需要写 override
构造器顺序
构造器的顺序是类的线性化的反向
特质不能有构造器参数。
采用抽象字段可能会有问题,如果构造器中需要使用该字段的话,其初始化在子类。
val x = new {val filename = ...} with Account with FileLogger
class X extends {val filename = ...} with Account with FileLogger{}
特质也可以扩展类,该类会自动成为所有混入该特质的超类。同时如果其中有多个超类,如特质的超类和子类的超类是超类关系,则没有问题。
当特质代码以如下代码开头定时,它就只能被混入指定类型的子类
trait ... {this: 类型 => ...}
自身类型也可以处理结构类型,这种结构只给出类必须拥有的方法,而不是类的名称
trait xx extends Logged{
this: {def getmessage():String} =>
...
}
变量、函数、类等的名称统称为标识符
可以使用很多字符
反引号中包含几乎任何字符序列
a 标识符 b, 标识符代表一个带有两个参数的方法(一个隐式参数和一个显式参数)。
a 操作符,为后置操作符
+-!~可以作为前置(prefix)操作符,被转换为 unary_操作符的方法调用
a 操作符= b / a = a 操作符 b
所有操作符都是左结合的,除了以:结尾的操作符和赋值操作符
1::2::Nil 是右结合的,用于构造列表
右结合的二元操作符是其第二个参数的方法
2::Nil === Nil.::(2)
如果f不是函数和方法则可将其扩展到apply方法上
f(arg1, arg2 ..) ---> f.apply(arg1, arg2 ..)
如果其出现在赋值语句的等号左边,则有如下,用于数组与映射
f(arg1...) = value ---> f.update(arg1 ... ,value)
apply方法同样被经常用在伴生对象中,用来构造对象而不用显式地使用new
object F(){def apply(n:Int) = new F(n)}
提取器是带有unapply方法的对象,可以将unapply方法当做是伴生对象中apply方法的反向操作。接受对象,并从中提取值,而这些值是当初用来构建该对象的值
val F(x, y) = ... // F.unapply(...)
case Currency(amount, "USD") => ...
没有单值,只能放回一个目标类型的Option
def unapply(intput:string):Option[Int]=...
object IsCompound{
def unapply(input:String) = input.contain("....")
}
author match{
case Name(first, last @ IsCompound()) =>
case Name(first, last) =>
}
def unapplySeq(input:String):Option[Seq[String]]
author match{
case Name(first,last) =>
case Name(f,m,l)=>
case Name(f,'xx','yy',l)=>
}
import scala.math._
val fun = ceil _ // _表示确实指的杉树,而不是碰巧忘记给参数
可以调用
可以传递,fun是一个包含函数的变量
val trip = (x:Double)=>3*x
与 def trip(x:Double) = 3*x 一样
def valueAtOne(f:(Double)=>Double)) = f(2.5) //((Double)=>Double)=>Double
valueAtOne(ceil _) / valueAtOne(sqrt _)
valueAtOne((x)=>3*x)
valueAtOne(x=>3*x)
valueAtOne(3*_)
val fun = 3*(: Double)
val fun:(Double)=>Double = 3*
foreach/map/filter/reduceLeft/sortWith
将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数作为参数的函数
scala支持简写来定义柯里化函数
def mulOneAtTime(x:Int)(y:Int) = x*y
def runInThread(block:()=>Unit){
new Thread{
override def run(){block()}
}.start()
}
runInThread{()=> println....}
def runInThread(block:=>Unit){ //换名调用表示法
new Thread{
override def run(){block}
}.start()
}
runInThread{ println....}
def until(condition:=>Blooean)(block:=>Unit){
if (!condition){
block
until(condition)(block)
}
}
var x = 10
until (x == 0){
x -= 1
...
}
不需要使用return语句来返回函数值,函数的返回值就是函数体的值
如果需要在带名函数中使用return的化,则必须给出其返回类型
def indexOf(str:String, ch:Char):Int = {
var i = 0
until (i == str.length){
if (str(i) == ch) return i
i += 1
}
return -1
}
val iter = coll.iterator
while (iter.hasNext){
dowith(iter.next())
}
Vector是ArrayBuffer的不可变版本
LinkedList
是指哪种以implicit关键字声明的单个参数的函数,将被自动应用,将值从一种类型转换为另一种类型,最好采用长命名 oldtype2newtype
以下情况不会尝试考虑隐式转换
如果在参数列表中设置了隐式参数,则当调用时不传递该参数,则会自动寻找为该类型的隐式值
def x(implicit delims:String, x:String)
order是隐式参数,省略情况下可以直接比较
def smaller[T](a:T,b:T)(implicit order: T => Order[T]) = if (order(a)<b) a else b
order是隐式函数,且单个参数,单标识符出现,则其可以用于隐式转换,因此函数调用中可以去掉
def smaller[T](a:T,b:T)(implicit order: T => Order[T]) = if (a<b) a else b
类型参数可以有一个形式为T:M的上下文界定,其中M为泛函类型,要求作用域中存在一个类型为M[T]的隐式值,可以使用在该类中
class Pair[T:Ording](val first:T,val second:T){
def smaller(implicit ord:Ording[T]) = {
if (ord.compare(first, second)<0) first else second
}
}
// 可以通过Predef中的implicitly获取该值,代替上面的ord
// implicitly[Ording[T]]
// 定义如下 def implicitly[t](implicit e:T) = e
class Pair[T:Ording](val first:T,val second:T){
def smaller = {
import Ordered._
if (first < second) first else second
}
}
implicit object PonitOrdering extends Ordering[Point]{
def compare(...)
}