笨蛋学Lambda表达式
函数式编程-Stream流
1.函数式编程思想
1.1概念
-
面向对象思想需要关注用什么对象完成什么事情,而函数式编程思想就类似于我们数学中的函数,它主要关注的是对数据进行了什么操作
-
lambda 表达式相当于是一种匿名函数,它可以在需要一个简单函数的地方快速定义一个函数,而不必专门为这个函数取名字。
1.2优点
- 代码简介,开发快速
- 接近自然语言,易于理解
- 易于并发编程
2.Lambda表达式
2.1概念
- 是JDK8中一个语法糖,可以对某些匿名内部类的写法进行简化,是函数式编程思想的一个重要体现。
- 让我们不用关注是什么对象,而是更加关注我们对数据进行了什么操作
- 关注的是方法中的参数,以及方法体
2.2核心原则
- 可推到可省略
2.3基本格式
(参数列表)->{代码}
2.4实例
2.4.1创建线程时的优化
-
使用匿名内部类的写法
new Thread(new Runnable() { @Override public void run() { System.out.println("使用匿名内部类的写法创建线程并启动"); } },"t1").start();
-
使用Lambda表达式的写法
new Thread(()->{ System.out.println("使用Lambda表达式创建线程并启动"); },"t2").start();
2.4.2使用IntBinaryOperator接口时的优化
public static int calculateNum(IntBinaryOperator operator){
int a=10;
int b=20;
return operator.applyAsInt(a,b);
}
-
使用匿名内部类的写法
int i = calculateNum(new IntBinaryOperator() { @Override public int applyAsInt(int left, int right) { return left + right; } });
-
使用Lambda表达式的写法
int j = calculateNum((int left, int right) -> { return left + right; }); System.out.println(j);
2.4.3使用IntPredicate接口时的优化
public static void printNum(IntPredicate predicate){
int[] arr={1,2,3,4,5,6,7,8,9,10};
for(int i:arr){
if(predicate.test(i)){
System.out.println(i);
}
}
}
-
使用匿名内部类的写法
printNum(new IntPredicate() { @Override public boolean test(int value) { return value%2==0; } });
-
使用Lambda表达式的写法
printNum((value)->{ return value%2==0; });
2.4.4在方法中使用泛型的优化
public static <R> R typeConver(Function<String,R> function){
String str="123";
R result=function.apply(str);
return result;
}
-
使用匿名内部类的写法
Integer ii = typeConver(new Function<String, Integer>() { @Override public Integer apply(String s) { return Integer.parseInt(s); } }); System.out.println(ii);
-
使用Lambda表达式的写法
Integer jj = typeConver((String str) -> { return Integer.parseInt(str); }); System.out.println(jj);
2.4.5使用foreach循环时的优化
public static void foreachArr(IntConsumer consumer){
int[] arr={1,2,3,4,5,6,7,8,9,10};
for(int i:arr){
consumer.accept(i);
}
}
-
使用匿名内部类的写法
foreachArr(new IntConsumer() { @Override public void accept(int value) { System.out.println(value); } });
-
使用Lambda表达式的写法
foreachArr((value)->{ System.out.println(value); });
2.5省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
- 以上这些规则都记不住也可以省略不记(使用Alt + Enter)
-
对2.4.1例子的优化
new Thread(()-> System.out.println("使用Lambda表达式创建线程并启动"),"t3").start();
-
对2.4.2例子的优化
calculateNum((left, right) -> left + right);
-
对2.4.3例子的优化
printNum(value->value%2==0);
-
对2.4.4例子的优化
typeConver(str -> Integer.parseInt(str));
-
对2.4.5例子的优化
foreachArr(value-> System.out.println(value));
3.Stream流
3.1概念
- Java8的Stream使用的是函数式编程模式,如其名一样,它可以被用来对集合或数组进行链状流式的操作,可以更方便的让我们对集合或数组进行操作
3.2案例数据准备
-
maven
<dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> <scope>compile</scope> </dependency>
-
Java代码
package com.technologystatck.lambdas; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class LambdaDemo2Stream { public static void main(String[] args) { List<Author> authors=getAuthors(); System.out.println(authors); } private static List<Author> getAuthors(){ Author author1 = new Author(1L, "张三", 18, "一个热爱《三国演义》的男人", null); Author author2 = new Author(1L, "张三", 18, "一个热爱《三国演义》的男人", null); Author author3 = new Author(3L, "王", 38, "一个热爱《西游记》的男人", null); Author author4 = new Author(4L, "赵六", 16, "一个热爱《红楼梦》的男人", null); //书籍列表 ArrayList<Book> books1 = new ArrayList<>(); ArrayList<Book> books2 = new ArrayList<>(); ArrayList<Book> books3 = new ArrayList<>(); books1.add(new Book(1L,"books1-《三国演义》","四大名著,历史演义小说",100,"中国四大名著之一")); books1.add(new Book(2L,"books1-《水浒传》","四大名著,英雄传奇小说",88,"中国四大名著之一")); books1.add(new Book(3L,"books1-《西游记》","四大名著,神魔小说",99,"中国四大名著之一")); books1.add(new Book(4L,"books1-《红楼梦》","四大名著,世情小说",66,"中国四大名著之一")); books2.add(new Book(1L,"books2-《三国演义》","四大名著,历史演义小说",77,"中国四大名著之一")); books2.add(new Book(2L,"books2-《水浒传》","四大名著,英雄传奇小说",55,"中国四大名著之一")); books2.add(new Book(3L,"books2-《西游记》","四大名著,神魔小说",66,"中国四大名著之一")); books2.add(new Book(4L,"books2-《红楼梦》","四大名著,世情小说",88,"中国四大名著之一")); books3.add(new Book(1L,"books1-《三国演义》","四大名著,历史演义小说",33,"中国四大名著之一")); books3.add(new Book(2L,"books1-《水浒传》","四大名著,英雄传奇小说",99,"中国四大名著之一")); books3.add(new Book(3L,"books1-《西游记》","四大名著,神魔小说",22,"中国四大名著之一")); books3.add(new Book(4L,"books1-《红楼梦》","四大名著,世情小说",100,"中国四大名著之一")); author1.setBooks(books1); author2.setBooks(books1); author3.setBooks(books2); author4.setBooks(books3); List<Author> arrayList = new ArrayList<>(Arrays.asList(author1,author2,author3,author4)); return arrayList; } } @Data @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode class Author{ //id private Long id; //姓名 private String name; //年龄 private Integer age; //简介 private String intro; //作品 private List<Book> books; @Override public String toString() { return "\nAuthor{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", intro='" + intro + '\'' + ", books=" + books + '}'+"\n"; } } @Data @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode class Book{ //id private Long id; //书名 private String name; //分类 private String category; //评分 private Integer score; //简介 private String intro; @Override public String toString() { return "\nBook{" + "id=" + id + ", name='" + name + '\'' + ", category='" + category + '\'' + ", score=" + score + ", intro='" + intro + '\'' + '}'; } }
3.3常用操作
3.3.1创建流
3.3.1.1单列集合
-
集合对象.stream()
List<Author> authors=getAuthors(); Stream<Author> stream=authors.stream();
3.3.1.2数组
-
Arrays.stream(数组)或者使用Stream.of来创建
Integer[] arr={1,2,3,4,5}; Stream<Integer> stream=Arrays.stream(arr); Stream<Integer> stream2=Stream.of(arr);
3.3.1.3双列集合
-
转换成单列集合后再创建
Map<String,Integer> map=new HashMap<>(); map.put("蜡笔小新",19); map.put("小白",4); map.put("动感超人",29); Stream<Map.Entry<String,Integer>> stream=map.entrySet().stream(); stream.filter(new Predicate<Map.Entry<String, Integer>>() { @Override public boolean test(Map.Entry<String, Integer> stringIntegerEntry) { return stringIntegerEntry.getValue()>16; } }).forEach(stringIntegerEntry -> System.out.println(stringIntegerEntry.getKey()+"="+stringIntegerEntry.getValue()));
3.3.2中间操作
3.3.2.1filter
-
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中
/** * 打印所有姓名长度大于1的作家的姓名 */ authors.stream() //去重 .distinct() //过滤 .filter(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getName().length()>1; } }) .forEach(new Consumer<Author>() { @Override public void accept(Author author) { System.out.println(author.getName()); }
3.3.2.2map
-
可以对流中的元素进行计算或转换
-
map中第一个泛型必须要跟stream中的类型是一致的,比如这里就要跟authors一个类型
/** * 打印所有作家的姓名 */ authors.stream() .map(new Function<Author, String>() { @Override public String apply(Author author) { return author.getName(); } }) .forEach(new Consumer<String>() { @Override public void accept(String s) { System.out.println(s); } }); /** * 给所有作家的年龄+10 */ authors.stream() .map(author->author.getAge()) .map(age->age+10) .forEach(age-> System.out.println(age));
3.3.2.3distinct
-
去除流中的重复元素
-
依赖于Object的equals方法来判断是否是相同对象的,所以需要注意重写equals方法
@EqualsAndHashCode:该注解替我们重写了equals和hashcode方法
/** * 打印所有的作家姓名,并且要求其中不能有重复元素 */ authors.stream() .distinct() .forEach(author -> System.out.println(author.getName()));
3.3.2.2sorted
-
对流中的元素进行排序
-
调用空参的sorted()方法,需要实现Comparable,重写compareTo方法
@Data @NoArgsConstructor @AllArgsConstructor @EqualsAndHashCode class Author implements Comparable<Author>{ //id private Long id; //姓名 private String name; //年龄 private Integer age; //简介 private String intro; //作品 private List<Book> books; @Override public String toString() { return "\nAuthor{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", intro='" + intro + '\'' + ", books=" + books + '}'+"\n"; } @Override public int compareTo(Author o) { return this.getAge()-o.getAge(); } }
/** * 对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素 */ authors.stream() .distinct() .sorted() .forEach(author -> System.out.println(author.getAge()));
-
使用有参方法修改,不需要实现接口
/** * 使用sorted的有参方法修改 */ authors.stream() .distinct() .sorted(new Comparator<Author>() { @Override public int compare(Author o1, Author o2) { return o1.getAge()-o2.getAge(); } }).forEach(author -> System.out.println(author.getAge()));
3.3.2.2limit
-
可以设置流的最大长度,超出的部分将被抛弃
/** * 对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素, * 然后打印其中年龄最大的两个作家的姓名 */ authors.stream() .distinct() .sorted((o1,o2)->o2.getAge()-o1.getAge()) .limit(2) .forEach(author -> System.out.println(author.getName()));
3.3.2.2skip
-
跳过流中的前n个元素,返回剩下的元素
/** * 打印除了年龄最大的作家外的其他作家, * 要求不能有重复元素,并且按照年龄降序排序 */ authors.stream() .distinct() .sorted((o1,o2)->o2.getAge()-o1.getAge()) .skip(1) .forEach(author -> System.out.println(author));
3.3.2.2flatMap
-
map只能把一个对象转成另一个对象来作为流中的元素。
-
而flatMap可以把一个对象转换成多个对象作为流中的元素
/** * 打印所有书籍的名字,要求对重复的元素进行去重 */ authors.stream() .flatMap(new Function<Author, Stream<Book>>() { @Override public Stream<Book> apply(Author author) { return author.getBooks().stream(); } }) .distinct() .forEach(new Consumer<Book>() { @Override public void accept(Book book) { System.out.println(book.getName()); } });
/** * 打印现有数据的所有分类,要求对分类进行去重 * 不能出现这种格式:四大名著,神魔小说 */ authors.stream() .flatMap(author -> author.getBooks().stream()) //对书籍进行去重 .distinct() .flatMap(new Function<Book, Stream<?>>() { @Override public Stream<?> apply(Book book) { return Arrays.stream(book.getCategory().split(",")); } }) //对分类进行去重 .distinct() .forEach(category-> System.out.println(category));
3.3.3终结操作
3.3.3.1forEach
-
对流中的元素进行遍历操作,通过传入的参数去指定对遍历到的元素进行什么具体的操作
/** * 输出所有作家的名字 */ authors.stream() .map(author -> author.getName()) .distinct() .forEach(name -> System.out.println(name));
3.3.3.2count
-
可以用来获取当前流中元素的个数
/** * 打印这些作家所出书籍的数目,注意删除重复元素 */ long count = authors.stream() .flatMap(author -> author.getBooks().stream()) .distinct() .count(); System.out.println(count);
3.3.3.3max&min
-
可以用来获取流中的最大 / 小值
/** * 分别获取这些作家的所出书籍的最高分和最低分并打印 */ Optional<Integer> max = authors.stream() .flatMap(author -> author.getBooks().stream()) .map(book -> book.getScore()) .max(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } }); System.out.println(max.get()); Optional<Integer> min = authors.stream() .flatMap(author -> author.getBooks().stream()) .map(book -> book.getScore()) .min(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } }); System.out.println(min.get());
3.3.3.4collect
-
将当前流转换成一个集合
/** * 获取一个存放所有作者名字的List集合 */ List<String> collect = authors.stream() .map(author -> author.getName()) .distinct() .collect(Collectors.toList()); for (String s : collect) { System.out.println(s); } /** * 获取一个存放所有书籍名称的Set集合 */ Set<String> collect1 = authors.stream() .flatMap(author -> author.getBooks().stream()) .distinct() .map(book -> book.getName()) .distinct() .collect(Collectors.toSet()); //模拟一个循环遍历set集合 Iterator<String> iterator = collect1.iterator(); while (iterator.hasNext()){ String next = iterator.next(); System.out.println("next="+next); } /** * 获取一个map集合,map的key为作者名,value为List<Book> */ Map<String, List<Book>> collect2 = authors.stream() //key值去重 .distinct() .collect(Collectors.toMap(new Function<Author, String>() { @Override public String apply(Author author) { return author.getName(); } }, new Function<Author, List<Book>>() { @Override public List<Book> apply(Author author) { return author.getBooks(); } })); Set keySet= collect2.keySet(); Iterator iteratorMap = keySet.iterator(); while (iteratorMap.hasNext()){ Object key = iteratorMap.next(); System.out.println("key="+key+","+"value="+collect2.get(key)); }
3.3.3.5查找与匹配
-
anyMatch
-
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型
/** * 判断是否有年龄在29以上的作家 */ boolean b = authors.stream() .anyMatch(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getAge() > 29; } }); System.out.println(b);
-
-
allMatch
-
可以用来判断是否都符合匹配条件,结果为boolean类型
-
若都符合结果为true,否则结果为false
/** * 判断是否所有的作家都是成年人 */ boolean b1 = authors.stream() .allMatch(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getAge() >= 18; } }); System.out.println(b1);
-
-
noneMatch
-
可以判断流中的元素是否都不符合匹配条件
-
若都不符合结果为true,否则结果为false
/** * 判断作家是否都没有超过100岁 */ boolean b2 = authors.stream() .noneMatch(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getAge() > 100; } }); System.out.println(b2);
-
-
findAny
-
获取流中的任意一个元素
-
该方法没有办法保证获取的一定是流中的第一个元素
/** * 获取任意一个年龄大于18的作家,若存在就输出他的名字 */ Optional<Author> optionalAuthor = authors.stream() .filter(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getAge()>18; } }) .findAny(); //方式1: System.out.println(optionalAuthor.get().getName()); //方式2: optionalAuthor.ifPresent(new Consumer<Author>() { @Override public void accept(Author author) { System.out.println(author.getName()); } });
-
-
findFirst
-
获取流中的第一个元素
/** * 获取一个年龄最小的作家,并输出他的姓名 */ Optional<Author> first = authors.stream() .sorted((age1, age2) -> age1.getAge() - age2.getAge()) .distinct() .findFirst(); first.ifPresent(author -> author.getName());
-
-
reduce归并
-
对流中的数据按照自己制定的计算方式计算出一个结果
-
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和在初始化值的基础上进行计算,计算结果在和后面的元素计算
-
内部的计算方式如下
T result = identity; for(T element: this stream) result=accmulator.apply(result,element) return result;
可以看作是
int[] arr={1,2,3,4,5,6,7,8,9,10}; int sum=0; for(int i:arr){ sum+=i; } System.out.println(sum);
其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的
-
reduce两个参数的重载形式
/** * 使用reduce求所有作者年龄的和 */ Integer sums = authors.stream() .distinct() .map(author -> author.getAge()) .reduce(0, new BinaryOperator<Integer>() { @Override public Integer apply(Integer result, Integer element) { //result:相当于定义的变量 //element:相当于集合中的元素 return result + element; } }); System.out.println(sums);
/** * 使用reduce求所有作者中年龄的最大值 */ Integer max = authors.stream() .distinct() .map(author -> author.getAge()) .reduce(Integer.MIN_VALUE, new BinaryOperator<Integer>() { @Override public Integer apply(Integer result, Integer element) { return result < element ? element : result; } }); System.out.println(max); /** * 使用reduce求所有作者中年龄的最小值 */ Integer min = authors.stream() .distinct() .map(author -> author.getAge()) .reduce(Integer.MAX_VALUE, new BinaryOperator<Integer>() { @Override public Integer apply(Integer result, Integer element) { return result > element ? element : result; } }); System.out.println(min);
- reduce一个参数的重载形式
- 会将变量的第一个值作为初始化值
boolean foundAny=false; T result=null; for(T element : this stream){ if(!foundAny){ foundAny = true; result = element; }else{ result = accumulator.apply(result,element); } } return foundAny ? Optional.of(result) : Optional.empty();
/** * 使用reduce求所有作者中年龄的最小值 */ Optional<Integer> reduce = authors.stream() .map(author -> author.getAge()) .reduce(new BinaryOperator<Integer>() { @Override public Integer apply(Integer result, Integer element) { return result > element ? element : result; } }); reduce.ifPresent(age -> System.out.println(age));
-
3.3.4注意事项
-
惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
-
流是一次性的(一旦一个流对象经过一个终结操作(forEach、count、max、min、collect、查找与匹配)后,这个流就不能再被使用)
-
不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的)
即不调用set方法等
4.Optional
4.1概念
-
在我们编写代码的时候,出现最多的就是空指针异常,所以在很多情况下我们需要做各种非空的判断
-
将数据封装成Optional当中的一个属性
-
例如
Author author = getAuthor(); if(author != null){ System.out.println(author.getName()); }
尤其是对象中的属性还是一个对象的情况下,这种判断会更多
而过多的判断语句会让我们的代码显得臃肿不堪
所以在JDK8中引入了Optional,养成使用Optional的习惯后,可以通过写出更优雅的代码来避免空指针异常
并且在很多函数式编程相关的API中也都用到了Optional,若不会使用Optional也会对函数式编程的学习造成影响
4.2使用
4.2.1创建对象
-
Optional就像是包装类,可以把我们的具体数据封装Optional对象内部,然后我们去使用Optional中封装好的方法操作 封装进去的数据,就可以非常优雅的避免空指针的异常
-
一般使用Optional的静态方法ofNullable来把数据封装成一个Optional对象。无论传入的参数是否为null都不会出现问题
Author author = getAuthor(); Optional<Author> authorOptional = Optional.ofNullable(author);
package com.technologystatck.lambdas; import java.util.Optional; public class LambdaDemo3Optional { public static void main(String[] args) { Author author = getAuthor(); //即时getAuthor返回的值为null,也不会出现报错 Optional<Author> authorOptional = Optional.ofNullable(author); authorOptional.ifPresent(authors-> System.out.println(authors.getName())); } public static Author getAuthor(){ Author author = new Author(1L, "马奇", 22, "著有《11111》", null); return author; } }
-
还可以改造getAuthor方法,让其返回值就是封装好的Optional的话,就可以在使用时方便很多
package com.technologystatck.lambdas; import java.util.Optional; import java.util.function.Consumer; public class LambdaDemo3Optional { public static void main(String[] args) { // Author author = getAuthor(); // // Optional<Author> authorOptional = Optional.ofNullable(author); // authorOptional.ifPresent(authors-> System.out.println(authors.getName())); Optional<Author> authorOptional = getAuthorOptional(); authorOptional.ifPresent(new Consumer<Author>() { @Override public void accept(Author author) { System.out.println(author.getName()); } }); } // public static Author getAuthor(){ // Author author = new Author(1L, "马奇", 22, "著有《11111》", null); // return author; // } public static Optional<Author> getAuthorOptional(){ Author author = new Author(1L, "马奇", 22, "著有《11111》", null); return Optional.ofNullable(author); } }
-
而且在实际开发中我们的数据很多是从数据库获取的,MyBatis从3.5版本也已经支持Optional了,可以直接把dao方法的返回值类型定义为Optional类型,MyBatis会自己将数据封装成Optional对象返回,封装的过程也不需要我们自己操作
-
若确定一个对象不是空的则可以使用Optional的静态方法of来把数据封装成Optional对象
Author author = new Author(); Optional<Author> authorOptional = Optional.of(author);
- 注意:若使用of的时候传入的参数必须不为null
-
若一个方法的返回值类型是Optional类型,而如果我们经常判断发现某次计算得到的返回值为null,这个时候就需要把null封装成Optional对象返回,这时则可以使用Optional的静态方法empty来进行封装
Optional.empty()
- Optional.empty结合of方法使用
public static Optional<Author> getAuthorOptionals(){ Author author = new Author(1L, "马奇", 22, "著有《11111》", null); return author==null ? Optional.empty():Optional.of(author); }
4.2.2安全消费值
-
我们获取一个Optional对象后肯定需要对其中的数据进行使用,这时我们可以使用ifPresent方法来消费其中的值,这个方法会判断其内封装的数据是否为空,不为空时才会执行具体的消费代码,这样使用起来就更加安全了
Optional<Author> authorOptional = Optional.ofNullable(getAuthor()); authorOptional.ifPresent(author -> System.out.println(author.getName()));
4.2.3获取值
-
若想获取值自己进行处理可以使用get方法获取,但是不推荐
-
因为当Optional内部的数据为空的时候会出现异常
Optional<Author> authorOptional = Optional.ofNullable(getAuthor()); Author author = authorOptional.get();
4.2.4安全获取值
- 若期望安全的获取值,不推荐使用get方法,而是使用Optional提供的以下方法
4.2.4.1orElseGet
-
获取数据并设置数据为空时的默认值,若数据不为空就能获取到该数据,若为空则根据你传入的参数来创建对象作为默认值返回
Optional<Author> authorOptional1 = getAuthorOptional(); Author author = authorOptional1.orElseGet(new Supplier<Author>() { @Override public Author get() { //若值为空,则返回赵八的author return new Author(2L, "赵八", 33, "著有《22222》", null); } }); System.out.println(author.getName());
4.2.4.2orElseThrow
-
获取数据,若数据不为空就能获取到该数据,若为空则根据你传入的参数来创建异常抛出
Optional<Author> authorOptional2 = getAuthorOptional(); try { Author author1 = authorOptional2.orElseThrow(new Supplier<Throwable>() { @Override public Throwable get() { return new RuntimeException("数据为null"); } }); } catch (Throwable e) { e.printStackTrace(); }
4.2.5过滤
-
可以使用filter方法对数据进行过滤,若原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象
Optional<Author> authorOptional3 = getAuthorOptional(); Optional<Author> author1 = authorOptional3.filter(new Predicate<Author>() { @Override; public boolean test(Author author) { //若年龄大于18就返回true //若不符合就返回新的Optional,其值为空 return author.getAge() > 18; } }); author1.ifPresent(author2-> System.out.println(author2.getName()));
4.2.6判断
-
使用isPresent方法进行是否存在数据的判断,
-
若为空,返回值为false;若不为空,返回值为true,
-
但是这种方式并不能体现Optional的好处,更推荐使用ifPresent方法
Optional<Author> authorOptional4 = getAuthorOptional(); if(authorOptional4.isPresent()){ System.out.println(authorOptional4.get().getName()); System.out.println(authorOptional4.get().getAge()); }
4.2.7数据转换
-
Optional还提供了map可以让我们对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证了我们的使用安全
public static Optional<Author> getAuthorOptional(){ Author author = new Author(1L, "马奇", 22, "著有《11111》", null); ArrayList<Book> books1 = new ArrayList<>(); books1.add(new Book(1L,"books1-《三国演义》","四大名著,历史演义小说",100,"中国四大名著之一")); books1.add(new Book(2L,"books1-《水浒传》","四大名著,英雄传奇小说",88,"中国四大名著之一")); books1.add(new Book(3L,"books1-《西游记》","四大名著,神魔小说",99,"中国四大名著之一")); books1.add(new Book(4L,"books1-《红楼梦》","四大名著,世情小说",66,"中国四大名著之一")); author.setBooks(books1); return Optional.ofNullable(author); } Optional<Author> authorOptional5 = getAuthorOptional(); Optional<List<Book>> books = authorOptional5.map(author6 -> author6.getBooks()); books.ifPresent(new Consumer<List<Book>>() { @Override public void accept(List<Book> books) { System.out.println(books); } });
5.函数式接口
5.1概念
-
只有一个抽象方法的接口称之为函数接口
-
JDK的函数式接口都加上了**@FunctionalInterface**注解进行标识,但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口
5.2常见函数式接口
5.2.1Consumer消费接口
-
根据其中抽象方法的参数列表和返回值类型可以知道,我们可以在方法中对传入的参数进行消费
5.2.2Function计算转换接口
-
根据其中抽象方法的参数列表和返回值类型可以知道,我们可以在方法中对传入的参数计算或转换,把结果返回
5.2.3Predicate判断接口
-
根据其中抽象方法的参数列表和返回值类型可以知道,我们可以在方法中对传入的参数条件进行判断,返回判断结果
5.2.4生产型接口
-
根据其中抽象方法的参数列表和返回值类型可以知道,我们可以在方法中创建对象,将创建好的对象返回
5.3函数式接口常用默认方法
5.3.1and
-
在使用Predicate接口时,可能需要进行判断条件的拼接,而and方法相当于是使用 && 来拼接两个判断条件
/** * 打印作家中年龄大于17并且姓名的长度大于1的作家 */ authors.stream() .distinct() .filter(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getAge()>17; } }.and(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getName().length()>1; } })) .forEach(author -> System.out.println(author.getName()+","+author.getAge()));
printNum(new IntPredicate() { @Override public boolean test(int value) { return value % 2 == 0; } }, new IntPredicate() { @Override public boolean test(int value) { return value>5; } }); private static void printNum(IntPredicate predicate1,IntPredicate predicate2){ int[] arr={1,2,3,4,5,6,7,8,9,10}; for(int i: arr){ if(predicate1.and(predicate2).test(i)){ System.out.println(i); } } }
5.3.2or
-
在使用Predicate接口时,可能需要进行判断条件的拼接,而or方法相当于时使用 | | 来拼接两个判断条件
/** * 打印作家中年龄大于17或姓名的长度小于2的作家 */ authors.stream() .distinct() .filter(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getAge()>17; } }.or(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getName().length()<2; } })) .forEach(author -> System.out.println(author.getName()+","+author.getAge()));
5.3.3negate
-
在使用Predicate接口时,negate方法相当于是在判断添加前面加了个!表示取反
/** * 打印作家中年龄不大于17的作家 */ authors.stream() .distinct() .filter(new Predicate<Author>() { @Override public boolean test(Author author) { return author.getAge()>17; } }.negate()) .forEach(author -> System.out.println(author.getName()+","+author.getAge()));
6.方法引用
- 在使用lambda时,若方法体中只有一个方法的调用的话(包括构造方法),可以用方法引用进一步简化代码
- Alt + Enter --> 选择Replace with method reference
6.1推荐用法
- 在使用lambda时不需要考虑什么时候用方法引用,用哪种方法引用,方法的引用格式是什么
- 只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能转换成方法引用即可
- 当方法引用使用的多了慢慢的也可以直接写出方法引用
6.2基本格式
类名或者对象名::方法名
6.3语法详解(了解)
6.3.1引用静态方法
-
引用类的静态方法
-
格式
类名: : 方法名
-
使用前提:
-
在重写方法时,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法
-
并且我们要把重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,就可以引用类的静态方法
List<Author> authors1 = getAuthors(); Stream<Author> stream = authors1.stream(); stream.map(author -> author.getAge()) .map(age->String.valueOf(age));
-
优化后的写法
stream.map(author-> author.getAge()) .map(String::valueOf);
-
-
注意:
- 若我们所重写的方法是没有参数的,调用的方法也是没有参数的也相当于符合以上规则
6.3.2引用对象的实例方法
-
引用对象的实例方法
-
格式
对象名: : 方法名
-
使用前提:
-
在重写方法时,方法体中只有一行代码,并且这行代码是调用了某个类的成员方法
-
并且我们要把重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,就可以引用对象的实例方法
List<Author> authors2 = getAuthors(); Stream<Author> stream1 = authors2.stream(); StringBuilder sb = new StringBuilder(); stream1.map(author->author.getName()) .forEach(new Consumer<String>() { @Override public void accept(String s) { sb.append(s); } });
-
优化后的方法
stream1.map(author->author.getName()) .forEach(sb::append);
-
6.3.3引用类的实例方法
-
引用类的实例方法
-
格式
类名: : 方法名
-
使用前提:
-
在重写方法时,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法
-
并且我们要把重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,就可以引用类的实例方法
package com.technologystatck.lambdas; public class LambdaDemo4Method { public static void main(String[] args) { //调用的第一个参数,剩余的参数按顺序传入 subAuthorName("赵八", new UseString() { @Override public String use(String str, int start, int length) { return str.substring(start,length); } }); } public static String subAuthorName(String str,UseString useString){ int start=0; int length=1; return useString.use(str,start,length); } } interface UseString{ String use(String str,int start,int length); }
-
优化后的方法
package com.technologystatck.lambdas; public class LambdaDemo4Method { public static void main(String[] args) { //调用的第一个参数,剩余的参数按顺序传入 subAuthorName("赵八", String::substring); } public static String subAuthorName(String str,UseString useString){ int start=0; int length=1; return useString.use(str,start,length); } } interface UseString{ String use(String str,int start,int length); }
-
6.3.4构造器引用
-
若方法体中的一行代码是构造器的话就可以使用构造器引用
-
格式:
类名: : new
-
使用前提:
-
在重写方法时,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法
-
并且我们要把重写的抽象方法中所有的参数都按照顺序传入了这个构造方法中,就可以引用构造器
List<Author> authors3 = getAuthors(); authors3.stream() .map(author -> author.getName()) .map(name->new StringBuilder(name)) .map(sb2->sb2.append("马奇").toString()) .forEach(str-> System.out.println(str));
-
优化后的方法
List<Author> authors3 = getAuthors(); authors3.stream() .map(author -> author.getName()) .map(StringBuilder::new) .map(sb2->sb2.append("马奇").toString()) .forEach(str-> System.out.println(str));
-
继续优化
List<Author> authors3 = getAuthors(); authors3.stream() .map(Author::getName) .map(StringBuilder::new) .map(sb2->sb2.append("马奇").toString()) .forEach(System.out::println);
-
7.高级用法
7.1基本数据类型优化
-
之前用到的很多Stream的方法由于都使用了泛型,所以涉及到的参数和返回值都是引用数据类型
-
即使我们操作的是整数小数,但是实际用的是它们的包装类。
-
JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便,
但是一定要知道装箱和拆箱肯定是需要消耗时间的,在这个时间的消耗下,大量的数据不断地重复装箱拆箱,就不无视该时间损耗
-
为了能对这部分时间损耗进行优化,Stream还提供了很多专门针对基本数据类型地方法
-
mapToInt、mapToLong、mapToDouble、flatMapToInt、flatMapToDouble等
List<Author> authors4 = getAuthors(); authors4.stream() .map(author -> author.getAge()) .map(age->age+10) .filter(age->age>18) .map(age->age+2) .forEach(System.out::println);
-
优化后
authors4.stream() .mapToInt(author->author.getAge()) .map(age->age+10) .filter(age->age>18) .map(age->age+2) .forEach(System.out::println);
-
7.2并行流
- 当流中有大量元素时,可以使用并行流去提高操作的效率。
- 并行流就是把任务分配给多个线程去完成,若我们自己去用代码实现的话其实会非常的复杂,并且要求自己对并发编程的理解和认识。而当使用Stream的话,只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率
7.2.1将串行流转为并行流(默认串行流)
-
parallel()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); //stream.parallel以多个线程的形式并行执行 Integer sum = stream.parallel() .peek(new Consumer<Integer>() { @Override public void accept(Integer num) { System.out.println(num+Thread.currentThread().getName()); } }) .filter(num -> num > 5) .reduce((result, element) -> result + element) .get(); System.out.println(sum);
-
parallelStream()
authors4.stream() .mapToInt(author->author.getAge()) .map(age->age+10) .filter(age->age>18) .map(age->age+2) .forEach(System.out::println);
-
使用parallelStream()
authors4.parallelStream() .mapToInt(author->author.getAge()) .map(age->age+10) .filter(age->age>18) .map(age->age+2) .forEach(System.out::println);
-
使用parallel()
authors4.stream().parallel() .mapToInt(author->author.getAge()) .map(age->age+10) .filter(age->age>18) .map(age->age+2) .forEach(System.out::println);
-