快学Scala 学习笔记-1: (第一章到第三章)

第一章.
1.1 Scala解释器(REPL)
Scala> 8*5+2
res0: Int = 42
Scala> 0.5 * res0
res1: Double = 21.0
Scala解释器 读取到一个表达式,对它进行求值,将它打印出来,接着再继续读取下一个表达式。这个过程被称作读取-求值-打印-循环,即 REPL 。
从技术上讲,scala程序并不是一个解释器。实际上你输入的内容被快速的编译成字节码,然后JVM执行。因此,大多数程序员称它做REPL.

1.2 声明变量
val:常量
var:变量
声明值或变量 不做初始化会报错,因为scala会根据值推断类型。

·语句无需分号,换行即可
·指定值、变量类型
var greet,message : String = null

1.3 常用类型
· Scala和Java不同,不区分基本数据类型和包装类,所有的都是类。
譬如可以 1.toString() //产生字符串"1" 或者
1.to(10) //产出Range(1,2,3,4,5,6,7,8,9,10)
其实,基本类型和包装类型的转换是Scala编译器的工作。举例,你创建了一个Int[] 数组,最终在虚拟机得到的是一个 

int[]数组
· Scala底层用java.lang.String类来表示字符串。不过,它通过StringOps类给字符串追加了上百种操作。举例说
"Hello".intersect("World")//共有部分,输出 "lo"
这个表达式中"Hello"被隐式转换成了一个StringOps对象,接着StringOps类的intersect方法被调用。
同样,Scala还提供RichInt,RichDouble等,他们分别为Int、Double提供了很多不具备的便捷方法,譬如前面用到的 1.to
(10)
· 数值类型间转换
99.44toInt //得到99
99.toChar //得到'c'
"99.44".toDouble 

1.4 算数和操作符重载
操作符 实际上是方法,a+b 实际上是a.+(b),因为:
Scala 几乎可以使用任何符号来为方法命名,这里+就是方法名。举例:
BigInt类中有一个 /%方法,返回一个对偶(商和余数)

Scala中,可以用
a 方法 b  作为 a.方法(b) 的简写,如
1.to(10)  简写为 1 to 10

Scala没有 ++ 、--  可以用+= ,-=  

1.5 调用函数和方法
· 数学函数
import scala.math._  // _是Scala的通配符(java中的*)
sqrt(2) //1.4142...
pow(2,4) //16.0
min(3,Pi) //3.0

注意,使用以scala.开头的包是,可以省去scala前缀。
如import scala.math._ 简写为 import math._
scala.math.sqrt(2)  简写为 math.sqrt(2)

· 没有静态方法
有单例对象。一个类对应一个伴生对象(companion object),其方法就跟Java中的静态方法一样。举例:
BigInt类的BigInt伴生对象有一个生成指定位数的随机素数的方法probablePrime:
BigInt.probablePrime(100,scala.util.Random)
注意:这里的Random是一个单例的随机数生成器对象,而该对象是在scala.util包中定义的。在 Java中为每个随机数都构造一个新的java.util.Random对象是一个常见的错误。

· 不带参数的Scala方法通常不使用括号。一般来讲:没有参数且不改变当前对象的方法不带圆括号。
"Hello".distinct    //获取字符串中不重复的字符

1.6 apply方法
C++ : s[i]
Java: s.charAt(i)
Scala: s(i)
例: "Hello"(4) //'o'
背后的实现原理是一个叫做apply的方法(你可以把这当做是()操作符的重载)。
StringOps类中有这样的方法:
def apply(n:Int):Char
也就是说  "Hello"(4) 实际上调用的是 "Hello".apply(4)
apply是Scala中创建对象的手法,如
BigInt("1234567890") * BigInt("1234567890")
Array(1,3,5,67,222)返回一个数组 就是调用Array伴生对象的apply方法。

