Lambda
什么是Lambda表达式
Lambda expression:a function (or a subroutine) defined, and possibly called, without being bound to an identifier。
Lambda的组成
Lambda表达式的语法由参数列表
、箭头符号->
和函数体
组成。
函数体
既可以是一个表达式,也可以是一个语句块:
表达式:表达式会被执行然后返回执行结果。
语句块:语句块中的语句会被依次执行,就像方法中的语句一样。
return语句会把控制权交给匿名方法的调用者
break和continue只能在循环中使用
如果函数体有返回值,那么函数体内部的每一条路径都必须返回值
Lambda语法详解
Lambda表达式的一般语法
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
省略参数类型,编译器都可以从上下文环境中推断出Lambda表达式的参数类型
(param1,param2, ..., paramN) -> {
statment1;
statment2;
//.............
return statmentM;
}
参数的个数只有一个,可以省略小括号
param1 -> {
statment1;
statment2;
//.............
return statmentM;
}
只包含一条语句时,可以省略大括号,return和语句结尾的分号
param1 -> statment
Lambda的基本使用
简单例子
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
基本的Lambda例子
String[] atp = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka",
"David Ferrer","Roger Federer",
"Andy Murray","Tomas Berdych",
"Juan Martin Del Potro"};
List<String> players = Arrays.asList(atp);
// 以前的循环方式
for (String player : players) {
System.out.print(player + "; ");
}
// 使用 lambda 表达式以及函数操作(functional operation)
players.forEach((player) -> System.out.print(player + "; "));
// 在 Java 8 中使用双冒号操作符(double colon operator)
players.forEach(System.out::println);
// 使用匿名内部类
btn.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("Hello World!");
}
});
// 或者使用 lambda expression
btn.setOnAction(event -> System.out.println("Hello World!"));
排序集合
// 1.1 使用匿名内部类根据 surname 排序 players
Arrays.sort(players, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" "))));
}
});
// 1.2 使用 lambda expression 排序,根据 surname
Comparator<String> sortBySurname = (String s1, String s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) );
Arrays.sort(players, sortBySurname);
// 1.3 或者这样,怀疑原作者是不是想错了,括号好多...
Arrays.sort(players, (String s1, String s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) )
);
// 2.1 使用匿名内部类根据 name lenght 排序 players
Arrays.sort(players, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return (s1.length() - s2.length());
}
});
// 2.2 使用 lambda expression 排序,根据 name lenght
Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());
Arrays.sort(players, sortByNameLenght);
// 2.3 or this
Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length()));
// 3.1 使用匿名内部类排序 players, 根据最后一个字母
Arrays.sort(players, new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
}
});
// 3.2 使用 lambda expression 排序,根据最后一个字母
Comparator<String> sortByLastLetter =
(String s1, String s2) ->
(s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
Arrays.sort(players, sortByLastLetter);
// 3.3 or this
Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
方法引用和构造器引用
方法引用
前面介绍lambda表达式简化的时候,已经看过方法引用的身影了。方法引用可以在某些条件成立的情况下,更加简化lambda表达式的声明。方法引用语法格式有以下三种:
objectName::instanceMethod
ClassName::staticMethod
ClassName::instanceMethod
前两种方式类似,等同于把lambda表达式的参数直接当成instanceMethod|staticMethod的参数来调用。比如System.out::println等同于x->System.out.println(x);Math::max等同于(x, y)->Math.max(x,y)。
最后一种方式,等同于把lambda表达式的第一个参数当成instanceMethod的目标对象,其他剩余参数当成该方法的参数。比如String::toLowerCase等同于x->x.toLowerCase()。
构造器引用
构造器引用语法如下:ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)。
Stream
什么是Stream
- Stream是元素的集合,这点让Stream看起来用些类似Iterator(高级版本的Iterator);
- 可以支持顺序和并行的对原Stream进行汇聚的操作;
分析Stream通用语法
所以使用Stream的基本步骤:
- 创建Stream
- 转换Stream 每次转换原有Stream对象不改变 返回一个新的Stream对象(可以有多次转换)
- 对Stream进行聚合操作,获得想要的结果
创建Stream
- 通过集合的stream()方法或者parallelStream(),比如
Arrays.asList(1,2,3).stream()
。 - 通过Arrays.stream(Object[])方法, 比如
Arrays.stream(new int[]{1,2,3})
。 - 使用流的静态方法,比如
Stream.of(Object[])
,IntStream.range(int, int)
或者Stream.iterate(Object, UnaryOperator)
,如Stream.iterate(0, n -> n * 2)
,或者generate(Supplier<T> s)
如Stream.generate(Math::random)
。 BufferedReader.lines()
从文件中获得行的流。- Files类的操作路径的方法,如list、find、walk等。
- 随机数流Random.ints()。
- 其它一些类提供了创建流的方法,如
BitSet.stream()
,Pattern.splitAsStream(java.lang.CharSequence)
, 和JarFile.stream()
。 - 更底层的使用StreamSupport,它提供了将Spliterator转换成流的方法。
中间操作(转换操作)
distinct–>保证输出的流中包含唯一的元素,它是通过Object.equals(Object)来检查是否包含相同的元素
List<String> l = Stream.of("a","b","c","b")
.distinct()
.collect(Collectors.toList());
System.out.println(l); //[a, b, c]
filter–>返回的流中只包含满足断言(predicate)的数据
Filter接受一个predicate接口类型的变量,并将所有流对象中的元素进行过滤。该操作是一个中间操作,因此它允许我们在返回结果的基础上再进行其他的流操作(forEach)。
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));
javaProgrammers.stream()
.filter(ageFilter)
.filter(genderFilter)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
map–>将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同
List<Integer> l = Stream.of('a','b','c')
.map( c -> c.hashCode())
.collect(Collectors.toList());
System.out.println(l); //[97, 98, 99]
limit–>指定数量的元素的流。
javaProgrammers.stream()
.limit(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
sorted–>将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常
sorted只是创建一个流对象排序的视图,而不会改变原来集合中元素的顺序。原来string集合中的元素顺序是没有改变的。
String[] arr = new String[]{"b_123","c+342","b#632","d_123"};
List<String> l = Arrays.stream(arr)
.sorted((s1,s2) -> {
if (s1.charAt(0) == s2.charAt(0))
return s1.substring(2).compareTo(s2.substring(2));
else
return s1.charAt(0) - s2.charAt(0);
})
.collect(Collectors.toList());
System.out.println(l); //[b_123, b#632, c+342, d_123]
skip–>返回丢弃了前n个元素的流,如果流中的元素小于或者等于n,则返回空的流
终点操作
Match
public boolean allMatch(Predicate<? super T> predicate)
public boolean anyMatch(Predicate<? super T> predicate)
public boolean noneMatch(Predicate<? super T> predicate)
这一组方法用来检查流中的元素是否满足断言。
allMatch只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回true
anyMatch只有在任意一个元素满足断言时就返回true,否则flase,
noneMatch只有在所有的元素都不满足断言时才返回true,否则flase,
System.out.println(Stream.of(1,2,3,4,5).allMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().allMatch( i -> i > 0)); //true
System.out.println(Stream.<Integer>empty().anyMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().noneMatch( i -> i > 0)); //true
count
Count是一个终结操作,它的作用是返回一个数值,用来标识当前流对象中包含的元素数量
count方法返回流中的元素的数量。它实现为:
mapToLong(e -> 1L).sum();
collect
<R,A> R collect(Collector<? super T,A,R> collector)
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
使用一个collector执行mutable reduction操作。辅助类Collectors提供了很多的collector,可以满足我们日常的需求,你也可以创建新的collector实现特定的需求。它是一个值得关注的类,你需要熟悉这些特定的收集器,如聚合类averagingInt、最大最小值maxBy minBy、计数counting、分组groupingBy、字符串连接joining、分区partitioningBy、汇总summarizingInt、化简reducing、转换toXXX等。
第二个提供了更底层的功能,它的逻辑类似下面的伪代码:
R result = supplier.get();
for (T element : this stream)
accumulator.accept(result, element);
return result;
例子:
List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add,
ArrayList::addAll);
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,
StringBuilder::append)
.toString();
find
findAny()返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。
findFirst()返回第一个元素,如果流为空,返回空的Optional。
forEach、forEachOrdered
forEach遍历流的每一个元素,执行指定的action。它是一个终点操作,和peek方法不同。这个方法不担保按照流的encounter order顺序执行,如果对于有序流按照它的encounter order顺序执行,你可以使用forEachOrdered方法。
Stream.of(1,2,3,4,5).forEach(System.out::println);
max、min
max返回流中的最大值,
min返回流中的最小值。
reduce
reduce是常用的一个方法,事实上很多操作都是基于它实现的。
它有几个重载方法:
pubic Optional<T> reduce(BinaryOperator<T> accumulator)
pubic T reduce(T identity, BinaryOperator<T> accumulator)
pubic <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
第一个方法使用流中的第一个值作为初始值,后面两个方法则使用一个提供的初始值。
Optional<Integer> total = Stream.of(1,2,3,4,5).reduce( (x, y) -> x +y);
Integer total2 = Stream.of(1,2,3,4,5).reduce(0, (x, y) -> x +y);
值得注意的是accumulator应该满足结合性(associative)
toArray()
将流中的元素放入到一个数组中
转换
toArray方法将一个流转换成数组,而如果想转换成其它集合类型,西需要调用collect方法,利用Collectors.toXXX方法进行转换:
public static <T,C extends Collection<T>> Collector<T,?,C> toCollection(Supplier<C> collectionFactory)
public static …… toConcurrentMap(……)
public static <T> Collector<T,?,List<T>> toList()
public static …… toMap(……)
public static <T> Collector<T,?,Set<T>> toSet()
函数式接口
Predicate
Predicate是一个布尔类型的函数,该函数只有一个输入参数。Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and, or,negate)
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
Function
Function接口接收一个参数,并返回单一的结果。默认方法可以将多个函数串在一起(compse, andThen)
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
Supplier
Supplier接口产生一个给定类型的结果。与Function不同的是,Supplier没有输入参数。
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
Consumer
Consumer代表了在单一的输入参数上需要进行的操作。
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
Comparator
Comparator接口在早期的Java版本中非常著名。Java 8 为这个接口添加了不同的默认方法。
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
引用文档
Java8初体验(一)lambda表达式语法
JDK 1.8新特性Lambda入门
Java Stream 详解
Java8 简明教程