1 簡介
Lambda表達(dá)式是java8提供的新特性,是一種匿名函數(shù)芋绸,也是函數(shù)式接口實(shí)現(xiàn)的快捷方式,類似js中的閉包,它允許把函數(shù)當(dāng)做參數(shù)來使用,是面向函數(shù)式編程的思想证杭,Lambda的格式為: (參數(shù)) -> {方法體}送讲,具有如下特征:
- 可選類型聲明:不需要聲明參數(shù)類型揭保,編譯器可以統(tǒng)一識別參數(shù)值;
- 可選的參數(shù)圓括號:一個參數(shù)無需定義圓括號魄宏,但多個參數(shù)需要定義圓括號秸侣;
- 可選的大括號:如果主體包含了一個語句,就不需要使用大括號;
-
可選的返回關(guān)鍵字:如果主體只有一個表達(dá)式返回值則編譯器會自動返回值味榛,大括號需要指定明表達(dá)式返回了一個數(shù)值椭坚。
例子:
// 1 無參數(shù)的lambda表達(dá)式,返回值5
() -> 5
// 2 一個數(shù)值類型的參數(shù)搏色,返回其2倍的值
x -> 2 * x
// 3 兩個數(shù)值類型的參數(shù)善茎,返回差值
(x, y) -> x – y
// 4 string類型的參數(shù),在控制臺打印频轿,無返回類型
(String s) -> System.out.print(s)
java是一個面向?qū)ο蟮恼Z言垂涯,而Lambda表達(dá)式卻是一個匿名函數(shù),因此java把Lambda表達(dá)式抽象成一個匿名內(nèi)部類航邢,并沒有破壞面向?qū)ο蟮奶匦浴?/strong>
2 函數(shù)式接口
java8中通過注解@FunctionalInterface標(biāo)明接口為一個函數(shù)式接口耕赘,函數(shù)式接口是只包含一個抽象方法聲明的接口,但是允許有默認(rèn)實(shí)現(xiàn)的方法(比如: java.util.function.BinaryOperator)膳殷。如果定義了多個抽象方法操骡,編譯器會報(bào)錯,如下圖赚窃。所以册招,當(dāng)我們的代碼中定義了函數(shù)式接口的時(shí)候,要記得加上@FunctionalInterface注解勒极,防止被其他人增加額外的方法而破壞函數(shù)式接口的規(guī)范是掰。
java.lang.Runnable 就是一種函數(shù)式接口,每個 Lambda 表達(dá)式都能隱式地賦值給函數(shù)式接口河质,例如冀惭,我們可以通過 Lambda 表達(dá)式創(chuàng)建 Runnable 接口的引用:
Runnable r = () -> System.out.println("hello world");
當(dāng)不指明函數(shù)式接口時(shí),編譯器會自動進(jìn)行類型推斷掀鹅,如下面代碼,編譯器會自動根據(jù)線程類的構(gòu)造函數(shù)簽名 public Thread(Runnable r) { }媒楼,將該 Lambda 表達(dá)式賦給 Runnable 接口:
new Thread(
() -> System.out.println("hello world")
).start();
下表列舉了一些常用的函數(shù)式接口
接口 | 參數(shù) | 返回值 | 示例 |
---|---|---|---|
Consumer<T> | T | void | Consumer<Integer> c = (int x) -> { System.out.println(x) }; |
Supplier<T> | None | T | Supplier<Person> supplier = Person::new; |
Function<T,R> | T | R | Function<String, Integer> fuc = Integer::parseInt; |
BiFunction<T, R, U> | T, R | U | BiFunction<Integer, Integer, Integer> fuc = (x, y) -> x + y; |
Predicate<T> | T | boolean | Predicate<Person> p = (Person person) -> { "zhangsan".equals(person.getName()) }; |
3 Lambda表達(dá)式的作用
Lambda表達(dá)式可以讓代碼更加簡練乐尊,具有可讀性。
舉個栗子划址,這個例子大家都在舉扔嵌,當(dāng)創(chuàng)建新線程時(shí),不使用lambda的情況下:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("為了這一行有用的代碼夺颤,寫了那么多多余的行:( ");
}
}).start();
使用lambda表達(dá)式:
new Thread( () -> System.out.println("一起搖擺~~ ") ).start();
4 配合Stream讓代碼整潔到飛起
Stream是Java 8 API添加的一個新的抽象痢缎,稱為流Stream,可以讓我們以一種聲明的方式處理數(shù)據(jù)世澜。Stream類似SQL的存儲過程独旷,提供了一種對 Java 集合運(yùn)算和表達(dá)的高階抽象。
Stream的構(gòu)成
當(dāng)我們使用一個流的時(shí)候,通常包括三個步驟:
- 獲取一個數(shù)據(jù)源(source)嵌洼,可以是集合案疲,數(shù)組;
// method 1
Stream<String> stream1 = Stream.of("1", "2", "3");
// method 2
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
Stream<String> stream2 = list.stream();
// method 3
Stream<String> stream3 = list.parallelStream();
// method 4
Stream<String> stream4 = Arrays.stream(new String[]{"1", "2"});
// method 5
IntStream stream5 = IntStream.range(0, 100);
- 數(shù)據(jù)轉(zhuǎn)換麻养;
- 執(zhí)行操作獲取想要的結(jié)果褐啡。
流的操作
- 中間操作:map (mapToInt, flatMap 等)、 filter鳖昌、 distinct备畦、 sorted、 peek许昨、 limit萍恕、 skip,操作都會返回流對象本身车要。 這樣多個操作可以串聯(lián)成一個管道允粤, 如同流式風(fēng)格(fluent style)
- 最終操作:forEach、 forEachOrdered翼岁、 toArray类垫、 reduce、 collect琅坡、 min悉患、 max、 count榆俺、 anyMatch售躁、 allMatch、 noneMatch茴晋、 findFirst陪捷、 findAny、 iterator
使用
1 forEach(Consumer<T> action)
public static void main(String[] args) {
// 輸出Stream中的元素
Stream.of("1","2","3","4","5").forEach(e->System.out.println(e));
}
2 map(Function<T, R> mapper)
public static void main(String[] args) {
Stream.of("1","2","3","4","5")
.map(Integer::parseInt) //轉(zhuǎn)成int
.forEach(System.out::println);
}
3 flatMap(Function<T, Stream<R>> mapper): 會將每一個輸入對象輸入映射為一個新集合诺擅,然后把這些新集合連成一個大集合
public static void main(String[] args) {
Stream.of("a-b-c-d","e-f-g-h")
.flatMap(e->Stream.of(e.split("-")))
.forEach(e->System.out.print(e));
}
// abcdefgh
4 limit(long maxSize)
public static void main(String[] args) {
Stream.of(1,2,3,4,5,6)
.limit(3)
.forEach(e->System.out.println(e));
}
// 輸出前三個 1市袖,2,3
5 distinct()
Stream.of(1,1,2,3)
.distinct() //去重
.forEach(e->System.out.println(e));
// 1 2 3
6 filter(Predicate<T> predicate)
// 可以用and()烁涌、or()邏輯函數(shù)來合并Predicate
Predicate<String> p1 = (n) -> n.startsWith("chang"); //Predicate: 驗(yàn)證傳進(jìn)來的參數(shù)符不符合規(guī)則, 返回true|false
Predicate<String> p2 = (n) -> n.length() == 5;
names.stream()
.filter(p1.and(p2))
.forEach((n) -> System.out.print(n));
7 forEachOrdered(Predicate<T> predicate): 適用用于并行流的情況下進(jìn)行迭代苍碟,能保證迭代的有序性
Stream.of(0,1,2,3,4,5,6,7,8,9)
.parallel()
.forEachOrdered(e->{
System.out.println(Thread.currentThread().getName()+": "+e);});
8 Collectors: 將流轉(zhuǎn)換成集合和聚合元素。Collectors 可用于返回列表或字符串
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("篩選列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
// 篩選列表: [abc, bc, efg, abcd, jkl]
// 合并字符串: abc, bc, efg, abcd, jkl
List<Person> personList = Lists.newArrayList();
// 同名分組
Map<String, List<Person>> map = personList.stream().collect(Collectors.groupingBy(Person::getName));
// 根據(jù)唯一id映射, 保證Person::getId是唯一的撮执,否則報(bào)java.lang.IllegalStateException
Map<Integer, Person> map = personList.stream().collect(Collectors.toMap(Person::getId, p -> p));
9 更多例子
String k = "key";
HashMap<String, Integer> map = new HashMap<>() ;
map.put(k, 1);
map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal);
// 等同如下代碼
String k = "key";
HashMap<String, Integer> map = new HashMap<>() ;
map.put(k, 1);
int newVal = 2;
if(map.containsKey(k)) {
map.put(k, map.get(k) + newVal);
} else {
map.put(k, newVal);
}
復(fù)合lambda表達(dá)式
1 比較器復(fù)合
可以使用靜態(tài)方法Comparator.comparing定義比較器微峰,根據(jù)提取用于比較的鍵值的Function來返回一個Comparator,
如: Comparator<Apple> cmp = Comparator.comparing(Apple::getWeight)抒钱,該表達(dá)式表示比較蘋果的重量蜓肆,如果想對蘋果按重量遞減排序颜凯,此時(shí)無需重新定義比較器,接口有一個默認(rèn)方法reversed可以使給定的比較器逆序症杏。因此仍然用開始的那個比較器装获,只要簡單修改修改一下前一個例子就可以對蘋果按重量遞減排序: appleList.sort(cmp.reversed());
2 謂詞復(fù)合
謂詞接口包括三個方法:negate、and和or厉颤,讓我們可以重用已有的Predicate來創(chuàng)建更復(fù)雜的謂詞穴豫,如4.3.6中的filter方法
3 函數(shù)復(fù)合
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g); // andThen方法會返回一個函數(shù),它先對輸入應(yīng)用一個給定函數(shù)逼友,再對輸出應(yīng)用另一個函數(shù)
int result = h.apply(1); // 返回結(jié)果為4
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g); // compose方法精肃,先把給定的函數(shù)用作compose的參數(shù)里面給的那個函數(shù),然后再把函數(shù)本身用于結(jié)果
int result = h.apply(1); // 返回結(jié)果為3
總結(jié)
- stream只能遍歷一次帜乞,遍歷完之后司抱,這個流已經(jīng)被消費(fèi)掉了。 可以從原始數(shù)據(jù)源那里再獲得一個新的流來重新遍歷一遍黎烈,重復(fù)遍歷會報(bào)IllegalStateException
List<String> title = Arrays.asList("1", "2", "3");
Stream<String> s = title.stream();
s.forEach(System.out::println);
s.forEach(System.out::println); // Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
- 簡潔的lambda可以增加代碼可讀性习柠,但這簡歷在團(tuán)隊(duì)成員對lambda都比較熟悉的基礎(chǔ)上,如果寫了太復(fù)雜的表達(dá)式照棋,其他同學(xué)維護(hù)和理解起來也會增加相應(yīng)的成本资溃。