1.7 Scaladoc
Scaladoc 拥有比Java多得多的便捷方法。有些方法使用了你还没学到的特性。
www.scala-lang.org/api 在线浏览Scaladoc
www.scala-lang.org/downloads#api  下载
查看doc一些技巧:
· 数值类型看看RichXxx,字符串看 StringOps
· 数学函数位于 scala.math包中,而不是某一个类
· 标记为 implicit 的方法对应的是 自动(隐式)转换。
· 方法参数 可以传 函数。
def count(p:(char) => Boolean) :Int
如 s.count(_.isUpper) //清点所有大写字母的数量

1.8 练习题
Scala REPL中,
resX 变量是 val还是 var?   : val
"crazy"*3 得到什么?  "crazycrazycrazy"
用BigInt计算2的1024次方? BigInt(2).pow(1024)
创建随机文件的方式之一是 生成一个随机的BigInt,然后将它转换成三十六进制,输出类似"awsgrwsger52d42"这样的字符串。查阅Scaladoc,找到在Scala中实现该逻辑的办法?
scala.math.BigInt(scala.util.Random.nextInt).toString(36) 
// res1: String = utydx
Scala中如何获取字符串的首字符和尾字符?
"Hello"(0)   "Hello".take(1)
"Hello".reverse(0)   "Hello".takeRight(1)

take:  从字符串首开始获取字符串
drop:  从字符串首开始去除字符串

takeRight/dropRigth: 从字符串尾开始



二. 控制结构和函数
Scala和其他编程语言的一个根本性差异:Java或C++中 我们把表达式(3+4)和语句(if else)看作两样不同的东西。在Scala中,几乎所有构造出来的语法结构都有值。
· if表达式有值
val s = if (x>0) 1 else -1
类似于Java的三目运算符 ?:
val v = if (x>0) "1" else -1
v的类型是 Int和String 的超类 Any;
如果 else 没有输出值,引入Unit类,写作()
val v=if(x>0) "1" else   等于
val v=if(x>0) "1" else ()
补: Scala中没有switch语句,不过有一个强大的多的模式匹配机制。

注意: REPL中若想换行,用花括号 { }
REPL想粘贴成块代码,又不想担心REPL的“近视”问题(只读一行),输入 :paste 进入粘贴模式 
最后按 Ctrl+D ,这样REPL就会整体执行。

· void类型是 Unit
· 块也有值————就是它最后一个表达式
在Scala中,赋值动作本身是没有(返回)值的
一个以 赋值语句结束的块 返回的值 是Unit类型的
所以,不要这样做:
x=y=1   //x的值是 Unit

· 输入和输出
print()
println()
带有C风格 格式化字符串的 printf函数:
printf("Hello,%s!You are %d  years old.\n","godce",42);
输入:
readLine("提示字符串") 从控制台读取

· Scala的for循环就像 Java增强for循环(foreach)
Scala中没有 fro(int i=0;i<n;i++) 这样的结构,
用while替换 或者
for(i<- 1 to n)
r=r*i
让变量i遍历(<-) 右边的表达式的所有值。至于遍历具体如何执行,取决于表达式的类型。
补:在遍历字符串或数组时,通常需要使用从0到n-1区间。这个时候可以用until方法而不是to方法。util返回一个不包含上

限的区间
val s= "hello"
var sum=0
for(i<-0 util s.length)
sum+=s(i)
更简便的写法:
var sum = 0
for(ch<- "hello") sum+=ch
补:Scala没有提供 break或continue语句来推出循环。如何取代?
法一.使用Boolean型控制变量
法二.使用嵌套函数--你可以从函数中return
法三.使用Breaks对象中的break方法 scala.util.control.Breaks.break

