17、java8中Lambda表达式与Stream API的使用
17.1 Lambda 表达式(Lambda Expressions) 1课时
17.2 函数式(Functional)接口 1课时
17.3 方法引用与构造器引用 1课时
17.4 Stream API 1课时

Java 11 2018年9月25日发布,那么还要有必要学习java 8 吗?

Java 8新特性简介

Java 8 (又称为 jdk 1.8) 是 Java 语以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。

  • 速度更快
  • 代码更少(增加了新的语法:Lambda 表达式)
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常:Optional
  • Nashorn引擎,允许在JVM上运行JS应用

17-1 Lambda表达式

为什么使用 Lambda 表达式

  • Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
  • 从匿名类到 Lambda 的转换举例1



  • 从匿名类到 Lambda 的转换举例2



由一个问题的迭代看Lambda表达式

问题:针对员工的集合数据,有如下的一些需求,我们考虑如何完成?

需求1:获取当前公司中员工年龄大于30的员工信息

需求2:获取公司中工资大于 5000 的员工信息

....

Lambda 表达式语法

Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:

左侧:指定了 Lambda 表达式需要的参数列表

右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。

Lambda表达式使用

public class LambdaTest {
private List<Employee> data = EmployeeData.getEmployees(); //举例三:由一个具体的问题,展开对Lambda表达式和 Stream api的使用的理解 //解决层次六:使用Stream API
@Test
public void test11(){
//查询员工中年龄大于30岁的员工的信息
data.stream().filter(e -> e.getAge() > 30).forEach(System.out::println); System.out.println(); //查询员工中工资大于5000的员工的信息
data.stream().filter(e -> e.getSalary() > 5000).forEach(System.out::println);
} //解决层次五:使用Lambda表达式的进一步优化
@Test
public void test10(){
//查询员工中年龄大于30岁的员工的信息
List<Employee> list = filterData(data, e -> e.getAge() > 30);
list.forEach(System.out::println); System.out.println("*****************"); //查询员工中工资大于5000的员工的信息
List<Employee> list1 = filterData(data,e -> e.getSalary() > 5000);
list1.forEach(System.out::println);
} //解决层次四:使用Lambda表达式
@Test
public void test9(){
//查询员工中年龄大于30岁的员工的信息
List<Employee> list = filterData(data, e -> e.getAge() > 30);
for (Employee emp : list) {
System.out.println(emp);
} System.out.println("*****************"); //查询员工中工资大于5000的员工的信息
List<Employee> list1 = filterData(data,e -> e.getSalary() > 5000);
for (Employee emp : list1) {
System.out.println(emp);
} } //解决层次三:使用FilterData接口的匿名实现类
@Test
public void test8(){
//查询员工中年龄大于30岁的员工的信息
List<Employee> list = filterData(data, new FilterData<Employee>() {
@Override
public boolean filter(Employee employee) {
return employee.getAge() > 30;
}
});
for (Employee emp : list) {
System.out.println(emp);
} System.out.println("*****************"); //查询员工中工资大于5000的员工的信息
List<Employee> list1 = filterData(data, new FilterData<Employee>() {
@Override
public boolean filter(Employee employee) {
return employee.getSalary() > 5000;
}
});
for (Employee emp : list1) {
System.out.println(emp);
} } //解决层次二:策略的设计模式
@Test
public void test7(){
//查询员工中年龄大于30岁的员工的信息
List<Employee> list = filterData(data, new FilterByAge());
for (Employee emp : list) {
System.out.println(emp);
} System.out.println("*****************"); //查询员工中工资大于5000的员工的信息
List<Employee> list1 = filterData(data, new FilterBySalary());
for (Employee emp : list1) {
System.out.println(emp);
} } public List<Employee> filterData(List<Employee> list,FilterData<Employee> filter){ ArrayList<Employee> data = new ArrayList<>();
for (Employee emp : list) {
if(filter.filter(emp)){
data.add(emp);
}
}
return data; } //解决层次一:
@Test
public void test6(){
List<Employee> list = filterEmployeesBySalary(data);
for (Employee emp : list) {
System.out.println(emp);
}
} //问题二:查询员工中工资大于5000的员工的信息
public List<Employee> filterEmployeesBySalary(List<Employee> list){
ArrayList<Employee> data = new ArrayList<>();
for (Employee emp : list) {
if(emp.getSalary() > 5000){
data.add(emp);
}
}
return data; } @Test
public void test5(){
List<Employee> list = filterEmployeesByAge(data);
for (Employee employee : list) {
System.out.println(employee);
}
} //问题一:查询员工中年龄大于30岁的员工的信息
public List<Employee> filterEmployeesByAge(List<Employee> list){ ArrayList<Employee> data = new ArrayList<>(); for (Employee emp : list) {
if(emp.getAge() > 30){
data.add(emp);
}
} return data; } //*************************************************************************** //举例二:Thread中的Runnable的使用
//使用Lambda之后
@Test
public void test4(){
Runnable r = () -> {
for (int i = 0; i <= 100; i++) {
System.out.println(i);
}
}; Thread t = new Thread(r);
t.start();
} //使用Lambda之前
@Test
public void test3(){ //提供实现接口的匿名实现类的对象
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(i);
}
}
}; Thread t = new Thread(r);
t.start();
} //*************************************************************************** //举例一:TreeSet中使用Comparator的使用
//使用Lambda之后
@Test
public void test2(){ Comparator<String> com = (o1,o2) -> o1.compareTo(o2); TreeSet<String> set = new TreeSet<String>(com); set.add("HH");
set.add("MM");
set.add("GG");
set.add("JJ");
set.add("DD"); for (String s : set) {
System.out.println(s);
}
}
//使用Lambda之前
@Test
public void test1(){
// System.out.println("hello"); //提供匿名实现类的对象
Comparator<String> com = new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
}; TreeSet<String> set = new TreeSet<String>(com); set.add("HH");
set.add("MM");
set.add("GG");
set.add("JJ");
set.add("DD"); for (String s : set) {
System.out.println(s);
} } }

