第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.