第5章——善用类型

Scala 的关键优点之一便是 Scala 是静态类型的。通过静态类型,编译器充当了抵御错误 的第一道防线。它们可以验证当前的对象是否就是想要的类型。这是一种在编译时强制接口 约定的方式。这样的验证可以使我们相信,编译后的代码满足我们的预期。

5.1 类型推断

MakingUseOfTypes/DefiningVariableWithType.scala

val greet: String = "Ahoy!"

MakingUseOfTypes/DefiningVariable.scala

val greet = "Ahoy!"

MakingUseOfTypes/DefiningVariable.scala

println(greet)
println(greet.getClass)

运行结果

Ahoy!
class java.lang.String

MakingUseOfTypes/TypeInference.scala

class TypeInference {
  val greet = "Ahoy!"
}

使用命令

scalac -d bin TypeInference.scala
javap -classpath bin -private TypeInference

反编译结果

Compiled from "TypeInference.scala"
public class TypeInference {
  private final java.lang.String greet;
  public java.lang.String greet();
  public TypeInference();
}

REPL运行结果

scala> val greet = "Ahoy!"
greet: String = Ahoy!

scala> :quit

MakingUseOfTypes/Generics.scala

import java._

var list1: util.List[Int] = new util.ArrayList[Int]
var list2 = new util.ArrayList[Int]

MakingUseOfTypes/Generics2.scala

import java._

var list1 = new util.ArrayList[Int]
var list2 = new util.ArrayList
list2.add(???)

list2 = list1 // Compilation Error

运行结果

Generics2.scala:5: error: type mismatch;
 found   : java.util.ArrayList[Int]
 required: java.util.ArrayList[Nothing]
Note: Int >: Nothing, but Java-defined class ArrayList is invariant in 
type E.
You may wish to investigate a wildcard type such as `_ >: Nothing`. (SLS 
3.2.10)
list2 = list1 // Compilation Error
        ^
one error found

MakingUseOfTypes/Generics3.scala

import java._

var list1 = new util.ArrayList[Int]
var list2 = new util.ArrayList[Any]

var ref1: Int = 1
var ref2: Any = _

ref2 = ref1 //OK

list2 = list1 // Compilation Error

运行结果

Generics3.scala:11: error: type mismatch;
 found   : java.util.ArrayList[Int]
 required: java.util.ArrayList[Any]
Note: Int <: Any, but Java-defined class ArrayList is invariant in type E.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 
3.2.10)
list2 = list1 // Compilation Error
        ^
one error found

5.2 基础类型

MakingUseOfTypes/ExceptionThrowing.scala

def someOp(number: Int) =
  if (number < 10)
    number * 2
  else
    throw new RuntimeException("invalid argument")

运行结果

scala> def madMethod() = { throw new IllegalArgumentException() }
madMethod: ()Nothing

scala> :quit

MakingUseOfTypes/OptionExample.scala

def commentOnPractice(input: String) = {
  //rather than returning null
  if (input == "test") Some("good") else None
}

for (input <- Set("test", "hack")) {
  val comment = commentOnPractice(input)
  val commentDisplay = comment.getOrElse("Found no comments")
  println(s"input: $input comment: $commentDisplay")
}

运行结果

input: test comment: good
input: hack comment: Found no comments

MakingUseOfTypes/UsingEither.scala

def compute(input: Int) =
  if (input > 0)
    Right(math.sqrt(input))
  else
    Left("Error computing, invalid input")

MakingUseOfTypes/UsingEither.scala

def displayResult(result: Either[String, Double]): Unit = {
  println(s"Raw: $result")
  result match {
    case Right(value) => println(s"result $value")
    case Left(err) => println(s"Error: $err")
  }
}

MakingUseOfTypes/UsingEither.scala

displayResult(compute(4))
displayResult(compute(-4))

运行结果

Raw: Right(2.0)
result 2.0
Raw: Left(Error computing, invalid input)
Error: Error computing, invalid input

5.3 返回值类型推断

MakingUseOfTypes/Functions.scala

def function1(): Unit = { Math.sqrt(4) }
def function2 = { Math.sqrt(4) }
def function3 = Math.sqrt(4)
def function4: Double = { Math.sqrt(4) }

运行结果

scala> def function1 { Math.sqrt(4) }
function1: Unit

scala> def function2 = { Math.sqrt(4) }
function2: Double

scala> def function3 = Math.sqrt(4)
function3: Double

scala> def function4 : Double = { Math.sqrt(4) }
function4: Double

scala> :quit

MakingUseOfTypes/NotAllowed.scala

