- 函数式接口:参数是一个接口,并且接口中只有一个方法。
- 不关心接口、方法的名字,
只关心方法的参数和方法体
。- 选中函数式接口,
直接生成Lambda表达式快捷键 alt+enter
一、Lambda表达式
(参数列表) ->{代码}
省略规则:
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
- 以上这些规则都记不住也可以省略不记 alt+enter: Lambda转换成原来的代码
没有参数
public static void main(String[] args) {
//以前的代码
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("大家好");
}
}).start();
// Lambda表达式
new Thread(() ->{
System.out.println("早上好");
}).start();
}
有参数
public static void main(String[] args) {
// 以前写法
int i = calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left * right;
}
});
System.out.println(i);
// Lambda表达式
int i = calculateNum((int left, int right)-> {
return left * right;
});
System.out.println(i);
// Lambda表达式简写
int i1 = calculateNum((left, right) -> left * right);
System.out.println(i1);
// 参数是一个函数式接口
public static int calculateNum(IntBinaryOperator operator) {
int a = 10;
int b = 20;
return operator.applyAsInt(a, b);
}
1.1 alt+enter:Lambda转换成原来的代码
二、Stream流
- 对集合和数组进行链状流式的操作,更加方便操作集合和数组。
- 以前的是IO流,读写数据用的。这里的Stream流是对集合数组进行操作。
2.1 Debug过程
准备代码:实体类代码
//作者
public class Author implements Comparable<Author>{
private Long id;
private String name;
private String introduction;
private Integer age;
private List<Book> bookList;
}
//书
public class Book {
private Long id;
private String category;
private String name;
private Double score;
private String introduction;
}
//产品
public class Product{
int id;
String name;
Double price;
}
// 初始化一些数据
private static List<Author> getAuthors() {
Author author1 = new Author(1L, "杨杰炜", "my introduction 1", 18, null);
Author author2 = new Author(2L, "yjw", "my introduction 2", 19, null);
Author author3 = new Author(3L, "yjw", "my introduction 3", 14, null);
Author author4 = new Author(4L, "wdt", "my introduction 4", 29, null);
Author author5 = new Author(5L, "wtf", "my introduction 5", 12, null);
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
// 上面是作者和书
books1.add(new Book(1L, "类别,分类啊", "书名1", 45D, "这是简介哦"));
books1.add(new Book(2L, "高效", "书名2", 84D, "这是简介哦"));
books1.add(new Book(3L, "喜剧", "书名3", 83D, "这是简介哦"));
books2.add(new Book(5L, "天啊", "书名4", 65D, "这是简介哦"));
books2.add(new Book(6L, "高效", "书名5", 89D, "这是简介哦"));
books3.add(new Book(7L, "久啊", "书名6", 45D, "这是简介哦"));
books3.add(new Book(8L, "高效", "书名7", 44D, "这是简介哦"));
books3.add(new Book(9L, "喜剧", "书名8", 81D, "这是简介哦"));
author1.setBookList(books1);
author2.setBookList(books2);
author3.setBookList(books3);
author4.setBookList(books3);
author5.setBookList(books2);
return new ArrayList<>(Arrays.asList(author1, author2, author3, author4, author5));
}
2.2 入门案例
调用getAuthors方法获取到作家的集合。打印所有年龄小于18的作家的名字,并且要注意去重。
private static void test1(){
List<Author> authors = getAuthors();
authors.stream() //把集合转换成流
.distinct() //去重
.filter(author -> author.getAge()<18) //筛选年龄小于18
.forEach(author -> System.out.println(author.getName())); //遍历打印名字
}
2.3 方法
2.3.1 创建流
单列集合
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();
数组
Integer[] arr = {1,2,3,4};
//方式一
Stream<Integer> stream = Arrays.stream(arr);
//方式二
Stream<Integer> stream = Stream.of(arr);
双列集合:转换成单列集合再创建
Map<String,Integer> map = new HashMap<>();
map.put("波吉", 15);
map.put("卡克", 14);
map.put("波斯", 13);
Stream<Map.Entry<String,Integer>> stream = map.entrySet().stream();
2.3.2 中间操作
filter
可以对流中的元素进行条件过滤,
符合过滤条件的才能继续留在流中
。
//打印所有姓名长度大于1的作家的姓名
private static void test() {
List<Author> authors = getAuthors();
authors.stream()
.filter(author -> author.getName().length()>1)
.forEach(author -> System.out.println(author.getName()));
}
map
对流中的元素进行计算或转换
//打印所有作家的姓名
private static void test(List<Author> authors) {
authors.stream() // 返回值是stream对象,也就是集合转流
.map(author -> author.getName()) // 只要名字
.distinct() // 去重,这里不能放在前面,不然比较的是author元素不是名字
.forEach(System.out::println); // 遍历输出
}
authors.stream()Stream
.map(author -> author.getAge())
.map(age -> age+10)
.forEach(age -> System.out.println(age));
distinct
可以
去除流中的重复元素
注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法。
sorted
可以对流中的元素进行
排序
。
注意:如果调用空参的sorted()方法,需要流中的元紊是实现了Comparable。
@Data
@EqualsAndHashCode
public class Author implements Comparable<Author>{
private Long id;
private String name;
private String introduction;
private Integer age;
private List<Book> bookList;
/** 使用sorted时比较整个元素时,要实现比较接口,并重写方法
*/
@Override
public int compareTo(Author o) {
// 0表示年纪一样大,负数表示传入的大
// 这里sorted如果输出的是降序,你就把这俩顺序对换就可以了,不用记忆
return o.getAge() - this.getAge();
}
}
limit
可以
设置流的最大长度
,超出的部分将被抛弃。
skip
跳过流中的前个元素,返回剩下的元素
flatMap
map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。
2.3.3 终结操作
forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作。
count
可以用来获取当前流中元素的个数。
max & min
可以用来获取流中的最值。
collect
把当前流转换成一个集合。
查找与匹配 — anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型。
查找与匹配 — allMatch
可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则结果为false。
查找与匹配 — noneMatch
可以判断流中的元索是否都不符合匹配条件。如果都不符合结果为true,否则结果为fase
查找与匹配 — findany
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。
查找与匹配 — findFirst
获取流中的第一个元素。
reduce归并
对流中的数据按照你指定的计算方式计算出一个结果。
(缩减操作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值
,它会按照我们的计算方式依次拿流中的元素和在初始化值的基础上进行计算
,计算结果再和后面的元素计算
。
reduce两个参数的重载形式内部的计算原理:
案例1:
案例2:
案例3:
reduce一个参数的重载形式内部的计算原理:
private static void test19() {
Optional<Integer> reduce = getAuthors().stream()
.distinct()
.map(author -> author.getAge())
.reduce((result, element) -> result < element ? result : element);
System.out.println("单个参数的reduce方法使用,作者中年龄最小的是:" + reduce.get());
}
三、Optional
- 我们在编写代码的时候会出现空指针异常,所以很多情况下需要做各种非空判断。尤其是对象中属性还是一个对象的情况下。但过多的判断会让我们的代码显得臃肿。
Optional就可以帮助我们写出更优雅的代码来避免空指针异常
。
使用
3.1 创建对象
- Optional就好像是包装类,可以把我们的具体数据封装Optionaly对象内部。然后我们去使用Optionale中封装好的方法操作封装进去的数据就可以非常优雅的避免空指针异常。
3.1.1 Optional的静态方法ofNullable
- 一般
使用Optional的静态方法ofNullable
来把数据封装成一个Optional对象。无论传入的参数是否为null都不会出现问题。
3.1.2 Optional的静态方法empty
3.2 安全消费值
ifPresent
我们获取到一个Optional对象后肯定需要对其中的数据进行使用。可以使用其ifPresent方法对来消费其中的值。
这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码。这样使用起来就更加安全了。
例如,以下写法就优雅的避免了空指针异常。
Optional<Author> authoroptional = optional.ofNullable(getAuthor ());
authoroptional.ifPresent(author -> System.out.println(author.getName()));
3.3 获取值 get()
不推荐
3.4 安全获取值
推荐
3.4.1 orElseGet
获取数据并且设置数据为空时的默认值。如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建对象作为默认值返
回。
3.4.2 orElseThrow
获取数据,如果数据不为空就能获取到该数据。如果为空则根据你传入的参数来创建异常抛出。
3.5 过滤
我们可以使用flter方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optionaly对象。
3.6 判断
我们可以使用isPresent方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为true。但是这种方式并不能体现Optional的好处,更推荐使用ifPresent方法。
3.7 数据转换
Optionali还提供了map可以让我们的对数据进行转换,并且转换得到的数据也还是被Optionalf包装好的,保证了我们的使用安全。
四、函数式接口
只有一个抽象方法
的接口我们称之为函数接口。- JDK的函数式接口都加上了@Functionallnterface注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
五、方法引用
- 我们在使用lambdal时,如果方法体中
只有一个方法的调用
的话(包括构造方法),我们可以用方法引用进一步简化代码。- 类名或者对象名::方法名