Java 8 返回集合中第一个匹配的元素

在 Java 8 之前如果我们要找到集合中第一个匹配元素,要使用外部循环,如下面方法 findFirstMatch() 如果找到一个大于 3 的数字立即返回它,否则返回 null

  public Integer findFirstMatch() {
    List<Integer> integers = Arrays.asList(1, 4, 2, 5, 6, 3);
    for(int i: integers) {
      if(i > 3) return i;
    }
    return null;
  }

因为在 for 循环中找到第一个大于 3 的数字是 4, 并且立即返回,所以不管集合 integers 再大,也不会遍历整个集合。

注:不要纠结于上面示例方法的实际用途,实际上集体和匹配条件都该通过参数传入方法的,这里只作演示循环。

那么我们来到 Java 8 之后用 Stream API 该如何实现,翻遍了 Stream API, 能过滤元素的操作也就是 filter 方法,于是尝试这样的写法 阅读全文 >>

Java 8 Lambda 捕获外部变量

可能会把捕获外部变量的 Lambda 表达式称为闭包,那么 Java 8 的 Lambda 可以捕获什么变量呢?

  1. 捕获实例或静态变量是没有限制的(可认为是通过 final 类型的局部变量 this 来引用前两者)
  2. 捕获的局部变量必须显式的声明为 final 或实际效果的的 final 类型

回顾一下我们在 Java 8 之前,匿名类中如果要访问局部变量的话,那个局部变量必须显式的声明为  final,例如下面的代码放在 Java 7 中是编译不过的

java7-annymous-capture-local-variable

Java 7 要求 version 这个局部变量必须是 final  类型的,否则在匿名类中不可引用。 阅读全文 >>

Java 8 Stream 如何还原为集合

由于 Java 对集合的函数式操作并非原生态的,必须对得到的 stream() 进行过滤等操作,之后还是一个 stream(),一般我们最后返回给调用者需还原为相应的集合。这无法与 Scala 的 for ... yield 操作相比。例如下面在使用 Stream API 过滤获得所有大于 3 的数字之后,方法的返回值还应该还原为 List<Integer>, 这个需求非常自然

我们这儿的问题就是如何把上面的 streamOfInteger 转换为 List<Integer>, 有以下几种办法 阅读全文 >>

Java 8 Lambda 写法与简化

Java 8 的 Lambda 表达式的实现方式还是基于已有的字节码指令,由 Lambda 表达式的方法签名结合上下文并经由 SAM 推断出正确的类型来。Java 8 的 Lambda 完整书写格式是

(type parameter1 [type parameter2, ...type parametern]) -> { statements here }

这种标准格式所表示的就是方法签名。

虽不及其他语言的 Lambda 表达式,像 Swift, Scala 可省略参数部分,可用默认参数名 $0, $1, 或 _, 但 Java 8 的 Lambda 还是可以进行酌情简化

  1. 参数类型总是可省略   --     (x, y) -> { x + y; }
  2. 参数为一个时,参数括号可省略  --    x -> { System.out.println(x); }
  3. 语句为一条时,可省略大括号, 并且语句后不要分号 --  x -> System.out.println(x)
  4. 上面更进一步,如果是单条 return 语句,必须把 return 关键字去掉  --  x -> "Hello " + x
  5. 就差一点,参数部分总是不能省,无参必须写成 () -> System.out.println("hi")
  6. Java 8 中若要近似的实现无参数部分写法,那就是方法引用了 -- System.out::println

阅读全文 >>

sbt 项目通过 sbt-aspectj-plugin 使用 AspectJ

Java 在运用面向方向编程时,依照  AspectJ 的语法自己书写 *.aj 文件可以得到尽可能大的控制能力。如果是一个 sbt 的项目,有一个 sbt-aspectj-plugin 插件可以帮上我们的忙。那么如何应用这个插件呢? 该插件首页面告诉我们要在 project/plugins.sbt 中加上下面这句话

其他就是参考例子 runnable sample projects,然而这几个例子并非那么直白。所以还是自己做一个最简单的例子来体验 sbt 项目如何使用 AspectJ. 阅读全文 >>

Java 8 的泛型增强

Java 5 引入了泛型,这是一次重大的改进,从此集合中的东西不需要每次显式的去转型。不过 Java 5 还不具备类型推断的能力,所以声明泛型必须写成

List<String> list = new ArrayList<String>();

一直到 Java 6 也是如此。自 Java 7 起泛型增强为可根据声明类型进行推断,所以 Java 7 中可以这么写

List<String> list = new ArrayList<>();  //<> 中的参数可省略,如果类型参数多, 或多层嵌套时很省事

