- 在 Java 8 之前如果我们要找到集合中第一个匹配元素,要使用外部循环,如下面方法 findFirstMatch() 如果找到一个大于 3 的数字立即返回它,否则返回 null
public Integer findFirstMatch() {
因为在 for 循环中找到第一个大于 3 的数字是 4, 并且立即返回,所以不管集合 integers 再大,也不会遍历整个集合。
List<Integer> integers = Arrays.asList(1, 4, 2, 5, 6, 3);
for(int i: integers) {
if(i > 3) return i;
}
return null;
}
注:不要纠结于上面示例方法的实际用途,实际上集体和匹配条件都该通过参数传入方法的,这里只作演示循环。
那么我们来到 Java 8 之后用 Stream API 该如何实现,翻遍了 Stream API, 能过滤元素的操作也就是 filter 方法,于是尝试这样的写法 Read More - 可能会把捕获外部变量的 Lambda 表达式称为闭包,那么 Java 8 的 Lambda 可以捕获什么变量呢?
- 捕获实例或静态变量是没有限制的(可认为是通过 final 类型的局部变量 this 来引用前两者)
- 捕获的局部变量必须显式的声明为 final 或实际效果的的 final 类型
回顾一下我们在 Java 8 之前,匿名类中如果要访问局部变量的话,那个局部变量必须显式的声明为 final,例如下面的代码放在 Java 7 中是编译不过的
Java 7 要求 version 这个局部变量必须是 final 类型的,否则在匿名类中不可引用。
Read More - 由于 Java 对集合的函数式操作并非原生态的,必须对得到的 stream() 进行过滤等操作,之后还是一个 stream(),一般我们最后返回给调用者需还原为相应的集合。这无法与 Scala 的
for ... yield操作相比。例如下面在使用 Stream API 过滤获得所有大于 3 的数字之后,方法的返回值还应该还原为 List<Integer>, 这个需求非常自然1List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); 2List<Integer> greaterThan3(list) { 3 Stream<Integer> streamOfInteger = list.stream().filter( i -> i > 3); 4 return streamOfInteger.ToIntegerList......; 5}
我们这儿的问题就是如何把上面的 streamOfInteger 转换为 List<Integer>, 有以下几种办法 Read More - Java 8 的 Lambda 表达式的实现方式还是基于已有的字节码指令,由 Lambda 表达式的方法签名结合上下文并经由 SAM 推断出正确的类型来。Java 8 的 Lambda 完整书写格式是
(type parameter1 [type parameter2, ...type parametern]) -> { statements here }
这种标准格式所表示的就是方法签名。
虽不及其他语言的 Lambda 表达式,像 Swift, Scala 可省略参数部分,可用默认参数名$0,$1, 或_, 但 Java 8 的 Lambda 还是可以进行酌情简化- 参数类型总是可省略 --
(x, y) -> { x + y; } - 参数为一个时,参数括号可省略 --
x -> { System.out.println(x); } - 语句为一条时,可省略大括号, 并且语句后不要分号 --
x -> System.out.println(x) - 上面更进一步,如果是单条 return 语句,必须把 return 关键字去掉 --
x -> "Hello " + x - 就差一点,参数部分总是不能省,无参必须写成
() -> System.out.println("hi") - Java 8 中若要近似的实现无参数部分写法,那就是方法引用了 --
System.out::println
- 参数类型总是可省略 --
- 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 下编译不过1List<String> list = new ArrayList<>(); 2list.addAll(new ArrayList<>()); //根据 list.addAll() 上下文推断要创建的类型是 new ArrayList<String>()
Read More - 对于一个 Java 方法 foo(int id, String name); 我们如何能在代码中获得形式参数名 id 和 name 呢?
我们知道通过反射 APIMethod.getGenericParameterTypes()可以获得方法的参数类型,但是对于参数名一般就是 arg0, arg1, arg2 ..., 因为 Java 编译时把形式参数名擦除了。所以对完全擦除了形式参数名的字节码应该是没办法了,但我们自己写的类还是有能力去管控的。
对于自己写的类,有两种办法获得形式参数名,分别是
1) Java8 的 -parameters 编译参数,然后用 Java8 新引入的反射 API Parameter
我们先在 Java8 下运行下面的代码 Read More - 有了前面的 SAM,Lambda 表达式,以及默认接口方法作铺垫后,我们可以去很好的去理解 Java8 用 Lambda 表达式操作集合的基本原理了。此篇我们想要化解的示例代码如下:
1package cc.unmi; 2 3import java.util.Arrays; 4import java.util.Collection; 5import java.util.List; 6import java.util.stream.Collectors; 7 8/** 9 * @author Unmi 10 */ 11public class TestJava8Collection { 12 public static void main(String[] args) { 13 Collection collection = Arrays.asList("abc", "cde", "efg"); 14 List list = collection.stream().filter(x -> x.contains("c")).collect(Collectors.toList()); 15 list.forEach(x->System.out.println(x)); 16 } 17}
如果对其他支持闭包的语言,如 JavaScript, Groovy, Ruby, Scala 等有所了解的话,很容易看出前面的代码输出为abc
cde Read More 进入 Java8 之后我们会发现接口可以有方法实现了,这与我们一直看待 Java 接口的观念产生了冲突,不过也别急,接口中的方法实现必须是一个默认方法,即像
本文旨在探讨 Java8 的默认接口方法存在的合理性,Java8 在这点上如何保持与前面版本的兼容性。1interface Shape { 2 default boolean isShape() { 3 return true; 4 } 5}
Lambda 和方法引用使得 Java 语言更具表现力。说到 Lambda 和方法引用的关系,Lambda 表达式的目的就是让你更为便捷的去绕过对象直接引用方法。
接口应该是相对稳固的,我们应该有这样的经验,类中使用了接口中定义的常量,如果在接口中改变了该常量值,单纯的替换接口对应的 class 文件是不奏效的,因为编译类时其实是把接口中的常量直接固化在类中了。如果类中要体现出最新常量值,那么使用接口的类也要重新编译。即使在接口中添加或改变了方法定义,也不能强制使用到它的类重新编译,早先的类完全可以自由的运行,因为接口中定义的常量和方法的所有内容都在自身,类一旦编译后便可脱离所实现的接口而运转。 Read More