Lambda表达式Stream流-函数式编程-java8函数式编程(Lambda表达式,Optional,Stream流)从入门到精通-最通俗易懂
一:函数式编程思想
1:概念
面向对象思想需要关注用什么对象完成什么事情。而函数式编程思想就类似于我们数学中的函数。它主要关注的是对数据进行了什么操作
方法的参数就是数据,方法的内容就是所做的操作。
(参数列表) - > {代码块}
2:优点
1:代码简洁
2:易于理解
3:易于并发编程:大数据量中的并行流,是通过多线程进行操作。效率更高。
二:Lambda表达式
1:概述
Lambda来自JDK8,可以对匿名内部类进行简化。体现了函数式编程的思想。不需要关注对象,而是要关注要对什么数据做什么操作
Lambda表达式是基于"可省略可推导"原则的,关注数据如何进行操作的函数式编程思想的,在函数式接口的匿名内部类的省略。
2:核心原则
可推导可省略。
方法名可推导,那么就省略方法名。
3:基本格式
(参数列表) - > {代码块}
1:匿名内部类
Thread构造器需要传入Runnable类型的参数,那么就用匿名内部类即可,重写run方法。
new Thread(new Runnable() {@Overridepublic void run() {System.out.println("----这是匿名内部类----");}}).start();
2:Lambda表达式进行改造
使用Lambda表达式对匿名内部类进行简化
简化原则:匿名内部类是一个接口,并且只有一个抽象方法。
new Thread(new Runnable() {@Overridepublic void run() {System.out.println("----这是匿名内部类----");}}).start();
下面是Thread中的构造方法是基于构造方法的形参,推导出来Lambda表达式的返回值的。
new Thread(()-> System.out.println("----这是Lambda表达式----")).start();
4:Lambda表达式练习
1:练习一
public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("新线程中的方法被执行了");}}).start();// 这里的Lambda表达式使用了函数式编程思想,// 只关注了数据和执行过程,利用类型推导,代表了匿名内部类。new Thread(() -> System.out.println("新线程中的方法被执行了")).start();}
2:练习二
public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {System.out.println("新线程中的方法被执行了");}}).start();// 这里的Lambda表达式使用了函数式编程思想,// 只关注了数据和执行过程,利用类型推导,代表了匿名内部类。new Thread(() -> System.out.println("新线程中的方法被执行了")).start();}
3:练习三
@Testpublic void test02() {// 使用匿名内部类int i = calculateNum(new IntBinaryOperator() {@Overridepublic int applyAsInt(int left, int right) {return left+right;}});System.out.println(i);// 使用Lambda表达式,但是提供参数类型// 这里也体现了,可推导可省略的规则。int j = calculateNum((int a, int b) -> a + b);System.out.println(j);// 使用Lambda表达式int s = calculateNum((a, b) -> a + b);System.out.println(s);// replace lambda with method referenceint t = calculateNum(Integer::sum);System.out.println(t);}public static int calculateNum(IntBinaryOperator operator){int a = 10;int b = 20;return operator.applyAsInt(a, b);}
额外调用的Java中的接口
题外话:FunctionInterface定义在接口上,且接口中有且只有一个抽象方法。当是一个接口,并且只有一个抽象方法的时候,就可以优化成为Lambda表达式了。
@FunctionalInterface
public interface IntBinaryOperator {/*** Applies this operator to the given operands.** @param left the first operand* @param right the second operand* @return the operator result*/int applyAsInt(int left, int right);
}
4:练习四
@Testpublic void test03() {// 匿名内部类printNum(new IntPredicate() {@Overridepublic boolean test(int value) {return value%2 == 0;}});// 使用Lambda表达式,但是提供参数类型// 这里也体现了,可推导可省略的规则。// return 的话,一行也需要提供{}printNum((int value) -> {return value%2 == 0;});// 使用Lambda表达式printNum((int value) -> {return value%2 == 0;});}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);}}}
5:练习五
public void test04() {// 匿名内部类Integer result = typeConver(new Function<String, Integer>() {@Overridepublic Integer apply(String s) {return Integer.valueOf(s);}});System.out.println(result);// 使用Lambda表达式Integer result1 = typeConver((String s) -> {return Integer.valueOf(s);});System.out.println(result1);// 使用Lambda表达式Integer result2 = typeConver((s) -> {return Integer.valueOf(s);});System.out.println(result2);// 匿名内部类// 定义成泛型接口,加上Lambda表达式的方式,实现方式异常的灵活。String result3 = typeConver(new Function<String, String>() {@Overridepublic String apply(String s) {return s+"三更";}});System.out.println(result3);// 最佳方式final String s1 = typeConver(s -> s + "三更");System.out.println(s1);}public static <R> R typeConver(Function<String,R> function){String str = "12345";R result = function.apply(str);return result;}
5:省略规则
1:参数类型可以推导,所以参数类型可以省略
2:方法只有一个参数时,小括号可以省略
3:方法体只有一句代码时,大括号、return、唯一一句代码的分号也是可以省略的。
三:Stream流概述
1:概述
Java8的stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链状流式的操作。可以更方便的让我们对集合或数组操作。
2:数据准备
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.34</version></dependency>
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期去重使用
public class Author {private Long id;private String name;private Integer age;private String intro;private List<Book> books;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期去重使用
public class Book {private Long id;private String name;private String categroy;private Integer score;private String intro;
}
private static List<Author> getAuthors(){Author author = new Author(1L,"蒙多",33,"一个从猜到中明悟哲理的祖安人",null);Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);Author author3 = new Author(3L,"易",15,"是这个世界在限制他的思维",null);Author author4 = new Author(4L,"易",15,"是这个世界在限制他的思维",null);List<Book> books1 = new ArrayList<>();List<Book> books2 = new ArrayList<>();List<Book> books3 = new ArrayList<>();books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱情"));books1.add(new Book(2L,"一个人不能死在同一把刀下","个人爱情,成长",99,"讲述从失败中明悟真理"));books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"一个哲学家的恋爱观注定很难把他所在的时代理解"));books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"无法想象一个武者能对他的伴侣这么的宽容"));books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎样的火花呢?"));author.setBooks(books1);author2.setBooks(books2);author3.setBooks(books3);author4.setBooks(books3);List<Author> authors = new ArrayList<>(Arrays.asList(author, author2, author3, author4));return authors;}
3:快速入门
1:需求
获取作家集合,打印18岁以下的作者名字,并进行去重。
@Testpublic void test01(){final List<Author> authors = getAuthors();authors.stream()//集合转换成流.distinct()//去重.filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getAge() < 18;}})//过滤.forEach(new Consumer<Author>() {@Overridepublic void accept(Author author) {System.out.println(author.getAge());}});//打印authors.stream()//集合转换成流.distinct()//去重.filter(author -> author.getAge() < 18)//过滤.forEach(author-> System.out.println(author.getName()));//打印}
4:常用操作
1:创建流
创建流-流的中间操作-流的终结操作,必须得有终结操作,流才会被调用。否则不会被调用。
流的代码想要被调用起来,必须得有终结方法作为结尾。否则不会被调用。
/*** 方法目的:搞清楚流是怎么进行创建的*/@Testpublic void test02(){// 单列集合List<Author> authors = getAuthors();authors.stream();// 数组Integer[] arr = {1,2,3,4,5,6};Arrays.stream(arr);Stream.of(arr);// 双列集合 先转换为单列集合。Map<String,Integer> map = new HashMap<>();final Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();}
2:中间操作值fillter
他的作用是过滤。
打印姓名长度大于一的作家的姓名
@Testpublic void test02() {final List<Author> authors = getAuthors();// 使用匿名内部类实现authors.stream().filter(new Predicate<Author>() {@Overridepublic boolean test(Author author) {return author.getName().length() > 1;}}).forEach(new Consumer<Author>() {@Overridepublic void accept(Author author) {System.out.println(author.getName());}});// 使用lambda表达式实现。authors.stream().filter(author -> author.getName().length() > 1).forEach(author -> System.out.println(author.getName()));}
匿名内部类是传入了一个匿名内部类对象,Lambda表达式是传入了一段代码(这段代码本质上值对那哪个数据对象要做哪些事。更可以理解为匿名内部类经由一系列思想简化后的产物。)
3:中间操作之map
将流中的元素进行计算和类型转换。比方说将Stream<Author>转换为Stream<String>
感想:Stream你就想像成流水线上的一道道工序,终结操作就是流水线的开关,只不过放在了末尾。
@Testpublic void test03() {final List<Author> authors = getAuthors();// 使用匿名内部类实现authors.stream().map(new Function<Author, String>() {@Overridepublic String apply(Author author) {return author.getName();}}).forEach(new Consumer<String>() {@Overridepublic void accept(String name) {System.out.println(name);}});// 使用lambda表达式实现。authors.stream().map(author -> author.getName()).forEach(name -> System.out.println(name));}
4:中间操作之distinct
可以去除流中的重复元素
依赖object中的equals方法来判断是否相同对象,所以,需要重写方法。
5:中间操作之sorted
对流的元素按照年龄进行降序排序,并且要求不能有重复的因素。
使用空参的sorted
@Testpublic void test04(){List<Author> authors = getAuthors();authors.stream().distinct().sorted()//空参拍需,会将author转换为Compare在进行使用,如果转不了就会报错。所以,Author必须实现compare接口。.forEach(author -> System.out.println(author.getAge()));}
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.util.List;@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期去重使用
public class Author implements Comparable<Author>{private Long id;private String name;private Integer age;private String intro;private List<Book> books;@Overridepublic int compareTo(Author o) {return this.getAge()-o.getAge();}
}
使用有参数的sorted
@Testpublic void test05(){List<Author> authors = getAuthors();authors.stream().distinct().sorted(new Comparator<Author>() {@Overridepublic int compare(Author o1, Author o2) {return o1.getAge() - o2.getAge();}}).forEach(author -> System.out.println(author.getAge()));authors.stream().distinct().sorted((o1, o2) -> o1.getAge() - o2.getAge()).forEach(author -> System.out.println(author.getAge()));}
6:中间操作之limit
可以设置流的最大长度,超出的部分将被抛弃。
对流中的元素按照年龄进行降序排序,不能有重复元素,打印其中年龄最大的两个作家的名字。
@Testpublic void test06(){List<Author> authors = getAuthors();authors.stream().distinct().sorted(new Comparator<Author>() {@Overridepublic int compare(Author o1, Author o2) {return o1.getAge() - o2.getAge();}}).limit(2).forEach(author -> System.out.println(author.getAge()));authors.stream().distinct().sorted((o1, o2) -> o1.getAge() - o2.getAge()).limit(2).forEach(author -> System.out.println(author.getAge()));}
7:中间操作之skip
跳过流中的前N个元素,返回剩下的元素。
打印除了年龄最大的外的其他作家,要求不能按照重复元素,并且按照年龄排序。
@Testpublic void test06(){List<Author> authors = getAuthors();authors.stream().distinct().sorted(new Comparator<Author>() {@Overridepublic int compare(Author o1, Author o2) {return o1.getAge() - o2.getAge();}}).skip(1).forEach(author -> System.out.println(author.getAge()));authors.stream().distinct().sorted((o1, o2) -> o1.getAge() - o2.getAge()).skip(1).forEach(author -> System.out.println(author.getAge()));}
8:中间操作之flatMap
map只能把一个对象转换为另一个对象作为流中的元素,而flatMap可以把一个对象转换为多个对象作为流中的元素。
示例一:打印所有书籍的名字,要求对重复的元素进行去重。
@Testpublic void test07(){List<Author> authors = getAuthors();// 这样写是有问题的写法Stream<List<Book>>,我们是没办法获取单个的对象的。authors.stream().map(new Function<Author, List<Book>>() {@Overridepublic List<Book> apply(Author author) {return author.getBooks();}}).forEach(new Consumer<List<Book>>() {@Overridepublic void accept(List<Book> books) {System.out.println(books);}});authors.stream().flatMap(new Function<Author, Stream<Book>>() {@Overridepublic Stream<Book> apply(Author author) {return author.getBooks().stream();//List<Book>转换为一个一个Stream<Book>并且拼接到一起变成一个完整的String<Book>}}).distinct().forEach(new Consumer<Book>() {@Overridepublic void accept(Book book) {System.out.println(book.getName());}});}
@Testpublic void test08(){List<Author> authors = getAuthors();// 打印所有书籍的分类。要求对分类进行去重。不能出现这种格式:哲学,爱情authors.stream().flatMap(new Function<Author, Stream<Book>>() {@Overridepublic Stream<Book> apply(Author author) {return author.getBooks().stream();}}).distinct().flatMap(new Function<Book, Stream<String>>() {@Overridepublic Stream<String> apply(Book book) {return Arrays.stream(book.getCategroy().split(","));}}).forEach(System.out::println);authors.stream().flatMap(author->author.getBooks().stream()).distinct().flatMap(book -> Arrays.stream(book.getCategroy().split(","))).forEach(System.out::println);}
9:终结操作foreach
相对简单不写例子了。
10:终结操作之count
打印作家所写的书籍,注意去重
@Testpublic void test09(){List<Author> authors = getAuthors();// 打印所有作者的书籍数量,注意去重。final long count = authors.stream().distinct().flatMap(author -> author.getBooks().stream()).distinct().count();System.out.println(count);}