Tony Hoare, null
的發明者在2009
年公開道歉,並將此錯誤稱爲Billion-Dollar Mistake
。java
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.程序員
絕大多數public
的函數對於傳遞給它們的參數都須要進行限制。例如,索引值不能爲負數,對象引用不能爲空等等。良好的設計應該保證「發生錯誤應儘快檢測出來」。爲此,經常會在函數入口處進行參數的合法性校驗。編程
爲了消除大量參數前置校驗的重複代碼,能夠提取公共的工具類庫,例如:設計模式
public final class Precoditions { private Precoditions() { } public static void checkArgument(boolean exp, String msg = "") { if (!exp) { throw new IllegalArgumentException(msg); } } public static <T> T requireNonNull(T obj, String msg = "") { if (obj == null) throw new NullPointerException(msg); return obj; } public static boolean isNull(Object obj) { return obj == null; } public static boolean nonNull(Object obj) { return obj != null; } }
使用requireNonNull
等工具函數時,經常import static
,使其更具表達力。數組
import static Precoditions.*;
系統中大量存在前置校驗的代碼,例如:安全
public BigInteger mod(BigInteger m) { if (m.signum() <= 0) throw new IllegalArgumentException("must be positive: " + m); ... }
能夠被重構得更加整潔、緊湊,且富有表現力。數據結構
public BigInteger mod(BigInteger m) { checkArgument(m.signum() > 0 , "must be positive: " + m); ... } checkArgument(count > 0, "must be positive: %s", count);</pre>
一個常見的誤區就是:對全部參數都進行限制、約束和檢查。我將其稱爲「缺少自信」的表現,由於在一些場景下,這樣的限制和檢查純屬多餘。dom
以C++
爲例,若是public
接口傳遞了指針,對該指針作前置校驗無可厚非,但僅僅在此作一次校驗,其在內部調用鏈上的全部private
子函數,若是要傳遞此指針,應該將其變動爲pass by reference
;特殊地,若是是隻讀,爲了作到編譯時的安全,pass by const-reference
更是明智之舉。ide
能夠獲得一個推論,對於private
的函數,你對其調用具備徹底的控制,天然保證了其傳遞參數的有效性;若是非得對其private
的參數進行前置校驗,應該使用assert
。例如:函數
private static void <T> sort(T a[], int offset, int length) { assert a != null; assert offset >= 0 && offset <= a.length; assert length >= 0 && length <= a.length - offset; ... }
private final List<Product> stock = new ArrayList<>(); public Product[] filter(Predicate<Product> pred) { if (stock.isEmpty()) return null; ... }
客戶端不得不爲此校驗返回值,不然將在運行時拋出NullPointerException
異常。
Product[] fakes = repo.filter(Product::isFake); if (fakes != null && Arrays.asList(fakes).contains(Product.STILTON)) { ... }
通過社區的實踐總結出,返回null
的數組或列表是不明智的,而應該返回零長度的數組或列表。
private final List<Product> stock = new ArrayList<>(); private static final Product[] EMPTY = new Product[0]; public Product[] filter(Predicate<Product> pred) { if (stock.isEmpty()) return EMPTY; ... }
對於返回值是List
的,則應該使用Collections.emptyXXX
的靜態工廠方法,返回零長度的列表。
private final List<Product> stock = new ArrayList<>(); public Product[] filter(Predicate<Product> pred) { if (stock.isEmpty()) return Collections.emptyList(); ... }
private final List<Product> stock = new ArrayList<>(); public Product[] filter(Predicate<Product> pred) { if (stock.isEmpty()) return Collections.emptyList(); ... }
Collections.emptyList()
工廠方法返回的就是一個Null Object
,它的實現大體是這樣的。
public final class Collections { private Collections() { } private static class EmptyList<E> extends AbstractList<E> implements RandomAccess, Serializable { private static final long serialVersionUID = 8842843931221139166L; public Iterator<E> iterator() { return emptyIterator(); } public ListIterator<E> listIterator() { return emptyListIterator(); } public int size() {return 0;} public boolean isEmpty() {return true;} public boolean contains(Object obj) {return false;} public boolean containsAll(Collection<?> c) { return c.isEmpty(); } public Object[] toArray() { return new Object[0]; } public <T> T[] toArray(T[] a) { if (a.length > 0) a[0] = null; return a; } public E get(int index) { throw new IndexOutOfBoundsException("Index: "+index); } public boolean equals(Object o) { return (o instanceof List) && ((List<?>)o).isEmpty(); } public int hashCode() { return 1; } private Object readResolve() { return EMPTY_LIST; } } @SuppressWarnings("rawtypes") public static final List EMPTY_LIST = new EmptyList<>(); @SuppressWarnings("unchecked") public static final <T> List<T> emptyList() { return (List<T>) EMPTY_LIST; } }
Null Object
表明了一種例外,而且這樣的例外具備特殊性,它是一個有效的對象,對於用戶來講是透明的,是感受不出來的。使用Null Object
,遵循了"按照接口編程"的良好設計原則,而且讓用戶處理空和非空的狀況獲得了統一,使得因缺失null
檢查的錯誤拒之門外。
Null Object
雖然很優雅地使得空與非空獲得和諧,但也存在一些難以忍受的狀況。
接口發生變化(例如新增長一個方法),表明Null Object
的類也須要跟着變化;
Null Object
在不一樣的場景下重複這一實現方式,其本質是一種模式的重複;
有時候,引入Null Object
使得設計變得更加複雜,每每得不償失;
問題的本質在哪裏?null
表明的是一種空,與其對立的一面即是非空。若是將其放置在一個容器中,問題便獲得了很完美的解決。也就是說,若是爲空,則該容器爲空容器;若是不爲空,則該值包含在容器之中。
用Scala
語言表示,能夠創建一個Option
的容器。若是存在,則用Some
表示;不然用None
表示。
sealed abstract class Option[+A] { def isEmpty: Boolean def get: A } case class Some[+A](x: A) extends Option[A] { def isEmpty = false def get = x } case object None extends Option[Nothing] { def isEmpty = true def get = throw new NoSuchElementException("None.get") }
這樣的表示有以下幾個方面的好處:
對於存在與不存在的值在類型系統中得以表示;
顯式地表達了不存在的語義;
編譯時保證錯誤的發生;
問題並無那麼簡單,若是以下使用,並無發揮出Option
的威力。
def double(num: Option[Int]) = { num match { Some(n) => Some(n*2) None => None } }
將Option
視爲容器,讓其處理Some/None
獲得統一性和一致性。
def double(num: Option[Int]) = num.map(_*2)
也可使用for Comprehension
,在某些場景下將更加簡潔、漂亮。
def double(num: Option[Int]) = for (n <- num) yield(n*2)
經過上例的能夠看出來,Option
本質上是一個Monad
,它是一種函數式的設計模式。用Java8
簡單地形式化一下,能夠以下形式化地描述一個Monad
。
interface M<A> { M<B> flatMap(Function<A, M<B>> f); default M<B> map(Function<A, B> f) { return flatMap(a -> unit(f(a))); } static M<A> unit(A a) { ... } }
同時知足如下三條規則:
右單位元(identity),既對於任意的Monad m
,則m.flatMap(unit) <=> m
;
左單位元(unit),既對於任意的Monad m
,則unit(v).flatMap(f) <=> f(v)
;
結合律,既對於任意的Monad m
, 則m.flatMap(g).flatMap(h) <=> m.flatMap(x => g(x).flatMap(h))
在這裏,咱們將Monad
的數學語義簡化,爲了更深入的瞭解Monad
的本質,必須深刻理解Cathegory Theory
,這比如你要吃披薩的烹飪精髓,得學習意大利的文化。但這對於大部分的程序員要求優勢太高,但不排除部分程序員追求極致。
Option
的設計與List
類似,有以下幾個方面須要注意:
Option
是一個Immutablity Container
,或者是一個函數式的數據結構;
sealed
保證其類型系統的封閉性;
Option[+A]
類型參數是協變的,使得None
能夠成爲任意Option[+A]
的子對象;
能夠被for Comprehension
調用;
sealed abstract class Option[+A] { self => def isEmpty: Boolean def get: A final def isDefined: Boolean = !isEmpty final def getOrElse[B >: A](default: => B): B = if (isEmpty) default else this.get final def map[B](f: A => B): Option[B] = if (isEmpty) None else Some(f(this.get)) final def flatMap[B](f: A => Option[B]): Option[B] = if (isEmpty) None else f(this.get) final def filter(p: A => Boolean): Option[A] = if (isEmpty || p(this.get)) this else None final def filterNot(p: A => Boolean): Option[A] = if (isEmpty || !p(this.get)) this else None final def withFilter(p: A => Boolean): WithFilter = new WithFilter(p) class WithFilter(p: A => Boolean) { def map[B](f: A => B): Option[B] = self filter p map f def flatMap[B](f: A => Option[B]): Option[B] = self filter p flatMap f def foreach[U](f: A => U): Unit = self filter p foreach f def withFilter(q: A => Boolean): WithFilter = new WithFilter(x => p(x) && q(x)) } final def foreach[U](f: A => U) { if (!isEmpty) f(this.get) } final def collect[B](pf: PartialFunction[A, B]): Option[B] = if (!isEmpty) pf.lift(this.get) else None final def orElse[B >: A](alternative: => Option[B]): Option[B] = if (isEmpty) alternative else this } case class Some[+A](x: A) extends Option[A] { def isEmpty = false def get = x } case object None extends Option[Nothing] { def isEmpty = true def get = throw new NoSuchElementException("None.get") }
for Comprehension
的本質for Comprehension
實際上是對具備foreach, map, flatMap, withFilter
訪問方法的容器的一個語法糖。
首先,pat <- expr
的生成器被解釋爲:
// pat <- expr pat <- expr.withFilter { case pat => true; case _ => false }
若是存在一個生成器和yield
語句,則解釋爲:
// for (pat <- expr1) yield expr2 expr1.map{ case pat => expr2 }
若是存在多個生成器,則解釋爲:
// for (pat1 <- expr1; pat2 <- expr2) yield exprN expr.flatMap { case pat1 => for (pat2 <- expr2) yield exprN } expr.flatMap { case pat1 => expr2.map { case pat2 => exprN }}
對於for loop
,可解釋爲:
// for (pat1 <- expr1; pat2 <- expr2;...) exprN expr.foreach { case pat1 => for (pat2 <- expr2; ...) yield exprN }
對於包含guard
的生成器,可解釋爲:
// pat1 <- expr1 if guard pat1 <- expr1.withFilter((arg1, arg2, ...) => guard)
Stream
Promise
Either
Try
Validation
Transaction
後需文章將逐一解開它們的面紗,敬請期待!