一、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 对象的 and
、or
方法,对多个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 获取结果中 某个值最大最小的的对象
max
、min
可以按指定的条件,获取到最大、最小的对象,当集合里有多个满足条件的最大最小值时,只会返回一个对象。
// 获取 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); // 收集并打印
}
网友评论