public class Employee {

	private int id;
private String name;
private int age;
private double salary; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} public double getSalary() {
return salary;
} public void setSalary(double salary) {
this.salary = salary;
} public Employee(int id, String name, int age, double salary) { this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
} public Employee() { } @Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}

public interface FilterData {

	public boolean filter(T t);
}

/**
*
* 提供FilterData接口的实现类,实现按照工资过滤数据
*/
public class FilterBySalary implements FilterData {
@Override
public boolean filter(Employee employee) {
return employee.getSalary() > 5000;
}
}


/**
* 提供FilterData接口的实现类,实现按照年龄过滤数据
*/
public class FilterByAge implements FilterData<Employee> {
@Override
public boolean filter(Employee employee) { return employee.getAge() > 30; }
}

/**
* 提供用于测试的数据
*
*/
public class EmployeeData { public static List<Employee> getEmployees(){
List<Employee> list = new ArrayList<>(); list.add(new Employee(1001, "马化腾", 34, 6000.38));
list.add(new Employee(1002, "马云", 12, 9876.12));
list.add(new Employee(1003, "刘强东", 33, 3000.82));
list.add(new Employee(1004, "雷军", 26, 7657.37));
list.add(new Employee(1005, "李彦宏", 65, 5555.32));
list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
list.add(new Employee(1007, "任正非", 25, 4333.32));
list.add(new Employee(1008, "扎克伯格", 35, 2500.32)); return list;
} }

语法格式一:无参,无返回值

语法格式二:Lambda 需要一个参数,但是没有返回值

语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

语法格式四:Lambda 若只需要一个参数时,数参数的小括号可以省略

语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值

语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略

类型推断

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。

java中Lambda表达式使用