· 高级for循环和for推导
变量<-表达式  提供 多个生成器,用分号将他们隔开:
for(i<-1 to 3;j<-1 to 3) print((10*i+j)+" ")
// 11 12 13 21 22 23 31 32 33
每个生成器都可以带一个守卫——if开头的Boolean表达式
for(i<-1 to 3;j<-1 to 3 if i!=j) print((10*i+j)+" ")
//  12 13 21 23 31 32
可以使用任意多的定义,引入可以在循环中使用的变量:
for(i<-1 to 3;from = 4-i; j<-from to 3) print((10*i+j)+" ")
//  13 22 23 31 32 33
如果for循环的循环体以yield开始,则该循环会构造出一个集合,每次迭代生成集合中的一个值:
for(i<-1 to 10) yield i%3
//生成 Vector(1,2,0,1,2,0,1,2,0,1)
这类循环叫做 for推导式
for推导式 生成的集合 与它的第一个生成器是类型兼容的
for(c<-"hello";i<- 0 to 1) yield (c+i).toChar
//生成   "HIeflmlmop"
for(i<- 0 to 1;c <- "hello") yield (c+i).toChar
//生成 Vector('H','e','l','l','o','I','f','m','m','p')

· 函数
Scala还支持函数,不过在Java中我们只能用静态方法来模拟。
def abs(x:Double) = if(x>=0) x else -x
函数名 参数 类型  函数体
注意! 只要函数不是递归,你就不需要指定返回类型(Scala编译器会通过=符号右侧的表达式的类型推断出返回类型)
例子:
def fac(n:Int) = {
var r = 1
for(i<1 to n) r=r*i
r
}
一般不用return,不过也可以用return从函数中退出,不常见。
· 避免在函数定义中使用return

· 递归函数必须指定 返回类型
def fac(n:Int):Int = if(n<=0) 1 else n* fac(n-1)
如果没有返回类型,Scala编译器无法校验 n*fac(n-1)的类型是Int
扩展: 某些编程语言(如ML和Haske11)能够推断出递归函数的类型,用的是Hindley-Milner算法。不过,在面对对象的语言中这样做并不总是行得通。如何扩展Hindley-Milner算法让他能够处理子类型仍然是个科研命题。

· 默认参数和带名参数
def decorate(str:String,left:String="[",right:String="]") =
left+str+right
decorate("Hello") //[Hello]
decorate("Hello","<",">") //<Hello>
decorate("Hello","<") //<Hello]
decorate(left="<<<",str="Hello",right=">>>") //<<<Hello>>>

上面 带名参数 让函数更加可读(尤其是有很多默认参数的函数)


· 变长参数
def sum(args:Int*) = {
var result=0
for(arg <-args) result+=arg
result
}
val s = sum(1,4,9,16,25)
函数得到的是一个类型为Seq的参数。
val s = sum(1 to 5) //错误!传入单个参数那么改参数必须是单个正数,而不是一个整数区间!
val s = sum(1 to 5 :_*) //告诉编译器 将1to5当做参数序列处理
递归举例:
def recursiveSum(args:Int*):Int= {
if(args.length == 0) 0
else args.head + recursiveSum(args.tail:_*)
}

//这里 序列的head是它的首个元素,而tail是所有其他元素的区间,所以我们用 :_* 来将它 转换成 参数序列。


· 过程
没有返回值的函数。算作几个语句组合起来的块,调用仅仅为了执行这个过程,并不需要得到返回值,如打印五角星。
Scala中 过程没有 = 号
def box(s:String) {  //显示声明无返回值 加上 :Unit
var border = "-" * s.length+"--\n"
println(border +"|"+s+"|\n"+border)
}
· 注意别在函数式定义中漏掉了= 

· lazy 懒值 延迟赋值(被用到时才执行)
lazy val words = scala.io.Source.fromFile("/usr/words").mkString

