函数式编程-lambda
为什么学
- 看懂其他人的代码
- 大数量下处理集合效率高
- 代码可读性高
- 消灭嵌套地狱
以下是没有用函数式编程的方法
List<Book> bookList = new ArrayList<>();
Set<Book> uniqueBookValues = new HashSet<>();
Set<Author> uniqueAuthorValues = new HashSet<>();
for (Author author : authors){
if(uniqueAuthorValues.add(author)){
if(author.getAge() < 18){
List<Book> books = author.getBooks();
for(Book book : books){
if(book.getScore() > 70){
if(uniqueBookValues.add(book)){
bookList.add(book);
}
}
}
}
}
}
System.out.println(bookList);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
警告
嵌套严重,层级十分的多
下面使用函数式编程
List<Book> collect = author.stream()
.distinct()
.filter(author -> author.getAge() < 18)
.map(author -> author.getBooks())
.flatMap(Collection::stream)
.filter(book -> book.getScore() > 70)
.distinct()
.collect(Collection.toList());
System.out.println(collect);
2
3
4
5
6
7
8
9
优点:
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于多人协作编程
# Lambda表达式
核心原则:可推导可省略
Lambda是JDK8中一个语法糖。他可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现。让我们不用关注是什么对象。而是更关注我们对数据进行了什么操作。
基本格式:
(参数列表)->{代码}
什么情况下可以用lambda进行简化: 如果方法参数中是一个接口类型的匿名内部类,且类中只有一个抽象方法需要重写;这时就可以用lambda进行简化;
重点关注抽象方法中的参数和方法体
# 例一
我们在创建线程并启动时可以使用匿名内部类的写法:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("test");
}
}).start();
2
3
4
5
6
可以使用Lambda的格式对其进行修改。修改后如下:
new Thread(
()->{System.out.println("test");}
).start();
2
3
# 例二
现有方法定义如下,其中IntBinaryOperator是一个接口。先使用匿名内部类的写法调用该方法。
public static int calculateNum(IntBinaryOperator operator){
int a = 10;
int b = 20;
return operator.applyAsInt(a, b);
}
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);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Lambda写法:
public static void main(String[] args) {
int i = calculateNum((int left, int right)->{
return left + right;
});
System.out.println(i);
}
2
3
4
5
6
# 例三
现有方法定义如下,其中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);
}
}
}
public static void main(String[] args) {
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value%2==0;
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Lambda写法:
public static void main(String[] args) {
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);
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 例四
现有方法定义如下,其中Function是一个接口。先使用匿名内部类的写法调用该方法。
public static <R> R typeConver(Function<String,R> function){
String str = "1235";
R result = function.apply(str);
return result;
}
public static void main(String[] args) {
Integer result = typeConver(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.valueOf(s);
}
});
System.out.println(result);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Lambda写法:
public static void main(String[] args) {
Integer result = typeConver((String s)->{
return Integer.valueOf(s);
});
System.out.println(result);
}
2
3
4
5
6
# 例五
现有方法定义如下,其中IntConsumer是一个接口。先使用匿名内部类的写法调用该方法。
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);
}
}
public static void main(String[] args) {
foreachArr(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
Lambda写法:
public static void main(String[] args) {
foreachArr((int value)->{
System.out.println(value);
});
}
2
3
4
5
# 省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
- 以上这些规则都记不住也可以省略不记
# 高级格式
# 引用类的静态方法
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
格式:
类名::方法名
例如:
如下代码就可以用方法引用进行简化
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge())
.map(age->String.valueOf(age));
2
3
4
5
6
注意,如果我们所重写的方法是没有参数的,调用的方法也是没有参数的也相当于符合以上规则。
优化后如下:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge())
.map(String::valueOf);
2
3
4
5
6
# 引用对象的实例方法
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法
格式:
对象名::方法名
例如:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName())
.forEach(name->sb.append(name));
2
3
4
5
6
优化后:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName())
.forEach(sb::append);
2
3
4
5
6
# 引用类的实例方法
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
格式:
类名::方法名
例如:
interface UseString{
String use(String str,int start,int length);
}
public static String subAuthorName(String str, UseString useString){
int start = 0;
int length = 1;
return useString.use(str,start,length);
}
public static void main(String[] args) {
subAuthorName("三更草堂", new UseString() {
@Override
public String use(String str, int start, int length) {
return str.substring(start,length);
}
});
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
优化后如下:
public static void main(String[] args) {
subAuthorName("三更草堂", String::substring);
}
2
3
4
5
# 构造器引用
如果方法体中的一行代码是构造器的话就可以使用构造器引用。
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
格式:
类名::new
例如:
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getName())
.map(name->new StringBuilder(name))
.map(sb->sb.append("-三更").toString())
.forEach(str-> System.out.println(str));
2
3
4
5
6
优化后:
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getName())
.map(StringBuilder::new)
.map(sb->sb.append("-三更").toString())
.forEach(str-> System.out.println(str));
2
3
4
5
6