/**
* 一、Lambda表达式的基本语法。
* 1.格式: lambda形参列表 -> lambda体
* 2.说明: -> : lambda操作符 或箭头操作符
* ->左边 :lambda表达式的形参列表
* ->右边:lambda表达式的执行语句,称为lambda体
*
* 3.如何使用:分为六种情况
*
* 总结:
* 1)lambda形参列表:如果有形参的话,都可以省略变量的数据类型 ---类型推断
* 如果形参列表只有一个形参,还可以省略一对()
* 2)lambda体:正常情况下,需要使用一对{}包起来所有的执行语句。
* 特别的,①lambda体如果只有一条执行语句,可以省略这一对{}.
* ②如果此唯一的一条执行语句是return,则除了省略一对{}之外,return关键字也可以省略
*
*
* 二、函数式接口
* 1.特点:如果一个接口中只有唯一的一个抽象方法,则此接口称为函数式接口
* 2.可以在接口的声明上使用@FunctionalInterface注解去校验一个接口是否是函数式接口
* 3.lambda表达式的使用依赖于函数式接口
* 4.lambda表达式即为函数式接口的实例。
*
* 只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
* 所以以前用匿名类表示的现在都可以用Lambda表达式来写。
*/
public class LambdaTest { //情况六:lambda体如果只有一条执行语句,可以省略这一对{}.
//特别的,如果此唯一的一条执行语句是return,则除了省略一对{}之外,return关键字也可以省略
@Test
public void test7(){
Comparator<Integer> com1 = (o1,o2) -> o1.compareTo(o2);
int value = com1.compare(12, 34);
System.out.println(value);
} //情况五:lambda表达式的形参列表有两个或两个以上的变量,lambda体有多条执行语句,甚至有返回值
@Test
public void test6(){
Comparator<Integer> com1 = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
// return Integer.compare(o1,o2);
return o1.compareTo(o2);
}
};
int value = com1.compare(12, 34);
System.out.println(value); System.out.println("******************"); Comparator<Integer> com2 = (o1,o2) -> {
System.out.println(o1);
System.out.println(o2);
// return Integer.compare(o1,o2);
return o1.compareTo(o2);
}; int value1 = com2.compare(32, 12);
System.out.println(value1); } //情况四:针对于情况三进行迭代。
//如果lambda表达式的形参列表只有一个变量,则可以省略一对()
@Test
public void test5(){
Consumer<String> con1 = s -> {
System.out.println(s);
}; con1.accept("你好我也好!");
} //以前在java程序中出现的类型推断
@Test
public void test4(){
//举例1:
int[] arr = new int[]{1,2,3,4};
//类型推断
int[] arr1 = {1,2,3,4};
int[] arr2 ;
arr2 = new int[]{1,2,3,4};
//举例2:
List<String> list = new ArrayList<String>();
//类型推断
List<String> list1 = new ArrayList<>(); //举例3
method(new HashMap<>());
}
public void method(HashMap<String,Employee> map){ } //情况三:针对于情况二进行迭代。lambda形参列表的变量的数据类型可以省略
//说明:java编译器可以根据上下文推断出变量的数据类型,故可以省略:类型推断
@Test
public void test3(){
Consumer<String> con1 = (s) -> {
System.out.println(s);
}; con1.accept("你好我也好!");
} //情况二:lambda形参列表有一个参数,无返回值
@Test
public void test2(){
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
}; con.accept("昨天过的挺好!"); System.out.println("*************"); Consumer<String> con1 = (String s) -> {
System.out.println(s);
}; con1.accept("你好我也好!"); } //情况一:无形参,无返回值
@Test
public void test1(){
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("昨天是七夕情人节!");
}
}; Thread t1 = new Thread(r);
t1.start(); System.out.println("*************"); Runnable r1 = () -> {
System.out.println("你们有没有因为爱情而鼓掌呢?");
}; Thread t2 = new Thread(r1);
t2.start();
}
}

17-2 函数式接口

什么是函数式(Functional)接口

  • 只包含一个抽象方法的接口,称为函数式接口。
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
  • 在java.util.function包下定义了java 8 的丰富的函数式接口

如何理解函数式接口

Java从诞生日起就是一直倡导“一切皆对象”,在java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)

在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。

所以以前用匿名内部类表示的现在都可以用Lambda表达式来写。

函数式接口举例

自定义函数式接口

函数式接口中使用泛型:

作为参数传递 Lambda 表达式

作为参数传递 Lambda 表达式

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

Java 内置四大核心函数式接口