var arr1 = new Array[Int](3)
var arr2: Array[Any] = _

arr2 = arr1 // Compilation ERROR

运行结果

NotAllowed.scala:4: error: type mismatch;
 found   : Array[Int]
 required: Array[Any]
Note: Int <: Any, but class Array is invariant in type T.
You may wish to investigate a wildcard type such as `_ <: Any`. (SLS 
3.2.10)
arr2 = arr1 // Compilation ERROR
       ^
one error found

5.4 参数化类型的型变

MakingUseOfTypes/Trouble.java

//Java code 
class Fruit {} 
class Banana extends Fruit {}
class Apple extends Fruit {}

public class Trouble {
  public static void main(String[] args) {
    Banana[] basketOfBanana = new Banana[2];
    basketOfBanana[0] = new Banana();
    
    Fruit[] basketOfFruits = basketOfBanana; 
    basketOfFruits[1] = new Apple(); 
    
    for(Banana banana : basketOfBanana) {
      System.out.println(banana);
    }    
  }
}

运行结果

Exception in thread "main" java.lang.ArrayStoreException: Apple
	at Trouble.main(Trouble.java:12)

MakingUseOfTypes/OKJava.java

// Java code
ArrayList<Integer> list = new ArrayList<Integer>();
ArrayList<Object> list2 = list; // Compilation error

绕开限制

ArrayList list3 = list;

运行结果

OKJava.java:8: error: incompatible types: ArrayList<Integer> cannot be
converted to ArrayList<Object>
    ArrayList<Object> list2 = list; // Compilation error
                              ^
1 error
class Pet(val name: String) {
  override def toString: String = name
}

class Dog(override val name: String) extends Pet(name)

def workWithPets(pets: Array[Pet]): Unit = {}

MakingUseOfTypes/PlayWithPets.scala

val dogs = Array(new Dog("Rover"), new Dog("Comet"))

MakingUseOfTypes/PlayWithPets.scala

def playWithPets[T <: Pet](pets: Array[T]): Unit =
  println("Playing with pets: " + pets.mkString(", "))

MakingUseOfTypes/PlayWithPets.scala

playWithPets(dogs)

运行结果

Playing with pets: Rover, Comet

MakingUseOfTypes/PlayWithPets.scala

def copyPets[S, D >: S](fromPets: Array[S], toPets: Array[D]): Unit = { //...
}

val pets = new Array[Pet](10)
copyPets(dogs, pets)

MakingUseOfTypes/MyList.scala

class MyList[+T] //...
var list1 = new MyList[Int]
var list2: MyList[Any] = _
list2 = list1 // OK

5.5 隐式类型转换

MakingUseOfTypes/DateHelper1.scala

import scala.language.implicitConversions
import java.time.LocalDate

class DateHelper(offset: Int) {
  def days(when: String): LocalDate = {
    val today = LocalDate.now
    when match {
      case "ago" => today.minusDays(offset)
      case "from_now" => today.plusDays(offset)
      case _ => today
    }
  }
}

MakingUseOfTypes/DateHelper1.scala

implicit def convertInt2DateHelper(offset: Int): DateHelper = new DateHelper(offset)

val ago = "ago"
val from_now = "from_now"

val past = 2.days(ago)
val appointment = 5.days(from_now)

println(past)
println(appointment)

运行结果

2015-08-11
2015-08-18

MakingUseOfTypes/DateHelper.scala

import scala.language.implicitConversions
import java.time.LocalDate

class DateHelper(offset: Int) {
  def days(when: String): LocalDate = {
    val today = LocalDate.now
    when match {
      case "ago" => today.minusDays(offset)
      case "from_now" => today.plusDays(offset)
      case _ => today
    }
  }
}

object DateHelper {
  val ago = "ago"
  val from_now = "from_now"
  implicit def convertInt2DateHelper(offset: Int): DateHelper = new DateHelper(offset)
}

MakingUseOfTypes/DaysDSL.scala

import DateHelper._

object DaysDSL extends App {
  val past = 2.days(ago)
  val appointment = 5.days(from_now)

  println(past)
  println(appointment)
}

运行结果

2015-08-11
2015-08-18

MakingUseOfTypes/DateUtil.scala

object DateUtil {
  val ago = "ago"
  val from_now = "from_now"

  implicit class DateHelper(val offset: Int) {
    import java.time.LocalDate
    def days(when: String): LocalDate = {
      val today = LocalDate.now
      when match {
        case "ago" => today.minusDays(offset)
        case "from_now" => today.plusDays(offset)
        case _ => today
      }
    }
  }
}

