一、Lambda表达式
1.1 函数式编程思想
1)概念
面向对象思想需要关注用什么对象完成什么事情。而函数式编程思想就类似于我们数学中的函数。它主要关注的是对数据进行了什么操作。
2)优点
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于"并发编程"
1.2 Lambda表达式基础
1)概述
Lambda
是JDK8
中一个语法糖。他可以对某些匿名内部类的写法进行简化。
它是函数式编程思想的一个重要体现。
让我们不用关注是什么对象。而是更关注我们对数据进行了什么操作。
2)核心原则
可推导可省略。
需要是函数式接口,既只有一个方法的接口。
3)基本格式
- 由三部分组成:
- 一些参数
- 一个箭头
- 一段代码
- 格式:
(参数列表)->{重写方法的代码}
- 解释说明格式:
()
: 接口中抽象方法的参数列表,没有参数就空着,有参数就写出参数,多个参数使用逗号分隔->
: 就是传递的意思。即把参数传递给方法体{}{}
: 重写接口的抽象方法的方法体
1.3 示例
- 创建并启动线程
// 在创建线程并启动时可以使用匿名内部类的写法:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello!");
}
}).start();
// ------------------------------分隔线-----------------------------
// 使用Lambda的格式对其进行修改
new Thread(()->{
System.out.println("hello!");
}).start();
-
函数式接口作为参数
现有方法定义如下,其中
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);
}
// ------------------------------分隔线-----------------------------
// 使用Lambda的格式对其进行修改
public static void main(String[] args) {
int i = calculateNum((int left, int right)->{
return left + right;
});
System.out.println(i);
}
1.4 省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号可以省略
这些规则不需要死记硬背
1.5 使用规则
函数式接口,或者只有一个抽象方法的匿名内部类才可以转换为Lambda
表达式。
二、函数式接口
2.1 概述
只有一个抽象方法的接口我们称之为函数接口。
JDK
自带的函数式接口都加上了**@FunctionalInterface** 注解进行标识。
但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
2.2 常见JDK自带的函数式接口
1)Supplier 生产者接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回。
使用示例:
/*
java.util.function.supplier<T>接口仅包含一个无参方法T.get()用来获取一个泛型参数指定类型的对象数据
suppier<T> 接口被称之为生产型接口,指定接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据
*/
public class Demo01Supplier {
//定义一个方法,方法的参数传递supplier<T>接口,泛型执行String.get方法就会返回一个String
public static String getString(Supplier<String> sup){
return sup.get();
}
public static void main(String[] args) {
//调用getString方法,方法的参数Supplier是一个函数式接口,所以可以传递一个Lambda表达式
String str = getString(() -> {
return "小萌新";
});
System.out.println(str);
}
}
2)Consumer消费接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
使用示例:
// consumer接口是一个消费型的接口,泛型执行什么类型,就可以使用accept方法消费什么类型的数据
public class DemoConsumer {
public static void consumerString(String name, Consumer<String> consumer){
//accept方法并没有返回值
consumer.accept(name);
}
public static void main(String[] args) {
consumerString("噜啦噜啦嘞", (String name)->{
//消费方式,直接输出字符串
System.out.println(name);
//消费方式,反转输出字符串
String newName = new StringBuilder(name).reverse().toString();
System.out.println(newName);
});
}
}
3)Function 计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回
使用示例:
public class DemoFunction {
/*
定义一个方法
方法的参数传递一个字符串类型的整数
方法的参数传递Function接口,泛型使用<String,Integer>
使用Function 接口中的方法apply,把字符串类型的整数,转换为Integer类型的整数
*/
public static void change(String s,Function<String,Integer> function){
//把泛型左边的类型转换成右边
Integer in = function.apply(s);
System.out.println(in);
}
public static void main(String[] args) {
change("1234",(String s)->{
return Integer.valueOf(s) + 1;
});
}
}
4)Predicate 判断型接口
使用示例:
- 基础用法
public class DemoPredicate {
/*
定义一个方法
参数传递一个String 类型的字符串
传递一个predicate接口,泛型使用String
使用Predicate中的方法test对字符串进行判断,并把判断的结果返回
*/
public static boolean checkString(String s, Predicate<String> predicate){
return predicate.test(s);
}
public static void main(String[] args) {
String s = "asd";
boolean result = checkString(s,(String str)->{
return str.length() > 5;
});
System.out.println(result);
}
}
Predicate
接口中有一个方法and
表示并且关系,也可用于连接两个判断条件
public class DemoPredicateAnd {
// 定义一个方法 传递一个字符串和两个Predicate接口
public static boolean checkString(String s, Predicate<String> predicate1, Predicate<String> predicate2){
return predicate1.and(predicate2).test(s);
}
public static void main(String[] args) {
String s = "sdfgg";
boolean a = checkString(s,
(String str) -> {
return str.length() > 5;
}, (String str) -> {
return str.contains("a");
});
System.out.println(a);
}
}
- 与
and
的与类似,默认方法or
实现逻辑关系中的或
public class DemoPredicateOr {
private static void method(Predicate<String> one, Predicate<String> two) {
boolean isValid = one.or(two).test("Helloworld");
System.out.println("字符串符合要求吗:" + isValid);
}
public static void main(String[] args) {
method(s -> s.contains("H"), s -> s.contains("W"));
}
}
negate
方法相当于是在判断添加前面加了个! 表示取反
public class DemoPredicateNegate {
private static void method(Predicate<String> predicate) {
boolean veryLong = predicate.negate().test("HelloWorld");
System.out.println("字符串很长吗:" + veryLong);
}
public static void main(String[] args) {
method(s -> s.length() < 5);
}
}
三、方法引用
在使用Lambda
表达式时,如果方法体中只有一个方法的调用的话(包括构造方法)时,我们可以用方法引用进一步简化代码。
6.1 小技巧
在使用Lambda
表达式时不需要考虑什么时候用方法引用,用哪种方法引用,方法引用的格式是什么。
只需要在写完Lambda
表达式时发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方
法引用即可。
当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
6.2 基本格式
类名或者对象名::方法名
6.3 语法详解
6.3.1 引用类的静态方法
其实就是引用类的静态方法
1)格式:
类名::方法名
2)使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法。
并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态
方法。
3)示例:
如下代码就可以用方法引用进行简化
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge()).map(age->String.valueOf(age));
注意,如果我们所重写的方法是没有参数的,调用的方法也是没有参数的也相当于符合以上规则。
4)使用方法引用优化:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
authorStream.map(author -> author.getAge()).map(String::valueOf);
6.3.2 引用对象的实例方法
1)格式:
对象名::方法名
2)使用前提:
如果在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且把要重写的
抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法。
3)示例:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName()).forEach(name->sb.append(name));
4)使用方法引用优化:
List<Author> authors = getAuthors();
Stream<Author> authorStream = authors.stream();
StringBuilder sb = new StringBuilder();
authorStream.map(author -> author.getName()).forEach(sb::append);
6.3.4 引用类的实例方法
1)格式:
类名::方法名
2)使用前提:
如果在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要
重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
3)示例:
// 函数式接口
interface UseString{
String use(String str,int start,int length);
}
// 使用函数式接口作为参数,因此可以使用lambda表达式进行参数传递
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);
}
});
}
4)使用方法引用优化:
public static void main(String[] args) {
subAuthorName("张三", String::substring);
}
6.3.5 构造器引用
如果方法体中的一行代码是构造器的话就可以使用构造器引用。
1)格式:
类名::new
2)使用前提:
如果在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把
要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
3)示例:
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getName())
.map(name->new StringBuilder(name))
.map(sb->sb.append("test").toString())
.forEach(str-> System.out.println(str));
4)使用方法引用优化:
List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getName())
.map(StringBuilder::new)
.map(sb->sb.append("test").toString())
.forEach(str-> System.out.println(str));