/**
*
* java8中关于Lambda表达式提供的4个基本的函数式接口:
*
* 1.Consumer<T> :消费型接口
* void accept(T t)
*
* 2.Supplier<T> : 供给型接口
* T get()
*
* 3. Function<T,R> : 函数型接口
* R apply(T t)
*
* 4. Predicate<T> : 断定型接口
* boolean test(T t)
*
* 总结:从方法的角度来说:
* 1.方法在调用时,如果发现方法的形参是一个函数型接口,那么我们调用此方法时,可以使用lambda表达式
* 作为实参传递给此接口形参
* 2.方法定义时,如果需要一个定义接口,且此接口只有一个抽象方法,(说明此接口就是函数式接口),那么
* 考虑是有有现成的函数式接口可用。如果有,则不需要我们再去定义。比如:FilterData 替换为Predicate */
public class LambdaTest1 { //针对于昨天讲的举例三,可以不用定义FilterData接口,而使用Predicate接口即可
@Test
public void test5(){
List list = EmployeeData.getEmployees();
List data1 = filterData(list, e -> e.getAge() > 30);
System.out.println(data1); List data2 = filterData(list, e -> e.getSalary() > 5000);
System.out.println(data2);
}
public List<Employee> filterData(List<Employee> list, Predicate<Employee> filter){ ArrayList<Employee> data = new ArrayList<>();
for (Employee emp : list) {
if(filter.test(emp)){
data.add(emp);
}
}
return data; } //4. Predicate<T> : 断定型接口
//boolean test(T t)
@Test
public void test4(){
List<String> list = Arrays.asList("北京","南京","东京","西京","普京","上海","深圳");
List<String> data = getStrings(list, s -> s.contains("京"));
System.out.println(data);
} public List<String> getStrings(List<String> list, Predicate<String> pre){
List<String> data = new ArrayList<>();
for (String s : list) {
if(pre.test(s)){
data.add(s);
}
}
return data;
} //3. Function<T,R> : 函数型接口
//R apply(T t)
@Test
public void test3(){
strHandler(" hel lo ",str -> str.trim()); strHandler("世界那么大,我想去看看",str -> str.substring(2,5));
} public void strHandler(String str, Function<String,String> func){
String s = func.apply(str);
System.out.println(s);
} // 2.Supplier<T> : 供给型接口
// T get()
@Test
public void test2(){
List<Double> list = getRandomValue(10, () -> Math.random() * 100);
for (Double d : list) {
System.out.println(d);
}
} public List<Double> getRandomValue(int num, Supplier<Double> sup){
List<Double> list = new ArrayList<>();
for(int i = 0;i < num;i++){
list.add(sup.get());
}
return list;
} //1.Consumer<T> :消费型接口
//void accept(T t)
@Test
public void test1(){
happyNight(500, s -> {
System.out.println("学习很辛苦,累了的话,可以去正规的足浴店放松一下。花费:" + s);
});
} public void happyNight(Integer money, Consumer<Integer> con){
con.accept(money);
} }

其他接口

17-3 方法引用与构造器引用

方法引用(Method References)

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
  • 方法引用就是Lambda表达式,就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
  • 要求:实现抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
  • 方法引用:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
  • 如下三种主要使用情况:
    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名

方法引用

例如:

等同于:

例如:

等同于:

例如:

等同于:

注意:当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName


/**
* 方法引用的使用
* 1.方法引用可以看做是Lambda表达式深层次的表达,或者可以理解为:方法引用就是Lambda表达式。
* 又因为Lambda表达式本身就是函数式接口的实例,进而方法引用也可以看做函数式接口的实例。
*
* 2.使用情境:
* 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
*
* 3.要求:函数式接口中抽象方法的形参列表和返回值类型 要与 方法引用对应的方法的形参列表和返回值类型一致!
*
* 4.格式:类(或 对象) :: 方法名
*
* 5.分为如下的三种情况:
* ① 对象 :: 实例方法
* ② 类 :: 静态方法
* ③ 类 :: 实例方法 (有难度)
*
* 6.在满足如上的第2条d的情况下,使用方法引用替换Lambda表达式。如果方法引用不熟悉,可以使用Lambda表达式。
*/
public class MethodRefTest {

	//情况三:类 :: 实例方法  (有难度)
//注意:当函数式接口方法的第一个参数是需要引用方法的调用者,
//并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName
@Test
public void test7(){
Employee emp = new Employee(1001, "Jerry", 32, 23430);
Function<Employee,String> func1 = (e) -> e.getName();
String name = func1.apply(emp);
System.out.println(name); System.out.println("***************"); Function<Employee,String> func2 = Employee::getName;
String name1 = func2.apply(emp);
System.out.println(name1); }
@Test
public void test6(){
BiPredicate<String,String> bi = (s1,s2) -> s1.equals(s2);
boolean b = bi.test("abc", "abc");
System.out.println(b); System.out.println("***************"); BiPredicate<String,String> bi1 = String::equals;
boolean b1 = bi1.test("abc", "abc");
System.out.println(b1);
}
@Test
public void test5(){
Comparator<String> com = (s1,s2) -> s1.compareTo(s2);
int value = com.compare("abad", "abdd");
System.out.println(value); System.out.println("***************"); Comparator<String> com1 = String::compareTo;
int value1 = com1.compare("aaaaaa", "aa");
System.out.println(value1);
} //情况二:类 :: 静态方法
@Test
public void test4(){
Function<Double,Long> func1 = d -> Math.round(d);
Long value = func1.apply(12.3);
System.out.println(value); System.out.println("***************"); Function<Double,Long> func2 = Math::round;
Long value1 = func2.apply(12.6);
System.out.println(value1); Function<Long,Long> func3 = Math::abs;
Long value2 = func3.apply(-1234L);
System.out.println(value2);
}
@Test
public void test3(){
Comparator<Integer> com = (num1,num2) -> Integer.compare(num1,num2);
int value = com.compare(12, 32);
System.out.println(value); System.out.println("***************"); Comparator<Integer> com1 = Integer::compare;
int value1 = com1.compare(43, 12);
System.out.println(value1); } //情况一:对象 :: 实例方法
@Test
public void test2(){
Employee emp = new Employee(1001, "Tom", 23, 4534);
Supplier<String> sup = () -> emp.getName();
String name = sup.get();
System.out.println(name); System.out.println("***************"); Supplier<String> sup1 = emp::getName;
String name1 = sup1.get();
System.out.println(name1);
}
@Test
public void test1(){
Consumer<String> con1 = str -> System.out.println(str);
con1.accept("beijing"); System.out.println("***************"); PrintStream ps = System.out;
Consumer<String> con2 = ps :: println;
con2.accept("shanghai"); }
}

