使用 Java8 Optional 的正确姿势

我们知道 Java 8 增加了一些很有用的 API, 其中一个就是 Optional. 如果对它不稍假探索, 只是轻描淡写的认为它可以优雅的解决 NullPointException 的问题, 于是代码就开始这么写了

Optional<User> user = ...... 
if (user.isPresent()) {
    return user.getOrders();
} else {
    return Collections.emptyList();
}

那么不得不说我们的思维仍然是在原地踏步, 只是本能的认为它不过是 User 实例的包装, 这与我们之前写成

User user = .....
if (user != null) {
    return user.getOrders();
} else {
    return Collections.emptyList();
}

实质上是没有任何分别. 这就是我们将要讲到的使用好 Java 8 Optional 类型的正确姿势.

在里约奥运之时, 新闻一再提起五星红旗有问题, 可是我怎么看都看不出来有什么问题, 后来才道是小星星膜拜中央的姿势不对. 因此我们千万也别对自己习以为常的事情觉得理所当然, 丝毫不会觉得有何不妥, 换句话说也就是当我们切换到 Java 8 的 Optional 时, 不能继承性的对待过往 null 时的那种思维, 应该掌握好新的, 正确的使用 Java 8 Optional 的正确姿势. 阅读全文 >>

Scala 中置, 前置, 后置操作符

拟此篇以温习 Scala 对方法调用上的一些约定. 标题中说是关于操作符的事, 其实 Scala 像有了访问方法和属性的一致性原则一样, 可以说操作符与方法更是统一的, 或者说只有方法调用. 此处所称的操作符只不过是 Scala 对无参(prrameterless), 或只有一个参数的方法, 和特殊的四个 unary_+, unary_-, unary_!, unary_~ 方法的便捷的调用约定格式.

一. 中置操作符(对只有一个参数方法的调用约定, a plus b)

case class MyNumber(value: Int) {
  def +(that: MyNumber) = MyNumber(this.value + that.value)
}

调用方式

第一行是用点语法的标准方法调用格式, Scala 在当方法只有一个参数时, 可以省略点, 以及括号, 因此可写为上面第二行种的格式. 所以方法 + 就化身为了中置操作符了. 阅读全文 >>

Java 语言的几个缺陷之六: 无字符串插值

Java 在定义字符串的时候不支持字符串插值, 即不能在字符串中捕获作用域中的变量, 用来组成当前字符串. 而这可以说是其他各种言都具备的基本特性. 例如 Bash 中可以这样

bash-3.2$ name=Yanbin
bash-3.2$ echo "Hello $name"
Hello Yanbin

Java 中不支持类似的方式

String name = "Yanbin";
String greeting = "Hello $name";  //Java 中无法把 $name 替换为 name 变量值 "Yanbin"

而使用 Java 必须用 String 的静态方法 format (注意是静态方法哦) 来间接的格式化出一个字符串, 可以这么写

String.format("Hello %s", "World");
String.format("%1$s %1$s %s %s %2$s", "aa", 10);    //aa aa aa 10 10

我们看到在用 format 格式化时匹配参数会是一个不小的问题, 相同使用法的打印输出方法是 System.out.printf(). 别说字符串插值了, 如果 format 方法是 String 的一个实例方法应用起来都会便利些, 如 "Hello %s".format("World") 会容易读些, Scala 的字符串实例就有 format 方法了. 阅读全文 >>

Java 语言的几个缺陷之五: 多返回值问题

希望 Java 能支持动态对象(匿名对象) 的特性是源于想要 Java 方法能优雅的返回多个值. 目前如果希望 Java 方法返回多个值的做法有返回一个自定义对象, 数组或列表, 或 Map. 这种需求多发生在私有方法上, 但目前的解决办法有如下弊端:

  1. 如果用自定义类来作为返回类型的话, 会使得类过于杂乱, 而且这些自定义类的复用性不高
  2. 数组或列表有太强的顺序依赖, 没有属性名告知每个位置上值的意义, 而且类型都必须为 Object
  3. 返回 Map 的, 在维护 Key 上容易出错(或须为 Key 定义很多字符串常量), 且类型也是为 Object.

