才不是什么手机评测的格式呢。
简介 option type 是一种用于消灭空指针(null pointer)的和类型(sum type)。
option type 用于表达可能不存在值的语义,在很多语言中都有相应的实现 。
评测对象为官方实现的 option type,而非第三方库。
主要从是否杜绝空指针和是否方便做空检查(null check)这两点评测。
C++ 的 option C++ 17 开始有了 option 的官方实现 std::optional
引用 cppreference 的一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <string> #include <iostream> #include <optional> std ::optional<std ::string > create(bool b) { if (b) return "Godzilla" ; return {}; } int main () { std ::cout << "create(false) returned " << create(false ).value_or("empty" ) << '\n' ; if (auto str = create(true )) { std ::cout << "create(true) returned " << *str << '\n' ; } }
天啊!居然是手动做空检查!吓得我赶紧看了一下这个类的方法和辅助函数,居然没有 map
或者 flatmap
。
这就是那种送到面前抄都抄错的教科书级例子。
为什么说手动做空检查是不受推荐的呢。
先忘掉 std::optional
的存在,回想以前的代码是怎么确保不会因为值不存在而崩溃的。
1 2 3 4 auto * pData = GetSomeThingMayReturnNull();if (!pData) { std ::cout << "value is " << *pData << '\n' ; }
对比上面使用 std::optional
的代码
1 2 3 if (auto str = create(true )) { std ::cout << "create(true) returned " << *str << '\n' ; }
是不是做法完全一样?都是先空检查再使用值。没毛病。
但是如果做法都一样为什么还要引入 std::optional
类型?
而且引入 std::optional
对于空指针问题没有明显的改善。
程序员是人,人是会犯错误的。忘记做空检查是最常见的错误。所以才会有因为忘了做空检查导致空指针问题。
使用 std::optional
还是得手动做空检查,那跟直接使用 nullptr
表达空值有什么区别?
也就是使用 std::optional
没有办法杜绝空指针,有出错的可能,除非能证明所有对 std::optional
的使用前都做了空检查。
那是不是没有办法杜绝空指针?
答案是否定的。在强类型语言如 Haskell 中会强迫用户检测,否则编译失败。从而杜绝了运行时空指针。
所以 std::optional
错在没有强制做空检查,没有强制做空检查的原因是编译器没有给予支持而空检查和取值操作是分开的两步操作,而不是一步原子操作。
评测结果:不推荐使用
Java 的 option Java 8 也引入了 option type ,类名也是 Optional
引用介绍 optional 的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 public class OptionalDemo { public static void main (String[] args) { Optional<String> name = Optional.of("Sanaulla" ); Optional empty = Optional.ofNullable(null ); if (name.isPresent()) { System.out.println(name.get()); } try { System.out.println(empty.get()); } catch (NoSuchElementException ex) { System.out.println(ex.getMessage()); } name.ifPresent((value) -> { System.out.println("The length of the value is: " + value.length()); }); System.out.println(empty.orElse("There is no value present!" )); System.out.println(name.orElse("There is some value!" )); System.out.println(empty.orElseGet(() -> "Default Value" )); System.out.println(name.orElseGet(() -> "Default Value" )); try { empty.orElseThrow(ValueAbsentException::new ); } catch (Throwable ex) { System.out.println(ex.getMessage()); } Optional<String> upperName = name.map((value) -> value.toUpperCase()); System.out.println(upperName.orElse("No value found" )); upperName = name.flatMap((value) -> Optional.of(value.toUpperCase())); System.out.println(upperName.orElse("No value found" )); Optional<String> longName = name.filter((value) -> value.length() > 6 ); System.out.println(longName.orElse("The name is less than 6 characters" )); Optional<String> anotherName = Optional.of("Sana" ); Optional<String> shortName = anotherName.filter((value) -> value.length() > 6 ); System.out.println(shortName.orElse("The name is less than 6 characters" )); } }
Java 8 的 Optional
有 map
和 flatmap
。很好。
最大的败笔在于提供了 get()
方法。
在没有提供之前可以保证代码不会有空指针。
提供 get()
后,就必须要手动做空检查。
这就跟 C++ 的 std::optional
一样。
根据墨菲定律,可能出错的终将会出错。所以很可能出现直接使用 get()
而忘了做空检查。
如果 get()
抛出的异常是编译时异常还好,这样不做空检查就会编译失败。
但是 get()
抛出的异常 NoSuchElementException
是运行时异常,简直就是变相的 null
。
听说 Optional
还可以是空指针 。
讲个笑话。
1 2 3 Optional<Customer> customer = customerService.retriveCustomerById(22492722 ); customer.map(Customer::getName).orElse("Hello world!" );
「retriveCustomerById返回null没有编译错误」
哇!这就意味着你要先检查 Optional
是不是空,在检查里面有没有值。
简直是帮倒忙,不解决原有的问题还引入了新的问题。
评测结果:凑合使用,不要使用 get()
,更不要使用 orElse(null)
。
没有空指针的未来 在有了正确的 option type 后,你离没有空指针的代码只有一个语法糖的距离。
终有一天我们会跨入无空指针的时代。
到时回看这时的代码。
那一天,人类终于回想起了,曾经一度被空指针所支配的恐怖,还有被囚禁于空检查大括号中的那份屈辱。