构造器引用

格式: ClassName::new

与函数式接口相结合,自动与函数式接口中方法兼容。

可以把构造器引用赋值给定义的方法,要求

构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。

例如:

等同于:

数组引用

格式: ClassName::new

例如:

等同于:


/**
* 一、构造器引用
* 1.格式: 类名 :: new
* 2.要求:函数式接口中抽象方法的形参列表与构造器形参列表一致(类型相同,个数相同),
* 同时,抽象方法的返回值类型即为构造器所属的类的类型。
*
*
* 二、数组引用
*/
public class ConstructorRefTest { @Test
public void test4(){
Function<Integer,String[]> func1 = (length) -> new String[length];
String[] arr = func1.apply(10);
System.out.println(arr.length); System.out.println("**********"); Function<Integer,String[]> func2 = String[]::new;
String[] arr1 = func2.apply(20);
System.out.println(arr1.length);
} @Test
public void test3(){
BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
Employee emp = func1.apply(10, "Jim");
System.out.println(emp); System.out.println("**********"); BiFunction<Integer,String,Employee> func2 = Employee::new;
Employee emp1 = func2.apply(20, "Jim");
System.out.println(emp1);
} @Test
public void test2(){
Function<Integer,Employee> func1 = (id) -> new Employee(id);
Employee emp1 = func1.apply(12);
System.out.println(emp1); System.out.println("**********"); Function<Integer,Employee> func2 = Employee::new;
Employee emp2 = func2.apply(12);
System.out.println(emp2);
} @Test
public void test1(){
Supplier<Employee> sup = () -> new Employee();
Employee emp = sup.get();
System.out.println(emp); System.out.println("**********"); Supplier<Employee> sup1 = Employee::new;
Employee emp1 = sup1.get();
System.out.println(emp1);
}
}

17-4 强大的Stream API

Stream API说明

Java8中有两大最为重要的改变。 第一个是 Lambda 表达式;另外一个则是 Stream API。

Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

为什么要使用Stream API

实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要java层面去处理。

什么是 Stream

Stream到底是什么呢?

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

“集合讲的是数据,Stream流讲的是计算!”

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream 的操作三个步骤

创建 Stream方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream stream() : 返回一个顺序流
  • default Stream parallelStream() : 返回一个并行流

####创建 Stream方式二:通过数组
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static Stream stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

创建 Stream方式三:通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T... values) : 返回一个流

####创建 Stream方式四:创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。

  • 迭代
    public static Stream iterate(final T seed, final UnaryOperator f)
  • 生成

    public static Stream generate(Supplier s)

