Java8 新增了非常多的特性,主要有以下几个:
- Lambda 表达式:Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)
- 函数式接口:指的是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,这样的接口可以隐式转换为 Lambda 表达式
- 方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码
- Stream API:新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
- Optional 类:Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
- Date Time API:加强对日期与时间的处理。
一、Lambda表达式
Lambda表达式,也称为闭包,它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。
Lambda表达式的语法格式:
(parameters) -> expression
或
(parameters) -> {statements; }
Lambda编程风格,可以总结为四类:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
- 可选的大括号:如果主体只包含了一行时,就不需要使用大括号;当主体包含多行时,需要使用大括号
- 可选的返回关键字:如果表达式中的语句块只有一行时,则可以不使用return语句返回值的类型也由编译器推理得出;如果语句块有多行,需要指定明表达式返回值是的接口
变量作用域
Lambda 表达式可以引用类成员和局部变量,但是会将这些变量隐式得转换成final
二、函数式编程
概念:
“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。
常用函数:
Function<T, R> 接口 R apply(T t); 有参数有返回值
Supplier 接口 T get(); 没参数有返回值
Consumer 接口 void accept(T t); 有参数没返回值
三、方法引用
方法引用使用一对冒号::,通过方法的名字来指向一个方法。
下面,我们在Car类中定义了 4 个方法作为例子来区分 Java 中 4 种不同方法的引用。
public class Car {
//Supplier是jdk1.8的接口,这里和lamda一起使用了
public static Car create(final Supplier<Car> supplier) {
return supplier.get();
}
public static void collide(final Car car) {
System.out.println("Collided " + car.toString());
}
public void follow(final Car another) {
System.out.println("Following the " + another.toString());
}
public void repair() {
System.out.println("Repaired " + this.toString());
}
}
3.1 构造器引用
语法:
Class::new,或者更一般的Class< T >::new
实例如下:
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
3.2、静态方法引用
语法
Class::static_method,
实例如下:
cars.forEach( Car::collide );
3.3、类的成员方法引用
语法
Class::method
实例如下:
cars.forEach( Car::repair );
3.4、实例对象的成员方法的引用
语法
instance::method
实例如下
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
四 Stream API
4.1 Stream流简介:
Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。
4.2 Stream对象的创建:
使用数组构建
String[] strArray = new String[] {"a", "b", "c"};
Stream<String> stream = Stream.of(strArray);
Stream<String> stream = Arrays.stream(strArray);
利用集合构建(不支持Map集合)
//在 Java 8 中, 集合接口有两个方法来生成流:
//stream() − 为集合创建串行流。
//parallelStream() − 为集合创建并行流。
stream = Arrays.asList(strArray).stream();
数值Stream的构建
//[1,3)
IntStream stream2 = IntStream.range(1, 3);
//[1,3]
IntStream stream3 = IntStream.rangeClosed(1, 3)
= list.stream();
对于基本数值类型,目前有三种对应的包装类型 Stream:IntStream、LongStream、DoubleStream。
4.3 Stream转换为其它类型:
// 1. 转换为Array
String[] strArray = stream.toArray(String[]::new);
// 2. 转换为Collection
Stream<String> stream = Stream.of("hello","world","tom");
//List<String> list1 = stream.collect(Collectors.toList());
//List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
//Set<String> set3 = stream.collect(Collectors.toSet());
Set<String> set4 = stream.collect(Collectors.toCollection(HashSet::new));
// 3. 转换为String
Stream<String> stream = Stream.of("hello","world","tom");
String str = stream.collect(Collectors.joining()).toString();
4.4 Stream常用操作
在使用流的时候,你首先需要从一些来源中获取一个流,执行一个或者多个中间操作,然后执行一个最终操作。
中间操作(Intermediate)包括:
filter、map、flatMap、peel、distinct、sorted、limit和substream
终止操作(Terminal):
forEach、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst和 findAny
java.util.stream.Collectors是一个非常有用的实用类。该类实现了很多归约操作,例如将流转换成集合和聚合元素。
4.4.1 map/flatMap
map/flatMap映射 根据指定的Function接口把 Stream中 的每一个元素,映射成另外一个元素。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
private static void mapTes() {
Stream<String> wordList = Stream.of("hello","world","tom");
//转换大写
Stream<String> stringStream = wordList.map(String::toUpperCase);
List<String> strings = stringStream.collect(Collectors.toList());
//也可以直接使用forEach循环输出
//strings.forEach(System.out::println);
//map生成的是个1:1映射,每个输入元素,都按照规则转换成为另外一个元素。还有一些场景,是一对多映射关系的,这时需要 flatMap。
//flatMap 把 stream1 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终新的 stream2 里面已经没有 List 了,都是直接的数字。
Stream<List<Integer>> streamList = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
// streamList.forEach(e->System.out.println(e));//输出[1] [2, 3] [4, 5, 6]
Stream<Integer> streamList2 = streamList. flatMap(e -> {
System.out.println("streamList2: "+e);
return e.stream();
});
System.out.println("streamList: "+streamList2); //java.util.stream.ReferencePipeline$7@694f9431
streamList2.forEach(e->System.out.println(e));//输出1 2 3 4 5 6
}
4.4.2 forEach 遍历
接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。
forEach 是 terminal 操作,执行完stream就不能再用了
private static void forEachTes() {
List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
list.stream().forEach(System.out::println);
}
4.4.3 filter 过滤
对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。
private static void filterTes() {
List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
List<String> collect = list.stream().filter(s -> s.length() > 4).collect(Collectors.toList());
collect.forEach(System.out::println);
}
4.4.4 peek
对每个元素执行操作并返回一个新的 Stream
注意:peek是一个intermediate 操作,调用peek之后,一定要有一个最终操作
private static void peekTes() {
List<String> list = Arrays.asList("one", "two", "three", "four");
List<String> list2 = list.stream()
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("第一次符合条件的值为: " + e))
.filter(e->e.length()>4)
.peek(e -> System.out.println("第二次符合条件的值为: " + e))
.collect(Collectors.toList());
list2.stream().forEach(System.out::println);//打印结果为 three
}
4.4.5 sort 排序
排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据list是不会被修改的。
private static void sortTes() {
List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
//按照字符串的长短排序
list.stream().sorted((s1,s2)->s1.length()-s2.length()).forEach(System.out::println);
}
4.4.6 match 匹配
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。
private static void matchTes() {
List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
boolean allMatch = list.stream().allMatch((s)->s.startsWith("j"));
System.out.println(allMatch);
}
4.4.7 Reduce 规约/合并
这是一个最终操作,允许通过指定的函数来将stream中的多个元素规约合并为一个元素。
Stream.reduce,常用的方法有average, sum, min, max, and count,返回单个的结果值, 并且reduce操作每处理一个元素总是创建一个新值。
1.先调用stream方法
2.再排序,按照字符串的长度进行排序,长的在前短的再后
3.再过滤,字符串必须是以字符’j’开头的
4.再进行映射,把每个字符串后面拼接上"_briup"
5.再调用reduce进行合并数据,使用"|"连接字符串
6.最后返回Optional类型数据,处理好的字符串数据就封装在这个对象中
private static void reduceTes() {
List<String> list =Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
Optional<String> reduce = list.stream()
.sorted((s1,s2)->s2.length()-s1.length())
.filter(s->s.startsWith("j"))
.map(s->s+"_briup")
.reduce((s1,s2)->s1+"|"+s2);
String s = reduce.get();
System.out.println(s);
}
4.4.8 limit/skip
limit 返回 Stream 的前面 n 个元素;skip 则是跳过前 n 个元素只要后面的元素
private static void skipTes() {
List<String> list = Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
list.stream().limit(5).forEach(System.out::println);
list.stream().skip(6).forEach(System.out::println);
}
4.4.9 Collectors
java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的操作。
例如把Stream转变输出为 Collection,或者把 Stream 元素进行分组。
private static void collectorsTes() {
List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
List<String> result = list.stream().filter(s->s.length()>4).collect(Collectors.toList());
System.out.println(result);
//分组:按照字符串的长度分组
Map<Integer, List<String>> collect = list.stream().collect(Collectors.groupingBy(String::length));
System.out.println(collect);
}
4.4.10 并行Streams
private static void parallelTes() {
//生成100万个不同的字符串放到集合中
int max = 1000000;
List<String> values = new ArrayList<String>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
//1纳秒*10^9=1秒
long t0 = System.nanoTime();
//串行stream
//long count = values.stream().sorted().count();
//并行stream
long count = values.parallelStream().sorted().count();
long t1 = System.nanoTime();
long time = t1 - t0;
System.out.println(count);
System.out.println(time); //1034319329 833816253 904603571 904603571
//537822397 681201464 944406460 552126394
}
扩展:
《JAVA8函数式编程》
读者可能已经发现,Lambda 表达式有一个常见的用法:Lambda 表达式经常调用参数。比 如想得到艺术家的姓名,Lambda 的表达式如下:
artist -> artist.getName() 这种用法如此普遍,因此Java 8 为其提供了一个简写语法,叫作方法引用,帮助程序员重 用已有方法。
用方法引用重写上面的Lambda 表达式,代码如下: Artist::getName 标准语法为Classname::methodName。
需要注意的是,虽然这是一个方法,但不需要在后面 加括号,因为这里并不调用该方法。
我们只是提供了和Lambda 表达式等价的一种结构, 在需要时才会调用。凡是使用Lambda 表达式的地方,就可以使用方法引用。
构造函数也有同样的缩写形式,如果你想使用Lambda 表达式创建一个Artist 对象,
可能 会写出如下代码: (name, nationality) -> new Artist(name, nationality) 使用方法引用,上述代码可写为: Artist::new
这段代码不仅比原来的代码短,而且更易阅读。
Artist::new 立刻告诉程序员这是在创建 一个Artist 对象,程序员无需看完整行代码就能弄明白代码的意图。
另一个要注意的地方 是方法引用自动支持多个参数,前提是选对了正确的函数接口。 还可以用这种方式创建数组,下面的代码创建了一个字符串型的数组: String[]::new 从现在开始,我们将在合适的地方使用方法引用,因此读者很快会看到更多的例子。
一开 始探索Java 8 时,有位朋友告诉我,方法引用看起来“就像在作弊”。他的意思是说,了 解如何使用Lambda 表达式让代码像数据一样在对象间传递之后,这种直接引用方法的方 式就像“作弊”。 放心,这不是在作弊。读者只要记住,每次写出形如x -> foo(x) 的Lambda 表达式时, 和直接调用方法foo 是一样的。方法引用只不过是基于这样的事实,提供了一种简短的语 法而已。
五、Optional类
常用方法:
isPresent:如果值存在返回true,否则返回false。
ifPresent:如果Optional实例有值则为其调用consumer,否则不做处理
get:如果Optional有值则将其返回,否则抛出NoSuchElementException。因此也不经常用。
orElse:如果有值则将其返回,否则返回指定的其它值。
orElseGet:orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值。
orElseGet方法可以接受Supplier接口的实现用来生成默认值。