· 异常的工作方式 和Java或C++中基本一样,不同的是你在catch语句中使用"模式匹配"
· Scala没有受检异常
在Java中,"受检"异常在编译期被检查。如果你的方法可能会抛出IOException,你必须做出声明。这就要求程序员必须去想哪些异常应该在哪里被处理掉,这是个值得称道的目标。不幸的是,它同时也催生了怪兽般的方法签名,比如void doSomething()throws IOException,InterruptedException,ClassNotFoundException。许多Java程序员很反感这个特性,最终过早捕获这些异常,或者使用超通用的异常类。Scala的设计者们决定不支持"受检"异常,因为他们意识到彻底的编译期检查并不总是好的。
例:
if(x>=0) sqrt(x) else throw new IllegalArgumentException("x should not be negative")
第一个分值类型是 Double,第二个分值类型是 Nothing。因此if/else表达式的返回类型是Double

· 练习题
2.1.编写函数:正数返回1,0返回0,负数返回-1
BigInt(10).signum
2.2.一个空的块表达式{}的值是什么?类型是什么?
在 REPL中可以看出:
Scala> val t = {}
t: Unit= ()
返回值是(),类型是Unit
2.3.指出在Scala中何种情况下赋值语句x=y=1是合法的
val x = {}
2.4.针对下列Java循环编写一个Scala版本:for(int i=10;i>=0;i--)System.out.println(i); 
for(i<- 0 to 10 reverse)print(i)
2.5.编写一个过程countdown(n:Int),打印从n到0的数字
def countdown(n:Int) {
for(i<-0 to n reverse) print(i)
// 0 to n reverse foreach print
}
2.6 编写一个for循环,计算字符串中所有字母的Unicode代码的乘积。举例来说,"Hello"中所有字符串的乘积为9415087488L 
for(i <- "Hello") {
t = t*i.toLong
}
2.7 不使用for循环解决2.6
Scaladoc中 查看StringOps 文档
var t:Long = 1
"Hello".foreach(t *= _.toLong)
2.8 将 2.6计算乘积的代码编成函数product(s:String)
def product(s:String):Long={
var t:Long = 1
for(i<-s){
t*=i.toLong
}
t
}
2.9 将2.8的函数改为递归
def product(s:String):Long={
if(s.length == 1) return s.charAt(0).toLong
else s.take(1).charAt(0).toLong * product(s.drop(1))
}

三. 数组相关操作
要点:
·如长度固定用Array,不固定用ArrayBuffer
·提供初始值时不要使用new
·用()来访问元素
·用for(elem<-arr)来遍历元素
·用for(elem<-arr if...)...yield...来将原数组转型为新数组
·scala数组和Java数组可以互操作;用ArrayBuffer,使用scala.collection.JavaConversions中的转换函数

3.1 定长数组
val nums = new Array[Int](10)
val s = Array("hello","world")
//已经提供初始值就无需new
s(0)="goodbye"
//Array("goodbye","world")
使用()而不是[]来访问元素

3.2 变长数组:数组缓冲
import scala.collection.mutable.ArrayBuffer
val b = ArrayBuffer[Int]()
//或者 new ArrayBuffer[Int]
//一个空的数组缓冲,准备存放整数
b+=1
//ArrayBuffer(1)
//用+=在尾端添加元素
b+=(1,2,4,5)
b++=Array(8,13,21)
//ArrayBuffer(1,1,2,4,5,8,13,21)
//你可以用++=操作符追加任何集合
b.trimEnd(5)
//ArrayBuffer(1,1,2)
//移除最后5个元素
b.insert(2,6,7)
//ArrayBuffer(1,1,6,7,2)
b.remove(2,2)
//ArrayBuffer(1,1,2)
val a = b.toArray
//Array(1,1,2)
val c = a.toBuffer

3.3 遍历数组和数组缓冲
for(i<-0 until a.length)
// 0 until 10 实际上是一个方法调用: 0.until(10)
如果想要每两个元素一跳,可以这样写:
0 until (a.length,2)
从数组尾端开始:
(0 until a.length).reverse
如果循环体中不需要用到数组下标:
for(elem <- a)
//这和 增强for循环 很相似