####Stream 的中间操作
![](https://i.imgur.com/cDcoGyW.png)

Stream 的终止操作

  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
  • 流进行了终止操作后,不能再次使用。





并行流与串行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。

java中Stream API使用

/**
* 1.Stream API:
* 可以理解为java提供的一套api,使用这套api可以实现对集合、数组中的数据进行过滤、映射、归约、查找等操作
*
* 2.注意点:
* ①Stream 自己不会存储元素。
* ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
* ③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
*
* 3.Stream的使用流程:
* 步骤一:Stream的实例化
* 步骤二:一系列的中间操作
* 步骤三:终止操作
*
* 注意:①步骤二中的中间操作可以有多个
* ②如果没有终止操作,那么一系列的中间操作是不会执行的。只有执行了步骤三的终止操作,步骤二才会执行:惰性求值
* ③终止操作一旦执行,就不可以再执行中间操作或其他的终止操作。
*
*
* 测试Stream的实例化
*
*/
public class StreamAPITest { //方式四:创建无限流
@Test
public void test4(){
// 迭代
// public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
stream.limit(10).forEach(System.out::println); // 生成
// public static<T> Stream<T> generate(Supplier<T> s)
Stream<Double> stream1 = Stream.generate(Math::random);
stream1.limit(10).forEach(System.out::println); } //方式三:Stream的静态方法of()
@Test
public void test3(){
//public static<T> Stream<T> of(T... values) : 返回一个流
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
} //方式二:通过数组
@Test
public void test2(){
//调用Arrays的static <T> Stream<T> stream(T[] array): 返回一个流
String[] arr = new String[]{"MM","GG","JJ","DD"};
Stream<String> stream = Arrays.stream(arr); } //方式一:通过集合
@Test
public void test1(){
// default Stream<E> stream() : 返回一个顺序流
List<Employee> list = EmployeeData.getEmployees();
Stream<Employee> stream = list.stream();
// default Stream<E> parallelStream() : 返回一个并行流
Stream<Employee> stream1 = list.parallelStream(); }
}

/**
* 测试中间操作
* 1.可以通过Stream的实例,执行多次中间操作
* 2.中间操作,只有在执行了终止操作以后才会执行。
*/
public class StreamAPITest1 {

	//3-排序
@Test
public void test4(){
// sorted()——自然排序
List<Integer> list = Arrays.asList(23,43,454,32,1,2,5,5,-8);
list.stream().sorted().forEach(System.out::println); //此时针对Employees进行排序:失败。原因:Employee类没有实现Comparable接口
// List<Employee> list1 = EmployeeData.getEmployees();
// list1.stream().sorted().forEach(System.out::println); // sorted(Comparator com)——定制排序
List<Employee> list1 = EmployeeData.getEmployees();
list1.stream().sorted((e1,e2) -> {
if(e1.getAge() != e2.getAge()){
return e1.getAge() - e2.getAge();
}else{
return -Double.compare(e1.getSalary(),e2.getSalary());
}
}).forEach(System.out::println); } @Test
public void test3(){
ArrayList list1 = new ArrayList();
list1.add(1);
list1.add(2);
list1.add(3); ArrayList list2 = new ArrayList();
list2.add(4);
list2.add(5);
list2.add(6); // list1.add(list2); ---map()
// System.out.println(list1);//[1, 2, 3, [4, 5, 6]] // list1.addAll(list2); -- flatMap()
// System.out.println(list1);//[1, 2, 3, 4, 5, 6] }
//2-映射
@Test
public void test2(){
// map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
List<String> list = Arrays.asList("aa","bb","cc","dd");
list.stream().map(String::toUpperCase).forEach(System.out::println);
// 练习:获取员工姓名长度大于3的员工的姓名。
Stream<Employee> stream = EmployeeData.getEmployees().stream();
Stream<String> stream1 = stream.map(Employee::getName);
stream1.filter(name -> name.length() > 3).forEach(System.out::println); Stream<Stream<Character>> stream2 = list.stream().map(StreamAPITest1::fromStringToChar);
stream2.forEach(
x ->{
x.forEach(System.out::println);
} ); System.out.println(); // flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
Stream<Character> stream3 = list.stream().flatMap(StreamAPITest1::fromStringToChar);
stream3.forEach(System.out::println);
}
//将str中的字符存在集合中,返回集合的Stream
public static Stream<Character> fromStringToChar(String str){
ArrayList<Character> list = new ArrayList<>();
// for(Character c : str.toCharArray()){
// list.add(c);
// }
for(int i = 0;i < str.length();i++){
list.add(str.charAt(i));
}
return list.stream();
} //1-筛选与切片
@Test
public void test1(){
List<Employee> list = EmployeeData.getEmployees();
//体会方法链的调用方式。比如:StringBuffer s = new StringBuffer(); s.append("A").append("B");
// filter(Predicate p)——接收 Lambda , 从流中排除某些元素。
Stream<Employee> stream = list.stream().filter(e -> e.getAge() > 30);
stream.forEach(System.out::println); System.out.println(); // limit(n)——截断流,使其元素不超过给定数量。
list.stream().filter(e -> e.getAge() > 30).limit(3).forEach(System.out::println); System.out.println(); // skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
list.stream().filter(e -> e.getAge() > 30).skip(3).forEach(System.out::println); System.out.println(); // distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
list.add(new Employee(1009,"刘强东",30,6000));
list.add(new Employee(1009,"刘强东",30,6000));
list.add(new Employee(1009,"刘强东",30,6000));
list.add(new Employee(1009,"刘强东",30,6000));
list.add(new Employee(1009,"刘强东",30,6000));
list.stream().distinct().forEach(System.out::println);
}
}

/**
* 步骤三:终止操作
*/
public class StreamAPITest2 {

	//3-收集:将集合--->Stream --->集合
@Test
public void test5(){
// collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,
// 用于给Stream中元素做汇总的方法 List<Employee> list = EmployeeData.getEmployees();
List<Employee> list1 = list.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toList());
//遍历
list1.forEach(System.out::println); System.out.println(); Set<Employee> set = list.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toSet());
set.forEach(System.out::println); System.out.println(); ArrayList<Employee> list2 = list.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toCollection(ArrayList::new));
for (Employee employee : list2) {
System.out.println(employee);
}
} //2-归约
@Test
public void test4(){
// reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
List<Integer> list = Arrays.asList(1,2,3,4,5,6);
// Integer sum = list.stream().reduce(0, (x1, x2) -> x1 + x2);
Integer sum = list.stream().reduce(10, Integer::sum);
System.out.println(sum); // reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
// 练习1:计算公司所有员工工资的总和
List<Employee> emps = EmployeeData.getEmployees();
Stream<Double> moneyStream = emps.stream().map(Employee::getSalary);
Optional<Double> moneyOptional = moneyStream.reduce(Double::sum);
System.out.println(moneyOptional.get()); // 练习2:员工姓名中包含“马”字的个数
Stream<String> nameStream = emps.stream().map(Employee::getName);
Stream<Character> charStream = nameStream.flatMap(StreamAPITest1::fromStringToChar);
//方式一:
// long count = charStream.filter(c -> c.equals('马')).count();
// System.out.println(count);
//方式二:
Optional<Integer> op = charStream.map(c -> {
if (c.equals('马')) {
return 1;
} else {
return 0;
}
}).reduce(Integer::sum); System.out.println(op.get()); //练习3:员工姓名中包含“马”的员工个数
long count = emps.stream().map(Employee::getName).filter(name -> name.contains("马")).count();
System.out.println(count); // 练习4:员工姓名中包含“马”的员工的姓名
emps.stream().map(Employee::getName).filter(name -> name.contains("马")).forEach(System.out::println); }
//1-匹配与查找
@Test
public void test2(){
List<Employee> list = EmployeeData.getEmployees();
// max(Comparator c)——返回流中最大值
// 练习:返回最高的工资:
Stream<Employee> stream = list.stream();
Stream<Double> stream1 = stream.map(Employee::getSalary);
Optional<Double> max = stream1.max(Double::compare);
System.out.println(max.get());
// min(Comparator c)——返回流中最小值
// 练习:返回最低工资的员工
Stream<Employee> stream2 = list.stream();
Optional<Employee> min = stream2.min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
System.out.println(min.get());
// forEach(Consumer c)——内部迭代
list.stream().forEach(System.out::println); } //外部迭代
@Test
public void test3(){
List<Employee> list = EmployeeData.getEmployees();
Iterator<Employee> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
} @Test
public void test1(){
List<Employee> list = EmployeeData.getEmployees();
// allMatch(Predicate p)——检查是否匹配所有元素
//是否所有的员工的年龄都大于18
boolean b = list.stream().allMatch(e -> e.getAge() > 18);
System.out.println(b); // anyMatch(Predicate p)——检查是否至少匹配一个元素
//是否存在员工的工资大于 10000
boolean b1 = list.stream().anyMatch(e -> e.getSalary() > 9900);
System.out.println(b1); // noneMatch(Predicate p)——检查是否没有匹配的元素
//是否存在员工姓“雷”
boolean b2 = list.stream().noneMatch(e -> e.getName().contains("雷"));
System.out.println(b2); // findFirst——返回第一个元素
Optional<Employee> emp = list.stream().sorted((e1,e2) -> {
if(e1.getAge() != e2.getAge()){
return e1.getAge() - e2.getAge();
}else{
return -Double.compare(e1.getSalary(),e2.getSalary());
}
}).findFirst();
System.out.println(emp.get()); // findAny——返回当前流中的任意元素
Optional<Employee> emp1 = list.parallelStream().findAny();
System.out.println(emp1.get());
// count——返回流中元素的总个数
long count = list.stream().filter(e -> e.getSalary() > 5000).count();
System.out.println(count); }
}

