Scala与Java代码的区别(2)
用Null还是不用,这是个问题
用Null还是不用,这是个问题
Tony Hoare introduced Null references in ALGOL W back in 1965 “simply because it was so easy to implement”, says Mr. Hoare. He talks about that decision considering it “my billion-dollar mistake”.
http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare
TODO: 另开session
用一种类型String
表示两种情况:它的确是个String,或者是个null
,使用前请自行判断
否则,需要用新类型来表达:Option[String]
, String a => Maybe a
, String?
我们在尝试以“通过自己小心谨慎的努力避免出错”来换取“代码的简单”
class Team {
private List<String> members = null;
public Team(List<String> members) {
this.members = members;
}
public int getTeamSize() {
return members.size();
}
public String getFirstMember() {
return members.get(0);
}
}
List<String> members = new ArrayList<String>();
members.add("Juan");
members.add("Chen");
members.add("...");
Team team = new Team(members);
System.out.println(team.getTeamSize());
System.out.println(team.getFirstMember());
如果我们的代码可以保证,构造这个Team
对象时,一定传入一个非空的列表,则不会抛异常。
team
对象,而你不知道它拥有的members
到底是什么样的所以我们在使用这些允许null的语言写代码时,每次使用一个变量或者一个方法的返回值时,都会在心里纠结一下:
它到底会不会是null?
Option[T]
, Some[T]
, None
对于所有可能为null
的变量,或者返回值为null
的方法/函数,都要声明为Option
当一个类型声明不是Option
,我们可以放心的认为它不会为null
(Haskell: Maybe a
, Just a
, Nothing
)
对于所有可能为null
的变量,或者返回值为null
的方法/函数,都要声明为Option
如果一个类型不是Option
,我们可以放心的认为它不会为null
这不是由编译器保证的,只能靠我们自己在写代码时注意。
(由于Scala需要跟Java互通,所以还要兼容null
,Haskell里没有null
)
class Team(members: List[String]) {
def getTeamSize: Int = members.size()
def getFirstMember: Option[String] = members.get(0)
}
为什么:
members
的类型不是Option[List[String]]
,而是List[String]
?getTeamSize
的返回类型是Int
,不是Option[Int]
?getFirstMember
的返回类型是Option[String]
,而不是String
?sealed trait Option[+T]
case class Some[T](t:T) extends Option[T]
case object None extends Option[Nothing]
(1) 命令式风格(通常不推荐):
val name: Option[String] = getNameFromSomeWhere()
if(name.isDefined) {
println("The name is: " + name.get())
}
if(name.isEmpty) {
println("The name is None")
}
因为其中的name.get()
有可能抛出Exception in thread "main" java.util.NoSuchElementException: None.get
,需要小心检查在它之前是否确保它肯定是Some
,在函数式风格的代码中,我们不应该这么做
这是一种比较推荐的方式,特别是需要针对不同的情况进行不同的处理的时候:
val name: Option[String] = getNameFromSomeWhere()
name match {
case Some(n) => // do something with `n`
case None => // do some other things
}
不方便之处,在于有点长。如果我们仅仅需要取值的话,这么写太累了:
name match {
case Some(n) => n + "!!!"
case None => "<empty name>"
}
val name: Option[String] = getNameWhichShouldBeSome()
name match {
case Some(n) => doSomethingWith(n)
case None => shouldNotHappen()
}
哪怕我已经确定name
的类型是Option[String]
,但为了得到其真实的值,我还要写三行代码!哪怕有两行我都不需要!
虽然我也可以使用option.get()
直接获取值,写成doSomethingWith(name.get())
,但前面说了,我们应该尽量避免使用它。
这是让人非常纠结的一件事。好痛恨Option
。
不要老想着先把东西从option
里取出来再用,而要善用它提供的方法,在一个安全的环境中使用它里面的值:
val name: Option[String] = getNameFromSomeWhere()
name.map(_ + "!!!").getOrElse("<empty name>")
name.filter(_.length>10).getOrElse("<a short name>")
name.foreach(println)
name orElse Some("another name")
你不需要判断,不需要取值,只需要在合适的方法中,就可以安全而方便的使用它的值。
list.headOption
list.lastOption
list.find
map.get
...
因为Option
中有map
,所以我们可以用for yield
val name: Option[String] = getNameFromSomeWhere()
val newName: Option[String]= for {
n <- name
} yield n + "!!!"
因为Option
中有foreach
,所以可以用for
for {
n <- name
} println(n + "!!!")
就算我们使用上面的技巧,还是要写很长的代码:
class User(name: Option[String], age: Option[Int], phone: Option[String]) {
def getProfile(): Option[String] = {
for {
n <- name
a <- age
p <- phone
} yield n + " " + a + " " + p
}
def getProfile2(): String = (name, age, phone) match {
case (Some(n), Some(a), Some(p)) => n + " " + a + " " + p
case _ => "some information is missing"
}
}
将在以后Manod那里详细说明,敬请期待