Java现在的版本迭代速度简直不要太快,一不留神,就错过了好几个版本了。官方版本虽然已经更新到Java12了,可是就目前来讲,大多数Java系统仍是运行在Java8上的,剩下一部分历史遗留系统还跑在Java7,甚至Java6上。我刚学Java的时候,正好处于Java7版本末期,彼时已经有不少关于Java8新特性的风声,当时做为初学者,其实对此关注很少,只是依稀记得“lambda表达式”、“函数式编程”之类的,也不甚明白其中真意。真正大量应用Java8,大概是我工做一年以后的事情了,还记得当时是从IBM论坛上的一篇文章开始的。java
前几天和一位大学同窗聊天的时候,谈到了他们公司的一些问题,他们的系统是基于JDK7的版本,而且大部分员工不肯意升级版本,由于不肯意接受Java8的新特性。 我是以为很是惊讶的,都快Java13了,你还不肯意了解Java8的新(旧)特性?所以有了这篇文章,本文将结合通俗易懂的代码介绍Java8的lambda和stream相关的新(旧)特性,从中体会函数式编程的思想。编程
咱们能够简单认为lambda表达式就是匿名内部类的更简洁的语法糖。看下面两种线程建立方式,直观感觉一下。数组
// 匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
// ...
}
}).start();
// lambda
new Thread(() -> {
// ...
}).start();
复制代码
想要熟练使用lambda表达式,首先要了解函数式接口,那么什么是函数式接口呢?首先必须得是interface
修饰的接口,而后接口有且只有一个待实现的方法。有个简单的方法能够区分函数式接口与普通接口,那就是在接口上添加@FunctionalInterface
注解,若是不报错,那就是函数式接口,就可使用lambda表达式来代替匿名内部类了。看下面几个例子,很显然,A和B都是函数式接口,而C没有抽象方法,D不是接口,因此都不能使用lambda表达式。并发
// 是函数式接口
interface A {
void test();
}
// 是函数式接口
interface B {
default void def() {
// balabala...
}
void test();
}
// 不是函数式接口
interface C {
default void def() {}
}
// 不是函数式接口
abstract class D {
public abstract void test();
}
复制代码
lambda表达式根据实现接口的方法参数、返回值、代码行数等,有几种不一样的写法:框架
interface A {
void test();
}
A a = () -> {
// ...
};
复制代码
interface B {
void test(String arg);
}
B b = arg -> {
// ...
};
复制代码
interface C {
void test(String arg1, String arg2);
}
C c = (a1, a2) -> {
// ...
};
interface D {
void test(String arg1, String arg2, String arg3);
}
D d = (a1, a2, a3) -> {
// ...
};
复制代码
interface B {
void test(String arg);
}
B b = arg -> System.out.println("hello " + arg);
复制代码
interface E {
String get(int arg);
}
E e = arg -> {
int r = arg * arg;
return String.valueOf(r);
};
// 只有一行代码能够省略return和大括号
e = arg -> String.valueOf(arg * arg);
复制代码
有一点须要注意,lambda表达式和匿名内部类同样,都只能引用final修饰的外部的资源,虽然Java8中能够不用显示的声明变量为final的,可是在lambda表达式内部是不能修改的。分布式
int i = 0;
A a = () -> {
i++; // 这里编译不经过
// ...
};
复制代码
lambda表达式还有更加简便的写法,看下面代码,这个::
符号是否是很熟悉啊?果真仍是脱离不了C体系的影响😆ide
class Math {
int max(int x, int y) {
return x < y ? y : x;
}
static int sum(int x, int y) {
return x + y;
}
}
interface Computer {
int eval(int arg1, int arg2);
}
// 直接经过类名引用
Computer sumFun = Math::sum;
// 和上面是等价的
sumFun = (x, y) -> x + y;
Math math = new Math();
// 经过对象引用
Computer maxFun = math::max;
// 和上面是等价的
maxFun = (x, y) -> x < y ? y : x;
int sum = sumFun.eval(1, 2);
int max = maxFun.eval(2, 3);
复制代码
将上面的例子扩展一下,看下面的代码,体会一下函数式编程的思想。咱们把函数做为参数,在真正调用compute
方法的时候,才肯定应该进行何种运算。函数式编程
class Biz {
int x, y;
Biz(int x, int y) {
this.x = x;
this.y = y;
}
int compute(Computer cpt) {
// ...
return cpt.eval(x, y);
}
}
Biz biz = new Biz(1, 2);
int result = biz.compute((x, y) -> x * y);
result = biz.compute(Math::sum);
复制代码
Java8内置了不少函数式接口,所有放在java.util.function
包下面,这些接口已经能知足平常开发中大部分的需求了,这些函数接口主要分为如下几类:函数
Consumer
类型Consumer<String> consumer = str -> {
// ...
};
BiConsumer<String, String> biConsumer = (left, right) -> {
// ...
};
复制代码
Supplier
类型Supplier<String> supplier = () -> {
// ...
return "hello word";
};
复制代码
Function
类型Function<Integer, String> function = i -> {
// ...
return "hello word " + i;
};
BiFunction<Integer, Integer, String> biFunction = (m, n) -> {
int s = m + n;
return "sum = " + s;
};
复制代码
Predicate
类型,能够看作是Function
的一种特例Predicate<String> predicate = str -> {
// ...
return str.charAt(0) == 'a';
};
BiPredicate<String, String> biPredicate = (left, right) -> {
// ...
return left.charAt(0) == right.charAt(0);
};
复制代码
Java8为集合框架添加了流式处理的功能,为咱们提供了一种很方便的处理集合数据的方式。
Stream大致上能够分为两种操做:中间操做和终端操做,这里先不考虑中间操做状态问题。中间操做能够有多个,可是终端操做只能有一个。中间操做通常是一些对流元素的附加操做,这些操做不会在添加中间操做的时候当即生效,只有当终端操做被添加时,才会开始启动整个流。并且流是不可复用的,一旦流启动了,就不能再为这个流附加任何终端操做了。工具
流的建立方式大概有如下几种:
String[] array =
Stream<String> stream;
// 1. 经过Stream的builder构建
stream = Stream.<String>builder()
.add("1")
.add("2")
.build();
// 2. 经过Stream.of方法构建,这种方法能够用来处理数组
stream = Stream.of("1", "2", "3");
// 3. 经过Collection类的stream方法构建,这是经常使用的作法
Collection<String> list = Arrays.asList("1", "2", "3");
stream = list.stream();
// 4. 经过IntStream、LongStream、DoubleStream构建
IntStream intStream = IntStream.of(1, 2, 3);
LongStream longStream = LongStream.range(0L, 10L);
DoubleStream doubleStream = DoubleStream.of(1d, 2d, 3d);
// 5. 其实上面这些方法都是经过StreamSupport来构建的
stream = StreamSupport.stream(list.spliterator(), false);
复制代码
若是你熟悉spark或者flink的话,就会发现,中间操做其实和spark、flink中的算子是同样的,连命名都是同样的,流在调用中间操做的方法是,并不会当即执行这个操做,会等到调用终端操做时,才会执行,下面例子中都添加了一个toArray
的终端操做,把流转换为一个数组。
// 将返回一个只包含大于1的元素的数组
// array = [2, 3]
Integer[] array = Stream.of(1, 2, 3)
.filter(i -> i > 1)
.toArray(Integer[]::new);
复制代码
// 将每一个元素都加10
// array = [11, 12, 13]
Integer[] array = Stream.of(1, 2, 3)
.map(i -> i + 10)
.toArray(Integer[]::new);
复制代码
// 把每一个元素都按","拆分,返回Stream
// array = ["1", "2", "3", "4", "5", "6"]
String[] array = Stream.of("1", "2,3", "4,5,6")
.flatMap(s -> {
String[] split = s.split(",");
return Stream.of(split);
})
.toArray(String[]::new);
复制代码
Stream.of(new User("James", 40), new User("Kobe", 45), new User("Durante", 35))
.peek(user -> {
user.name += " NBA";
user.age++;
}).forEach(System.out::println);
// User(name=James NBA, age=41)
// User(name=Kobe NBA, age=46)
// User(name=Durante NBA, age=36)
复制代码
equals
方法去重。// array = [hello, hi]
String[] array = Stream.of("hello", "hi", "hello")
.distinct()
.toArray(String[]::new);
复制代码
Comparable
类型,若是不能转换会抛出异常。也能够传入一个比较器Comparator
,而后会根据比较器的比较结果排序。// 根据字符串长度排序
// sorted = [hi, haha, hello]
String[] sorted = Stream.of("hello", "hi", "haha")
.sorted(Comparator.comparingInt(String::length))
.toArray(String[]::new);
复制代码
// 截取前三个
// array = [hello, hi, haha]
String[] array = Stream.of("hello", "hi", "haha", "heheda")
.limit(3)
.toArray(String[]::new);
复制代码
// 跳过前两个
// array = [haha, heheda]
String[] array = Stream.of("hello", "hi", "haha", "heheda")
.skip(2)
.toArray(String[]::new);
复制代码
每一个流只能有一个终端操做,调用终端操做方法后,流才真正开始执行中间操做,通过多个中间操做的处理后,最终会在终端操做这里产生一个结果。
Stream.of("hello", "hi", "haha", "heheda")
.limit(0)
.forEach(s -> System.out.println(">>> " + s));
复制代码
// array = [hello, hi, haha, heheda]
Object[] array = Stream.of("hello", "hi", "haha", "heheda")
.toArray();
复制代码
// b = false
boolean b = Stream.of("hello", "hi", "haha", "heheda")
.allMatch(s -> s.equals("hello"));
// b = true
b = Stream.of("hello", "hi", "haha", "heheda")
.anyMatch(s -> s.equals("hello"));
// b = true
b = Stream.of("hello", "hi", "haha", "heheda")
.noneMatch(s -> s.equals("nihao"));
复制代码
Optional
包装。String first = Stream.of("hello", "hi", "haha", "heheda")
.findFirst().get();
first = Stream.of("hello", "hi", "haha", "heheda")
.findAny().get();
复制代码
// 拼接字符串
// reduceS ="hello ; hi ; haha ; heheda"
String reduceS = Stream.of("hello", "hi", "haha", "heheda")
.reduce((x, y) -> x + " ; " + y)
.get();
// 统计全部字符串的长度
// lenght = 17
int length = Stream.of("hello", "hi", "haha", "heheda")
.map(String::length)
.reduce(0, (x, y) -> x + y);
// 同上,不同的是,第三个参数是个合并器,用于并行流各个并行结果的合并
int reduce = Stream.of("hello", "hi", "haha", "heheda")
.reduce(0, (x, y) -> x + y.length(), (m, n) -> m + n);
复制代码
// max = "heheda"
String max = Stream.of("hello", "hi", "haha", "heheda")
.max(Comparator.comparingInt(String::length))
.get();
// min = "hi"
String min = Stream.of("hello", "hi", "haha", "heheda")
.min(Comparator.comparingInt(String::length))
.get();
// count = 4
long count = Stream.of("hello", "hi", "haha", "heheda")
.count();
复制代码
// 转换为List [hello, hehe, hehe, hi, hi, hi]
List<String> list = Stream.of("hello", "hehe", "hehe", "hi", "hi", "hi")
.collect(Collectors.toList());
// 转换为Set [hi, hehe, hello]
Set<String> set = Stream.of("hello", "hehe", "hehe", "hi", "hi", "hi")
.collect(Collectors.toSet());
// 下面这个稍微复杂一些,实现了将字符串流转换为Map,map的key是字符串自己,value是字符串出现的次数
// map = {hi=3, hehe=2, hello=1}
Map<String, Integer> map = Stream.of("hello", "hehe", "hehe", "hi", "hi", "hi")
.collect(Collectors.toMap(s -> {
// 字符串做为map的key
return s;
}, s -> {
// 1做为map的value
return 1;
}, (x, y) -> {
// key相同时的合并操做
return x + y;
}, () -> {
// 还能够指定Map的类型
return new LinkedHashMap<>();
}));
复制代码
最后,我将上面介绍的一些操做结合起来,经过一个单词统计的例子,让你们更直观的感觉流式处理的好处。
Path path = Paths.get("/Users/.../test.txt");
List<String> lines = Files.readAllLines(path);
lines.stream()
.flatMap(line -> {
String[] array = line.split("\\s+");
return Stream.of(array);
})
.filter(w -> !w.isEmpty())
.sorted()
.collect(Collectors.toMap(w -> w, w -> 1,
(x, y) -> x + y,
LinkedHashMap::new))
.forEach((k, v) -> System.out.println(k + " : " + v));
复制代码
遗憾的是Java8的Stream并不支持分组和聚合操做,因此这里使用了toMap方法来统计单词的数量。
Java8的集合类提供了parallelStream方法用于获取一个并行流(底层是基于ForkJoin作的),通常不推荐这么作,数据规模较小时使用并行Stream反而不如串行来的高效,而数据规模很大的时候,单机的计算能力毕竟有限,我仍是推荐使用更增强大的spark或者flink来作分布式计算。
至此,Java8关于lambda和Stream的特性就分析完毕了,固然Java8做为一个经典版本,确定不止于此,Doug Lea大佬的并发包也在Java8版本更新了很多内容,提供了更加丰富多彩的并发工具,还有新的time包等等,这些均可以拿出来做为一个新的的话题讨论。指望以后的文章中能和你们继续分享相关内容。
原创不易,转载请注明出处!www.yangxf.top/