Java 8 Lambda 表达式 Stream
lambda表达式和Stream流是JDK8新增加的新特性,研究本文内容或者运行本文中的demo示例必须安装并使用JDK8以上的JDK版本。demo地址:https://gitee.com/huannzi/bigdataframework/tree/master/src/main/java/com/orkasgb/java
文章目录
- 1、什么是Lambda表达式
- 2、Lambda表达式初体验
- (1)、简单的使用Lambda表达式
- (2)、Lambda表达式作为参数传递
- (3)、Lambda表达式作为返回值
- 2、Lambda表达式高级体验--函数式接口
- (1)、Runnable/Callable
- (2)、Supplier/Consumer
- (3)、Comparator
- (4)、Predicate
- (5)、Function
- Lambda表达式语法格式总结:
- lambda表达式底层原理:
- 3、Stream流
- 3.1、Stream流的获取
- (1)、使用集合对象的stream()获取stream流对象
- (2)、使用集合对象的stream()获取stream流对象
- (3)、使用集合对象的stream()获取stream流对象
- 3.2、Stream流的常用方法
- (1)、forEach()方法:代替for循环
- (2)、count() 方法:统计,计数
- (3)、distinct():对集合去重
- (4)、map():对集合元素进行中间操作 peek():根据官方文档解释,用于调试使用,不建议使用
- (5)、filter():对集合元素进行进行过滤,根据过滤条件过滤复合要求的数据 anyMatch():对集合元素进行进行匹配,只要有一个元素符合条件就为true allMatch():对集合元素进行进行匹配,全部元素都符合条件就为true
- (6)、reduce():对集合元素进行进行求和或者归并处理
- (7)、skip():跳过集合中某几个元素 limit():一次获取几个元素,两个结合类似于mysql分页操作
- (8)、sorted():对集合中元素进行排序,接受一个Comparator
- (9)、mapToInt():将集合中元素转化成一个IntStream mapToDouble():将集合中元素转化成一个DoubleStream mapToLong():将集合中元素转化成一个LongStream
- (10)、flatMapToInt():将集合中元素转化成一个IntStream flatMapToDouble():将集合中元素转化成一个DoubleStream flatMapToLong():将集合中元素转化成一个LongStream
- (11)、Collectors.toList():将集合中元素转化成一个List Collectors.toSet():将集合中元素转化成一个Set Collectors.joining():将集合中每一个元素拼接一个字符串 Collectors.toMap:将集合元素按照给定的规则转化成一个Map Collectors.groupingBy:将集合元素按照给定的规则分组
- (12)、stream流综合测试
- 总结
1、什么是Lambda表达式
Lamdba表达式是JDK8加入的一种新特性,属于一种响应式编程体验,是一种关于函数定义,输入量,输出量的推演计算。简单的来说,Lambda就是函数式编程。
2、Lambda表达式初体验
public interface DemoFactory { Object getDemo();
} /** * 具体的实现类 */
class DemoFactoryImpl implements DemoFactory{ @Override public Object getDemo() { return new Demo(); }/** * Lambda表达式作为参数 * * @param factory 接口参数 * @return 实体 */ public static Object getDemo(DemoFactory factory) { return factory.getDemo(); }/** * Lambda表达式作为返回值 * * @return 实体 */ public static DemoFactory getDemo1() { return () -> new Demo(); }
} /** * 实体类 */
class Demo { private String call = "Hello Word!"; public Demo() { System.out.println(this.call); }
}
(1)、简单的使用Lambda表达式
/** * 调用类 */
class Main { public static void main(String[] args) { DemoFactory demo = () -> new Demo(); // 使用Lamdba表达式创建具体的实体对象System.out.println(demo.getDemo()); }
}
(2)、Lambda表达式作为参数传递
/*** 调用类 */
class Main { public static void main(String[] args) { DemoFactoryImpl.getDemo(() -> new Demo()); // 使用Lamdba表达式作为参数}
}
(3)、Lambda表达式作为返回值
/*** 调用类 */
class Main { public static void main(String[] args) { DemoFactory factory = DemoFactoryImpl.getDemo1();// Lambda作为返回值 factory.getDemo();}
}
2、Lambda表达式高级体验–函数式接口
函数式接口指的是只有一个抽象方法,且使用@FunctionalInterface注解注释的接口,称之为函数式接口。常见的函数式接口有以下几种:
(1)、Runnable/Callable
@FunctionalInterface
public interface Runnable { void run();
}public class RunnableLambda { public static void main(String[] args) { new Thread(() -> System.out.println("RunnableLambda"), "thread1").start(); }
}
(2)、Supplier/Consumer
@FunctionalInterface
public interface Supplier<T> { T get();
}/** * 函数式接口--Supplier */public class SupplierLambda { public static void main(String[] args) { int[] arr = new int[]{1, 2, 3, 4, 5}; int max = getMax(() -> Arrays.stream(arr).max().getAsInt()); System.out.println("最大值:" + max); } private static int getMax(Supplier<Integer> supplier) { return supplier.get(); }
}@FunctionalInterface
public interface Consumer<T> { void accept(T var1); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (t) -> { this.accept(t); after.accept(t); }; }
}/** * 函数式接口--Consumer */public class ConsumerLambda { public static void main(String[] args) { consumer(s -> System.out.println(s)); consumer(s -> System.out.println(s.toLowerCase()), s -> System.out.println(s.toUpperCase())); } /** * Consumer中的抽象方法-accept,意思是消费一次 * * @param consumer */ private static void consumer (Consumer<String> consumer) { consumer.accept("Hello Word!"); } /** * Consumer中的默认方法-andThen,意思是多个消费者连接起来,各自消费一次 * * @param first 第一个消费者 * @param second 第二个消费者 */ private static void consumer (Consumer<String> first, Consumer<String> second) { first.andThen(second).accept("Hello Word!"); }
}
(3)、Comparator
/** * 函数式接口-Comparator */public class ComparatorLambda { public static void main(String[] args) { List<Integer> arr = Arrays.asList(1, 2, 3, 4, 5); /** * sort()方法需要传入一个Comparator的实现,默认是按照自然排序。 */ arr.sort((o1, o2) -> o2 - o1); System.out.println(arr); }
}
(4)、Predicate
/** * 函数式接口-Predicate */public class PredicateLambda { public static void main(String[] args) { addMethod(str -> str.toString().equals("HelloWord!"), str -> str.toString().equals("HelloWord!")); notMethod(str -> !str.toString().equals("HelloWord!")); orMethod(str -> !str.toString().equals("HelloWord!"), str -> str.toString().equals("HelloWord!")); } /** * Predicate的and()方法,意思是两个判断都要满足条件 * * @param one 第一个断言 * @param two 第二个断言 */ private static void addMethod (Predicate one, Predicate two) { String result = one.and(two).test("HelloWord!") ? "字符串符合要求!" : "字符串不符合要求!"; System.out.println("addMethod:" + result); } /** * Predicate的not()方法,意思是当前判断结果取反 * * @param one 第一个断言 */ private static void notMethod (Predicate one) { Predicate.not(one).test(true); String result = String.valueOf(Predicate.not(one).test(true));; System.out.println("notMethod:" + result); } /** * Predicate的or()方法,意思是两个判断只要有一个满足条件就可以 * * @param one 第一个断言 * @param two 第二个断言 */ private static void orMethod (Predicate one, Predicate two) { String result = one.or(two).test("HelloWord!") ? "字符串符合要求!" : "字符串不符合要求!"; System.out.println("orMethod:" + result); }
}
(5)、Function
/** * 函数式接口-Function */public class FunctionLambda { public static void main(String[] args) { String result = getUpCase(str -> str.toUpperCase()); System.out.println("getUpCase:" + result); String result1 = String.valueOf(andThenMethod(str -> str.concat("0"), str -> Integer.valueOf(str) * 20)); System.out.println("andThenMethod:" + result1); } /** * apply("hello")的意思就是去处理"hello"这个字符串,然后返回处理的结果 * * @param function 函数功能 * @return 返回处理的结果 */ private static String getUpCase(Function<String, String> function) { return function.apply("hello"); } /** * andThen()的意思就是one和two都去处理"10"这个字符串,但是one处理后的结果要作为two的入参,然后再返回two处理的结果 * * @param one 第一个函数功能 * @param two 第二个函数功能 * @return 结果 */ private static Integer andThenMethod(Function<String, String> one, Function<String, Integer> two) { return one.andThen(two).apply("10"); }
}
Lambda表达式语法格式总结:
1、可选的{},当Lambda表达式的函数体只有一句的时候,{}可以省略。
(String str) -> System.out.println(str);
2、可选的(),当Lambda表达式的参数只有一个的时候,()可以省略。
str -> System.out.println(str);
3、可选的return关键字,当Lambda表达式的函数体只有一句的时候,且返回值匹配的时候,可以省略return关键字。
(int a, int b) -> a + b;
4、可选的参数类型声明,Lambda表达式可以省略参数类型声明,因为参数动态进行推断。
(a, b) -> return a + b;
lambda表达式底层原理:
创建TestLambda.java文件,简单的编写一个循环打印数组的测试代码。
1、使用 javac TestLambda.java
编译TestLambda.java文件,将会生成TestLambda.class文件。
2、使用 javap -v -p TestLambda
反编译TestLambda.class文件。
3、使用 java -Djdk.internal.lambda.dumpProxyClasses TestLambda
执行TestLambda程序,并会生成TestLambda$$Lambda$1.class
文件,该文件是JAVA在运行时生成的由jvm生成的类,实际上就是由lambda表达式生成的内部类的具体实现。
// TestLambda.java
public class TestLambda { static { System.setProperty("jdk.internal.lambda.dumpProxyClasses", "."); } public static void main(String[] args) throws CloneNotSupportedException { List<Integer> arr = Arrays.asList(1, 2, 3, 4, 5); arr.forEach(i -> System.out.println(i)); }
}// TestLambda.class,字节码文件中删除了部分内容,只保留了有用的地方
// class version 55.0 (55)
// access flags 0x21
public class TestLambda {// access flags 0x9public static main([Ljava/lang/String;)V throws java/lang/CloneNotSupportedException java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;// arguments:(Ljava/lang/Object;)V, // handle kind 0x6 : INVOKESTATICTestLambda.lambda$main$0(Ljava/lang/Integer;)V, (Ljava/lang/Integer;)V]INVOKEINTERFACE java/util/List.forEach (Ljava/util/function/Consumer;)V (itf)// access flags 0x100Aprivate static synthetic lambda$main$0(Ljava/lang/Integer;)VL0LINENUMBER 12 L0GETSTATIC java/lang/System.out : Ljava/io/PrintStream;ALOAD 0INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)VRETURNL1LOCALVARIABLE i Ljava/lang/Integer; L0 L1 0MAXSTACK = 2MAXLOCALS = 1
}// TestLambda$$Lambda$1.class
final class TestLambda$$Lambda$1 implements Consumer { private TestLambda$$Lambda$1() { } @Hidden public void accept(Object var1) { TestLambda.lambda$main$0((Integer)var1); }
}
**总结:TestLambda.class中多出了一个private static synthetic lambda$main$0(Ljava/lang/Integer;)
方法,根据JAVA的特性,这是一个动态生成的函数,而在forEach参数里面,由原本的lambda表达式变成了由Consumer强转的具体的实现,由此说明,lambda就是一个匿名内部类对象,因为forEach接收的是一个Consumer,所以这里原本的lambda表达式变成了由Consumer强转的具体的实现。
ambda表达式变成了由Consumer强转的具体的实现中有一个TestLambda.lambda$main$0(Ljava/lang/Integer;)V
参数,而这个参数指向的就是TestLambda.class中多出来的那个private static synthetic lambda$main$0(Ljava/lang/Integer;)
。
TestLambda$$Lambda$1.class
是由JVM在运行时生成并通过特定参数保存下来的lambda表达式的具体实现的文件,可以看出,TestLambda$$Lambda$1
实现了Consumer接口并重写了accept方法。而该方法中刚好调用的就是TestLambda.lambda$main$0((Integer)var1);
。那么也就是说,JVM在运行的时候,将lambda表达式的方法体封装成一个具体的方法,如private static synthetic lambda$main$0(Ljava/lang/Integer;)
,而将lambda表达式变成了由对应接口强转的具体的实现,实现中将生成的该方法作为参数传入,如TestLambda.lambda$main$0(Ljava/lang/Integer;)V
,后续在生成具体实现的,在重写的接口方法中直接调用该该方法,如该例中,accept方法中直接就调用了传入的TestLambda.lambda$main$0
参数,进而完成lambda表达式的功能。
3、Stream流
Stream流是专注于提供对容器的操作,比如遍历,过滤,统计等操作,提供了串行/并行两种模式,使用Fork/Join框架对任务任务拆分,已达到简化代码,提高编程效率与可读性的目的。
3.1、Stream流的获取
(1)、使用集合对象的stream()获取stream流对象
// 1、使用集合对象的stream()获取stream流对象 List<String> list = Arrays.asList("1", "2", "3", "4"); Stream<String> stream = list.stream(); System.out.println("使用集合对象的stream()获取stream流对象:" + stream);
(2)、使用集合对象的stream()获取stream流对象
// 2、使用stream流对象的静态方法of()、generate()、iterate()等方法获取获取stream流对象 Stream<List<String>> of = Stream.of(list); System.out.println("使用stream流对象的静态方法of()方法获取获取stream流对象:" + of); Stream<Integer> generate = Stream.generate(() -> 1 + 2); System.out.println("使用stream流对象的静态方法generate()方法获取获取stream流对象:" + generate); Stream<Integer> iterate = Stream.iterate(10, i -> i + 2); System.out.println("使用stream流对象的静态方法iterate()方法获取获取stream流对象:" + iterate);
(3)、使用集合对象的stream()获取stream流对象
// 3、使用Arrays.stream()方法获取stream流对象 int[] arr = {1,2,3}; IntStream stream1 = Arrays.stream(arr); System.out.println("使用Arrays.stream()方法获取stream流对象:" + stream1);
3.2、Stream流的常用方法
(1)、forEach()方法:代替for循环
/** * stream流对象的forEach方法 */
@Test
public void testStreamAPIForEach(){ List<String> list = Arrays.asList("1", "2", "3", "4"); Stream<String> stream = list.stream(); stream.forEach(System.out::println);
}
(2)、count() 方法:统计,计数
/** * stream流对象的count() */
@Test
public void testStreamAPICount(){ List<String> list = Arrays.asList("1", "2", "3", "4"); Stream<String> stream = list.stream(); long count = stream.count(); System.out.println(count);
}
(3)、distinct():对集合去重
@Test
public void testStreamAPIDistinct(){ List<String> list = Arrays.asList("1", "2", "2", "3", "3", "4", "4"); Stream<String> stream = list.stream(); Stream<String> distinct = stream.distinct(); distinct.forEach(System.out::println);
}
(4)、map():对集合元素进行中间操作 peek():根据官方文档解释,用于调试使用,不建议使用
@Test
public void testStreamAPIMap(){ List<String> list = Arrays.asList("1", "2", "2", "3", "3", "4", "4"); Stream<String> stream = list.stream(); Stream<Integer> map = stream.map(str -> str.concat("0")).map(Integer::parseInt); map.forEach(System.out::println); Stream<String> stream1 = list.stream(); Stream<String> peek = stream1.peek(str -> str.concat("0")).peek(Integer::parseInt); peek.forEach(System.out::println);
}
(5)、filter():对集合元素进行进行过滤,根据过滤条件过滤复合要求的数据 anyMatch():对集合元素进行进行匹配,只要有一个元素符合条件就为true allMatch():对集合元素进行进行匹配,全部元素都符合条件就为true
@Test
public void testStreamAPIFilter(){ List<Integer> list = Arrays.asList(1, 2, 3, 4); Stream<Integer> stream = list.stream(); Stream<Integer> filter = stream.filter(i -> i > 2); filter.forEach(System.out::println); Stream<Integer> stream1 = list.stream(); boolean anyMatch = stream1.anyMatch(i -> i == 2); System.out.println("能匹配到一个2?:" + anyMatch); Stream<Integer> stream2 = list.stream(); boolean allMatch = stream2.allMatch(i -> i == 2); System.out.println("全部元素都是2?:" + allMatch); Stream<Integer> stream3 = list.stream(); boolean noneMatch = stream3.noneMatch(i -> i == 2); System.out.println("全部元素中没有一个是2?:" + noneMatch);
}
(6)、reduce():对集合元素进行进行求和或者归并处理
@Test
public void testStreamAPIReduce(){ List<Integer> list = Arrays.asList(1, 2, 3, 4); Stream<Integer> stream = list.stream(); // 不指定初始值,返回Optional对象 Integer reduce = stream.reduce(Integer::sum).get(); System.out.println("求和:" + reduce); Stream<Integer> stream1 = list.stream(); // 指定初始值为0,当Stream流为空时就返回0,不为空就返回结果。 Integer reduce1 = stream1.reduce(0, Integer::sum); System.out.println("求和:" + reduce1); Stream<Integer> stream2 = list.stream(); // 指定初始值为0,并且将整个过程分成多个子流去处理,并且最后归并多个子流处理的结果(归并操作),当Stream流为空时就返回0,不为空就返回结果。 Integer reduce2 = stream2.reduce(0, (a, b) -> a + b, Integer::sum); System.out.println("求和:" + reduce2); Stream<String> stream3 = list.stream().map(i -> i.toString()); // 指定初始值为0,并且将整个过程分成多个子流去处理,并且最后归并多个子流处理的结果(归并操作),当Stream流为空时就返回0,不为空就返回结果。 String reduce3 = stream3.reduce("", (a, b) -> a.concat(b)); System.out.println("字符串拼接:" + reduce3); Stream<String> stream4 = list.stream().map(i -> i.toString()); String reduce4 = stream4.reduce("", String::concat); System.out.println("字符串拼接:" + reduce4);
}
(7)、skip():跳过集合中某几个元素 limit():一次获取几个元素,两个结合类似于mysql分页操作
@Test
public void testStreamAPISkipAndLimit(){ List<Integer> list = Arrays.asList(1, 2, 3, 4); Stream<Integer> stream = list.stream(); Stream<Integer> skip = stream.skip(2); skip.forEach(System.out::println); Stream<Integer> stream1 = list.stream(); Stream<Integer> limit = stream1.limit(1); limit.forEach(System.out::println);
}
(8)、sorted():对集合中元素进行排序,接受一个Comparator
@Test
public void testStreamAPISkip(){ List<Integer> list = Arrays.asList(1, 2, 3, 4); Stream<Integer> stream = list.stream(); Stream<Integer> sorted = stream.sorted(); sorted.forEach(System.out::println); Stream<Integer> stream1 = list.stream(); Stream<Integer> sorted1 = stream1.sorted((o1, o2) -> o2 - o1); sorted1.forEach(System.out::println);
}
(9)、mapToInt():将集合中元素转化成一个IntStream mapToDouble():将集合中元素转化成一个DoubleStream mapToLong():将集合中元素转化成一个LongStream
@Test
public void testStreamAPIMapTo(){ List<String> list = Arrays.asList("1", "2", "3", "4"); Stream<String> stream = list.stream(); IntStream mapToInt = stream.mapToInt(str -> Integer.parseInt(str.concat("0"))); mapToInt.forEach(System.out::println); Stream<String> stream1 = list.stream(); DoubleStream mapToDouble = stream1.mapToDouble(str -> Double.parseDouble(str.concat("0"))); mapToDouble.forEach(System.out::println); Stream<String> stream2 = list.stream(); LongStream mapToLong = stream2.mapToLong(str -> Long.parseLong(str.concat("0"))); mapToLong.forEach(System.out::println);
}
(10)、flatMapToInt():将集合中元素转化成一个IntStream flatMapToDouble():将集合中元素转化成一个DoubleStream flatMapToLong():将集合中元素转化成一个LongStream
@Test
public void testStreamAPIFlatMapTo(){ List<String> list = Arrays.asList("1", "2", "3", "4"); Stream<String> stream = list.stream(); IntStream flatMapToInt = stream.flatMapToInt(str -> IntStream.of(Integer.parseInt(str.concat("0")))); flatMapToInt.forEach(System.out::println); Stream<String> stream1 = list.stream(); DoubleStream flatMapToDouble = stream1.flatMapToDouble(str -> DoubleStream.of(Double.parseDouble(str.concat("0")))); flatMapToDouble.forEach(System.out::println); Stream<String> stream2 = list.stream(); LongStream flatMapToLong = stream2.flatMapToLong(str -> LongStream.of(Long.parseLong(str.concat("0")))); flatMapToLong.forEach(System.out::println);
}
(11)、Collectors.toList():将集合中元素转化成一个List Collectors.toSet():将集合中元素转化成一个Set Collectors.joining():将集合中每一个元素拼接一个字符串 Collectors.toMap:将集合元素按照给定的规则转化成一个Map Collectors.groupingBy:将集合元素按照给定的规则分组
@Test
public void testStreamAPICollect(){ List<String> list = Arrays.asList("1", "2", "3", "4", "4"); Stream<String> stream = list.stream(); List<String> collectToList = stream.collect(Collectors.toList()); collectToList.forEach(System.out::println); Stream<String> stream1 = list.stream(); Set<String> collectToSet = stream1.collect(Collectors.toSet()); collectToSet.forEach(System.out::println); Stream<String> stream2 = list.stream(); String collectJoining = stream2.collect(Collectors.joining("a")); System.out.println(collectJoining); Stream<String> stream3 = list.stream(); // 第一个参数:key的生成规则 第二个参数:value的生成规则 第三个参数(可选):当key重复时,value的处理规则 Map<Object, Object> collectToMap = stream3.collect(Collectors.toMap(str -> str + "a", str -> str, (o, n) -> n)); System.out.println(collectToMap); Stream<String> stream4 = list.stream(); Map<Object, List<String>> collectGroupingBy = stream4.collect(Collectors.groupingBy(str -> str)); System.out.println(collectGroupingBy);
}
(12)、stream流综合测试
@Test
public void test(){ User user1 = new User("name1", 18, "dep1", "女"); User user2 = new User("name2", 15, "dep2", "男"); User user3 = new User("name3", 28, "dep2", "女"); User user4 = new User("name4", 30, "dep4", "男"); User user5 = new User("name5", 20, "dep1", "男"); User user6 = new User("name6", 20, "dep1", "女"); User user7 = new User("name6", 20, "dep1", "女"); User user8 = new User("name8", 20, "dep1", "男"); List<User> list = Arrays.asList(user1, user2, user3, user4, user5, user6, user7, user8); Stream<User> stream = list.stream(); Map<String, List<User>> test = stream .distinct() // 去重 .filter(user -> "女".equals(user.getSex())) // 过滤,性别为女的 .peek(user -> user.setSalary(2000 * 0.3 + 6000)) // 设置薪资属性的值 .sorted((u1, u2) -> u2.getAge() - u1.getAge()) // 按照年龄从小到大排序 .limit(5) // 分页,一次只取5条数据 .collect(Collectors.groupingBy(User::getDep)); // 按照部门分组 System.out.println(test);
} /** * 用户测试类 */
@Data
class User { /** * 姓名 */ private String name; /** * 年龄 */ private int age; /** * 薪资 */ private Double salary; /** * 部门 */ private String dep; /** * 性别 */ private String sex; /** * 有参构造函数 * * @param name 姓名 * @param age 年龄 * @param dep 部门 * @param sex 性别 */ public User(String name, int age, String dep, String sex) { this.name = name; this.age = age; this.dep = dep; this.sex = sex; } @Override public String toString() { return "User{" + "name='" + name + '\'' + ", age=" + age + ", dep='" + dep + '\'' + ", sex='" + sex + '\'' + ", salary='" + salary + '\'' + '}'; }
}
总结
1、lambda表达式可以简化匿名内部类的写法,让编码更加便捷。
2、Stream流提供了一系列对容器或者集合API操作,提供了一系列的开箱即用的聚合操作函数。