3.4 数组转换
val a = Array(2,3,6,7,10)
val result = for(elem <- a) yield 2*elem
// result是Array(4,6,12,14,20)
还可以通过守卫:for中的if来实现只处理满足条件的元素
for(elem<-a if elem % 2==0) yield 2*elem

扩展: 另一种做法:
a.filter(_%2 == 0).map(2 * _)
甚至
a.filter{_%2 == 0} map{2*_}
某些有着函数式编程经验的程序员倾向于使用filter和map而不是守卫和yield.
这不过是一种风格罢了--for循环所做的事完全相同。
考虑如下示例:
给定一个整数的数组缓冲,要求移除 除了第一个负数外的所有负数。传统的依次执行的解决方案会在遇到第一个负数时置一个标记,然后移除后续出现的负数元素:
var first = true
var n = a.length
var i = 0
while(i<n) {
if(a(i) >=0) i+=1
else {
if(first) {first = false;i+=1}
else {a.remove(i);n-=1}
}
}
不过等一下————这个方案其实并不那么好:从数组缓冲中移除元素并不高效,把非负数值拷贝到前端要好得多。
首先收集需要保留的下标:
var first = true
val indexes = for(i<-0 until a.length if first || a(i)>=0) yield {
if (a(i)<0) first = false; i
}
然后将元素移动到该去的位置,并截断尾端:
for(j<-0 until indexes.length) a(j)=a(indexes(j))
a.trimEnd(a.length - indexes.length)

3.5 常用算法
求和:
Array(1,3,4,5).sum
比较:
ArrayBuffer("Mary","had","little").max  //"little"
排序:
val b = ArrayBuffer(1,4,2,5)
val bSorted = b.sorted(_<_) // 原数组并未被改变
val bDescending = b.sorted(_>_) // ArrayBuffer(5,4,2,1)
对自身排序:只能对数组,不能对数组缓冲
val a = Array(1,7.2,9)
scala.util.Sorting.quickSort(a)
// a现在是 Array(1,2,7,9)
显示: mkString 允许你指定元素间的分隔符
a.mkString(" and ")
//该方法的另一个重载 可以指定前缀后缀
b.mkString("<","-",">")
//<1-4-2-5>
它们的toString 方法:
a.toString
// 调用的是Java的 毫无意义的 toString
b.toString
//ArrayBuffer(1,4,2,5)
//便于调试 重写了toString

3.6 解读Scaladoc
Scala文档 读起来获取有些困难,又学会忽略那些复杂的细节,简化成简单的版本。

3.7 多维数组
和Java一样,多维数组是通过数组的数组来实现的。
Double的二维数组类型为  Array[Array[Double]]
//通过 ofDim方法
val matrix=Array.ofDim[Double](3,4) //三行 四列
matrix(row)(column) = 43
//创建不规则数组,每一行长度都不同
val triangle = new Array[Array[Int]](10)
for(i<- 0 util triangle.length)
triangle(i) = new Array[Int](i+1)

3.8 与Java的互操作
由于Scala数组是用Java数组实现的,你可以在java和Scala之间来回传递。
如果你调用接收或返回 java.util.List 的Java方法,则当然可以在Scala代码中使用Java的ArrayList————但那样做没什么意思。你完全可以引入scala.collection.JavaConversions里的隐式转换方法。这样你就可以在代码中使用scala缓冲,在调用Java方法时,这些对象会被自动包装成Java列表。
举例:java.lang.ProcessBuilder类有一个以List<String>为参数的构造器。以下是在Scala中调用它的写法:
import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable.ArrayBuffer
val command = ArrayBuffer("ls","-al","/home/cay")
val pb = new ProcessBuilder(command)    //Scala 到 Java的转化
Scala缓冲被包装成了一个实现了java.util.List接口的Java类的对象
反过来讲,当Java方法返回java.util.List时,我们可以让它自动转换成一个Buffer
import scala.collection.JavaConversions.bufferAsJavaList
import scala.collection.mutable.ArrayBuffer
val cmd : Buffer[String] = pb.command()  // Java 到 Scala的转换

