http://www.jianshu.com/p/0379d6a18c2e
为了消除大量参数前置校验的重复代码,可以提取公共的工具类库,例如:
以
可以得到一个推论,对于
用
首先,
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
public boolean isPresent() {
return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Optional)) {
return false;
}
Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
}
http://www.jianshu.com/p/ea41fad02851
Preconditions
绝大多数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);
...
}
一个常见的误区就是:对所有参数都进行限制、约束和检查。我将其称为“缺乏自信”的表现,因为在一些场景下,这样的限制和检查纯属多余。以
C++
为例,如果public
接口传递了指针,对该指针做前置校验无可厚非,但仅仅在此做一次校验,其在内部调用链上的所有private
子函数,如果要传递此指针,应该将其变更为pass by reference
;特殊地,如果是只读,为了做到编译时的安全,pass by const-reference
更是明智之举。可以得到一个推论,对于
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;
...
}
Avoid Pass/Return Null
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();
...
}
Null Object
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
检查的错误拒之门外。Monadic Option
Null Object
虽然很优雅地使得空与非空得到和谐,但也存在一些难以忍受的情况。- 接口发生变化(例如新增加一个方法),代表
Null Object
的类也需要跟着变化; -
Null Object
在不同的场景下重复这一实现方式,其本质是一种模式的重复; - 有时候,引入
Null Object
使得设计变得更加复杂,往往得不偿失;
Option的引入
问题的本质在哪里?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的本质
通过上例的可以看出来,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的实现
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 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)
......
}
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)
JDK8 Optional
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
public boolean isPresent() {
return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Optional)) {
return false;
}
Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
}
从原生的
Java API
创建线程谈起,讲述Scala
对「控制结构」抽象的设计与实现.创建线程
在Java8
之前,创建一个线程的典型方法如下。Thread t = new Thread(new Runnable() {
@Override
public void run() {
...
}
});
t.start();
使用Java8
使用Java8
,可以除去一部分冗余的语法噪声,表达力得到了提升。Thread t = new Thread(() -> {
...
});
t.start();
使用Scala
尝试使用Scala
,对Java
的接口进行包装处理,可以得到更加人性化的接口。首先定义runnable
的控制结构:def runnable(callback: => Unit) = new Runnable {
override def run() = callback
}
然后,定义thread
的关键字,实现Thread
的创建。def thread(callback: Unit) = new Thread(runnable(callback))
用户API
也变得更加简洁,其感觉形如if, while
等内置的控制结构,表达力非常强。thread {
...
}
多样化
上例创建的是匿名的线程,如果想创建有名线程,并将其设置为Daemon
线程,可以如下设计。daemon("daemon-service-1") {
...
}
可以如下实现:def daemon(name: String)(callback: => Unit): Thread = {
val t = new Thread(runnable(callback), name)
t.setDaemon(true)
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler {
override def uncaughtException(t: Thread, e: Throwable) =
error(s"Uncaught exception in ${t.getName}:${e.toString}")
})
t
}
t.setUncaughtExceptionHandler
的入参有点复杂,可以通过「提取函数」改善表达力。def daemon(name: String)(callback: => Unit): Thread = {
val t = new Thread(runnable(callback), name)
t.setDaemon(true)
t.setUncaughtExceptionHandler(handler)
t
}
private def handler = new Thread.UncaughtExceptionHandler {
override def uncaughtException(t: Thread, e: Throwable) =
error(s"Uncaught exception in ${thread.getName}:${e.toString}")
}
接下来,以此类推,可以提取「抽象结构」,改善程序的表现力。private def handler = onException { (thread, except) =>
error(s"Uncaught exception in ${thread.getName}:${except.toString}")
}
private def onException(h: (Thread, Throwable) => Unit) =
new Thread.UncaughtExceptionHandler {
override def uncaughtException(t: Thread, e: Throwable): Unit = h(t, e)
}
也就是说,onException
和runnable, thread, daemon
一样,是对Java
接口的修饰或隐藏。总结
借助于柯里化,及其漂亮的大括号语法,使得Scala
是设计DSL
的利器。
Scala
创建自定义的「控制结构」变得非常容易;同时可以有效地除去冗余的语法噪声,提升代码的可读性。