说了上面许多, 各位可能还不定清楚我想要的是什么, 其实就是类似于 C# 的匿名对象(或曰动态对象), 看一段 C#  的方法返回一个匿名对象的例子(.NET3.5 开始引入匿名对象, .NET 4.0 后匿名对象可以作为函数值) 阅读全文 >>

Java 语言的几个缺陷之四: 过时的 JavaBean

曾几何时在业务分层结构中的 VO 或 DTO 层充斥着无数的标准 JavaBean 类, 那些碍手脚的 getter/setter 方法简值不忍直视. 或许 JavaBean 设定规范的用意是当某些属性为只读时不提供 setter 方法, 而实际使用时, 因 getter/setter 都同时具备, 那么 JavaBean 的所有私有属性又何异于公有属性呢. 

更别说对于某些形式的属性名, 若属性名为 xCoordinate  时, 它所对应的 getter 方法分别是 getxCoordinate(),一般的 IDE 都会为它自动生成 getXCoordinate() 方法, 这是错误的.  实际上 getXCoordinate() 对应的属性名是 XCoordinate

所以 Play Framework 1 以及 Play Framework 2.4.6 之前的版本采用了字节码增强的技术, 实现了像 Objective-C 的 @property 的特性, 即只要声明公有属性, 编译器为该属性生成默认的 getter/setter 方法, 您也可以手工去覆盖个别默认的 getter/setter 方法.

因此在 Play Framework 中书写的的  model 类就只需要属性了, 像

就这么简单, 想像一下如果我们为一个众多属性的 model 类补全所有的 getter/setter 方法读起来有多恐怖. 阅读全文 >>

Java 语言的几个缺陷之三: 不支持 var 类型推断

虽说 Java 的支持泛型以及近期 Lambda  表达式的加入, 在对类型进行推断上已经很强大了, 但在类型声明的时候仍然略显冗余, 最主要的一点是 Java 不能像 Scala 那样在声明变量有赋值的情况下进行类型推断. 我们先来看下 Java  已经为我们所进行的改进:

List<String> strings = new ArrayList<String>();  //刚引入泛型时我们是这样
List<String> strings = new ArrayList<>();             //后来变成这样了, 可以钻石符号推断, Java 7

List<String> list = new ArrayList<>();
list.addAll(new ArrayList<>()); //根据 list.addAll() 上下文推断要创建的类型是 new ArrayList<String>(), Java 8

interface Foo {
  void dodo();
}
Foo foo = () -> System.out.println("Foo:dodo()");   //Java 8 Lambda 可以根据 Lambda 表达式的签名推断出接口类型
foo.dodo();

在 Java 7 的泛型方法其实也是可以通过声明类型与方法参数来推断要返回的具体类型的. 阅读全文 >>

Java 语言的几个缺陷之二: equals() 比较字符串

对于面向对象的语言不知道除了 Java 还有没别的语言会拿怎么比较两个字符串相等频频作为面试题来考. 原本是在编程语言中两个字符串内容是否相等时用 == 比较时却可能是不对的. 在 Java 中

"ab" == "ab"                                                                                   //true
"ab" == "new String("ab")                                                          //false
"ab" == String.value("ab")                                                         //true
new String("ab").equals(new String("ab"))                            //true
new String("ab").intern() == new String("ab").intern()      //true

在 Java 中明明看到两个字符串内容一样用 == 进行比较多数时候不是你想要的结果, 只有用 equals() 方法才是王道.  使用 Java 的字符串必须了解它内部是怎么存储的. 比于上面的结果我不作细说, 主要涉及到字符串常量池及内部状态, == 比较引用, equals() 比较内容.

Java 还常常对 equals 比较字符串津津乐道, 而我仍然认为它是语言设计上的一个缺陷, 所以 JVM 上的其他编程语言如 Groovy, Scala 纷纷倒勾, 无一不是用== 来比较字符串的内容, 它们也提供字符串引用的比较, 但多少人实际关心两个字符串的引用是否相同呢, 反正字符串设计的是 Immutable 的.

若说是因为 Java 不支持操作符的重载, 但可以像 Scala, Groovy 那样在编译器上下功夫的. 最终我想依然是受累于 100% 源代码与二进制的兼容性, 改进的话会造成早先代码的行为错乱. 阅读全文 >>

