提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、Lambda表达式
1.1 概述
Lambda表达式的使用:
- 举例:
(o1,o2) -> Integer.compare(o1,o2);
- Lambda表达式的本质:
作为函数式接口的实例
- 格式:
- ->:
lambda操作符或箭头操作符
- 左边:
lambda形参列表(其实就是抽象方法的形参列表)
- 右边:
lambda体(其实就是重写的抽象方法的方法体)
- ->:
1.2 语法格式
- 语法格式一:无参数,无返回值
//语法格式一:无参数,无返回值
@Test
public void test01(){
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
};
// r1.run();
//使用lambda表达式
Runnable r2 = ()-> System.out.println("hello girl");
r2.run();
}
- 语法格式二:有参数,无返回值
//语法格式二:有参数,无返回值
@Test
public void test02(){
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("人人为我我为人人");
System.out.println("***********************************");
//参数里的String类型可以省略,因为编译器可以做出”类型推断“
//参数如果只有一个,那么参数的小括号也可以省略
Consumer<String> con2 = s ->{
System.out.println(s);
};
con.accept("你笑起来真好看");
}
- 语法格式三:有多个参数多条语句,有返回值
@Test
public void test04(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
System.out.println(com.compare(15, 20));
System.out.println("============================================");
Comparator<String> com2 = (s1,s2) ->{
System.out.println(s1);
System.out.println(s2);
return s1.compareTo(s2);
};
System.out.println(com2.compare("a","b"));
}
- 语法格式四:当Lambda体只有一条语句时
//语法格式四:当Lambda体只有一条语句时,return与大括号都可以省略
@Test
public void test05(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return -o1.compareTo(o2);
}
};
System.out.println(com.compare(12, 50));
System.out.println("==================================");
Comparator<Integer> com2 = (o1,o2) -> -o1.compareTo(o2);
System.out.println(com2.compare(15,10));
}
1.3 总结
Lambda表达式的形参列表的参数类型可以省略(类型推断);如果Lambda形参列表只有一个参数,()也可以省略
Lambda体应该使用一对{}包裹;如果Lambda体只有一条执行语句(可能有返回值),return关键字与{}都可以省略(如果省略{}那么return关键字也必须省略)
二、函数式接口
2.1 概述
- 只包含一个抽象方法的的接口,称为函数式接口
- 你可以通过Lambda表达式创建该接口的对象(
若Lambda表达式抛出一个受检异常(非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明
) - 我们可以在接口上使用@Fuctionallnterface 注解,这样做可以检查他是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口
- java.util.fuction包下定义了丰富的函数式接口
- 以前使用匿名实现类表示的现在都可以使用Lambda表达式来书写
2.2 Java内置函数式接口
- 四大函数式接口
- 其他接口
三、方法的引用与构造器的引用
3.1 方法引用(Method Reference)
- 当要传递给Lambda体的操作,已经有了实现的方法,可以使用方法的引用
- 方法引用可以看作是Lambda表达式深层次的表达,
换句话说,方法的引用就是Lambda表达式,也就是函数式接口的一个实例
,通过方法的名字来指向一个方法,可以把它作为Lambda表达式的语法糖 - 要求:实现接口的抽象方法的参数类型和返回值类型,必须与方法引用的方法参数列表和返回值类型保持一致
- 格式:使用操作符
::
将类(或对象)与方法名分隔开来 - 三种主要使用情况:
对象::实例方法名
类::静态方法名
类::实例方法名
- 方法引用的使用要求:要求接口中的抽象方法的【形参列表】和【返回值类型】与【方法引用的方法】的【形参列表】和【返回值类型】相同
3.2 案例分析
情况一:对象 :: 实例方法
//情况一:对象 :: 实例方法
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test01(){
//常规方式
Consumer<String> con = str -> System.out.println(str);
con.accept("hello world");
System.out.println(line);
//当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用
PrintStream ps = System.out;
Consumer<String> con2 = ps::println;
con2.accept("我爱你中华!");
}
//Supplier中的T get()
//Employee中的String getName()
@Test
public void test02(){
//Lambda的常规用法
Supplier<Integer> s = () ->10;
System.out.println(s.get());
System.out.println(line);
Employee emp = new Employee(12, "张三", 10, 50000000);
//对象 :: 实例方法
Supplier<String> s2 = emp::getName;
System.out.println(s2.get());
}
情况二:类 :: 静态方法
// 情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test03(){
// Comparator<Integer> com = (Integer i1,Integer i2) -> {
// return Integer.compare(i1, i2);
// };
Comparator<Integer> com3 = (i1,i2) -> Integer.compare(i1, i2);
System.out.println(com3.compare(10, 20));
System.out.println(line);
// 情况二:类 :: 静态方法
Comparator<Integer> com2 = Integer::compare;
System.out.println(com2.compare(112, 32));
}
//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test04(){
Function<Double,Long> f = new Function<Double, Long>() {
@Override
public Long apply(Double aDouble) {
return Math.round(aDouble);
}
};
//Lambda表达式的写法
Function<Double,Long> f2 = (aDouble) -> Math.round(aDouble);
//方法的引用
Function<Double,Long> f3 = Math::round;
System.out.println(f3.apply(12.4));
}
情况三:类 :: 实例方法
// 情况三:类 :: 实例方法 (有难度)
// Comparator中的int compare(T t1,T t2)
// String中的int t1.compareTo(t2)
@Test
public void test05(){
Comparator<Integer> com = (i1,i2) -> i1.compareTo(i2);
System.out.println(com.compare(10, 15));
System.out.println(line);
//类 :: 实例方法
Comparator<String> com2 = String::compareTo;
System.out.println(com2.compare("abc", "aph"));
}
//BiPredicate中的boolean test(T t1, T t2);
//String中的boolean t1.equals(t2)
@Test
public void test06(){
BiPredicate<String,String> b = (s1,s2) -> s1.equals(s2);
System.out.println(b.test("abc", "abc"));
System.out.println(line);
BiPredicate<String,String> b2 = String::equals;
System.out.println(b2.test("acb", "sbf"));
}
四、强大的Stream API
4.1 Stream API概述
4.1.1 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 提供了一种高效且易于使用的处理数据的方式
4.1.2 为什么要使用Stream API
- 实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。
- Collection 和 Stream 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算
4.1.3 什么是Stream
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
集合讲的是数据,Stream讲的是计算!
注意:
Stream 自己不会存储元素
Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream
Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行
4.1.4 Stream操作的三个步骤
创建 Stream:
一个数据源(如:集合、数组),获取一个流中间操作:
一个中间操作链,对数据源的数据进行处理终止操作(终端操作):
一旦执行终止操作,就执行中间操作链,并产生结果。之后,不会再被使用
4.2 创建Stream的方式
- 创建 Stream方式一:通过集合
//创建Stream方式一:通过集合
@Test
public void test01(){
List<Employee> employees = EmployeeData.getEmployees();
//default Stream<E> stream():返回一个顺序流
Stream<Employee> stream = employees.stream();
//default Stream parallelStream():返回一个并行流
Stream<Employee> parallelStream = employees.parallelStream();
}
- 创建 Stream方式二:通过数组
//创建Stream方式二:通过数组
@Test
public void test02(){
int[] arr = new int[]{1, 2, 3, 4, 5, 6};
IntStream stream = Arrays.stream(arr);
Employee emp1 = new Employee(1, "dick");
Employee emp2 = new Employee(2, "jack");
Employee[] emps = new Employee[]{emp1,emp2};
Stream<Employee> stream1 = Arrays.stream(emps);
}
- 创建 Stream方式三:通过Stream的of()
//创建Stream方式三:通过Stream的of()
@Test
public void test03(){
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
}
- 创建 Stream方式四:创建无限流
//创建Stream方式四:创建无限流
@Test
public void test04(){
//遍历前十个数
Stream.iterate(0, t -> t+ 2).limit(10).forEach(System.out::println);
}
4.3 Stream 的中间操作
4.3.1 概述
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”
4.3.2 中间操作分类
- 筛选与切片
案例分析:
//筛选与切片
@Test
public void test01(){
List<Employee> list = EmployeeData.getEmployees();
//从流中排除某些元素,找出工资大于7000的员工
Stream<Employee> stream = list.stream();
stream.filter(s -> s.getSalary() > 7000).forEach(System.out::println);
//limit():截断流,是元素不超过给定的数量
Stream<Employee> stream1 = list.stream();
stream1.filter(s -> s.getSalary() > 5000).limit(2).forEach(System.out::println);
//skip():跳过元素
Stream<Employee> stream2 = list.stream();
stream2.filter(s -> s.getSalary() > 5000).skip(2).forEach(System.out::println);
//distinct():筛选去重
Stream<Employee> stream3 = list.stream();
stream3.filter(s -> s.getSalary() > 2000).distinct().forEach(System.out::println);
}
- 映 射
案例分析:
@Test
public void test03(){
String[] str = new String[]{"aa","ss","dd","mm"};
List<String> array = Arrays.asList(str);
//通过map遍历
Stream<Stream<Character>> streamStream = array.stream().map(StreamTest02::fromStringToStream);
streamStream.forEach(s -> {
s.forEach(System.out::println);
});
//flatMap(Function f)
//接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
Stream<Stream<Character>> streamStream2 = array.stream().map(StreamTest02::fromStringToStream);
Stream<Character> characterStream = array.stream().flatMap(StreamTest02::fromStringToStream);
characterStream.forEach(System.out::println);
}
//将字符串中的多个字符构成的集合转换为对应的Stream实例
public static Stream<Character> fromStringToStream(String str){
ArrayList<Character> list1 = new ArrayList<>();
for (Character c : str.toCharArray()) {
list1.add(c);
}
return list1.stream();
}
- 排序
案例分析:
//排序sort()
@Test
public void test05(){
List<Employee> employees = EmployeeData.getEmployees();
List<Integer> arrays = Arrays.asList(5, 8, 6, 3, 10, 25, 99, 21);
//整型可以自然排序,因为其实现了Comparable接口
arrays.stream().sorted().forEach(System.out::println);
//Employee类没有实现Comparable接口,无法实现自然排序,只能定制排序
//根据员工的年龄进行排序
employees.stream().sorted((s1,s2) -> {
int com1 = Integer.compare(s1.getAge(), s2.getAge());
if (com1 != 0){
return com1
}else {
return Double.compare(s1.getSalary(), s2.getSalary());
}
}).forEach(System.out::println);
}
}
4.4 Stream 的终止操作
4.4.1 概述
- 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void
- 流进行了终止操作后,不能再次使用
4.4.2 终止操作的分类
-
匹配与查找
-
归约
-
收集