Java1.8新特性 Lambda/Stream/函数式编程

Lambda

什么是Lambda表达式

Lambda expression:a function (or a subroutine) defined, and possibly called, without being bound to an identifier。

Lambda的组成

Lambda表达式的语法由参数列表箭头符号->函数体组成。
函数体既可以是一个表达式,也可以是一个语句块
表达式:表达式会被执行然后返回执行结果。
语句块:语句块中的语句会被依次执行,就像方法中的语句一样。
return语句会把控制权交给匿名方法的调用者
breakcontinue只能在循环中使用
如果函数体有返回值,那么函数体内部的每一条路径都必须返回值

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 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() {  
          @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() {  
    @Override  
    public int compare(String s1, String s2) {  
        return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" "))));  
    }  
});  

// 1.2 使用 lambda expression 排序,根据 surname  
Comparator 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() { @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() { @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通用语法

Java1.8新特性 Lambda/Stream/函数式编程_第1张图片

所以使用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 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 l = Stream.of("a","b","c","b")
        .distinct()
        .collect(Collectors.toList());
System.out.println(l); //[a, b, c]

filter–>返回的流中只包含满足断言(predicate)的数据

Filter接受一个predicate接口类型的变量,并将所有流对象中的元素进行过滤。该操作是一个中间操作,因此它允许我们在返回结果的基础上再进行其他的流操作(forEach)。

    Predicate ageFilter = (p) -> (p.getAge() > 25);
    Predicate 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 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 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 predicate)
public boolean  anyMatch(Predicate predicate)
public boolean  noneMatch(Predicate 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.empty().allMatch( i -> i > 0)); //true
      System.out.println(Stream.empty().anyMatch( i -> i > 0)); //false
      System.out.println(Stream.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   reduce(BinaryOperator accumulator)
pubic T     reduce(T identity, BinaryOperator accumulator)
pubic  U     reduce(U identity, BiFunctionsuper T,U> accumulator, BinaryOperator combiner)
第一个方法使用流中的第一个值作为初始值,后面两个方法则使用一个提供的初始值。

Optional 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 > Collector  toCollection(Supplier collectionFactory)
public static ……    toConcurrentMap(……)
public static  Collector>    toList()
public static ……    toMap(……)
public static  Collector>     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 personSupplier = Person::new;
personSupplier.get();   // new Person

Consumer

Consumer代表了在单一的输入参数上需要进行的操作。

Consumer greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));

Comparator

Comparator接口在早期的Java版本中非常著名。Java 8 为这个接口添加了不同的默认方法。

Comparator 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 简明教程

你可能感兴趣的:(Java学习笔记)