Java 语言的几个缺陷之一: 无 Here Document

Java  语言由于一直要保持源代码及二进制的向后兼容, 所以尽管语法上有了很大的演进, 但有些东西仍然无法触及.  不像有些新生代的语言设计时可以博采众长, 或者像 Scala 那种语言向后兼容性的要求没这么苛刻, Scala 只要保持主, 次版本相同时的兼容性. Java 则不同, 1.0 的代码或字节码放到 1.8 下还要能跑.

实际应用中在多个语言切换时, 很自然的会对其他语言与 Java 进行横向对比. 感觉 Java 语言有几个缺陷, 不在此篇中一一列举, 只把第一个不足之处道来.

Java 没有 Here Document 的支持, Here Document 又称 heredoc, hereis, here-string, here-script, 再通俗点讲就是多行字符串(multiline string). 在 Java 中如果要定义多行字符串, 需要连串的加号及换行符(\n), 如下

曾经为了寻求 Java 对 Here Document 的支持, 尝试过 Java 的  APT(Java Annotation Processing Tool), 定义注解 @HereDocument 使用注释, 编译时带 -processor 参数, 但是注释中的格式很容易被 IDE 给自动格式化掉, 见 Java 的多行字符串 Here Document 的实现. 所以这种尝试很快就放弃了, 因为项目中用 Java 和  Scala 混合编程, 真正需要 Here Document 的地方直接用 Scala 代码就行了. 阅读全文 >>

JMockit 如何 Mock 部分方法/属性

现在的 JMockit 已经偷偷升级到了 1.23 版了,在 JVM 上的 Mock 工具中就数它最无敌了,因为它抢夺了最佳控制点  --javaagent,可以说它是无所不能的。一般我们使用 JMockit 是通过两种方式,new MockUpnew Expectations. JMock 不仅能够 Mock 类的所有方法,还能部分 Mock -- 这个是 new Expectations 的默认行为。所以这里我们来看下在使用 new Expectations 的情况下如何对类的部份静态方法或部分实例方法进行 Mock。

大致表述一下,共分为三种情况

  1. 针对类进行 Mock, 只有录制的静态方法被 Mock 住,其他的静态方法或实例方法都会调用实际实现
  2. 针对某一实例进行 Mock,只在调用该实例已录制的方法才被 Mock 住,静态方法或新建实例调用任何方法都是实际实现
  3. 针对类进行 Mock,但录制的是一个实例方法,那么该实例或任何新建实例在调用该录制方法时都会被 Mock 住

不知道上面在说什么,本来就是空洞无凭,所以还是下实例,假定要测试下面这个类,或者说是测试使用到下面类的其他类 阅读全文 >>

sbt 最简单的带输入任务 inputTask

在 sbt 中我们可以定义 settingKey, taskKey 和 inputKey. inputKey 接收输入的任务更具灵活性,虽然在 sbt 中 taskKey 和  inputKey 数量比例为 25:1,但仍然不可忽视了 inputKey 的贡献。

起初在阅读 sbt 关于 inputKey 的资料时,一不小心就被带入到 Parser 上去了。其实还不如开门见山,先跳过 Parser 部份,示范 inputKey 任务中直接处理用户的原生输入。

这里有两个最简单的 sbt inputKey 的示例

一. 命令后非空格起全部输入当作一个字符串

trimmed() 方法能去除了两边的空格,如果是 token(any.* map(_.mkString)).parse 则两边空格都会算上,在 demo1 abc  的 abc 两边的空格也都会算在输入参数里

> demo1 abc "abc"
abc "abc"


sbt  的  input task  参数居然默认是紧接着命令开始的,如上面如果输入是 demo1xxx, demo1 任务接收到的参数是 xxx. 如果同时也定义了另一个任务是 demo1x, 那么输入 demo1xx 就会执行 demo1x 任务,参数是 xx. 从后往前进行匹配任务名。这个还有点奇怪,因为我以前任何时候都是自然的把命令与参数用空格分开,它却可以把命令与参数写在一起。

 


二. 像调用 main 方法一样传入参数

阅读全文 >>