2.4 代替++和–
- 问题:你想要在其他语言用++和–操作符增长数值或减少数值,但是在Scala中没有这些运算符。
2.4.1 解决方案
因为val字段时不可改变的,他们不能增加或者减少,但是var整形字段可以通过+=和-=方法改变。
scala> var a = 1 a: Int = 1 scala> a += 1 scala> println(a) 2 scala> a −= 1 scala> println(a) 1
作为额外的好处,可以用同样的方法进行乘法和除法:
scala> var i = 1 i: Int = 1 scala> i *= 2 scala> println(i) 2 scala> i *= 2 scala> println(i) 4 scala> i /= 2 scala> println(i) 2
注意这些符号不是操作符,它们是在声明为var后的Int字段里存在的方法。如果在val字段里使用就会报错编译时错误:
scala> val x = 1 x: Int = 1 scala> x += 1 <console>:9: error: value += is not a member of Int x += 1 ^
- 如前所述,+=,-+,*=,/=这些符号不是操作符,它们是方法。
2.4.2 讨论
另一个好处就是可以在除了Int之外的其他类型里调用同样的方法。举例使用Double和Float:
scala> var x = 1d x: Double = 1.0 scala> x += 1 scala> println(x) 2.0 scala> var x = 1f x: Float = 1.0 scala> x += 1 scala> println(x) 2.0
- 查看更多
Martin Odersky 讨论Actors如何作为一个库添加到Scala中。drdobbs.com
2.5 浮点数比较
- 问题:需要比较两个浮点数,但是和在其他语言一样,本该相等的两个浮点数可能不一样。
2.5.1 解决方案
就像在Java和其他语言一样,通过创建一个指定比较精度的方法来解决这个问题。下面的“约等于”方法进行演示:
def ~=(x: Double, y: Double, precision: Double) = { if ((x - y).abs < precision) true else false } scala> val a = 0.3 a: Double = 0.3 scala> val b = 0.1 + 0.2 b: Double = 0.30000000000000004 scala> ~=(a, b, 0.0001) res0: Boolean = true scala> ~=(b, a, 0.0001) res1: Boolean = true
2.5.2 讨论
当你开始使用浮点数时,会学到0.1加0.1等于0.2:
scala> 0.1 + 0.1 res38: Double = 0.2
但是0.1加0.2并不精确等于0.3
scala> 0.1 + 0.2 res37: Double = 0.30000000000000004
这种微妙误差使得浮点数比较是一个真实的问题:
scala> val a = 0.3 a: Double = 0.3 scala> val b = 0.1 + 0.2 b: Double = 0.30000000000000004 scala> a == b res0: Boolean = false
- 因此最终编写自己的函数使用精度(或公差)来比较浮点数。
可以使用隐式转换在Double类里添加方法,这会使得下面的代码非常可读:
if (a ~= b) ...
或者可以使用相同的方法添加到公用程序对象
object MathUtils { def ~=(x: Double, y: Double, precision: Double) = { if ((x - y).abs < precision) true else false } } //调用 println(MathUtils.~=(a, b, 0.000001))
- 在隐式转换中,~=名字非常可读。但是在共用程序对象中他看起来不太正确,所以叫做approximatelyEqual,equalWithinTolerance或者其他名字更加合适。
- 查看更多:
Arbitrary-precision arithmetic
What every computer scientist should know about floating-point arithmetic
2.6 处理非常大的数
- 问题:写一个程序需要处理非常大的整数或者小数。
2.6.1 解决方案
使用Scala的BigInt和BigDecimal类。
scala> var b = BigInt(1234567890) b: scala.math.BigInt = 1234567890 scala> var b = BigDecimal(123456.789) b: scala.math.BigDecimal = 123456.789
和Java中不同,这些类支持所使用的数值类型的所有操作。
scala> b + b res0: scala.math.BigInt = 2469135780 scala> b * b res1: scala.math.BigInt = 1524157875019052100 scala> b += 1 scala> println(b) 1234567891
数值类型转换:
scala> b.toInt res2: Int = 1234567891 scala> b.toLong res3: Long = 1234567891 scala> b.toFloat res4: Float = 1.23456794E9 scala> b.toDouble res5: Double = 1.234567891E9
为避免发生错误,可先测试是否可以转换成其他类型
scala> b.isValidByte res6: Boolean = false scala> b.isValidChar res7: Boolean = false scala> b.isValidShort res8: Boolean = false scala> if (b.isValidInt) b.toInt res9: AnyVal = 1234567890
2.6.2 讨论
- Scala的BigInt和BigDecimal比Java中的BigInteger和BigDecimal更容易使用,就像其他数值类型一样工作,并且是精确的,比Java改善很多。
使用之前可以检查其他数值类型的最大值:
scala> Byte.MaxValue res0: Byte = 127 scala> Short.MaxValue res1: Short = 32767 scala> Int.MaxValue res2: Int = 2147483647 scala> Long.MaxValue res3: Long = 9223372036854775807 scala> Double.MaxValue res4: Double = 1.7976931348623157E308
在基本数值类型中使用无穷大(PositiveInfinity)和无穷小(NegativeInfinity)
scala> Double.PositiveInfinity res0: Double = Infinity scala> Double.NegativeInfinity res1: Double = -Infinity scala> 1.7976931348623157E308 > Double.PositiveInfinity res45: Boolean = false
- 查看更多
2.7 随机数生成
- 问题:需要创建随机数,比如当测试应用时进行模拟,或者其他的使用情况。
2.7.1 解决方案
通过scala.util.Random类创建随机数:
scala> val r = scala.util.Random r: scala.util.Random = scala.util.Random@13eb41e5 scala> r.nextInt res0: Int = −1323477914
限制随机数的最大值:
scala> r.nextInt(100) res1: Int = 58
- Int返回值在0(包括)和指定值(不包括)之前,所以指定100返回整形从0到99。
创建Float随机数:
// returns a value between 0.0 and 1.0 scala> r.nextFloat res2: Float = 0.50317204
创建Double随机数:
// returns a value between 0.0 and 1.0 scala> r.nextDouble res3: Double = 0.6946000981900997
可以在创建随机数对象的时候指定最大的Int或Long:
scala> val r = new scala.util.Random(100) r: scala.util.Random = scala.util.Random@bbf4061
也可以在随机数对象创建之后设置seed.
r.setSeed(1000L)
2.7.2 讨论
除上述用法外,还可以生成随机字符:
// random characters scala> r.nextPrintableChar res0: Char = H scala> r.nextPrintableChar res1: Char = r
Scala可创建随机长度的数字,对于测试非常有用:
// create a random length range scala> var range = 0 to r.nextInt(10) range: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3) scala> range = 0 to r.nextInt(10) range: scala.collection.immutable.Range.Inclusive = Range(0, 1)
添加for/yield循环修改数值:
scala> for (i <- 0 to r.nextInt(10)) yield i * 2 res0: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 2, 4)
创建随机长度集合的其他类型,下面是Float型:
scala> for (i <- 0 to r.nextInt(10)) yield (i * r.nextFloat) res1: scala.collection.immutable.IndexedSeq[Float] = Vector(0.0, 0.71370363, 1.0783684)
随机参数集合的可打印字符:
scala> for (i <- 0 to r.nextInt(10)) yield r.nextPrintableChar res2: scala.collection.immutable.IndexedSeq[Char] = Vector(x, K, ^, z, w)
- 使用nextPrintableChar方法需要注意,更好的方法是去控制使用的字符,查看更多“如何创建一个字母或字母数字字符的集合”
相反,可以创建一个已知长度的序列,填满随机数:
scala> for (i <- 1 to 5) yield r.nextInt(100) res3: scala.collection.immutable.IndexedSeq[Int] = Vector(88, 94, 58, 96, 82)
- 查看更多:
The Scala Random class
11.29章 “Using a Range”
Scala: How to create a list of alpha or alphanumeric characters
Different ways to create random strings in Scala
2.8 创建数值范围,集合或者数组
- 问题:在一个for循环中或者因为测试目的,需要创建一个数值范围,集合或者数组。
2.8.1 解决方案
使用Int类中的方法创建需要元素的范围:
scala> val r = 1 to 10 r: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
设置步长:
scala> val r = 1 to 10 by 2 r: scala.collection.immutable.Range = Range(1, 3, 5, 7, 9) scala> val r = 1 to 10 by 3 r: scala.collection.immutable.Range = Range(1, 4, 7, 10)
通常使用在for循环中:
scala> for (i <- 1 to 5) println(i) 1 2 3 4 5
创建范围的时候,可以用until代替to
scala> for (i <- 1 until 5) println(i) 1 2 3 4
2.8.2 讨论
转换Range成其他序列,比如Array或者List:
scala> val x = 1 to 10 toArray x: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> val x = 1 to 10 toList x: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
虽然中缀表达式语法在很多情况下是清晰的(比如for循环),但它最好使用下面这种方法:
scala> val x = (1 to 10).toList x: List[Int] = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> val x = (1 to 10).toArray x: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
使得其工作的主要是to和until方法,这个可在RichInt类找到。当键入以下代码时,其实是在调用RichInt类的方法:
1 to
在REPL通过在一个整型上使用这个语法证明to是一个方法:
1.to(10)
与上章结合,创建一个随机长度范围,对于测试非常有用。
scala> var range = 0 to scala.util.Random.nextInt(10) range: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3)
在for/yield结构使用一个范围,你没必要限制你的范围到一个序列化数字:
scala> for (i <- 1 to 5) yield i * 2 res0: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10)
也不必限制你的范围只是整数:
scala> for (i <- 1 to 5) yield i.toDouble res1: scala.collection.immutable.IndexedSeq[Double] = Vector(1.0, 2.0, 3.0, 4.0, 5.0)
- 查看更多
2.9 格式化数字和货币
- 格式化数字控制小数位和逗号,尤其是在打印输出时。
2.9.1 解决方案
对于基本数值转换,可用1.4章说的使用f插值器:
scala> val pi = scala.math.Pi pi: Double = 3.141592653589793 scala> println(f"$pi%1.5f") 3.14159
更多例子:
scala> f"$pi%1.5f" res0: String = 3.14159 scala> f"$pi%1.2f" res1: String = 3.14 scala> f"$pi%06.2f" //6表示6位,包括小数位和小数点,不足补0,和C不一样 res2: String = 003.14
使用2.10之前的版本,或者更喜欢显式调用format方法,使用以下方法:
scala> "%06.2f".format(pi) res3: String = 003.14
通过使用java.text.NumberFormat类的getIntegerInstance方法添加逗号:
scala> val formatter = java.text.NumberFormat.getIntegerInstance formatter: java.text.NumberFormat = java.text.DecimalFormat@674dc scala> formatter.format(10000) res0: String = 10,000 scala> formatter.format(1000000) res1: String = 1,000,000
可以通过getIntegerInstance方法设置locale符号:
scala> val locale = new java.util.Locale("de", "DE") locale: java.util.Locale = de_DE scala> val formatter = java.text.NumberFormat.getIntegerInstance(locale) formatter: java.text.NumberFormat = java.text.DecimalFormat@674dc scala> formatter.format(1000000) res2: String = 1.000.000
通过getInstance格式化处理浮点数:
scala> val formatter = java.text.NumberFormat.getInstance formatter: java.text.NumberFormat = java.text.DecimalFormat@674dc scala> formatter.format(10000.33) res0: String = 10,000.33
使用getCurrencyInstance格式化输入货币:
scala> val formatter = java.text.NumberFormat.getCurrencyInstance formatter: java.text.NumberFormat = java.text.DecimalFormat@67500 scala> println(formatter.format(123.456789)) $123.46 scala> println(formatter.format(1234.56789)) $1,234.57 scala> println(formatter.format(12345.6789)) $12,345.68 scala> println(formatter.format(123456.789)) $123,456.79
这种方法处理国际货币:
scala> import java.util.{Currency, Locale} import java.util.{Currency, Locale} scala> val de = Currency.getInstance(new Locale("de", "DE")) de: java.util.Currency = EUR scala> formatter.setCurrency(de) scala> println(formatter.format(123456.789)) EUR123,456.79
- 查看更多
My printf cheat sheet
Joda Money library
JSR 354: Money and Currency API