第十七章 java8特性的更多相关文章

  1. 20190903 On Java8 第十七章 文件

    第十七章 文件 在Java7中对 文件的操作 引入了巨大的改进.这些新元素被放在 java.nio.file 包下面,过去人们通常把nio中的n理解为new即新的io,现在更应该当成是non-bloc ...

  2. Pro ASP.NET MVC –第四章 语言特性精华

    C#语言有很多特性,并不是所有的程序员都了解本书我们将会使用的C#语言特性.因此,在本章,我们将了解一下作为一个好的MVC程序员需要了解C#语言的特性. 每个特性我们都只是简要介绍.如果你想深入了解L ...

  3. Linux内核设计第十七章笔记

    第十七章 设备与模块 关于设备驱动和设备管理,四种内核成分 设备类型:在所有unix系统中为了统一普通设备的操作所采用的分类 模块:Linux内核中用于按需加载和卸载目标代码的机制 内核对象:内核数据 ...

  4. 进击的Python【第十七章】:jQuery的基本应用

    进击的Python[第十七章]:jQuery的基本应用

  5. <构建之法>第十三章到十七章有感以及这个项目读后感

    <构建之法>第十三章到十七章有感 第13章:软件测试方法有哪些? 主要讲了软件测试方法:要说有什么问题就是哪种效率最高? 第14章:质量保障 软件的质量指标是什么?怎么样能够提升软件的质量 ...

  6. 程序员编程艺术第三十六~三十七章、搜索智能提示suggestion,附近点搜索

    第三十六~三十七章.搜索智能提示suggestion,附近地点搜索 作者:July.致谢:caopengcs.胡果果.时间:二零一三年九月七日. 题记 写博的近三年,整理了太多太多的笔试面试题,如微软 ...

  7. 《Linux命令行与shell脚本编程大全》 第二十七章 学习笔记

    第二十七章:shell脚本编程进阶 监测系统统计数据 系统快照报告 1.运行时间 uptime命令会提供以下基本信息: 当前时间 系统运行的天数,小时数,分钟数 当前登录到系统的用户数 1分钟,5分钟 ...

  8. [汇编学习笔记][第十七章使用BIOS进行键盘输入和磁盘读写

    第十七章 使用BIOS进行键盘输入和磁盘读写 17.1 int 9 中断例程对键盘输入的处理 17.2 int 16 读取键盘缓存区 mov ah,0 int 16h 结果:(ah)=扫描码,(al) ...

  9. 第十七章——配置SQLServer(3)——配置“对即时负载的优化”

    原文:第十七章--配置SQLServer(3)--配置"对即时负载的优化" 前言: 在第一次执行查询或者存储过程时,会创建执行计划并存储在SQLServer的过程缓存内存中.在很多 ...

随机推荐

  1. .gitignore无效

    有个别文件不想跟踪,比如pycharm的配置文件. 但是这个文件已经被git跟踪的情况下,再加入.gitignore是无效的. 只需要: git rm --cached .idea/workspace ...

  2. js没事干应该瞧瞧的一些博客

    https://zhidao.baidu.com/question/1736568808722198187.html http://www.cnblogs.com/webpush/p/4963002. ...

  3. centos 7.4 u盘安装

    因为核心视频服务要运行在centos环境下,so,配了台牛逼的thinkstation(其实有点浪费) 1:u盘制作 下载centos7.4 的iso镜像文件CentOS-7-x86_64-DVD-1 ...

  4. django项目的部署

    django项目部署到云服务器: 0.通过xshell连接远程主机服务器ip 1.使用xftp将项目发送到服务器端(也可以使用git) a.路径推荐为/var/project/(项目名) 2.给服务器 ...

  5. golang 报错illegal rune literal

    记录一下,今天运行一端代码遇到这个报错"illegal rune literal",代码如下: func main() { http.HandleFunc('/login', lo ...

  6. unity中Event Trigger组件应用代码

    using UnityEngine; using System.Collections; using UnityEngine.Events; using UnityEngine.EventSystem ...

  7. LeetCode Best to buy and sell stock

    Best Time to Buy and Sell Stock 题目大意;给定数组a[..],求解max a[j]-a[i]    j>i 解决思路:将数组a的相邻值相减(右边减左边)变换成数组 ...

  8. 负载均衡----实现配置篇(Nginx)

    同类文章:负载均衡----概念认识篇 吐槽:自从那篇“探讨负载均衡”那篇文章被博客园吐槽后,心里极度不平衡,思来想去还是把名字改成“负载均衡----概念认识篇”,再加多几篇文章来诠释上篇文章的精华所在 ...

  9. sql 中取整,四舍五入取整,向下取整,向上取整。

    SELECT round(52.45, 0) AS round4, round(52.54, 0) AS round5, round(52.45, 1) AS round41, round(52.54 ...

  10. SqlServer表中添加新字段

    表中添加新字段ALTER TABLE 表名 ADD 字段名 VARCHAR(20) NULL 表中添加自增idalter table lianxi add id int primary key IDE ...