Lambda表达式
Lambda 表达式是 Java8 新增的重要特性,Lambda 使 Java 具有了类似函数式编程的风格,其实质也是由编译器根据表达式推断最终生成原始语法的字节码方式。
Lambda表达式的使用与
函数式思想
有关,函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”而我们要学习的Lambda表达式就是函数式思想的体现。
Lambda表达式是Java 8引入的一种新特性,它是一种简洁、灵活的函数式编程方式,可以用来替代匿名内部类。Lambda表达式的使用规则如下:
语法格式:
(parameters) -> expression
或
(parameters) -> { statements; }
。其中,
parameters
是参数列表,
expression
是单个表达式,
statements
是代码块。参数列表可以为空,或者包含一个或多个参数。当只有一个参数时,可以省略参数括号。
表达式可以是一个变量、一个方法调用、一个对象创建、一个数值或一个字符串等。
代码块可以包含多条语句,用花括号括起来。如果代码块中只有一条语句,则可以省略花括号和分号。
Lambda表达式可以赋值给一个变量,或者作为参数传递给一个方法。
Lambda表达式可以访问外部变量,但是这些变量必须是
final
或者
effectively final
的。Lambda表达式可以使用Java 8提供的函数式接口,例如
Predicate
、
Consumer
、
Function
等,从而实现函数式编程。总之,Lambda表达式是一种简洁、灵活的函数式编程方式,可以用来替代匿名内部类。通过使用Lambda表达式,可以使代码更加简洁、易读、易维护,从而提高开发效率和代码质量。
可以先看如下例子:
先定义一个借口,接口中只有一个抽象方法
接口:
public interface RunTest {
public void run();
}
测试:
public class LambdaTest1 {
public static void main(String[] args) {
//匿名内部类方式输出
fun1(new RunTest() {
@Override
public void run() {
System.out.println("匿名内部类方式输出方式");
}
});
System.out.println("~~~~~~分割线~~~~~~");
//Lambda表达式输出方式,直接省略的部分可以参照上边的匿名内部类来理解。
fun1(() -> System.out.println("Lambda表达式写法输出。。。"));
}
public static void fun1(RunTest runTest){
runTest.run();
}
}
代码说明讲解:
在这段代码中,Lambda表达式的实现方式如下:
fun1(() -> System.out.println("Lambda表达式写法输出。。。"));
复制代码
其中,
->
符号是Lambda表达式的箭头符号,它将参数列表和Lambda表达式的主体分隔开来。在这里,参数列表为空,因为
RunTest
接口中的
run()
方法没有参数。Lambda表达式的主体是一个输出语句,用于输出一条信息。Lambda表达式的主体可以是一个表达式或一个代码块。在这里,Lambda表达式的主体是一个输出语句,因此可以直接使用一个表达式来实现。在Lambda表达式中,如果主体只有一条语句,则可以省略花括号和分号,从而使代码更加简洁。
因此,这段代码中的Lambda表达式实现方式是一种简洁、灵活的函数式编程方式,可以用来替代匿名内部类。通过使用Lambda表达式,可以使代码更加简洁、易读、易维护,从而提高开发效率和代码质量。
当接口中只有一个抽象方法时,满足函数式编程的思想
,此时可以采用Lambda表达式进行省略。
如何理解Lambda表达式的省略?
打个比方:一班的教室只有一个学生叫李明(大家都知道1班教室只有李明一个学生),当你们看到一班的教室里灯是亮的,那么可以推断教室里的学生一定是李明,因此不需要再打开教室门查看是哪个学生。
也就是说,在代码中,你所要重写的接口中的抽象方法
只有一个时
,不用明确指出是哪个抽象方法,系统知道你要实现的接口中只有那一个方法,此时可以省略采用Lambda方式来实现。
省略规则
参数类型可以省略
。但是有多个参数的情况下,不能只省略一个如果参数有且仅有一个,那么
小括号可以省略
如果代码块的语句只有一条,可以
省略大括号和分号,和return关键字
Stream流
获取流的方式
一、集合获取流的方式
1. List:
List list = new ArrayList();
Stream stream1 = list.stream();
2. Set:
Set set = new HashSet();
Stream stream2 = set.stream();
3. Map:
Set keySet = map.keySet();
Stream stream3 = keySet.stream();
Set entrySet = map.entrySet();
Stream stream4 = entrySet.stream();
二、数组获取流的方式:
Stream 接口的静态方法 of 可以获取数组对应的流
Stream<T> stream = Stream.of(T…args);
Stream中常用的方法:
Stream<T> filter(Predicate predicate) | 用于对流中的数据进行过滤 |
Stream<T> limit(long maxSize) | 返回此流中的元素组成的流,截取前指定参数个数的数据 |
Stream<T> skip(long n) | 跳过指定参数个数的数据,返回由该流的剩余元素组成的流 |
static <T> Stream<T> concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
Stream<T> distinct() | 返回由该流的不同元素(根据Object.equals(Object) )组成的流 |
Stream<T> sorted() | 返回由此流的元素组成的流,根据自然顺序排序 |
<R> Stream<R> map(Function mapper) | 返回由给定函数应用于此流的元素的结果组成的流 |
IntStream mapToInt(ToIntFunction mapper) | 返回一个IntStream其中包含将给定函数应用于此流的元素的结果 |
public class LambdaTest2 {
public static void main(String[] args) {
List<String> list=new ArrayList();
List list2=new ArrayList();
list.add("李星云");
list.add("姬如雪");
list.add("李世民");
list.add("倾国");
list.add("倾城");
list.add("张飞");
list.add("关云长");
//挑选出姓李的人
System.out.println("~~~~~~~~~~~filter使用效果演示~~~~~~~~~~~~~");
list.stream().filter(s->s.startsWith("李")).forEach(System.out::println);
System.out.println("~~~~~~~~~~~limit使用效果演示~~~~~~~~~~~~~");
list.stream().limit(2).forEach(System.out::println);
System.out.println("~~~~~~~~挑选出姓李的人,不包含第一个(跳过第一个)~~~~~~~~~");
list.stream().filter(s->s.startsWith("李")).skip(1).forEach(System.out::println);
System.out.println("~~~~~~~~~~~华丽的分割线~~~~~~~~~~~~~");
List<Integer> list2=new ArrayList();
list2.add(12);
list2.add(32);
list2.add(12);
list2.add(52);
list2.add(12);
list2.add(92);
//未去除重复的元素之前输出
list2.stream().forEach(System.out::println);
System.out.println("~~~~~~~~~~~华丽的分割线(去除重复的元素)~~~~~~~~~~~~~");
//去除重复的元素
list2.stream().distinct().forEach(System.out::println);
}
}
运行结果如下:
E:\Downlode\java\jdk\bin\java.exe ...
~~~~~~~~~~~filter使用效果演示~~~~~~~~~~~~~
李星云
李世民
李云龙
~~~~~~~~~~~limit使用效果演示~~~~~~~~~~~~~
李星云
姬如雪
~~~~~~~~~~~//挑选出姓李的人,不包含第一个(跳过第一个)~~~~~~~~~~~~~
李世民
李云龙
~~~~~~~~~~~华丽的分割线~~~~~~~~~~~~~
12
32
12
52
12
92
~~~~~~~~~~~华丽的分割线(去除重复的元素)~~~~~~~~~~~~~
12
32
52
92
92
Process finished with exit code 0
Java中的Stream.concat方法用于将两个流连接在一起,返回一个新的流。使用时需要注意以下几点:
-
Stream.concat方法的参数必须是两个Stream对象,否则会抛出NullPointerException异常。
-
Stream.concat方法返回的是一个新的流,原始的流不会被修改。
-
Stream.concat方法返回的流是惰性求值的,也就是说只有在调用终止操作时才会执行中间操作。
-
如果两个流的元素类型不一致,Stream.concat方法会抛出IllegalArgumentException异常。
实例:
List<String> list1 = Arrays.asList("a", "b", "c");
List<String> list2 = Arrays.asList("d", "e", "f");
Stream<String> stream1 = list1.stream();
Stream<String> stream2 = list2.stream();
Stream<String> resultStream = Stream.concat(stream1, stream2);
//遍历 流中的元素
resultStream.forEach(System.out::println);
运行结果:
E:\Downlode\java\jdk\bin\java.exe ...
a
b
c
d
e
f
Process finished with exit code 0
Java中的Stream.sorted()方法用于对流中的元素进行排序,返回一个新的流。使用时需要注意以下几点:
-
Stream.sorted()方法可以使用自然排序或者自定义排序器来对元素进行排序。
-
Stream.sorted()方法返回的是一个新的流,原始的流不会被修改。
-
Stream.sorted()方法返回的流是惰性求值的,也就是说只有在调用终止操作时才会执行中间操作。
List<String> list = Arrays.asList("b","c","cdb","a", "aasd", "ewrb");
Stream<String> stream = list.stream();
Stream<String> sortedStream = stream.sorted();
sortedStream.forEach(System.out::println);
结果:
E:\Downlode\java\jdk\bin\java.exe ...
a
aasd
b
c
cdb
ewrb
Process finished with exit code 0
Java中的Stream.map(Function mapper)方法用于将流中的每个元素按照指定的Function(函数式接口)进行映射,返回一个新的流。使用时需要注意以下几点:
-
Stream.map()方法的参数是一个Function对象,该对象的apply()方法接收一个元素并返回一个新的元素。
-
Stream.map()方法返回的是一个新的流,原始的流不会被修改。
-
Stream.map()方法返回的流是惰性求值的,也就是说只有在调用终止操作时才会执行中间操作。
下面是一个示例代码:
List<Integer> list = Arrays.asList(1, 2, 3);
Stream<Integer> stream = list.stream();
Stream<String> mappedStream = stream.map(i -> "number " + i);
mappedStream.forEach(System.out::println);
运行:
E:\Downlode\java\jdk\bin\java.exe...
number 1
number 2
number 3
Process finished with exit code 0
如果根据上述实例还不明白这个map映射的意思,请带着问题看下面的讲解以及问答说明:
在上面的这个例子中,我们创建了一个整数列表 list,其中包含了1、2、3三个整数。我们使用 list.stream() 方法将这个列表转换成了一个流 stream,然后使用 map 方法将流中的每个整数都转换成了一个字符串 “number i”,其中 i 表示原来的整数值。这个转换的过程就是将原来的整数元素映射成了新的字符串元素。最后,我们使用 forEach 方法打印出了这个新的字符串流中的每个元素。
所以,map 方法的作用就是将一个元素流转换成另一个元素流,这个转换过程是通过一个函数来实现的。在这个例子中,我们使用了一个 Lambda 表达式来实现这个函数,将整数转换成了字符串。
可以参考下面的问答帮助你理解stream中map映射:
问1:也就是说我可以通过映射将1,2,3,4的集合流转换成一二三四的集合流可以这样打个比方吧?
答:是的。通过映射,我们可以将一个集合流中的元素转换成另一种类型的元素,比如将整数转换成字符串,或者将字符串转换成大写字母。在你的例子中,你可以使用映射将整数 1,2,3,4 转换成字符串 “一”、”二”、”三”、”四”,从而得到一二三四的集合流。这个转换的过程可以使用一个函数来实现,这个函数将整数映射成对应的字符串。
问2:是不是可以理解成可以将原来的元素替换成其他任意类型的元素?
答:
不完全正确
。
通过映射,我们并不能直接替换原来的元素,而是将原来的元素转换成了另一种类型的元素,并生成了一个新的流。原来的元素不会被修改,而是在新的流中被替换成了转换后的元素。也就是说,通过映射,我们可以将一个元素流转换成另一个元素流,但是原来的元素并没有被替换掉,而是被转换成了新的元素。
问3:是不是可以理解成可以将原来的元素流换成其他任意类型的元素以流的形式存在,但原来的元素流依然存在?
答:是的,你理解得很对。通过映射,我们可以将原来的元素流转换成另一个类型的元素流,但原来的元素流依然存在,没有被修改或替换掉。新的元素流是在原来的元素流的基础上生成的,它包含了转换后的新元素。这样,我们就可以在不改变原来的元素流的情况下,得到一个新的元素流,以便进行后续的操作。
问4:新旧元素流是不是需要一一对应?
是的,新元素流和原来的元素流是一一对应的。在映射过程中,每个原来的元素都会被映射成一个新的元素,而且新元素的数量和原来元素的数量是相同的。这就意味着,新元素流中的元素的顺序和原来元素流中的元素的顺序是一致的,它们是一一对应的。这也是为什么我们可以使用新元素流来替代原来的元素流进行后续的操作,因为它们是等价的。
当你看完这4个问答你应该了解到Stream.map(Function mapper)方法的使用了,出此方法还有:
一下相关映射的方法:
<R> Stream<R> map(Function<? super T,? extends R> mapper)
接收函数型接口,将元素转换成其他形式或提取信息;接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
IntStream mapToInt(ToIntFunction<? super T> mapper)
接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
LongStream mapToLong(ToLongFunction<? super T> mapper)
接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。