Lambda表达式

  Lambda expression:一个不用被绑定到一个标识符上,并且可能被调用的函数。即一段带有输入参数的可执行语句块。

      Java 8 中 Stream 是对集合(Collection)对象功能的增强,专注于对集合对象进行各种便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。

       函数编程,可理解是将一个函数(也称为“行为”)作为一个参数进行传递。

  使用stream的步骤如下:
  1.创建stream;
  2.通过一个或多个中间操作(intermediate operations)将初始stream转换为另一个stream;
  3.通过中止操作(terminal operation)获取结果;该操作触发之前的懒操作的执行,中止操作后,该stream关闭,不能再使用

参数类型省略–绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。lambda表达式就变成:

(param1,param2, ..., paramN) -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

单参数语法:可以省略小括号。lambda表达式简写为:

param1 -> {
  statment1;
  statment2;
  //.............
  return statmentM;
}

例如:将列表中的字符串转换为全小写

List<String> proNames = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String> lowercaseNames = proNames.stream().map(name -> {return name.toLowerCase();}).collect(Collectors.toList());

当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。lambda表达式简化为:

  param1 -> statment

例子再次简化为:

List<String> lowercaseNames = names.stream().map(name -> name.toLowerCase()).collect(Collectors.toList());

this:是声明它的外部对象

方法引用:语法格式有以下三种:

objectName::instanceMethod
ClassName::staticMethod
ClassName::instanceMethod

构造器引用

ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)

Stream

  1. Stream是元素的集合
  2. 可以支持顺序和并行的对原Stream进行汇聚的操作;

创建Stream有两种途径:

  1. 通过Stream接口的静态工厂方法(注意:Java8里接口可以带静态方法);
  2. 通过Collection接口的默认方法(默认方法:Default method,也是Java8中的一个新特性,就是接口中的一个带有实现的方法)–stream(),把一个Collection对象转换成Stream

使用Stream静态方法来创建Stream

1. of方法:有两个overload方法,一个接受变长参数,一个接口单一值

Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");

2. generator方法:生成一个无限长度的Stream,其元素的生成是通过给定的Supplier

Stream.generate(new Supplier<Double>() {
    @Override
    public Double get() {
        return Math.random();
   }
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);

3. iterate方法:也是生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环

Stream的转换:转换Stream其实就是把一个Stream通过某些行为转换成一个新的Stream。

  0. distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;

  

 

  1.filter操作。即使原stream中满足条件的元素构成新的stream:

    String[] arr={"ab","bc","abc","dbc"};
        Stream<String> stream = Arrays.stream(arr);
        stream.filter(str->str.contains("a")).forEach(System.out::println);

  注:System.out是一个PrintStream实例的引用;System.out::println 是对一个实例方法的引用,可以看作 lambda表达式 e -> System.out.println(e) 的缩写形式

  

        2.map操作

  map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。

  <R> Stream<R> map(Function<? super T, ? extends R> mapper);

  方法传入一个Function的函数式接口,接口接收一个泛型T,返回泛型R,map函数的定义,返回的流,表示的泛型是R对象,这个表示调用函数后,可以改变返回的类型

  把Integer,变成了String输出

        Integer[] list={1,2,3};
        Stream<Integer> integerStream = Arrays.stream(list);
        integerStream.map(e->Integer.toString(e)).forEach(System.out::println);

      

  3.flatMap操作

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

  跟map一样,接收一个Fucntion的函数式接口,不同的是Function接收的泛型参数,第二个参数是一个Stream流;方法返回的也是泛型R,具体的作用是把两个流,变成一个流返回

 String[] strs = { "aaa", "bbb", "ccc" };
 Arrays.stream(strs).map(str -> str.split("")).forEach(System.out::println);// Ljava.lang.String;@53d8d10a
 Arrays.stream(strs).map(str -> str.split("")).flatMap(Arrays::stream).forEach(System.out::println);// aaabbbccc
 Arrays.stream(strs).map(str -> str.split("")).flatMap(str -> Arrays.stream(str)).forEach(System.out::println);// aaabbbccc

  第一段输出代码里,先看map操作,通过上面对map的介绍,可以看到map可以改变返回的Stream的泛型,str.split(""),根据空字符串分隔,返回的类型是一个数组,返回的流也是Stream<String[]>,而不是Stream<String>;第二段代码,数组的流经过map操作,返回Stream<String[]>后,再经过flatMap,把数组通过Arrays.stream变成一个新的流,再返回到原来的流里;两个流就合并成一个流;

  

  5. peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;

6. limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;

 

 7. skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;

  

  性能问题

  转换操作都是lazy的,多个转换操作只会在汇聚操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数  

  汇聚(Reduce)Stream

  汇聚操作(也称为折叠)接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。比如查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。Stream接口有一些通用的汇聚操作,比如reduce()和collect();也有一些特定用途的汇聚操作,比如sum(),max()和count()。注意:sum方法不是所有的Stream对象都有的,只有IntStream、LongStream和DoubleStream是实例才有。

  1 可变汇聚

  可变汇聚对应的只有一个方法:collect,可以把Stream中的要有元素收集到一个结果容器中(比如Collection)。

<R> R collect(Supplier<R> supplier,BiConsumer<R, ? super T> accumulator,BiConsumer<R,R> combiner);

  Supplier supplier是一个工厂函数,用来生成一个新的容器;BiConsumer accumulator也是一个函数,用来把Stream中的元素添加到结果容器中;BiConsumer combiner还是一个函数,用来把中间状态的多个结果容器合并成为一个(并发的时候会用到)

List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10);
    List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null).
            collect(() -> new ArrayList<Integer>(),
                    (list, item) -> list.add(item),
                    (list1, list2) -> list1.addAll(list2));
  • 第一个函数生成一个新的ArrayList实例;
  • 第二个函数接受两个参数,第一个是前面生成的ArrayList对象,二个是stream中包含的元素,函数体就是把stream中的元素加入ArrayList对象中。第二个函数被反复调用直到原stream的元素被消费完毕;
  • 第三个函数也是接受两个参数,这两个都是ArrayList类型的,函数体就是把第二个ArrayList全部加入到第一个中;

     <R, A> R collect(Collector<? super T, A, R> collector);

     Collectors.toCollection()收集到Collection中, Collectors.toList()收集到List中和Collectors.toSet()收集到Set中

  List<Integer> numsWithoutNull_1 = nums.stream().filter(num -> num != null).
        collect(Collectors.toList());