List<String> list = Collections.emptyList(); //见  Java 泛型 -- 依据声明的变量类型自动推断

Java 8 开始对泛型类型推断又进一步增强:可根据方法上下文进行推断,例如下面的代码在 Java 7 下编译不过

阅读全文 >>

早先为 Jackson 写的 Json-Path 支持

今天才发现 Jackson 其实是支持 Json-Path 的,但以前一直不知道,关键是 Jackson 的文档也没见提到。所以很久以前是自己给 Jackson 写了一个简陋的 Json-Path 支持类,这个是为测试代码用的,所以基本够用。想要更全面的功能可使用 https://github.com/jayway/JsonPath 这个项目。对于 Jackson 中如何使用 Json-Path, 我还会进一步研究下。

我写的 RichJsonNode 类是一个 Scala 版本,最早也写过一个 Java 版本,还有一个基于 GSON 的 Java 版本的。这里只贴出 Scala 版源码。支持的基本方法及语法如下:

a/b, a/b[2], a/b[1]/c, a[1]/b, $[0]

selectNode(path), selectString(path), selectInt(path), selectDouble(path), selectBollean(path), hasField(path) 和  arrayLength(path)

源码如下: 阅读全文 >>

Scala 如何测试异常

几年前整理过一篇 JUnit 4 如何正确测试异常,类似的问题:如何在 Scala 中测试异常呢?因 Scala 可以完全采用 JUnit 测试用例的风格,所以当然可以运用 Java 的三种方式来测试异常,即

  1. try { 待测试代码; fail() } catch(某种异常) {断言}
  2. @Test(expected = Exception.class)
  3. @Rule

回到 Scala 中来,我并不那么情愿在 Scala 中使用 JUnit 的 @Test def testMethod() 这样的测试方法风格,而多少采用 BDD 或者叫更 DSL 的风格。

那看看我们 Scala 有些什么独到的异常测试方法

一: intercept 阅读全文 >>

自定义 Jackson 注解与禁用某一特定的注解

Jackson 是 Playfrmework 2 中默认的 JSON 处理框架,先前是  GSON,JSON 是 Playframework 中的第一等公民,可见 Jackson 在 Playframewok 中的重要地位。Jackson 提供了一系列的注解可用,像 @JsonIgnore, @JsonProperty, @JsonUnwrapped, @JsonFilter 等。人的需求总是很难得到满足,所以免不了还是要定义自己的注解。比如有这样一个需求,JavaBean 中被 @MaskField(这个即将成为我第一个自定义的注解) 标记的属性或 getter 方法,总是输出为 ******, 无此标记的属性或方法输出原始值。

我尝试过 @JsonFilter  或是单纯的自定义  JsonSerializer, 并不怎么如意。本人最终的实现方式涉及到

  • @JacksonAnnotationsInside -- 用来创建自己的 @MaskField 注解
  • JsonSerializer  -- 被 @MaskField 标记的字段采用自定义的 JsonSerializer 来序列化
  • JacksonAnnotationIntrospector  -- 禁用某一特定的注解,这样可以在做任意时候启用或禁用 @MaskField 阅读全文 >>

Java 接口常量反模式及如何定义 Java 常量

初学 Java 的人很不经意间就会把常量定义在接口中,大概唯一的理由是接口不能实例化,而使用接口中定义的常量也是不用附着在实例上的。这主要还是 JDK 本身给我们做了很多这样的榜样, 如  java.io.ObjectStreamConstans,多是出现在 Enum 类型到来之前。

其实 Java 的接口常量是一种反模式,理由如下:

1. 接口是不能阻止被实现或继承的,也就是说子接口或实现中是能够覆盖掉常量的定义(重名),这样通过父,子接口(或实现) 去引用常量是可能不一致的
2. 同样的,由于被实现或继承,造成在继承树中可以用大量的接口, 类 或实例去引用 同一个常量,从而造成接口中定义的常量污染了命名空间。(Java 编译器竟然允许使用实例去引用类变量)
3. 接口暗含的意思是:它是需被实现的,代表着一种类型,它的公有成员是要被暴露的 API。而在接口中定义的常量说不上是 API

4. 这点有些重复,Java 允许通过子类去引用父类中定义的常量,各级对像实例去引用父类的常量,所以这会造成相当的混乱不堪。定义的常量不能保证单一的引用方式。

参见: Effective java 第 19 条: 接口只用于定义类型

既然接口中不适于定义常量,那么该在何处为常量安家呢?接口为 实现/继承 而生,如果放在类中,并且这个类是 final,且封闭掉构造方法就行。于是我们先前的接口常量定义 阅读全文 >>