3.9 练习题
3.9.1. 编写一段代码,将a设置为一个n个随机整数的数组,要求随机数介于0和n之间。
val n = 100
val rand = scala.util.Random
val a = new Array[Int](n)
for(i<- 0 util n) a(i)=rand.nextInt(n)
println(a.mkString(" and "))

3.9.2. 编写一个循环,将整数数组中相邻的元素置换。
例如,Array(1,2,3,4,5)置换后变为Array(2,1,4,3,5)
var a = Array(1,2,3,4,5)
for(i<-0 util (a.length-1,2) ) {
a[i] = a[i] ^ a[i+1]
a[i+1] = a[i] ^ a[i+1]
a[i] = a[i] ^ a[i+1]
}
3.9.3  重复第2题,交换过的数组生成新的值,用for/yield
var a = Array(1,2,3,4,5)
val b = for(i<-0 util (a.length-1,2)) 
yield 
Array(a(i+1),a(i))
println(b.mkString(" and "))

3.9.4. 给定一个整数数组,产出一个新的数组,包含元素组中的所有正值,以原有顺序排列,之后的元素是所有零或负值,以原有顺序排序。
val a = Array(1,-1,-2,4,0,33,23,0,-34,-9)
val b = for(i<-0 util a.length if a(i)>0) yield a(i)
val c = for(i<-0 util a.length if a(i)<1) yield a(i)
val buffer = b.toBuffer
buffer++=c
println(buffer.mkStirng(" and ")

3.9.5 如何计算Array[Double]的平均值
val h = Array(1.2,23.24,22,112.3)
val avg = h.sum/h.length

3.9.6 如何重新组织Array[Int]的元素将它们以反序排列?对于ArrayBuffer[Int]你又会怎么做呢?
// Array[Int]
val arr = Array(1,2,3,4,5)
for(i<-0 util arr.length/2) {
arr(i) = arr(i) ^ arr(arr.length-1-i)
arr(arr.length-1-i) = arr(i) ^ arr(arr.length-1-i)
arr(i) = arr(i) ^ arr(arr.length-1-i)
}
println(arr.mkString(" "))
//ArrayBuffer
val arr = ArrayBuffer(1,2,3,4,5)
val arrReverse = arr.reverse
println(arr.mkStirng(" "))

3.9.7 编写代码,去掉数组中的所有值,去掉重复项。(查看Scaladoc)
val arr = Array(1,2,3,4,5,3,4,5,6,7)
println(arr.distinct.mkString(" "))

3.9.9 创建一个由java.util.TimeZone.getAvailableIDs返回的时区集合,判断条件是它们在美洲。去掉”America/”前缀并排序.
val timeZone = java.util.TimeZone.getAvailableIDs
val americaTimeZone = timeZone.filter(_.take(8)=="America/")
val sortedAmericaTimeZone = americaTimeZone.map(_.drop(8)).sorted

3.9.10 引入java.awt.datatransfer._并构建一个类型为SystemFlavorMap类型的对象
val flavors = SystemFlavorMap.getDefaultFlavorMap().asInstanceOf[SystemFlavorMap]
然后以DataFlavor.imageFlavor为参数调用getNativesForFlavor方法,以Scala缓冲保存返回值。(为什么用这样一个晦涩难懂的类?因为在Java标准中很难找得到使用java.util.List的代码。)
import java.awt.datatransfer._
val flavors = SystemFlavorMap.getDefaultFlavorMap().asInstanceOf[SystemFlavorMap]
flavors.getNativesForFlavor(DataFlavor.imageFlavor).toArray.toBuffer
println(xx.mkString("  "))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值