Scala Cookbook翻译 Chapter 2.Numbers 第一部分

总体介绍

  • Scala中所有的数值类型都是对象,包括Byte,Char,Double,Float,Int,Long和Short。这7个数值类型继承自AnyVal特质,Unit和Boolean类被认为是“非数字的值类型”。
  • 这七个内置的数值类型有相同的数据范围,和java中是等价的。

    Data    type Range
    Char    16-bit unsigned Unicode character
    Byte    8-bit signed value
    Short    16-bit signed value
    Int    32-bit signed value
    Long    64-bit signed value
    Float    32-bit IEEE 754 single precision float
    Double    64-bit IEEE 754 single precision float
    
  • 除了这些类型之外,Boolean有true或者false的值。

  • 如果想要知道这些数据范围的精确值,可以通过Scala的REPL中查到。

    scala> Short.MinValue
    res0: Short = −32768
    
    scala> Short.MaxValue
    res1: Short = 32767
    
    scala> Int.MinValue
    res2: Int = −2147483648
    
    scala> Float.MinValue
    res3: Float = −3.4028235E38
    
  • 除了这些基本类型外,理解BigInt和BigDecimal类是有帮助的,以及在scala.math包中封装的方法。这些都包括在本章中。
  • 复数和日期:如果需要比标准Scala更强大的math类,可以查看Spire project,里面包括了像有理数、复数、真值等等,或者查看 ScalaLab,提供了可以在Scala使用的科学计算Matlab-like。
  • Java中Joda Time项目用于处理日期是非常流行,一个叫做nscala-time的项目实现了Scala包裹Joda Time,可以让你用Scala方法写日期表达式,包括下面例子:

    DateTime.now // returns org.joda.time.DateTime
    DateTime.now + 2.months
    DateTime.nextMonth < DateTime.now + 2.months
    (2.hours + 45.minutes + 10.seconds).millis
    

2.1 从字符串中解析数字

  • 问题:要把字符串转成Scala的任意数值类型。

    2.1.1 解决方案

  • 在字符串后面使用to*方法(StringLike特质)

    scala> "100".toInt
    res0: Int = 100
    
    scala> "100".toDouble
    res1: Double = 100.0
    
    scala> "100".toFloat
    res2: Float = 100.0
    
    scala> "1".toLong
    res3: Long = 1
    
    scala> "1".toShort
    res4: Short = 1
    
    scala> "1".toByte
    res5: Byte = 1
    
  • 小心,因为这些方法会抛出Java的NumberFormatException异常:

    scala> "foo".toInt
    java.lang.NumberFormatException: For input string: "foo"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java)
        at java.lang.Integer.parseInt(Integer.java:449)
        ... more output here ...
    
  • BigInt和BigDecimal实例也可以直接从字符串中创建(也可以抛出NumberFormatException异常):

    scala> val b = BigInt("1")
    b: scala.math.BigInt = 1
    
    scala> val b = BigDecimal("3.14159")
    b: scala.math.BigDecimal = 3.14159
    

    2.1.2 进制处理

  • 如果需要使用其他基于10的基数进行计算,可以发现在Scala的Int类里的toInt方法没有一个让你根据进制处理的方法。解决这个问题,使用java.lang.Integer类里的parseInt方法。

    scala> Integer.parseInt("1", 2)
    res0: Int = 1
    
    scala> Integer.parseInt("10", 2)
    res1: Int = 2
    
    scala> Integer.parseInt("100", 2)
    res2: Int = 4
    
    scala> Integer.parseInt("1", 8)
    res3: Int = 1
    
    scala> Integer.parseInt("10", 8)
    res4: Int = 8
    
  • 如果你是隐式转换的粉丝,你可以创建一个隐式类和方法来解决这个问题:

    implicit class StringToInt(s: String) {
        def toInt(radix: Int) = Integer.parseInt(s, radix)
    }
    
  • 定义一个隐类并添加到范围里,添加一个toInt方法,以一个基数参数到String类,现在可以调用这个方法代替Integer.parseInt:

    scala> implicit class StringToInt(s: String) {
        | def toInt(radix: Int) = Integer.parseInt(s, radix)
        | }
    defined class StringToInt
    
    scala> "1".toInt(2)
    res0: Int = 1
    
    scala> "10".toInt(2)
    res1: Int = 2
    
    scala> "100".toInt(2)
    res2: Int = 4
    
    scala> "100".toInt(8)
    res3: Int = 64
    
    scala> "100".toInt(16)
    res4: Int = 256
    

    2.1.3 讨论

  • 如果使用过Java的字符串转数值类型,就会对NumberFormatException很熟悉,然后Scala没有异常检查,所以可能会想以不同的方式处理这种情况。
  • 首先,没有必须声明在Scala方法可能会抛出一个异常,所以像这样声明一个Scala方法是完全合法的:

    // not required to declare "throws NumberFormatException"
    def toInt(s: String) = s.toInt
    
  • 如果你允许像这样抛出一个异常,你方法的调用者可能需要提前知道这可能发生,考虑给你的方法添加一个Scala文档。
  • 如果你更喜欢给你的方法抛出一个异常,使用@throws注解进行标记:

    @throws(classOf[NumberFormatException])
    def toInt(s: String) = s.toInt
    
  • 如果这个方法在Java代码中被调用是需要进行@throws注解标注的,详细看第17.2章。
  • 然而,在Scala中这种情况通常使用Option/Some/None模式进行处理,详细描述见20.6章,使用这种方式如下定义方法:

    def toInt(s: String):Option[Int] = {
        try {
            Some(s.toInt)
        } catch {
            case e: NumberFormatException => None
        }
    }
    
  • 现在可以根据不同需求用不同的方法调用toInt,一种使用getOrElse:

    println(toInt("1").getOrElse(0)) // 1
    println(toInt("a").getOrElse(0)) // 0
    
    // assign the result to x
    val x = toInt(aString).getOrElse(0)
    
  • 另一个方法就是使用匹配表达式,使用匹配表达式打印toInt结果:

    toInt(aString) match {
        case Some(n) => println(n)
        case None => println("Boom! That wasn't a number.")
    }
    
  • 使用匹配表达式分配结果给一个变量:

    val result = toInt(aString) match {
        case Some(x) => x
        case None => 0 // however you want to handle this
    }
    
  • 如果这些例子还是没有让你很好的使用 Option/Some/None方法,可以查看第10章和第11章,这个模式在处理集合的时候会非常有用和方便。

    2.1.4 Option的选择

  • 如果你喜欢Option/Some/None思想,但是需要访问异常信息,有几个额外的可能性:
    • Try, Success, and Failure (2.10描述)
    • Either, Left, and Right
  • 查看更多

    The StringLike trait

