美文网首页
Java8 之 Lambda 表达式与函数式接口

Java8 之 Lambda 表达式与函数式接口

作者: 小道萧兮 | 来源:发表于2020-01-10 11:29 被阅读0次

    一、Lambda 表达式

    Lambda 表达式,也可称为闭包,它是 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。

    基本语法:
    (parameters) -> expression 或 (parameters) -> { statements; }

    例如:

    // 无参数,返回值为 5  
    () -> 5  
      
    // 接收一个参数(数字类型),返回其2倍值  
    x -> 2 * x  
      
    // 接受2个参数(数字),并返回他们的差
    (x, y) -> x – y
    
    // 接收2个int型整数,返回他们的和
    (int x, int y) -> x + y
    
    // 接受一个 String 对象,在控制台打印,不返回任何值
    (String s) -> System.out.print(s)
    

    特点:

    • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
    • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
    • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
    • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。

    下面举几个例子,先准备数据:

    public class Person {
        private String name;
        private Integer age;
    
        public Person(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
        public List<Person> getPersonList() {
            return Arrays.asList(new Person("zhang", 20),new Person("liu", 21), new Person("wang", 19),new Person("li", 22));
        }
    }
    

    1、forEach 遍历集合
    使用 forEach 方法,直接通过一行代码即可完成对集合的遍历:

    List<Person> personList = getPersonList();
    personList.forEach(e -> System.out.println(e));
    
    /**
    * ========结果========
    * Person{name='zhang', age=20}
    * Person{name='liu', age=21}
    * Person{name='wang', age=19}
    * Person{name='li', age=22}
    */
    

    2、filter 对集合进行过滤

    // 过滤 age > 20 的对象
    Stream<Person> personStream = personList.stream().filter(e -> e.getAge() > 20);
    // stream 转 list
    List<Person> collect = personStream.collect(Collectors.toList());
    // print 打印
    collect.forEach(e -> System.out.println(e));
    

    当需要通过 多个过滤条件对集合进行过滤时,可以通过调用多次 filter 通过传入不同的 Predicate 对象来进行过滤

    Predicate<Person> ilter1 = e -> e.getAge() > 19;
    Predicate<Person> filter2 = e -> e.getAge() < 22;
    // 过滤 age > 19 并且 age < 22 的对象
    personList.stream().filter(filter1).filter(filter2).forEach(System.out::println);
    
    /**
    * ========结果========
    * Person{name='zhang', age=20}
    * Person{name='liu', age=21}
    */
    

    也可以通过 Predicate 对象的 andor 方法,对多个Predicate 对象进行过滤。

    Predicate<Person> filter1 = e -> e.getAge() > 20;
    Predicate<Person> filter2 = e -> e.getName().startsWith("l");
    // 过滤 age < 20 或者 name 以 l 开头的对象
    personList.stream().filter(filter1.or(filter2)).forEach(System.out::println);
    
    /**
    * ========结果========
    * Person{name='wang', age=19}
    * Person{name='li', age=22}
    */
    

    3、limit 限制结果集的数据量
    limit 可以控制结果集返回的数据条数

    // age > 19 的数据有3条,通过limit只返回2条
    personList.stream().limit(2).filter(e -> e.getAge() > 19).forEach(System.out::println);
    
    /**
    * ========结果========
    * Person{name='zhang', age=20}
    * Person{name='liu', age=21}
    */
    

    4、sorted 排序
    通过 sorted,可以按自定义的规则,对数据进行排序

    // 按照 age 排序
    personList.stream().sorted((e1, e2) -> e1.getAge() - e2.getAge()).forEach(System.out::println);
    System.out.println("---------------");
    // 按照 name 排序
    personList.stream().sorted((e1, e2) -> e1.getName().compareTo(e2.getName())).forEach(System.out::println);
    
    /**
    * ========结果========
    * Person{name='wang', age=19}
    * Person{name='zhang', age=20}
    * Person{name='liu', age=21}
    * Person{name='li', age=22}
    * ---------------
    * Person{name='li', age=22}
    * Person{name='liu', age=21}
    * Person{name='wang', age=19}
    * Person{name='zhang', age=20}
    */
    

    5、max、min 获取结果中 某个值最大最小的的对象
    maxmin 可以按指定的条件,获取到最大、最小的对象,当集合里有多个满足条件的最大最小值时,只会返回一个对象。

    // 获取 age 最大的对象
    Person person = personList.stream().max(Comparator.comparing(Person::getAge)).get();
    System.out.println(person); // Person{name='li', age=22}
    
    // 获取数组中最大/小的数字
    int a = Stream.of(2,1,4,5,3).max(Integer::compare).get(); // 5
    int b = Stream.of(2,1,4,5,3).min(Integer::compare).get(); // 1
    

    6、map 集合转换
    map 对集合中的每个元素进行遍历,并且可以对遍历的元素进行操作,转化为其他对象。

    // 例1:把 Integer 数组转为 String 数组
    List<Integer> num = Arrays.asList(19, 20, 21);
    List<String> dollar = num.stream().map(e -> "$" + e).collect(Collectors.toList());
    dollar.forEach(System.out::println);
    /**
    * ========结果========
    * $19
    * $20
    * $21
    */
    
    // 例2:由数组构建对象
    List<String> strings = Arrays.asList("zhang 20", "liu 21", "wang 19", "li 22");
    // 先把数组中每个元素用空格切分,然后将切分后的第0个元素传入构造方法的name,第1个元素传入构造方法的age
    strings.stream().map(e -> e.split(" ")).map(e -> new Person(e[0], Integer.valueOf(e[1]))).forEach(System.out::println);
    /**
    * ========结果========
    * Person{name='zhang', age=20}
    * Person{name='liu', age=21}
    * Person{name='wang', age=19}
    * Person{name='li', age=22}
    */
    

    7、reduce 聚合函数
    reduce 也是对集合中所有值进行操作,但它是将所有值,按照传入的处理逻辑,将结果处理合并为一个

    如:将集合中的所有整数相加,并返回其总和

    // 将所有元素加起来求和
    List<Integer> nums = Arrays.asList(2, 5, 3, 4, 7);
    int num1 = nums.stream().reduce((e1, e2) -> e1 + e2); // 21
            
    // 带初始值的计算
    int num2 = nums.stream().reduce(10, (e1, e2) -> e1 + e2); // 31
    

    8、summaryStatistics 计算集合元素的最大、最小、平均等值

    IntSummaryStatistics statistics= personList.stream().mapToInt(e -> e.getAge()).summaryStatistics();
    System.out.println("最大值: " + statistics.getMax()); // 22
    System.out.println("最小值: " + statistics.getMin()); // 19
    System.out.println("平均值: " + statistics.getAverage()); // 20.5
    System.out.println("总和: " + statistics.getSum()); // 82
    System.out.println("个数: " + statistics.getCount()); // 4
    

    二、函数式接口

    函数式接口(Functional Interface)是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口

    函数式接口可以被隐式转换为 Lambda 表达式。最常见的函数式接口可能就是 Runnable 接口了

    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    

    特点:

    • 接口有且仅有一个抽象方法
    • 允许定义静态方法
    • 允许定义默认方法
    • 允许 java.lang.Object 中的 public 方法
    • @FunctionalInterface 注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface 那么编译器会报错

    例如,启动一条线程,旧写法如下:

    new Thread((new Runnable() {
        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            System.out.println("线程: " + thread.getName());
        }
    })).start();
    

    但是可以使用 lambda 表达式和函数式接口组合方式来简化代码:

    new Thread((() -> {
        Thread thread = Thread.currentThread();
        System.out.println("线程: " + thread.getName());
    })).start();
    

    三、单词统计

    说了这么多,来点实战的例子。

    在 Spark 中的入门例子就是单词统计了。这个例子很好的说明了 Lambda 表达式与函数式接口有多么方便。

    首先,新建一个 Maven 项目,在 pom 文件中添加 Spark 依赖。

    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_2.12</artifactId>
        <version>2.4.4</version>
    </dependency>
    

    不使用 Lambda 表达式时,代码如下:

    public static void main(String[] args) {
        // 创建Spark配置对象
        SparkConf conf = new SparkConf().setAppName("name").setMaster("local");
        // 通过conf创建上下文
        JavaSparkContext sc = new JavaSparkContext(conf);
        // 加载文件
        JavaRDD<String> rdd1 = sc.textFile("/Users/terry-jri/Desktop/test2.txt");
        // 压扁
        JavaRDD<String> rdd2 = rdd1.flatMap(new FlatMapFunction<String, String>() {
            @Override
            public Iterator<String> call(String s) {
                String[] arr = s.split(" ");
                List<String> list = new ArrayList<>(Arrays.asList(arr));
                return list.iterator();
            }
        });
        // 映射
        JavaPairRDD<String, Integer> rdd3 = rdd2.mapToPair(new PairFunction<String, String, Integer>() {
            @Override
            public Tuple2<String, Integer> call(String s) {
                return new Tuple2<>(s, 1);
            }
        });
        // 聚合
        JavaPairRDD<String, Integer> rdd4 = rdd3.reduceByKey(new Function2<Integer, Integer, Integer>() {
            @Override
            public Integer call(Integer v1, Integer v2) {
                return v1 + v2;
            }
        });
        // 收集,打印输出
        for (Object o : rdd4.collect()) {
            System.out.println(o);
        }
    }
    

    使用 Lambda 表达式如下:

    public static void main(String[] args) {
        // 创建Spark配置对象
        SparkConf conf = new SparkConf().setAppName("name").setMaster("local");
        // 通过conf创建上下文
        JavaSparkContext sc = new JavaSparkContext(conf);
    
        JavaPairRDD<String, Integer> rdd = sc.textFile("/Users/terry-jri/Desktop/test2.txt") // 加载文件
                .flatMap(line -> Arrays.asList(line.split(" ")).iterator()) // 压扁
                .mapToPair(s -> new Tuple2<>(s, 1)) // 映射
                .reduceByKey((v1, v2) -> (v1 + v2)); // 聚合
    
        rdd.collect().forEach(System.out::println); // 收集并打印
    }
    

    相关文章

      网友评论

          本文标题:Java8 之 Lambda 表达式与函数式接口

          本文链接:https://www.haomeiwen.com/subject/raeuactx.html