目录
Lambda表达式
概述
Lambda表达式是Java编程语言中引入的一个重要特性,它是一种轻量级的匿名函数,也可以理解为一种简洁的语法糖。Lambda表达式的引入主要是为了支持函数式编程风格,让Java更好地适应现代编程的需求。
正确使用
我将举几个例子方便你理解什么是Lambda表达式,以及怎么使用:
例子1 创建线程
我们先看传统的写法,使用匿名内部内来写:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程中run方法被执行");
}
}).start();
接下来我们使用Lambda表达式来实现:
/**
* 1,lambda表达式不需要你关注方法名是什么
* 2,只需要关注有什么参数就行了
* 3,没有参数的话只需要写()
*/
new Thread(() -> {
System.out.println("线程中run方法被执行");
}).start();
来,不懂的话我多给你举几个例子,记住:我们只需要关注参数和写方法体就行了
例子2
public static void main(String[] args) {
// 使用匿名内部内实现
int sum = calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return 0;
}
});
// 使用lambda表达式实现
/**
* 记住我们不需要关注接口、方法名是什么
* 只需要关注有什么参数和写方法体,所以我们把
* new IntBinaryOperator() {
* @Override
* public int applyAsInt 都给去掉,就变成下面这个样子了
*/
int sum1 = calculateNum((int left, int right)-> {
return 0;
}
);
}
// IntBinaryOperator为一个接口 里面只有一个方法: int applyAsInt(int left, int right);
public static int calculateNum(IntBinaryOperator operator) {
int a = 10;
int b = 20;
return operator.applyAsInt(a, b);
}
注意:以上代码还可以简化,但是我们循序渐进,慢慢来
例子3 输出所有的偶数
public static void main(String[] args) {
// 使用匿名内部内实现
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value%2 == 0;
}
});
// 使用lambda表达式实现
/**
* 记住我们不需要关注接口、方法名是什么
* 只需要关注有什么参数和写方法体,所以我们把
* new IntPredicate() {
* @Override
* public boolean test 给去掉,就变成下面这个样子了
*/
printNum((int value) ->{
return value%2 == 0;
});
}
/**
* 用于练习
* @param predicate
*/
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);
}
}
}
例子4
public static void main(String[] args) {
// 先使用匿名内部内实现
String str = typeConver(new Function<String, String>() {
public String apply(String s) {
return s + " hxp";
}
});
System.out.println(str);
// 使用lambda表达式实现 注意:我们按照前两步教你的来就变成以下
String lambda = typeConver((String s) ->{
return s + " hxp";
});
}
public static <R> R typeConver(Function<String,R> function) {
String str = "hello";
R result = function.apply(str);
return result;
}
现在我们就开始进阶!
省略规则
- 参数类型可以省略
- 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时,小括号可以省略
现在我们回到上面这个例子:
public static void main(String[] args) {
/**
* 原来的写法
* String lambda = typeConver((String s) ->{
* return s + " hxp";
* });
*/
// 参数类型可以省略 就变成
String lambda = typeConver((s) ->{
return s + " hxp";
});
// 方法只有一个参数时,小括号可以省略
String lambda1 = typeConver(s ->{
return s + " hxp";
});
// 方法体只有一句代码时大括号return和唯一一句代码的分号可以省略
String lambda2 = typeConver(s -> s+" hxp");
}
/**
* 练习
*/
public static <R> R typeConver(Function<String,R> function) {
String str = "hello";
R result = function.apply(str);
return result;
}
前面几个例子都可以进行简化,大家可以作为练习
Stream流(重要)
stream流的话可以学习这篇文章,里面有详细讲到怎么使用:
Optional
Optional
是 Java 8 引入的一个类,用于解决在处理可能为 null 的对象时引发的空指针异常问题。它的设计目标是防止在代码中出现 null 值,使得代码更加健壮,并提高可读性。
以下是 Optional
的主要特点和用法:
1,避免空指针异常: Optional
主要用于包装可能为 null 的值,通过一系列的方法调用,可以避免直接操作可能为空的对象而引发的空指针异常。
2,创建 Optional 对象: 可以使用静态方法 Optional.of(value)
来创建一个包含非 null 值的 Optional
对象。如果要允许值为 null,可以使用 Optional.ofNullable(value)
。
// 不为null的情况下
String value = "Hello, Optional!";
Optional<String> optionalValue = Optional.of(value);
String value = // 当不确定,有可能为null的情况下
Optional<String> optionalValue = Optional.ofNullable(value);
3,检查是否有值:
第一种:
可以使用 isPresent()
方法检查 Optional
对象是否包含值,避免直接对可能为 null 的对象进行操作。
if (optionalValue.isPresent()) {
// 执行操作
}
第二种:
-
ifPresent(Consumer<? super T> action)
方法: 如果Optional
对象包含值,则执行给定的操作,否则不执行。 -
ifPresent(value -> System.out.println("Value: " + value));
4,获取值: 使用 get()
方法可以获取 Optional
对象中的值,前提是该值不为 null。但最好避免直接使用 get()
,而是通过其他方法来获取值,以避免空指针异常。
- orElseGet
如果数据不为空则获取到该数据,如果为空则返回你设置的默认值。例如:
public static void main(String[] args) {
Optional<String> optionalValue = Optional.ofNullable(getValueFromExternalSource());
// 如果optionalValue中的数据不为null的话则返回该数据 否则返回:"设置的默认值"
String result = optionalValue.orElseGet(() -> "设置的默认值");
System.out.println("Result: " + result); //Result: 设置的默认值
}
private static String getValueFromExternalSource() {
return null;
}
- orElseThrow
获取数据,如果数据不为空则获取数据,如果为空则抛出异常。
public static void main(String[] args) {
Optional<String> optionalValue = Optional.ofNullable(getValueFromExternalSource());
// 如果optionalValue中的数据不为null的话则返回该数据 否则抛出异常
String result = optionalValue.orElseThrow(() -> {
// 写你的业务...
return new RuntimeException("optionalValue为空");
});
}
private static String getValueFromExternalSource() {
return null;
}
5,函数式风格操作: Optional
提供了一系列函数式风格的方法,如 map
、filter
等,可以方便地对 Optional
进行链式操作。
- 使用 map 方法将 Optional 中的字符串转换为大写:
Optional<String> name = Optional.of("Alice");
Optional<String> upperName = name.map(String::toUpperCase); // Optional["ALICE"]
- 使用 filter 方法根据条件过滤 Optional 中的值,如果不满足条件则返回空的 Optional:
Optional<Integer> age = Optional.of(18);
Optional<Integer> adultAge = age.filter(a -> a >= 21); // Optional.empty
函数式接口
只有一个抽象方法的接口我们称为函数接口。
JDK的函数式接口都加上了@FunctionalInterface注解进行标识。但是无论是否加上该注解,只要接口中只有一个抽象方法,就是函数式接口。
以下的函数式接口是我们能常用到的:
Consumer 消费接口
这个接口表示一个消费者,它的作用是接收一个参数,然后对这个参数进行一些操作,比如打印、修改、存储等,但是不返回任何结果。你可以把它看成是一个没有返回值的方法,只是为了产生一些副作用。比如,你可以用Consumer接口来遍历一个集合,对每个元素进行打印,就像这样:
第一种方法
//创建一个Consumer接口的实例,用lambda表达式来实现它的accept方法,打印参数
Consumer<String> consumer = s -> System.out.println(s);
//创建一个字符串列表
List<String> list = Arrays.asList("Hello", "World", "Java");
//用forEach方法遍历列表,传入Consumer接口的实例作为参数
list.forEach(consumer);
//输出结果:
//Hello
//World
//Java
第二种方法
//创建一个Consumer接口的实例,用lambda表达式来实现它的accept方法,打印参数
List<String> list = Arrays.asList("Hello", "World", "Java");
testConsumer(list,s -> System.out.println(s));
}
/**
* 数据使用打印
* @param list 数据
* @param consumer 用Consumer接口来遍历一个集合,对每个元素进行打印
* @param <T> 定义的一个泛型,可以任意对象
*/
public static <T> void testConsumer(List<T> list, Consumer<T> consumer){
// forEach中的参数本身就是Consumer<? super T> action
list.forEach(consumer);
}
Function 计算转换接口
这个接口表示一个函数,它的作用是接收一个参数,然后对这个参数进行一些计算或转换,然后返回一个结果。你可以把它看成是一个有返回值的方法,用来实现一些功能。比如,你可以用Function接口来实现一个字符串转换为整数的功能,就像这样:
//创建一个Function接口的实例,用lambda表达式来实现它的apply方法,将字符串转换为整数
Function<String, Integer> function = s -> Integer.parseInt(s);
//创建一个字符串
String str = "123";
//用apply方法调用Function接口的实例,传入字符串作为参数,得到返回值
Integer num = function.apply(str);
//输出结果:
//123
Predicate 判断接口
这个接口表示一个谓词,它的作用是接收一个参数,然后对这个参数进行一些判断,返回一个布尔值。你可以把它看成是一个有返回值的条件语句,用来实现一些逻辑判断。比如,你可以用Predicate接口来实现一个判断字符串是否为空的功能,就像这样:
//创建一个Predicate接口的实例,用lambda表达式来实现它的test方法,判断字符串是否为空
Predicate<String> predicate = s -> s == null || s.isEmpty();
//创建一个字符串
String str = "";
//用test方法调用Predicate接口的实例,传入字符串作为参数,得到返回值
boolean result = predicate.test(str);
//输出结果:
//true
Supplier 生产型接口
这个接口表示一个供应者,它的作用是不接收任何参数,但是返回一个结果。你可以把它看成是一个无参数的方法,用来提供一些数据。比如,你可以用Supplier接口来实现一个生成随机数的功能,就像这样:
//创建一个Supplier接口的实例,用lambda表达式来实现它的get方法,返回一个随机数
Supplier<Double> supplier = () -> Math.random();
//用get方法调用Supplier接口的实例,得到返回值
Double num = supplier.get();
//输出结果:
//0.4567890123456789
其他
stream流中的各个方法就使用了这些函数式接口作为参数,使得我们可以灵活的操作:
- filter(Predicate<T> predicate):使用Predicate接口来对流中的元素进行过滤,只保留满足条件的元素。
- map(Function<T, R> mapper):使用Function接口来对流中的元素进行转换,将每个元素映射为另一个元素。
- forEach(Consumer<T> action):使用Consumer接口来对流中的元素进行消费,执行一些操作,但不返回结果。
- reduce(BinaryOperator<T> accumulator):使用BinaryOperator接口来对流中的元素进行归约,将所有元素累积为一个结果。
- max(Comparator<T> comparator):使用Comparator接口来对流中的元素进行比较,找出最大的元素。
- min(Comparator<T> comparator):使用Comparator接口来对流中的元素进行比较,找出最小的元素。
- anyMatch(Predicate<T> predicate):使用Predicate接口来对流中的元素进行判断,如果有任意一个元素满足条件,就返回true。
- allMatch(Predicate<T> predicate):使用Predicate接口来对流中的元素进行判断,如果所有元素都满足条件,就返回true。
- noneMatch(Predicate<T> predicate):使用Predicate接口来对流中的元素进行判断,如果没有元素满足条件,就返回true。
方法引用
我们在使用lambda时,如果方法体中只有一个方法的调用的话,我们可以用方法引用进一步简化代码。
引用类的静态方法
基本格式:类名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
List<User> users= getUsers();
users.stream()
.map(user-> user.getAge())
.map(new Function<Integer, String>() {
@Override
public String apply(Integer age) {
return String.valueOf(age);
}
});
使用lambda表达式优化之后:
List<User> users= getUsers();
users.stream()
.map(user-> user.getAge())
.map(String::valueOf);
引用对象的实例方法
基本格式
:对象名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法。
匿名内部内实现:
List<User> users = getUsers();
StringBuilder sb = new StringBuilder();
users.stream()
.map(user -> user.getName())
.forEach(new Consumer<String>() {
@Override
public void accept(String name) {
sb.append(name);
}
});
lambda表达式实现:
List<User> users = getUsers();
StringBuilder sb = new StringBuilder();
users.stream()
.map(user -> user.getName())
.forEach(sb::append);
引用类的实例方法
格式:类名::方法名
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入这个成员方法中,这个时候我们就可以引用类的实例方法。
List<User> users = getUsers();
users.stream()
.map(user -> user.getName())
.forEach(System.out::println);
// 优化后,使用User::getName
users.stream()
.map(User::getName)
.forEach(System.out::println);
如果不明白的话可以仔细看这两点:
1,调用了第一个参数的成员方法第一参数是:user
user调用了成员方法:user.getName()
2,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入这个成员方法中
因为只有user这一个参数,而且getName()也不需要参数
所以也满足了这一点
构造器引用
如果方法体中的一行代码是构造器的话就可以使用构造器引用。
格式:类名::new
使用前提:
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
List<User> users = getUsers();
users.stream()
.map(User::getName)
.map(name -> new StringBuilder(name))
.forEach(System.out::println);
优化后:
List<User> users = getUsers();
users.stream()
.map(User::getName)
.map(StringBuilder::new)
.forEach(System.out::println);
写到这里就差不多结束了,喜欢博主的可以关注一波,后续会分享更多技术知识,让我们一起进步!