2 其他汇聚

  reduce方法的第一种形式,其方法定义如下:

Optional<T> reduce(BinaryOperator<T> accumulator);
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -> sum + item).get());

  reduce方法接受一个函数,这个函数有两个参数,第一个参数是上次函数执行的返回值(也称为中间结果),第二个参数是stream中的元素,这个函数把这两个值相加,得到的和会被赋值给下次执行这个函数的第一个参数。要注意的是:**第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素**。这个方法返回值类型是Optional.

  

  Java传统的语法规则编写一个线程:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello World!");
    }
});

  使用Lambda表达式则只需要使用一句话就可代替上面使用匿名类的方式

new Thread(() -> System.out.println("Hello World!"));

  Lambda表达式一共有三部分组成:

  

  能够接收Lambda表达式的参数类型,是一个只包含一个方法的接口。只包含一个方法的接口称之为“函数接口”。

  例如上面创建一个线程的示例,Runnable接口只包含一个方法,所以它被称为“函数接口”,所以可以使用Lambad表达式来代替匿名内部类。

  写一个函数接口,并使用Lambda表达式作为参数传递。

/**
 * 函数接口:只有一个方法的接口。作为Lambda表达式的类型
 */
public interface FunctionInterface {
    void test();
}

  测试:

import org.junit.Test;

/**
 * 函数接口测试
 */
public class FunctionInterfaceTest {

    @Test
    public void testLambda() {
        func(new FunctionInterface() {
            @Override
            public void test() {
                System.out.println("Hello World!");
            }
        });
        //使用Lambda表达式代替上面的匿名内部类
        func(() -> System.out.println("Hello World"));
    }

    private void func(FunctionInterface functionInterface) {
        functionInterface.test();
    }
}

  包含参数,不包含返回值

public interface FunctionInterface {
    void test(int param);
}
import org.junit.Test;

public class jdk8Test {

    @Test
    public void testFunc(){
        func(new FunctionInterface() {
            @Override
            public void test(int x) {
                System.out.println("Hi,jdk8 "+x);
            }
        });

        func((x)->System.out.println("lambda ok "+x));
    }

    private void func(FunctionInterface functionInterface){
        int x=100;
        functionInterface.test(x);
    }
}

  关注Lambda表达式“(x) -> Sysout.out.println("Hello World" + x)”,左边传递的是参数,此处并没有指明参数类型,因为可以通过上下文进行类型推导,默认Object类型

  哪种情况不能推导出参数类型呢?就是函数接口是一个泛型的时候。

public interface FunctionInterface<T> {
    void test(T param);
}

  测试

public class jdk8Test {

    @Test
    public void testFunc(){

        func((x)->System.out.println("lambda ok "+x));
        
    }

    private void func(FunctionInterface functionInterface){
        int x=100;
        functionInterface.test(x);
    }
}

  有参数,有返回值

package com.smart.boot_mybatis.jdk8;

public interface FunctionInterface<T> {
    boolean test(T param);
}

  测试:

public class jdk8Test {

    @Test
    public void testFunc(){
        func((x)->{
            System.out.println("Hello World");
            return true;
        });
    }

    private void func(FunctionInterface functionInterface){
        int x=100;
        functionInterface.test(x);
    }
}

集合的使用

for (Student student : studentList) {
    if (student.getCity().equals("wuhan")) {
        count++;
    }
}

JDK8使用集合

 userList.stream().filter((user->user.getAddress().equals("wuhan"))).count();
posted on 2019-02-01 22:33  溪水静幽  阅读(461)  评论(0)    收藏  举报