1、什么是函数式编程?
答:每个人对函数式编程的理解不尽相同。但其核心是:在思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。 2、Java 8的最大变化是引入了Lambda表达式——一种紧凑的、传递行为的方式。
答:和使用匿名内部类的另一处不同在于声明参数的方式。使用匿名内部类时需要显式地声明参数类型,而在Lambda表达式中无需指定类型,程序依然可以编译。这是因为javac根据程序的上下文在后台推断出了参数的类型。这意味着如果参数类型不言而明,则无需显式指定。
尽管与之前相比,Lambda表达式中的参数需要的样板代码很少,但是Java 8仍然是一种静态类型语言。为了增加可读性并迁就我们的习惯,声明参数时也可以包括类型信息,而且有时编译器不一定能根据上下文推断出参数的类型!
注意:->符号将参数和Lambda表达式的主体分开。
3、Java8之Lambda的简单案例使用,如下所示:
1 package com.demo.main;
2
3 import java.awt.event.ActionListener;
4 import java.util.function.BinaryOperator;
5
6 public class LambdaMain {
7
8 // 目标类型是指Lambda表达式所在上下文环境的类型。
9 // 比如,将Lambda表达式赋值给一个局部变量,或传递给一个方法作为参数,局部变量或方法参数的类型就是Lambda表达式的目标类型。
10 public static void main(String[] args) {
11 // Lambda表达式的不同形式
12 // 1、方式一,Lambda表达式不包含参数,使用空括号()表示没有参数。
13 // 该Lambda表达式实现了Runnable接口,该接口也只有一个run方法,没有参数,且返回类型为void。
14 Runnable noArguments = () -> System.out.println("方式一,hello Lambda!!!");
15 noArguments.run();
16
17 // 2、方式二,Lambda表达式包含且只包含一个参数,可省略参数的括号
18 ActionListener oneArgument = event -> System.out.println("方式二,button clicked !!!");
19
20 // 3、方式三,Lambda表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号({})将代码块括起来
21 Runnable multiStatement = () -> {
22 System.out.println("方式三,hello Lambda first !!!");
23 System.out.println("方式三,hello Lambda second!!!");
24 };
25 multiStatement.run();
26
27 // 4、方式四,Lambda表达式也可以表示包含多个参数的方法。
28 // 这行代码并不是将两个数字相加,而是创建了一个函数,用来计算两个数字相加的结果。
29 // 变量add的类型是BinaryOperator<Long>,它不是两个数字的和,而是将两个数字相加的那行代码。
30 BinaryOperator<Long> add = (x, y) -> x + y;
31 System.out.println("方式四,add : " + add.apply(2L, 3L));
32
33 // 5、方式五,有时最好也可以显式声明参数类型,此时就需要使用小括号将参数括起来,多个参数的情况也是如此
34 BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
35 System.out.println("方式五,addExplicit : " + addExplicit.apply(1L, 2L));
36
37 }
38
39 }
4、Java的函数接口?
答:函数接口是只有一个抽象方法的接口,用作Lambda表达式的类型。
1 package com.demo.main;
2
3 public interface LambdaType<T> {
4
5 // 函数接口是只有一个抽象方法的接口,用作Lambda表达式的类型。
6 abstract int add(T x,T y);
7
8 public static void main(String[] args) {
9 LambdaType<Integer> lambdaType = (x, y) -> x + y;
10 System.out.println(lambdaType.add(1, 2));
11 }
12
13 }
5、Java8的Lambda要点内容:
答:1)、Lambda表达式是一个匿名方法,将行为像数据一样进行传递。
2)、Lambda表达式的常见结构: BinaryOperator<Integer> add=(x, y) → x+y。
3)、函数接口指仅具有单个抽象方法的接口,用来表示Lambda表达式的类型。
6、Stream是用函数式编程方式在集合类上进行复杂操作的工具。
答:1)、Stream里的一些方法却略有不同,它们虽是普通的Java方法,但返回的Stream对象却不是一个新集合,而是创建新集合的配方。
2)、像filter这样只描述Stream,最终不产生新集合的方法叫作惰性求值方法,而像count这样最终会从Stream产生值的方法叫作及早求值方法。
3)、判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是Stream,那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值。使用这些操作的理想方式就是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果,这正是它的合理之处。
1 package com.demo.main;
2
3 import java.util.ArrayList;
4 import java.util.Iterator;
5 import java.util.List;
6 import java.util.stream.Stream;
7
8 public class StramMain {
9
10 public static void main(String[] args) {
11 List<String> lists = new ArrayList<String>();
12 lists.add("Java");
13 lists.add("SpringCloud");
14 lists.add("SpringBoot");
15 lists.add("Spring");
16
17 // 习惯写法一
18 for (String str : lists) {
19 if (str.contains("Spring")) {
20 System.out.println("for循环:" + str.toString());
21 }
22 }
23 System.out.println();
24
25 // 习惯写法二,方法是返回一个控制迭代的Iterator对象
26 Iterator<String> iterator = lists.iterator();
27 while (iterator.hasNext()) {
28 String str = iterator.next();
29 if (str.contains("Spring")) {
30 System.out.println("while循环:" + str.toString());
31 }
32 }
33 System.out.println();
34
35 // Java8流式写法,stream()方法的调用,返回内部迭代中的相应接口:Stream。
36 long count = lists.stream().filter(list -> {
37 System.out.println("filter是惰性求值方法");
38 return list.contains("Spring");
39 }).count();
40 System.out.println("count及早求值方法: " + count);
41
42 }
43
44 }
7、Stream常用的流操作。
答:1)、collect(toList())方法由Stream里的值生成一个列表,是一个及早求值操作。
2)、Stream的of方法使用一组初始值生成新的Stream。
1 package com.demo.main;
2
3 import java.util.List;
4 import java.util.stream.Collectors;
5 import java.util.stream.Stream;
6
7 public class StramMain {
8
9 public static void main(String[] args) {
10 // Stream<String> list = Stream.of("Spring", "SpringBoot", "SpringCloud");
11 // List<String> collected = list.collect(Collectors.toList());
12 // 可以缩写为下面一句
13 List<String> collected = Stream.of("Spring", "SpringBoot", "SpringCloud").collect(Collectors.toList());
14
15 // 使用流Stream进行遍历操作,可以直接打印信息
16 collected.forEach((String str) -> System.out.println(str.toString()));
17 // 省略Lanbda的参数类型
18 collected.forEach(str -> System.out.println(str.toString()));
19 }
20
21 }
3)、map如果有一个函数可以将一种类型的值转换成另外一种类型,map操作就可以使用该函数,将一个流中的值转换成一个新的流。
1 package com.demo.main;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.List;
6 import java.util.stream.Collectors;
7 import java.util.stream.Stream;
8
9 public class StramMain {
10
11 public static void main(String[] args) {
12 // 习惯写法,使用for循环将字符串转换为大写
13 List<String> list = new ArrayList<String>();
14 for (String str : Arrays.asList("Spring", "SpringBoot", "SpringCloud")) {
15 String upperStr = str.toUpperCase();
16 list.add(upperStr);
17 }
18
19 // Stream流式写法,使用map操作将字符串转换为大写形式
20 Stream<String> stream = Stream.of("Spring", "SpringBoot", "SpringCloud").map(str -> str.toUpperCase());
21 List<String> collected = stream.collect(Collectors.toList());
22 collected.forEach(str -> System.out.println(str.toString()));
23 System.out.println();
24
25 // 一句写法
26 Stream.of("Spring", "SpringBoot", "SpringCloud")
27 // 传给map的Lambda表达式只接受一个String类型的参数,返回一个新的String类型。
28 .map(str -> str.toUpperCase())
29 // 参数和返回值不必属于同一种类型,但是Lambda表达式必须是Function接口的一个实例,
30 // Function接口是只包含一个参数的普通函数接口。
31 .collect(Collectors.toList())
32 .forEach(str -> System.out.println(str.toString()));
33
34 }
35
36 }
4)、filter遍历数据并检查其中的元素时,可尝试使用Stream中提供的新方法filter。
1 package com.demo.main;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.List;
6 import java.util.stream.Collectors;
7 import java.util.stream.Stream;
8
9 public class StramMain {
10
11 public static void main(String[] args) {
12 // 习惯写法,使用循环遍历列表,使用条件语句做判断
13 List<String> list = new ArrayList<String>();
14 for (String str : Arrays.asList("Spring", "SpringBoot", "SpringCloud")) {
15 if (str.startsWith("Spring")) {
16 list.add(str);
17 }
18 }
19 // 这里使用流式遍历
20 list.stream().forEach(str -> System.out.println(str.toString()));
21 System.out.println();
22
23 // Stream流式写法,使用循环遍历列表,使用条件语句做判断
24 Stream<String> stream = Stream.of("Spring", "SpringBoot", "SpringCloud")
25 .filter(str -> str.startsWith("Spring"));
26 List<String> collected = stream.collect(Collectors.toList());
27 collected.forEach(str -> System.out.println(str.toString()));
28 System.out.println();
29
30 // Stream一句写法
31 Stream.of("Spring", "SpringBoot", "SpringCloud")
32 // 和map很像,filter接受一个函数(函数接口)作为参数,该函数用Lambda表达式表示。
33 .filter(str -> str.startsWith("Spring"))
34 .collect(Collectors.toList())
35 .forEach(str -> System.out.println(str.toString()));
36
37 }
38
39 }
5)、flatMap方法可用Stream替换值,然后将多个Stream连接成一个Stream。map操作,它可用一个新的值代替Stream中的值。但有时,如果希望让map操作有点变化,生成一个新的Stream对象取而代之。用户通常不希望结果是一连串的流,此时flatMap最能派上用场。
1 package com.demo.main;
2
3 import java.util.Arrays;
4 import java.util.List;
5 import java.util.stream.Collectors;
6 import java.util.stream.Stream;
7
8 public class StramMain {
9
10 public static void main(String[] args) {
11 // 习惯写法,使用循环遍历列表,使用条件语句做判断
12
13 // Stream流式写法,使用循环遍历列表,使用条件语句做判断
14 Stream<List<String>> stream = Stream.of(Arrays.asList("Spring"), Arrays.asList("SpringBoot", "SpringCloud"));
15 Stream<String> flatMap = stream.flatMap(list -> list.stream());
16 List<String> list = flatMap.collect(Collectors.toList());
17 list.forEach(str -> System.out.println(str.toString()));
18 System.out.println();
19
20 // Stream一句写法
21 Stream.of(Arrays.asList("Spring"), Arrays.asList("SpringBoot", "SpringCloud"))
22 .flatMap(list2 -> list2.stream())
23 .collect(Collectors.toList())
24 .forEach(str -> System.out.println(str.toString()));
25 }
26
27 }
6)、Stream上常用的操作之一是求最大值和最小值。Stream API中的max和min操作足以解决这一问题。
1 package com.demo.main;
2
3 import java.util.Arrays;
4 import java.util.Comparator;
5 import java.util.List;
6 import java.util.Optional;
7
8 public class StramMain {
9
10 public static void main(String[] args) {
11 List<BookInfo> list = Arrays.asList(new BookInfo("Spring", 100), new BookInfo("SpringBoot", 200),
12 new BookInfo("SpringCloud", 300));
13
14 // 习惯写法,使用循环遍历列表,使用条件语句做判断
15 BookInfo bookInfo = list.get(0);
16 for (BookInfo book : list) {
17 if (book.getPrice() < bookInfo.getPrice()) {
18 bookInfo = book;
19 }
20 }
21 System.out.println(bookInfo.toString());
22 System.out.println();
23
24 // Stream流式写法,使用循环遍历列表,使用条件语句做判断
25 Optional<BookInfo> optionalMin = list.stream().min(Comparator.comparing(BookInfo::getPrice));
26 BookInfo bookStreamMin = optionalMin.get();
27 System.out.println(bookStreamMin.toString());
28 System.out.println();
29
30 // Stream一句写法
31 // 取出最小值
32 BookInfo bookStreamMin2 = list.stream().min(Comparator.comparing(BookInfo::getPrice)).get();
33 System.out.println(bookStreamMin2.toString());
34 System.out.println();
35
36 // 取出最大值
37 BookInfo bookStreamMax = list.stream().max(Comparator.comparing(BookInfo::getPrice)).get();
38 System.out.println(bookStreamMax.toString());
39 System.out.println();
40
41 // java1.8的双冒号(::),双冒号(::)运算符在Java 8中被用作方法引用(method reference),方法引用是与lambda表达式相关的一个重要特性。它提供了一种不执行方法的方法。为此,方法引用需要由兼容的函数接口组成的目标类型上下文。
42 // 大致意思是,使用lambda表达式会创建匿名方法, 但有时候需要使用一个lambda表达式只调用一个已经存在的方法(不做其它), 所以这才有了方法引用!
43
44 // 以下是Java 8中方法引用的一些语法:
45 // 1)、静态方法引用(static method)语法:classname::methodname 例如:Person::getAge
46 // 2)、对象的实例方法引用语法:instancename::methodname 例如:System.out::println
47 // 3)、对象的超类方法引用语法: super::methodname
48 // 4)、类构造器引用语法: classname::new 例如:ArrayList::new
49 // 5)、数组构造器引用语法: typename[]::new 例如: String[]:new
50
51 }
52
53 }
7)、reduce操作可以实现从一组值中生成一个值。如count、min和max方法,因为常用而被纳入标准库中。事实上,这些方法都是reduce操作。
1 package com.demo.main;
2
3 import java.util.Arrays;
4 import java.util.function.BinaryOperator;
5 import java.util.stream.Stream;
6
7 public class StramMain {
8
9 public static void main(String[] args) {
10 // 习惯写法,使用命令式编程方式求和
11 int sum = 0;
12 for (Integer nums : Arrays.asList(1, 2, 3, 4)) {
13 sum = sum + nums;
14 }
15 System.out.println("sum : " + sum);
16
17 // Stream流式写法,使用reduce求和
18 // Lambda表达式的返回值是最新的sum2。
19 // Lambda表达式就是reducer,它执行求和操作,有两个参数:传入Stream中的当前元素num2和sum2。将两个参数相加,sum2是累加器,保存着当前的累加结果。
20 BinaryOperator<Integer> accumulator = (acc, element) -> acc + element;
21 int count = accumulator.apply(accumulator.apply(accumulator.apply(accumulator.apply(0, 1), 2), 3), 4);
22 System.out.println("count: " + count);
23
24 // Stream一句写法
25 Integer sumStream = Stream.of(1, 2, 3, 4).reduce(0, (sum2, num2) -> sum2 + num2);
26 System.out.println("sumStream : " + sumStream);
27
28 }
29
30 }
8)、综合案例,如何根据图书列表,找出图书所属的公司团队,如下所属:
1 package com.demo.main;
2
3 import java.util.HashSet;
4 import java.util.Set;
5 import java.util.stream.Collectors;
6 import java.util.stream.Stream;
7
8 public class StramMain {
9
10 private Set<BookInfo> getAllBookInfos() {
11 Set<BookInfo> set = new HashSet<BookInfo>();
12 // 维护图书集合
13 set.add(new BookInfo("Spring", "Pivotal-1", 100));
14 set.add(new BookInfo("SpringBoot", "Pivotal-2", 200));
15 set.add(new BookInfo("SpringCloud", "Pivotal-3", 300));
16 return set;
17 }
18
19 private Set<String> getAllBookNames() {
20 Set<BookInfo> bookInfos = this.getAllBookInfos();
21 Set<String> set = new HashSet<String>();
22 // 遍历获取到图书名称
23 for (BookInfo bookInfo : bookInfos) {
24 set.add(bookInfo.getBookname());
25 }
26 return set;
27 }
28
29 public static void main(String[] args) {
30 // 习惯写法
31
32 // Stream流式写法,找出图书技术所属的公司
33 StramMain stramMain = new StramMain();
34 // 首先获取到所有的图书信息并过滤到自己想要的图书信息
35 Stream<BookInfo> stream = stramMain.getAllBookInfos().stream()
36 .filter(bookInfo -> bookInfo.getBookname().startsWith("Spring"));
37 // 使用map将技术图书映射为所属的公司
38 Stream<String> streaMap = stream.map(bookInfo -> bookInfo.getTeam());
39 // 使用collect(Collectors.toList())方法将图书所属的公司放入一个列表。
40 Set<String> collect = streaMap.collect(Collectors.toSet());
41 // 循环遍历输出
42 collect.forEach(bookTeam -> System.out.println(bookTeam.toString()));
43
44 // Stream一句写法
45 // filter和map方法都返回Stream对象,因此都属于惰性求值,而collect方法属于及早求值。
46 // List或Set这样的集合类,只要调用List或Set的stream方法就能得到一个Stream对象。
47 stramMain.getAllBookInfos().stream()
48 .filter(bookInfo -> bookInfo.getBookname().startsWith("Spring"))
49 // map方法接受一个Lambda表达式,使用该Lambda表达式对Stream上的每个元素做映射,形成一个新的Stream。
50 .map(bookInfo -> bookInfo.getTeam())
51 .collect(Collectors.toSet())
52 .forEach(bookTeam -> System.out.println(bookTeam.toString()));
53 }
54
55 class BookInfo {
56 private String bookname;
57 private String team;
58 private int price;
59
60 public String getBookname() {
61 return bookname;
62 }
63
64 public void setBookname(String bookname) {
65 this.bookname = bookname;
66 }
67
68 public String getTeam() {
69 return team;
70 }
71
72 public void setTeam(String team) {
73 this.team = team;
74 }
75
76 public int getPrice() {
77 return price;
78 }
79
80 public void setPrice(int price) {
81 this.price = price;
82 }
83
84 @Override
85 public String toString() {
86 return "BookInfo [bookname=" + bookname + ", team=" + team + ", price=" + price + "]";
87 }
88
89 public BookInfo(String bookname, String team, int price) {
90 super();
91 this.bookname = bookname;
92 this.team = team;
93 this.price = price;
94 }
95
96 public BookInfo() {
97 super();
98 }
99 }
100
101 }
8、如何使用Stream流和Lambda重构遗留代码。
1 package com.demo.main;
2
3 import java.util.HashSet;
4 import java.util.Iterator;
5 import java.util.Set;
6 import java.util.stream.Collectors;
7
8 public class StramMain {
9
10 private Set<BookInfo> getAllBookInfos() {
11 Set<BookInfo> set = new HashSet<BookInfo>();
12 // 维护图书集合
13 set.add(new BookInfo("Spring", "Pivotal-1", 100));
14 set.add(new BookInfo("SpringBoot", "Pivotal-2", 200));
15 set.add(new BookInfo("SpringCloud", "Pivotal-3", 300));
16 return set;
17 }
18
19 private Set<String> getAllBookNames() {
20 Set<BookInfo> bookInfos = this.getAllBookInfos();
21 Set<String> set = new HashSet<String>();
22 // 遍历获取到图书名称
23 for (BookInfo bookInfo : bookInfos) {
24 set.add(bookInfo.getBookname());
25 }
26 return set;
27 }
28
29 public static void main(String[] args) {
30 StramMain stramMain = new StramMain();
31 // 习惯写法,找出图书价格大于等于200的图书信息列表
32 Set<BookInfo> seBookInfos = new HashSet<BookInfo>();
33 for (BookInfo bookInfo : stramMain.getAllBookInfos()) {
34 // 如果图书价格大于等于200
35 if (bookInfo.getPrice() >= 200) {
36 seBookInfos.add(bookInfo);
37 }
38 }
39 // 循环输出
40 Iterator<BookInfo> iterator = seBookInfos.iterator();
41 while (iterator.hasNext()) {
42 System.out.println(iterator.next().toString());
43 }
44 System.out.println();
45
46
47
48 // Stream流式写法,找出图书价格大于等于200的图书信息列表
49 Set<BookInfo> bookInfoStream = new HashSet<BookInfo>();
50 stramMain.getAllBookInfos().stream().forEach(bookInfo -> {
51 if (bookInfo.getPrice() >= 200) {
52 bookInfoStream.add(bookInfo);
53 }
54 });
55 bookInfoStream.forEach(bookInfo -> System.out.println(bookInfo.toString()));
56 System.out.println();
57
58
59
60 // Stream一句写法,找出图书价格大于等于200的图书信息列表
61 Set<BookInfo> bookInfoStream2 = new HashSet<BookInfo>();
62 stramMain.getAllBookInfos().stream()
63 // 找出满足图书价格大于200的
64 .filter(bookInfo -> bookInfo.getPrice() >= 200)
65 // 将满足条件的图书列表生成一个新的Stream流
66 .map(bookInfo -> bookInfo)
67 // 使用循环将新生成的流Stream循环遍历到Set集合中
68 .forEach(bookInfo -> bookInfoStream2.add(bookInfo));
69 // 循环遍历输出
70 bookInfoStream2.forEach(bookInfo -> System.out.println(bookInfo.toString()));
71 System.out.println();
72
73
74
75 // Stream一句写法,找出图书价格大于等于200的图书信息列表
76 // 使用collect(Collectors. toList())可以将Stream中的值转换成一个列表,
77 // 使用collect(Collectors.toSet())可以将Stream中的值转换成一个集合。
78 stramMain.getAllBookInfos().stream()
79 // 找出满足图书价格大于200的
80 .filter(bookInfo -> bookInfo.getPrice() >= 200)
81 // 将满足条件的图书列表生成一个新的Stream流
82 .map(bookInfo -> bookInfo)
83 // 将生成的stream流转换为set集合
84 .collect(Collectors.toSet())
85 .forEach(bookInfo -> System.out.println(bookInfo.toString()));;
86
87 }
88
89 class BookInfo {
90 private String bookname;
91 private String team;
92 private int price;
93
94 public String getBookname() {
95 return bookname;
96 }
97
98 public void setBookname(String bookname) {
99 this.bookname = bookname;
100 }
101
102 public String getTeam() {
103 return team;
104 }
105
106 public void setTeam(String team) {
107 this.team = team;
108 }
109
110 public int getPrice() {
111 return price;
112 }
113
114 public void setPrice(int price) {
115 this.price = price;
116 }
117
118 @Override
119 public String toString() {
120 return "BookInfo [bookname=" + bookname + ", team=" + team + ", price=" + price + "]";
121 }
122
123 public BookInfo(String bookname, String team, int price) {
124 super();
125 this.bookname = bookname;
126 this.team = team;
127 this.price = price;
128 }
129
130 public BookInfo() {
131 super();
132 }
133 }
134
135 }
9、高阶函数是指接受另外一个函数作为参数,或返回一个函数的函数。高阶函数不难辨认:看函数签名就够了。如果函数的参数列表里包含函数接口,或该函数返回一个函数接口,那么该函数就是高阶函数。
1 /**
2 * Returns a stream consisting of the results of applying the given
3 * function to the elements of this stream.
4 *
5 * <p>This is an <a href="package-summary.html#StreamOps">intermediate
6 * operation</a>.
7 *
8 * @param <R> The element type of the new stream
9 * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
10 * <a href="package-summary.html#Statelessness">stateless</a>
11 * function to apply to each element
12 * @return the new stream
13 */
14 <R> Stream<R> map(Function<? super T, ? extends R> mapper);
10、Java 8中的另一个变化是引入了默认方法和接口的静态方法,它改变了人们认识类库的方式,接口中的方法也可以包含代码体了。
答:1)、Java1.8的默认方法,如果子类未实现父接口的方法,则使用父接口里面的方法,这样的方法叫作默认方法,在任何接口中,无论函数接口还是非函数接口,都可以使用该方法。
2)、和类不同,接口没有成员变量,因此默认方法只能通过调用子类的方法来修改子类本身,避免了对子类的实现做出各种假设。
11、Java 8中的默认方法,理解:
答:1)、类胜于接口。如果在继承链中有方法体或抽象的方法声明,那么就可以忽略接口中定义的方法。
2)、子类胜于父类。如果一个接口继承了另一个接口,且两个接口都定义了一个默认方法,那么子类中定义的方法胜出。
3)、如果上面两条规则不适用,子类要么需要实现该方法,要么将该方法声明为抽象方法。
。