JAVA Lambda表达式与Stream流
1 Lambda表达式
从JDK1.8开始为了简化使用者进行代码的开发,专门提供有Lambda表达式的支持,利用此操作可以实现函数式的编程,对于函数式编程比较著名的语言有:Haskell、Scala,利用函数式的编程可以避免掉面向对象编程之中一些繁琐的处理问题。
代码范例
public class Demo {
public static void main(String[] args) {
PrintString ps = new PrintString() {
@Override
public void print(String str) {
System.out.println(str);
}
};
ps.print("Lambda");
}
}
interface PrintString{
void print(String str);
}
在这样的程序里,实际上核心的功能只有一行语句System.out.println(str),但是为了这一行的核心语句,我们仍然需要按照完整的面向对象给出的结构进行开发。于是这些问题随着技术的发展也是越来越突出了。
使用Lambda表达式后的代码
public class Demo {
public static void main(String[] args) {
PrintString ps = str -> System.out.println(str);
ps.print("Lambda");
}
}
interface PrintString{
void print(String str);
}
1.1 Lambda表达式的几种格式
- 方法没有参数: () -> {};
- 方法有参数::(参数,…,参数) -> {};
1.2 Lambda表达式的使用
1.2.1 无参
如果要创建接口的实现类,且该类只是使用一次,那么可以使用匿名内部类,但是匿名内部类写起来很麻烦。而当接口中只有一个抽象方法时,该接口就是一个函数式接口,那么我们就可以使用 Lambda 来代替匿名内部类。Lambda 体就是接口的实现。
public class Demo_a {
public static void main(String[] args) {
PrintString ps = () -> System.out.println("Lambda");
ps.print();
}
}
interface PrintString{
void print();
}
注意:
抽象方法如果没有参数,则 lambda 表达式不能省略 ();
1.2.2 有参
public class Demo {
public static void main(String[] args) {
Addition addition = new Addition() {
@Override
public int add(Integer i1, Integer i2) {
return i1 + i2;
}
};
System.out.println(addition.add(1,2));
}
}
interface Addition{
int add(Integer i1, Integer i2);
}
---------简化后------------
public class Demo {
public static void main(String[] args) {
Addition addition = (Integer i1, Integer i2) -> i1 + i2;
System.out.println(addition.add(1,2));
}
}
interface Addition{
int add(Integer i1, Integer i2);
}
1.3 使用总结
- lambda 表达式形式为 ()->{},-> 左边是抽象方法的形参列表, -> 是抽象方法的实现体。
- lambda 方法如果没有参数或有两个及以上的参数,则 小括号不能省略。
- lambda 方法如果只有一个参数,则小括号可以省略。
- lambda 方法体如果只有一行语句,则 大括号和return都可省略。
- 省略了大括号,则必须省略 return,省略了 return ,则必须省略 {},这俩要么成对出现,要么都不出现。
Lambda 的本质就是函数式接口的一个实现类。
2 Stream流
2.1 Stream流的介绍
- stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
- stream不会改变数据源,通常情况下会产生一个新的集合。
- stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
- 对stream操作分为终端操作和中间操作,那么这两者分别代表什么呢?
终端操作:会消费流,这种操作会产生一个结果的,如果一个流被消费过了,那它就不能被重用的。
中间操作:中间操作会产生另一个流。因此中间操作可以用来创建执行一系列动作的管道。一个特别需要注意的点是:中间操作不是立即发生的。相反,当在中间操作创建的新流上执行完终端操作后,中间操作指定的操作才会发生。所以中间操作是延迟发生的,中间操作的延迟行为主要是让流API能够更加高效地执行。 - stream不可复用,对一个已经进行过终端操作的流再次调用,会抛出异常。
2.2 创建Stream流
public class Demo {
public static void main(String[] args) {
// 获得 Stream 对象
int[] arr = {1,2,3,4};
// 1.List 获得stream对象
List<String> list = List.of("张三", "李四", "哈尔吉亚斯");
Stream<String> stream1 = list.stream();
// 2.Set获得 stream对象
Set<String> set = Set.of("牛肉馅包子", "鸡肉馅饺子", "猪肉陷阱");
Stream<String> stream2 = set.stream();
// 3.Map获得Stream对象
Map<String, Integer> map = Map.of("杯子", 23, "勺子", 2, "本子", 5,"奶茶",30);
Stream<String> stream3 = map.keySet().stream();
Stream<Integer> stream4 = map.values().stream();
Stream<Map.Entry<String, Integer>> stream5 = map.entrySet().stream();
// 4.数组获得Stream对象
String[] strArr = {"联想", "华硕", "惠普", "华为"};
Stream<String> stream6 = Arrays.stream(strArr);
// 5.直接将很多元素包装成Stream
Stream<Integer> stream7 = Stream.of(18, 20, 31, 12, 10);
System.out.println(stream1);
}
}
2.3 常用方法
- 延迟方法: 返回值类型仍然是
Stream
接口自身类型的方法,因此支持链式调用。(除了终结方法
外,其余方法均为延迟方法。) - 终结方法: 返回值类型不再是
Stream
接口自身类型的方法,因此不再支持类似StringBuilder
那
样的链式调用。
2.3.1 逐一处理:forEach
虽然方法名字叫 forEach
,但是与for循环中的“for-each”昵称不同。
void forEach(Consumer<? super T> action); 1
该方法接收⼀个 Consumer
接口函数,会将每一个流元素交给该函数进行处理。
public class Demo {
public static void main(String[] args) {
Stream<String> stream = Stream.of("张⽆忌", "张三丰", "周芷若");
stream.forEach(name-> System.out.println(name));
}
}
2.3.2 过滤:filter
可以通过 filter
方法将⼀个流转换成另一个子集流。
Stream<T> filter(Predicate<? super T> predicate);
该接口接收⼀个 Predicate
函数式接口参数(可以是⼀个Lambda或方法引用)作为筛选条件。
示例代码
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("张⽆忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s -> s.startsWith("张"));
}
}
2.3.3 映射:map
如果需要将流中的元素映射到另⼀个流中,可以使用 map
方法。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
该接口需要⼀个 Function
函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
示例代码
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(str->Integer.parseInt(str));
}
}
2.3.4 统计个数:count
正如旧集合 Collection 当中的 size
方法⼀样,流提供 count 方法来数⼀数其中的元素个数
long count();
该方法返回⼀个long值代表元素个数(不再像旧集合那样是int值)。
示例代码
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("张⽆忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s -> s.startsWith("张"));
System.out.println(result.count()); // 2
}
}
2.3.5 取用前几个:limit
limit
方法可以对流进行截取,只取用前n个。
Stream<T> limit(long maxSize);
参数是⼀个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。
示例代码
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("张⽆忌", "张三丰", "周芷若");
Stream<String> result = original.limit(2);
System.out.println(result.count()); // 2
}
}
2.3.6 跳过前几个:skip
如果希望跳过前几个元素,可以使用 skip
方法获取⼀个截取之后的新流:
Stream<T> skip(long n);
如果流的当前长度大于n,则跳过前n个;否则将会得到⼀个长度为0的空流。
示例代码
public class Demo {
public static void main(String[] args) {
Stream<String> original = Stream.of("张⽆忌", "张三丰", "周芷若");
Stream<String> result = original.skip(2);
System.out.println(result.count()); // 1
}
}
2.3.7 组合:concat
如果有两个流,希望合并成为⼀个流,那么可以使⽤ Stream
接口的静态方法 concat
:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
示例代码
public class Demo {
public static void main(String[] args) {
Stream<String> streamA = Stream.of("张⽆忌");
Stream<String> streamB = Stream.of("张翠⼭");
Stream<String> result = Stream.concat(streamA, streamB);
}
}