2.2 数值类型间转换(强转)

  • 问题:需要从一个数值类型转换成另一个数值类型,比如Int转成Double。

    2.2.1 解决方案

  • 除了在Java中使用的“cast”方法,在所有数值类型上都可使用to*方法,演示如下:

    scala> val b = a.to[Tab]
    toByte toChar toDouble toFloat toInt toLong
    toShort toString
    
    scala> 19.45.toInt
    res0: Int = 19
    
    scala> 19.toFloat
    res1: Float = 19.0
    
    scala> 19.toDouble
    res2: Double = 19.0
    
    scala> 19.toLong
    res3: Long = 19
    
    scala> val b = a.toFloat
    b: Float = 1945.0
    

    讨论:

  • 在Java中通过强转从一个数值类型转换到另一个数值类型:

    int a = (int) 100.00;
    
  • 但是在Scala中使用to*方法即可。
  • 如果你想避免潜在的转换错误,可以在转换之前尝试使用相关的isValid方法测试类型是否可以被转换。举例,Double对象有一个isValidInt和isValidShort方法:

    scala> val a = 1000L
    a: Long = 1000
    
    scala> a.isValidByte
    res0: Boolean = false
    
    scala> a.isValidShort
    res1: Boolean = true
    
  • 查看更多

    The RichDouble class

2.3 重写默认数值类型

  • 问题:Scala会自动给数值分配类型,当你创建一个数值字段时需要重写默认类型。

    2.3.1 解决方案:

  • 如果你把1赋值给一个变量,Scala会分配Int类型:

    scala> val a = 1
    a: Int = 1
    
  • 下面的例子显示一种重写简单数值类型的方法:

    scala> val a = 1d
    a: Double = 1.0
    
    scala> val a = 1f
    a: Float = 1.0
    
    scala> val a = 1000L
    a: Long = 1000
    
  • 另一个方法是用一个类型进行注解:

    scala> val a = 0: Byte
    a: Byte = 0
    
    scala> val a = 0: Int
    a: Int = 0
    
    scala> val a = 0: Short
    a: Short = 0
    
    scala> val a = 0: Double
    a: Double = 0.0
    
    scala> val a = 0: Float
    a: Float = 0.0
    
  • 冒号后面的间距不重要,如果首选的话使用下面的格式:

    val a = 0:Byte
    
  • 根据 Scala Style Guide,这些例子显示了首选的注解类型,但是个人而言,当分配类型给变量时我更喜欢下面的语法,在变量名后面指定类型:

    scala> val a:Byte = 0
    a: Byte = 0
    
    scala> val a:Int = 0
    a: Int = 0
    
  • 可以在数字之前添加0x或者0X创建十六进制数值,然后可以把他们当做整形或者长整形存储:

    scala> val a = 0x20
    a: Int = 32
    
    // if you want to store the value as a Long
    scala> val a = 0x20L
    a: Long = 32
    
  • 在一些罕见的情况下,可能需要利用类型归属,Stack Overflow显示了一个String向上转型到Object的例子:

    scala> val s = "Dave"
    s: String = Dave
    
    scala> val p = s: Object
    p: Object = Dave
    
  • 这个技术和这章节很像,向上转型常见于类型归属。官方文档描述如下:
  • 归属是基本的向上转型,编译执行时的类型检查,使用并不常见。但是可能在某些情况发生,最常见的情况是使用一个单一序列参数调用一个可变方法。

    2.3.2 讨论

  • 创建对象实例的时候很有用,大体语法如下:

    // general case
    var [name]:[Type] = [initial value]
    
    // example
    var a:Short = 0
    
  • 当需要在一个类中初始化数值变量字段时,这种形式将非常有用。

    class Foo {
        var a: Short = 0 // specify a default value
        var b: Short = _ // defaults to 0
    }
    
  • 当分配一个初始化值时可以使用下划线作为占位符,当创建类变量时起作用,但是在其他地方不起作用,比如在一个方法里不起作用。对于数值类型这不是问题,你可以指定值为0,但是在其他类型中,你可以在方法中用下面的方法:

    var name = null.asInstanceOf[String]
    
  • 更好的方式是使用 Option/Some/None模式,它有助于消除你代码中的空值,这是一个非常好的东西。你可以在Scala最好的库和框架中看到这个模式,比如Play框架。最好的例子看12.4章。
  • 更多讨论这个话题的信息看20.5章和20.6章。
  • 查看更多

    The Scala Style Guide
    What is the purpose of type ascriptions in Scala?