2、Scala小练习A
说明:在使用Source.fromFile时,如果你使用的是scalaIDE, 那么很有可能你会遇到“Exception in thread "main" java.nio.charset.MalformedInputException: Input length = 1”错误,这个时候你需要换用命令行:“D:\scalaproject>scala Main.scala”
带有包名的scala程序自己编译和运行的方法如下: image.png$ scalac Main.scala //编译 $ scala Main //运行
我自己scalaIDE写的代码是这样运行起来的: image.png
说明:源代码中可能会出现\$前面有个反斜杠,这是由于如果不这样写,页面显示的时候会不正常
第一章
- val hello:String = "hello" //这里val声明的都是常量,这里的String可以自动推到出来,可省去
- Scala中数字类型也是对象,不区分primitive类型和对象类型
"Hello".intersect("world") //> res1: String = lo
val range = 1.to(10) //> Range 1 to 10
for(a <- range)
println(a)
//第一个也可以加上(),第二个不能加,原则是:a parameterless method that doesn’t modify the object has no parentheses
99.14.toString //> res2: String = 99.14
"99.44".toDouble //> res3: Double = 99.44
3.+(4) 得到的是7,就是3+4。这里+完全被看成是一个函数
In general, you can write a method b as a shorthand for a.method(b)
即1 to 10 等价于1.to(10)
scala么有++和--操作,可以用+=1和-=1来代替
scala> import scala.math._
import scala.math._
scala> sqrt(4)
res3: Double = 2.0
scala> pow(2,3)
res4: Double = 8.0
scala> min(3, Pi)
res7: Double = 3.0
scala> max(3,Pi)
res8: Double = 3.141592653589793
apply方法:
scala> scala.math.pow(3,4)
scala> "hello"(4)
res0: Char = o
等价于
scala> "hello".apply(4)
res1: Char = o
注意:
BigInt("1234567890")
is a shortcut for
BigInt.apply("1234567890")
它的实现是产生一个新对象
You can think of a sequence s of element type T as
a function from { 0, 1, . . . , n – 1 } to T that maps i to s(i), the ith element of the
sequence.
scala> "BonJour".sorted
res6: String = BJnooru
----------------------------------val和var
如下可见,var的值是可变的(val是常量)
scala> var e = 3;
e: Int = 3
scala> e = 4
e: Int = 4
scala> "crazy" * 2
res6: String = crazycrazy
scala> 10 max 2
res7: Int = 10
scala> scala.math.max (10,2)
res9: Int = 10
-------------------------------
scala> val m:BigInt = 2
m: BigInt = 2
scala> m.pow(1024)
res10: scala.math.BigInt = 179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216
scala> m.pow(10)
res11: scala.math.BigInt = 1024
------------------------------take和drop
scala> "hello".take(2)
res13: String = he
scala> "hello".drop(1)
res14: String = ello
scala> "hello".takeRight(1)
res16: String = o
scala> "hello".dropRight(1)
res15: String = hell
scala> "hallo".last
res0: Char = o
scala> "hallo".head
res2: Char = h
------------------
scala> new sun.misc.BASE64Encoder().encode(BigInt.probablePrime(100,scala.util.Random).toByteArray)
res4: String = Dtmu+cgWeZJzqNsBFw==
第二章
---------------------------注意Any类型:
scala> val x = 3
x: Int = 3
--注意Any类型:这个if可能有两个返回不同类型值的分支,你可以理解为Any为他们的父类
scala> if (x > 0) "positive" else -1
res7: Any = positive
--注意()
scala> if (x > 0) 1 else ()
res9: AnyVal = ()
-----------------------------repl和compiler不同的地方
注意,下面这个例子你可以看到,需要使用大括号来避免每行回车后repl即执行的行为(下面的竖线是回车后自动产生的)
scala> if(x > 0) {x
| } else if(x == 0){0
| } else -x
res11: Int = 0
if(x > 0) x
else if(x == 0) 0
else -x
------------------------------ 语句的终结(分号到底要不要),当然如果你想要像Java那样加上当然没有问题
a semicolon is never required if it
falls just before the end of the line(一行的最后一个可以省去). A semicolon is also optional before an }, an
else(结束大括号和else之前也可以省去), and similar locations where it is clear from context that the end of a statement
has been reached.
If you want to continue a long statement over two lines, make sure that the first
line ends in a symbol that cannot be the end of a statement. An operator is often
a good choice:
s = s0 + (v - v0) * t + // The + tells the parser that this is not the end
0.5 * (a - a0) * t * t
也就是说你别人parser糊涂
---------------------------{}这个块的值为这个块里面最后一个表达式的值
----------------------------赋值语句返回值是Unit,即表示没有返回值
赋值语句没有返回值,所以不要做级联赋值操作
x = y = 1 // No
scala> print("The Answer: “");println("world")
The Answer: world
scala> printf("Hello, %s! You are %d years old.%n", "me" ,18)
Hello, me! You are 18 years old.
----三个插值器(interpolator):f s raw
scala> val age = 18
age: Int = 18
scala> val name = "Hello"
name: String = Hello
scala> print(f"Hello, \$name! In six months, you'll be \${age + 0.5}%7.2f years old.%n")
Hello, Hello! In six months, you'll be 1111111234.50 years old.
scala> print(f"Hello, \$name! In six months, you'll be \${age + 0.5}%7.2f years old.\n")
Hello, Hello! In six months, you'll be 1111111234.50 years old.
--With a prefix of raw, escape sequences in a string are not evaluated
scala> raw"a\nb"
res5: String = a\nb
--With a prefix of s, strings can contain expressions but not format directives
scala> s"a\nb"
res6: String =
a
b
------------------------------------------------------------readLine,readInt
import scala.io.StdIn
object Hello {
def main(args: Array[String])
{
val name = StdIn.readLine("Your Name: ");
print(s"Your age:");
val age = StdIn.readInt();
print(s"Hello \$name, next year your age is \${age + 1}")
}
}
-------while and do同Java
-------另外没有break和continue语句
1 to n的作用:returns a Range of the numbers from 1 to n (inclusive)
scala> for (i <- 1 to 4){
| print(i)}
1234
---for对字符串的处理
val s = "World"
for(oneChar <- s)
print(oneChar)
scala> for(i<- 1 to 3; j<- 4 to 6) print(s"($i,$j)")
(1,4)(1,5)(1,6)(2,4)(2,5)(2,6)(3,4)(3,5)(3,6)
注意带guard的generator
scala> for(i <- 1 to 100 if i%2!=0) print(s" $i")
1 3 5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99
多个generator
scala> for(i<- 1 to 3; j<- 1 to 4 if i != j) print(s"($i,$j)")
(1,2)(1,3)(1,4)(2,1)(2,3)(2,4)(3,1)(3,2)(3,4)
--When the body of the for loop starts with yield, the loop constructs a collection of values, one for each iteration
scala> for(i <- 0 to 10) yield i
res7: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scala> for (c <- "Hello"; i <- 0 to 1) yield (c + i).toChar
res8: String = HIeflmlmop
scala> for (i <- 0 to 1; c <- "Hello") yield (c + i).toChar
res9: scala.collection.immutable.IndexedSeq[Char] = Vector(H, e, l, l, o, I, f, m, m, p)
----如果function的body有多个表达式,请使用一个block,最后一个表达式的值为块的返回值
def fac(n : Int) ={
var total =1;
for(i <- 1 to n)
{
total = total * i
}
total
}
-----递归函数函数声明的时候需要给出返回值
def facRecur(n:Int) : Int = {
if(n <= 0) 1
else n * facRecur(n-1);
}
------注意一下函数默认参数和命名参数的用法(下面例子一对{}可以省去)
def decorate(str:String, left:String="[", right :String="]") =
{
left + str + right;
}
注意调用方法的多种形式
println(decorate("hello"))
println(decorate("hello", "<<<",">>>"))//指定值
println(decorate("hello", "<<<")) //最考虑右侧的使用默认值,即right使用默认值"]"
println(decorate(left="<-",str="hello", right="->"))
函数变参
下面的Int,还有Range对象在做实参(或者递归调用)时候需要:_这样的语法
def recursiveSum(args:Int*):Int =
{
if(args.length == 0) 0
else args.head + recursiveSum(args.tail:_*)
}
def sum2(args:Int*) =
{
var result = 0
for(i <- args)
{
result += i;
}
result
}
println(sum2(1 to 10 : _*));
println(recursiveSum(1 to 10:_*));
Procedure
一个函数定义如果没有=,则它是一个Procedure,且没有返回值(即Unit),我们使用它只是为了使用它的side effect
def box(s:String)
{
val border = "-"*(s.length + 2)
print(f"\$border%n|\$s|%n\$border%n")
}
image.png
异常
和Java不同,Scala没有“checked” exceptions—你不需要去declare一个函数或者方法throw an exception.
注意Java里面:“checked” exceptions are checked at compile time,如果需要,则你需要在方法什么上加上throws
throw 表达式有一个特殊的类型:Nothing,在if-else中如果有一个分支的类型为Nothing,则整个if-else的类型为另一个分支返回类型
scala> var x = 3
x: Int = 3
scala> if (x >= 0) { scala.math.sqrt(x)
| } else throw new IllegalArgumentException("x should not be negative")
res9: Double = 1.7320508075688772
scala> x = -1
x: Int = -1
scala> if (x >= 0) { scala.math.sqrt(x)
| } else throw new IllegalArgumentException("x should not be negative")
java.lang.IllegalArgumentException: x should not be negative
... 29 elided
后面会说明:try catch结构里面的catch块里面用的是模式匹配
第三章
Map是不可变的,scala.collection.mutable.Map则是可变的
var m1 = Map("1" -> "one", "2"->"two")
var m2= scala.collection.mutable.Map("3"->"three", "4"->"foure");
var m3 = Map[String, String]()
var m4 = Map(("5","five"),("6","six"));
m4("5") //获取对应的value值,如果不存在这个key,则抛出异常 //> res0: String = five
//而下面的get则不会抛出异常
m4.get("7") //> res3: Option[String] = None
m4.get("5") //> res4: Option[String] = Some(five)
m4.contains("6"); //> res1: Boolean = true
//如果取不到,则返回指定的值
m4.getOrElse("7","no-seven") //> res2: String = no-seven
//m4("5") = "V" 更新或者赋值会报错,因为Map不可变
//Map不可变,但是可以新生成一个Map
val m5 = m4 + (("what","no answer"))
m2("5")="what"
m2("3")="III"
m2 //> res0: scala.collection.mutable.Map[String,String] = Map(5 -> what, 4 -> four
//| e, 3 -> III)
//增加映射内容:
m2 += ("7"->"seven",("8","eight"), "9"->"nine")
//删除某些映射内容:
m2 -= "3"
map的迭代访问
for((k,v) <- m4)
{
println(s"(\$k,\$v)") //> (5,five) (6,six)
}
for(v <- m4.keySet)
{
print(s" \$v ") //> 5 6
}
for(v <- m4.values)
{
print(s" \$v ") //> five six
}
-----------------如果把一个Map的key和value反置
var m4 = Map(("5","five"),("6","six"))
m4 = m4 + (("what","no answer"))
for((k,v) <- m4) yield (v,k)
val months = scala.collection.mutable.LinkedHashMap("January" -> 1,"February" -> 2, "March" -> 3, "April" -> 4, "May" -> 5)
//> months : scala.collection.mutable.LinkedHashMap[String,Int] = Map(January -
//| > 1, February -> 2, March -> 3, April -> 4, May -> 5)
val months2 = scala.collection.mutable.SortedMap("January" -> 1,"February" -> 2, "March" -> 3, "April" -> 4, "May" -> 5)
//> months2 : scala.collection.mutable.SortedMap[String,Int] = TreeMap(April ->
//| 4, February -> 2, January -> 1, March -> 3, May -> 5)
Tuple
取tuple里面的component(特别注意,索引从1开始的,不是0):
注意:
- t._3可以用空格替代点号,即写为t _3
- 最好使用patternMatch来获取值
val tuple = (1, "hello",3.14)
println(s"\${tuple._1} \${tuple._2} \${tuple _3}")
val (one, two, three) = tuple
println(s"\$one \$two \$three")
val (first, second, _) = tuple //这里第三个元素我根本不关注
println(s"\$first \$second")
第四章 class相关练习
image.pngclass的声明
//一个源文件可以包含多个class,他们都是public visibility
class Counter
{
private var value = 0 // You must initialize the field
def increment() { value += 1 } // Methods are public by default
def current() = value
var v = 3;
}
val myCounter = new Counter // Or new Counter()
myCounter.increment()
println(myCounter.current)
对于没有参数的方法,方法可以带也可以去掉括号:
myCounter.increment
println(myCounter.current())
实践中如何选择呢,建议如下:It is considered good style to use () for a mutator method (a method that changes the object state), and to drop the () for an accessor method (a method that does not change the object state).
Properties with Getters and Setters
- Scala 为每个field都提供了 getter and setter methods,如果我们不愿意有些field被访问,需要加上private,另外val类型的变量只有getter没有setter
- In Scala, get方法即 对象.field
set方法即 对象.field = 要设置的值
其中set方法也可以写为field_=(要设置的值) (注意是方法名,调用时候需要使用括号把参数包裹起来)
myCounter.v //get方法 //> res0: Int = 3
myCounter.v=4 //set方法
myCounter.v_= (4) //set方法 v_= 是方法名称 4是实参
myCounter.v //> res1: Int = 4
自定义get和set方法:
class Person
{
private var pv = 1
def pvValue = pv //注意没有括号
def pvValue_=(v:Int) ={ if (v > 0) pv = v }
}
var p = new Person //> p : what.Person = what$Person@1a86f2f1
p.pvValue //> res0: Int = 1
p.pvValue = -2 //等价于p.pvValue_=(-2)
p.pvValue //> res1: Int = 1
p.pvValue = 2 //等价于p.pvValue_=(2)
p.pvValue
对于get和setter有一点需要提及: With a class-private field, Scala generates private getter and setter methods. However, for an object-private field, no getters and setters are generated at all.
Object-Private Fields:
一个class的方法里面可以访问同一个class所有对象的private成员(这一点和Java是一样的)
class Counter {
private var value = 0
def increment() { value += 1 }
def isLess(other : Counter) = value < other.value //可以访问other对象的private成员value
}
但是private[this]修饰的字段则不可以,这就是所谓的Object-Privete Field
class Counter2 {
private[this] var value = 0
def increment() { value += 1 }
def isLess(other : Counter) = value < other.value//这会报错
}
Bean Properties
因为JavaBean规范,需要我们的类能产生setXXX和getXXX方法
class Person
{
import scala.beans.BeanProperty
@BeanProperty var name :String = "Hello"
}
var p = new Person() //> p : what.Person = what$Person@1a86f2f1
p.name = "hello1"
p.name //> res0: String = hello1
p.getName //> res1: String = hello1
p.setName("hello2")
p.name //> res2: String = hello2
p.getName //> res3: String = hello2
p.name_=("hello3")
p.name //> res4: String = hello3
构造函数
- primary constructor(注意不是指无参构造函数)
1.1 In Scala, every class has a primary constructor. The primary constructor is not defined with a this method(注意方法名称不是this,有别于auxiliary构造函数). Instead, it is interwoven with the class definition.
1.2 NOTE: If there are no parameters after the class name, then the class has a primary constructor with no parameters. That constructor simply executes all statements in the body of the class.
1.3 The primary constructor executes all statements in the class definition
下面这个构造函数的两个参数name和age会自动成为这个class的两个成员变量
class Person(val name: String, val age: Int) {
// Parameters of primary constructor in (...)
...
}
例如:
class T(var age :Int)
{
}
val t1 = new T(4)
println(t1.age)
t1.age=8
println(t1.age)
t1.age_=(16)
println(t1.age)
image.png
2. auxiliary constructors
class Person {
private var name = ""
private var age = 0
//auxiliary构造函数必须首先调用其他的auxiliary构造函数或者调用primary构造函数
def this(name: String) { // An auxiliary constructor
this() // Calls primary constructor
this.name = name
}
def this(name: String, age: Int) { // Another auxiliary constructor
this(name) // Calls previous auxiliary constructor
this.age = age
}
}
//下面的几种方法可以构造出对象
val p1_0 = new Person // Primary constructor
val p1_1 = new Person() // Primary constructor
val p2 = new Person("Fred") // First auxiliary constructor
val p3 = new Person("Fred", 42) // Second auxiliary constructor
提示: 我们可以通过在primary constructor中使用默认参数来消除auxiliary constructors的必要性
比如:
class Person(val name: String = "", val age: Int = 0)
给primary constructor加上private,使得这个class的用户只能使用auxiliary constructor生成对象
内部类
注意在类外部生成内部类的实例方法和Java不同
import scala.collection.mutable.ArrayBuffer;
object Hello
{
def main(args: Array[String])
{
val chatter = new Network
val myFace = new Network
val fred = chatter.join("Fred")
val wilma = chatter.join("Wilma")
var innerObject = new chatter.Member("")//和Java不一樣
fred.contacts += wilma // OK
val barney = myFace.join("Barney") // Has type myFace.Member
fred.contacts += barney
}
}
class Network
{
class Member(val name: String) {
val contacts = new ArrayBuffer[Network#Member]
}
private val members = new ArrayBuffer[Member]
def join(name: String) = {
val m = new Member(name)
members += m
m
}
}
进一步解释:
image.png
提示几点:
a. 方法有返回类型时,理解为函数(表达式);
b. 无返回类型时,即procedure,可以省略result type 和=;
c. 无入参时,可以省略方法名后面的括号,但是无返回类型时(省略result type和=),建议带上括号。
第五章 object相关练习
Scala 没有静态字段和方法,这些问题可以通过object来解决:
object Accounts
{
private var lastNumber = 0
def newUniqueNumber() = {lastNumber += 1; lastNumber}
}
println(Accounts.newUniqueNumber()) //打印1
println(Accounts.newUniqueNumber()) //打印2
----------------------------伴随对象
The class and its companion object can access each other’s private features. They
must be located in the same source file(必须要写在一个源文件里面)
例子(注意object和class后面的名称是一样的)
object Account
{
private var lastNumber = 0
def newUniqueNumber() = {lastNumber += 1; lastNumber}
}
class Account
{
val id = Account.newUniqueNumber()//注意调用的是object Account的方法
private var balance = 0.0
//一个函数定义如果没有=,则它是一个Procedure,且没有返回值(即Unit),我们使用它只是为了使用它的side effect
def deposit(amount: Double) { balance += amount }
}
---------------------------------Objects Extending a Class or Trait
abstract class Action(val desc : String)
{
def undo():Unit
def redo():Unit
}
object DoNothing extends Action("do Nothing")
{
def undo {}
def redo {}
}
apply方法
为objects提供一个apply method是一个常见行为,apply方法调用形式为Object(arg1, ..., argN),
通常,这样一个apply方法会返回companion class的一个object
那么为啥不是用一个构造函数?因为这样做可以省去加new的必要性:
Array(Array(1, 7), Array(2, 9))
注意Array(100)(这实际是调用apply(100),产生一个只有一个元素100的类型为Array[100]的Array)和
new Array(100)(这实际调用的是this(100), 产生含有100个null元素,类型为Array[Nothing]的Array)的区别:
前者length是1,后者length是100
测试如下:
val a1 = Array(3) //> a1 : Array[Int] = Array(3)
a1.length //> res0: Int = 1
val a2 = Array.apply(3) //> a2 : Array[Int] = Array(3)
a2.length //> res1: Int = 1
val b = new Array(3) //> b : Array[Nothing] = Array(null, null, null)
b.length //> res2: Int = 3
注意我们在第一章也说过String也有一个apply,它是取指定位置的字符:
scala> "hello".apply(4)
res1: Char = o
例子:
object Hello
{
def main(args: Array[String])
{
val acct = Account(1000.0)
println(s"id is ${acct.id}, balance is ${acct.initialBalance}")
val acct2 = Account(1000.0)
println(s"id is ${acct2.id}, balance is ${acct2.initialBalance}")
/*
id is 1, balance is 1000.0
id is 2, balance is 1000.0
*/
}
}
class Account private (val id: Int, val initialBalance: Double)
{
private var balance = initialBalance
}
//accompany object,它也可以访问accompnay class
object Account
{
private var number:Int = 0
//定义了apply的行为
def apply(initialBalance: Double) = new Account(newUniqueNumber(), initialBalance)
def newUniqueNumber() = {number += 1;number}
}
Application Objects
第一种方法提供main(args:Arrray[String])函数:
object Hello
{
def main(args:Array[String])
{
println("Hello, World");
}
}
第二种方法extends App
把程序代码写在contructor body里面
object Scala2 extends App
{
println("Hello, Earth")
//如果是从命令行敲入运行命令,则args代表输入的参数
if(args.length > 0)
{
println(f"args[0] is ${args(0)}")
}
}
image.png
枚举
object Scala2 extends App
{
var yellow = MyEnum.YELLOW
println(doWhat(yellow))
def doWhat(color: MyEnum.Value) =
{
if (color == MyEnum.RED) "stop"
else if (color == MyEnum.YELLOW) "hurry up"
else "go"
}
}
object MyEnum extends Enumeration
{
val YELLOW = Value
val GREEN = Value(3,"green")
val RED = Value(5)
val GRAY = Value("gray")
}
第六章 包
*************************************************下面这种方式也可以定义包
You can access names from the enclosing scope:
package a
{
package ab
{
object Utils
{
def percentOf(value: Double, rate: Double) = value * rate / 100
}
package abc
{
class Employer(var salary: Double)
{
//注意utils的包在上一层,这里是可以访问的
def giveRaise(rate: scala.Double) {salary += Utils.percentOf(salary, rate)}
}
}
}
}
************************************************下面这种格式也是支持的
package one.two.three.four
{
class Go {}
}
******************************************Top-of-File Notation格式(像Java一样定义package)
package one.two.three
object Hello extends App{
println("Hello, World !")
}
Package Objects
Every package can have one package object. You define it in the parent package,and it has the same name as the child package:
没有多少用处,看看行了:
package one.two.three.four
//注意package object名称和后面的package名称一样,这样,在package内部class中就可以直接访问package object中字段或者函数了
package object five
{
var NAME = "YAY"
def f() = "Hello"
}
package five
{
class Five
{
var d = NAME //直接访问
println(f) //直接调用
}
}
private[包名]
package one.two.three
import one.two.three.four.W
object Hello extends App
{
println("Hello, World !")
var w = new W;
//注意,下面的PIPI为何可以在这里被访问,注意W类里面private[three]
println(w.PIPI)
}
package one.two.three.four
class W {
private[three] def PIPI = 3.14
}
有几个包已经默认导入
Every Scala program implicitly starts with
import java.lang._
import scala._
import Predef._
import的用法
//像Java里面的 import static语法
import java.awt.Color._
val c1 = RED // Color.RED
val c2 = decode("#ff0000") // Color.decode
printf(f"$c1 $c2")
//Once you import a package, you can access its subpackages with shorter names
import java.awt._
def handler(evt: event.ActionEvent)
{
}
import java.util.{HashMap => JavaHashMap}
import scala.collection.mutable._
var javaHashMap = new JavaHashMap()
var mutableHashMap = new HashMap
import java.util.{HashMap => _, _}//把这个包里面的都导入,只是隐藏HashMap类
import scala.collection.mutable._
有一个典型用法:
val numbers = for (w <- tokens) yield w.toDouble
也可以写为
val numbers = tokens.map(_.toDouble)