MakingUseOfTypes/DateUtil.scala

object UseDateUtil extends App {
  import DateUtil._

  val past = 2.days(ago)
  val appointment = 5.days(from_now)

  println(past)
  println(appointment)
}

运行结果

       5: invokevirtual #73                 // Method
DateUtil$.DateHelper:(I)LDateUtil$DateHelper;
       8: getstatic     #69                 // Field 
DateUtil$.MODULE$:LDateUtil$;
      11: invokevirtual #77                 // Method 
DateUtil$.ago:()Ljava/lang/String;
      14: invokevirtual #83                 // Method 
DateUtil$DateHelper.days:(Ljava/lang/String;)Ljava/time/LocalDate;

5.6 值类

MakingUseOfTypes/ValDateUtil.scala

implicit class DateHelper(val offset: Int) extends AnyVal {

反编译结果

       8: invokevirtual #78                 // Method
DateUtil$.DateHelper:(I)I
      11: getstatic     #74                 // Field 
DateUtil$.MODULE$:LDateUtil$;
      14: invokevirtual #82                 // Method 
DateUtil$.ago:()Ljava/lang/String;
      17: invokevirtual #86                 // Method 
DateUtil$DateHelper$.days$extension:(ILjava/lang/String;)Ljava/time/LocalDate;

MakingUseOfTypes/NameExample.scala

class Name(val value: String) {
  override def toString: String = value
  def length: Int = value.length
}

object UseName extends App {
  def printName(name: Name): Unit = {
    println(name)
  }

  val name = new Name("Snowy")
  println(name.length)
  printName(name)
}

反编译结果

       5: ldc           #76                 // String Snowy
       7: invokespecial #79                 // Method 
Name."<init>":(Ljava/lang/String;)V
      10: putfield      #71                 // Field name:LName;
      13: getstatic     #64                 // Field 
scala/Predef$.MODULE$:Lscala/Predef$;
      16: aload_0
      17: invokevirtual #81                 // Method name:()LName;
      20: invokevirtual #85                 // Method Name.length:()I
      23: invokestatic  #91                 // Method 
scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
      26: invokevirtual #68                 // Method 
scala/Predef$.println:(Ljava/lang/Object;)V
      29: aload_0
      30: aload_0
      31: invokevirtual #81                 // Method name:()LName;
      34: invokevirtual #93                 // Method printName:(LName;)V
      37: return

MakingUseOfTypes/NameValExample.scala

class Name(val value: String) extends AnyVal {

反编译结果

       1: ldc           #78                 // String Snowy
       3: putfield      #75                 // Field 
name:Ljava/lang/String;
       6: getstatic     #64                 // Field 
scala/Predef$.MODULE$:Lscala/Predef$;
       9: getstatic     #83                 // Field Name$.MODULE$:LName$;
      12: aload_0
      13: invokevirtual #85                 // Method 
name:()Ljava/lang/String;
      16: invokevirtual #89                 // Method 
Name$.length$extension:(Ljava/lang/String;)I
      19: invokestatic  #95                 // Method 
scala/runtime/BoxesRunTime.boxToInteger:(I)Ljava/lang/Integer;
      22: invokevirtual #72                 // Method 
scala/Predef$.println:(Ljava/lang/Object;)V
      25: aload_0
      26: aload_0
      27: invokevirtual #85                 // Method 
name:()Ljava/lang/String;
      30: invokevirtual #97                 // Method 
printName:(Ljava/lang/String;)V
      33: return

MakingUseOfTypes/NameVal2Example.scala

val any: Any = name

5.7 使用隐式转换

MakingUseOfTypes/Mask.scala

import MyInterpolator._

val ssn = "123-45-6789"
val account = "0246781263"
val balance = 20145.23

println(mask"""Account: $account
  |Social Security Number: $ssn
  |Balance: $$^$balance
  |Thanks for your business.""".stripMargin)

MakingUseOfTypes/MyInterpolator.scala

object MyInterpolator {
  implicit class Interpolator(val context: StringContext) extends AnyVal {
    def mask(args: Any*): String = {
      val processed = context.parts
        .zip(args)
        .map { item =>
          val (text, expression) = item
          if (text.endsWith("^"))
            s"${text.split('^')(0)}$expression"
          else
            s"$text...${expression.toString.takeRight(4)}"
        }
        .mkString

      new StringBuilder(processed).append(context.parts.last).toString()
    }
  }
}

使用命令

scalac MyInterpolator.scala mask.scala
scala UseInterpolator

运行结果

Account: ...1263
Social Security Number: ...6789
Balance: $20145.23
Thanks for your business.