一、Lambda 表達式
Lambda 表達式,也可稱為閉包,它是 Java 8 發(fā)布的最重要新特性。Lambda 允許把函數(shù)作為一個方法的參數(shù)(函數(shù)作為參數(shù)傳遞進方法中)。使用 Lambda 表達式可以使代碼變的更加簡潔緊湊。
基本語法:
(parameters) -> expression 或 (parameters) -> { statements; }
例如:
// 無參數(shù),返回值為 5
() -> 5
// 接收一個參數(shù)(數(shù)字類型),返回其2倍值
x -> 2 * x
// 接受2個參數(shù)(數(shù)字),并返回他們的差
(x, y) -> x – y
// 接收2個int型整數(shù),返回他們的和
(int x, int y) -> x + y
// 接受一個 String 對象,在控制臺打印,不返回任何值
(String s) -> System.out.print(s)
特點:
- 可選類型聲明:不需要聲明參數(shù)類型,編譯器可以統(tǒng)一識別參數(shù)值。
- 可選的參數(shù)圓括號:一個參數(shù)無需定義圓括號,但多個參數(shù)需要定義圓括號。
- 可選的大括號:如果主體包含了一個語句,就不需要使用大括號。
- 可選的返回關(guān)鍵字:如果主體只有一個表達式返回值則編譯器會自動返回值,大括號需要指定明表達式返回了一個數(shù)值。
下面舉幾個例子,先準(zhǔn)備數(shù)據(jù):
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));
/**
* ========結(jié)果========
* 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 轉(zhuǎn) list
List<Person> collect = personStream.collect(Collectors.toList());
// print 打印
collect.forEach(e -> System.out.println(e));
當(dāng)需要通過 多個過濾條件對集合進行過濾時,可以通過調(diào)用多次 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);
/**
* ========結(jié)果========
* 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);
/**
* ========結(jié)果========
* Person{name='wang', age=19}
* Person{name='li', age=22}
*/
3、limit 限制結(jié)果集的數(shù)據(jù)量
limit 可以控制結(jié)果集返回的數(shù)據(jù)條數(shù)
// age > 19 的數(shù)據(jù)有3條,通過limit只返回2條
personList.stream().limit(2).filter(e -> e.getAge() > 19).forEach(System.out::println);
/**
* ========結(jié)果========
* Person{name='zhang', age=20}
* Person{name='liu', age=21}
*/
4、sorted 排序
通過 sorted,可以按自定義的規(guī)則,對數(shù)據(jù)進行排序
// 按照 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);
/**
* ========結(jié)果========
* 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 獲取結(jié)果中 某個值最大最小的的對象
max、min 可以按指定的條件,獲取到最大、最小的對象,當(dāng)集合里有多個滿足條件的最大最小值時,只會返回一個對象。
// 獲取 age 最大的對象
Person person = personList.stream().max(Comparator.comparing(Person::getAge)).get();
System.out.println(person); // Person{name='li', age=22}
// 獲取數(shù)組中最大/小的數(shù)字
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 集合轉(zhuǎn)換
map 對集合中的每個元素進行遍歷,并且可以對遍歷的元素進行操作,轉(zhuǎn)化為其他對象。
// 例1:把 Integer 數(shù)組轉(zhuǎn)為 String 數(shù)組
List<Integer> num = Arrays.asList(19, 20, 21);
List<String> dollar = num.stream().map(e -> "$" + e).collect(Collectors.toList());
dollar.forEach(System.out::println);
/**
* ========結(jié)果========
* $19
* $20
* $21
*/
// 例2:由數(shù)組構(gòu)建對象
List<String> strings = Arrays.asList("zhang 20", "liu 21", "wang 19", "li 22");
// 先把數(shù)組中每個元素用空格切分,然后將切分后的第0個元素傳入構(gòu)造方法的name,第1個元素傳入構(gòu)造方法的age
strings.stream().map(e -> e.split(" ")).map(e -> new Person(e[0], Integer.valueOf(e[1]))).forEach(System.out::println);
/**
* ========結(jié)果========
* Person{name='zhang', age=20}
* Person{name='liu', age=21}
* Person{name='wang', age=19}
* Person{name='li', age=22}
*/
7、reduce 聚合函數(shù)
reduce 也是對集合中所有值進行操作,但它是將所有值,按照傳入的處理邏輯,將結(jié)果處理合并為一個
如:將集合中的所有整數(shù)相加,并返回其總和
// 將所有元素加起來求和
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("個數(shù): " + statistics.getCount()); // 4
二、函數(shù)式接口
函數(shù)式接口(Functional Interface)是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。
函數(shù)式接口可以被隱式轉(zhuǎn)換為 Lambda 表達式。最常見的函數(shù)式接口可能就是 Runnable 接口了
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
特點:
- 接口有且僅有一個抽象方法
- 允許定義靜態(tài)方法
- 允許定義默認方法
- 允許 java.lang.Object 中的 public 方法
-
@FunctionalInterface注解不是必須的,如果一個接口符合"函數(shù)式接口"定義,那么加不加該注解都沒有影響。加上該注解能夠更好地讓編譯器進行檢查。如果編寫的不是函數(shù)式接口,但是加上了@FunctionInterface那么編譯器會報錯
例如,啟動一條線程,舊寫法如下:
new Thread((new Runnable() {
@Override
public void run() {
Thread thread = Thread.currentThread();
System.out.println("線程: " + thread.getName());
}
})).start();
但是可以使用 lambda 表達式和函數(shù)式接口組合方式來簡化代碼:
new Thread((() -> {
Thread thread = Thread.currentThread();
System.out.println("線程: " + thread.getName());
})).start();
三、單詞統(tǒng)計
說了這么多,來點實戰(zhàn)的例子。
在 Spark 中的入門例子就是單詞統(tǒng)計了。這個例子很好的說明了 Lambda 表達式與函數(shù)式接口有多么方便。
首先,新建一個 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) {
// 創(chuàng)建Spark配置對象
SparkConf conf = new SparkConf().setAppName("name").setMaster("local");
// 通過conf創(chuàng)建上下文
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) {
// 創(chuàng)建Spark配置對象
SparkConf conf = new SparkConf().setAppName("name").setMaster("local");
// 通過conf創(chuàng)建上下文
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); // 收集并打印
}