天天看點

Java8新特性——lambda表達式什麼是lambda表達式?

什麼是lambda表達式?

Lambda 表達式是Java 8 的新特性,是一種新的程式設計文法。lambda語義簡潔明了,性能良好,是Java 8 的一大亮點。廢話不多說,我們來看個例子。

從内部類到lambda

lambda簡化了内部類的使用,說起内部類,我第一個想到的就是建立一個線程:

Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("hello anonymity class.");
    }
});
           

想必大家對這段代碼都不陌生,在jdk1.8之前,我們可以使用内部類便捷的建立一個線程,并指定執行内容,這裡列印了

"hello anonymity class."

。接着我們看下lambda的寫法:

是不是簡化了好多,從之前的“怎麼做”到現在的“做什麼”,我們不用再手動重寫run方法,隻需要寫這個Thread要做什麼事情就可以了。在Thread方法參數中,空括号 () 代表沒有參數,列印語句就是要執行的方法體,而 -> 則是分割參數與方法體的符号。

我們再來看個例子,選取檔案夾内的隐藏檔案:

File[] hiddenFiles = new File(".").listFiles(new FileFilter() {
    @Override
    public boolean accept(File pathname) {
        return pathname.isHidden();
    }
});
           

lambda:

這裡lambda表達式用到了類、方法和兩個冒号“::”。當我們已經有了isHidden方法時,就可以用Java8 的方法引用:: 文法把方法當做參數傳給

listFiles()

。這就叫做 函數式程式設計 ,即把方法(函數)當做參數來傳遞。

這時你可能會有疑問,這lambda表達式究竟是怎麼實作的呢?我們來跟一下代碼。

函數式接口

Thread類有很多個構造方法,如何确定

new Thread(() -> System.out.println("hello anonymity class."))

這個用的是哪個構造方法?

我們可以開發工具可以輕松定位到上面所調用的Thread的構造方法,是

public Thread(Runnable target) {
	init(null, target, "Thread-" + nextThreadNum(), 0);
}
           

一個入參為

Runnable

的方法。為什麼偏偏是這個,而不是

Thread(String name)

呢。這裡要引入一個概念,函數式接口。

函數式接口與普通接口的唯一不同點是多了

@FunctionalInterface

注解。标注這個接口可以用作函數式接口。

Runnable

就被标注為函數式接口,隻有一個抽象方法:

public abstract void run();

,入參為空,且無傳回。同樣的,

listFiles(FileFilter filter)

的參數

FileFilter

也是一個函數式接口,它也有一個方法:

boolean accept(File pathname);

入參為

File

對象,傳回一個

boolean

類型。

是以lambda表達式是以入參+傳回值類型來比對函數式接口的, "->"号左邊為入參(多個參數用括号包裹),右邊是與接口方法的傳回類型相同的方法體(多行用大括号包裹)。

JDK已經為我們提供了現成的函數式接口(當然我們也可以自己寫),在

java.util.function

包下,有了這些擴充,lambda才能展現它真正的魅力。

下面我們看一些執行個體

  1. 對清單疊代(Consumer)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 9, 8, 7, 6);
list.forEach(e -> System.out.print(e + " "));
//1 2 3 4 5 9 8 7 6 
           
  1. 對清單排序(Comparator)
//打亂順序
Collections.shuffle(list);
System.out.println(list);
//[2, 7, 5, 6, 9, 1, 8, 4, 3]
list.sort(Integer::compare);
System.out.println(list);
//[1, 2, 3, 4, 5, 6, 7, 8, 9]
           
  1. 過濾偶數(Predicate)

    Predicate有一個抽象方法

    boolean test(T t);

    入參為T,傳回boolean。我們可以用它來做過濾操作
Predicate<Integer> p = i -> i % 2 == 0;
List<Integer> result = predicateFilter(list, p);
System.out.println(result);
//[2, 4, 6, 8]

public static  <T> List<T> predicateFilter(List<T> list, Predicate<T> p) {
	List<T> result = new ArrayList<>();
	for (T t : list) {
		if (p.test(t)) {
			result.add(t);
		}
	}
	return result;
}
           
  1. 循環處理(Consumer)

    Consumer有一個抽象方法

    void accept(T t);

    入參為T,無傳回。
Consumer<Integer> c = System.out::println;
consumerForEach(list, c);

public static <T> void consumerForEach(List<T> list, Consumer<T> c) {
	for (T t : list) {
		c.accept(t);
	}
}
/*1
* 2
* 3
* 4
* 5
* 6
* 7
* 8
* 9
*/   
           
  1. 擷取商品的id(Function)

    Function有一個抽象方法

    R apply(T t);

    入參為T,傳回R類型,用處更加廣泛。
List<Product> productList = Arrays.asList(
        new Product(1L, "蕃茄"),
        new Product(2L, "綿白糖"),
        new Product(3L, "黃瓜"),
        new Product(4L, "大蒜")
);
List<Long> productIds = productFunction(productList, Product::getId);

public static <T, R> List<R> productFunction(List<T> list, Function<T, R> f) {
    List<R> result = new ArrayList<>();
    for (T t : list) {
        result.add(f.apply(t));
    }
    return result;
}
/*
* 4-大蒜
* 2-綿白糖
* 1-蕃茄
* 3-黃瓜
*/
           
  1. 根據商品的權重排序(Comparator)

    即便Comparator有多個抽象方法,lambda表達式一樣可以比對的到。

比對了

int compare(T o1, T o2);

方法

Comparator還重載了compare方法,入參是一個

Function<? super T, ? extends U>

public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }
           

是以我們還可以調用這個方法來實作排序

Java8中常用的函數式接口

函數式接口 函數描述符
Predicate<T> T -> boolean
Consumer<T> T -> void
Function<T,R> T -> R
Supplier<T> () -> T
UnaryOperator<T> T -> T
BinaryOperator<T> (T, T) -> T
BiPredicate<L, R> (L, R) -> boolean
BiConsumer<T, U> (T, U) -> void
BiFunction<T, U, R> (T, U) -> R

lambda及函數式接口的例子

使用案例 lambda的例子 對應的接口函數
布爾表達式 (List<String> list) -> list.empty() Predicate<List<String>>
建立對象 () -> new Product(1L, “蕃茄”) Supplier<Product>
消費對象 (Product p) -> System.out.println(a.getId()) Consumer<Product>
從一個對象中提取/選擇 (String s) -> s.length() Function<String, Integer>
合并兩個值 (int a, int b) -> a * b IntBinaryOperator
比較兩個對象 (Product p1, Product p2) -> p1.getSortWeight().compareTo(p2.getSortWeight()) Comparator<Product>或 BiFunction<Product, Product, Integer>

函數式資料處理——Stream API

流(stream)是Java API的新成員,它允許你以聲明性方式處理資料集合(通過查詢語句來表達,而不是臨時編寫一個實作)。就現在來說,你可以把它們看成周遊資料集的進階疊代器。此外,流還可以透明地并行處理,你無需寫任何多線程代碼了!

我們來看個例子體會一下。

同樣是提取商品id,現在我們不再需要調用

productFunction()

方法了。

  • stream()

    擷取

    productIdList

    的流
  • map()

    把流中的

    Product

    映射成

    Long

  • collect(Collectors.toList())

    從流中收集内容生成新的

    List

中間操作和終端操作

  • 在使用流時,會有兩步操作,

    map()

    對流進行操作,稱之為中間操作;

    collect()

    對流進行整合,稱之為終端操作。

    stream提供的操作方法:

    中間操作

操作 類型 傳回類型 操作參數 函數描述符 作用
filter 中間 Stream<T> Predicate<T> T -> boolean 篩選
map 中間 Stream<T> function<T,R> T -> R 映射
limit 中間 Stream<T> 數量限制
sorted 中間 Stream<T> Comparator<T> (T,T) -> int 排序
distinct 中間 Stream<T> 去重
flatMap 中間 Stream<R> Function<T, R> T -> Stream<R> 平鋪(展開)映射
peek 中間 Stream<T> Consumer T -> void 疊代
skip 中間 Stream<T> 跳過
builder 中間 Builder<T> Builder<T>
empty 中間 Stream<T> () -> Stream<T> 建立一個空流
of 中間 Stream<T> T -> Stream<T> 建立包含T的流
iterate 中間 Stream<T> UnaryOperator<T> (T, UnaryOperator<T>) -> Stream<T> 以一個初始值和疊代規則建立一個T流
generate 中間 Stream<T> Supplier<T> () -> Stream<T> 建立一個流
concat 中間 Stream<T> (Stream<T>, Stream<T>) -> Stream<T> 合并兩個流

終端操作

操作 類型 傳回類型 作用
forEach 終端 void 消費流中的每個元素并對其應用Lambda。這一操作傳回void
count 終端 long 傳回流中元素的個數。這一操作傳回long
collect 終端 R 把流歸約成一個集合,比如List、Map甚至是Integer。
toArray 終端 T[] 把流歸約成一個數組
reduce 終端 T 規約函數,以一個初始值開始,按照規則計算
min 終端 Optional<T> 擷取最小值
max 終端 Optional<T> 擷取最大值
anyMatch 終端 boolean 流中的任意一個對象滿足條件
allMatch 終端 boolean 流中的所有對象滿足條件
noneMatch 終端 boolean 流中的所有對象都不滿足條件
findFirst 終端 Optional<T> 傳回第一個對象
findAny 終端 Optional<T> 傳回任意一個對象

來一些具體的使用執行個體:

村頭的老王最近包地賺了些錢,開了一個小超市主要經營了下面這幾類商品:

/**
 * 商品種類
 */
enum GType {
    //水果
    FRUITS(1),
    //啤酒
    BEER(2),
    //香煙
    SMOKE(3),
    //肉類
    MEAT(4),
    //零食
    SNACKS(5),
    ;

    GType(int goodsType) { this.goodsType = goodsType; }
    
    private int goodsType;
    public int getGoodsType() { return goodsType; }
    public void setGoodsType(int goodsType) { this.goodsType = goodsType; }
}
           

然後老王進了一批貨

//商品
    class Goods {
        //名稱
        private String name;
        //種類
        private GType goodsType;
        //價格
        private Float price;
        //數量
        private Integer count;
    
        public Goods(String name, GType goodsType, Float price, Integer count) {
            this.name = name;
            this.goodsType = goodsType;
            this.price = price;
            this.count = count;
        }
        //setter and getter
	}
	
    //進貨
    private static List<Goods> stock() {
        return Arrays.asList(new Goods("apple", GType.FRUITS, 5.0F, 30),
                new Goods("banana", GType.FRUITS, 3.5F, 20),
                new Goods("orange", GType.FRUITS, 8.0F, 40),
                new Goods("bread", GType.FOODS, 3.9F, 15),
                new Goods("milk", GType.DRINK, 6.0F, 100),
                new Goods("grape", GType.FRUITS, null, 10),
                new Goods("beer", GType.DRINK, 8.0F, 100),
                new Goods("cookie", GType.FOODS, 2.6F, 300),
                new Goods("sugar", GType.FOODS, 1.0F, 150),
                new Goods("moutai", GType.DRINK, 2980.0F, 1));
    }
           

接着要面對各種購物需求

public static void main(String[] args) {
        List<Goods> goods = stock();

        System.out.println("有哪些商品?------------------");
        //都買哪些商品?
        String goodsNames = goods.stream()
                .map(Goods::getName)
                .collect(Collectors.joining(", "));
        System.out.println(goodsNames);
		//apple, banana, orange, bread, milk, grape, beer, cookie, sugar, moutai

        System.out.println("有幾類商品?------------------");
        //有幾類商品?
        long types = goods.stream()
                .map(Goods::getGoodsType)
                .count();
        System.out.println(types);
		//10
		
        System.out.println("有哪些水果?------------------");
        System.out.println("名稱-種類-價格-數量");
        //有哪些水果?
        goods.stream()
                .filter(e -> e.getGoodsType().equals(GType.FRUITS))
                .forEach(System.out::println);
		//名稱-種類-價格-數量
		//apple - 1 - 5.0 - 30
		//banana - 1 - 3.5 - 20
		//orange - 1 - 8.0 - 40
		//grape - 1 - 18.3 - 10

        System.out.println("最貴的商品------------------");
        //最貴的商品
        Goods dearly = goods.stream()
                .max(Comparator.comparingDouble(Goods::getPrice))
                .get();
        System.out.println(dearly.getName());
		//moutai
	
        System.out.println("最便宜的價格------------------");
        //最便宜的價格
        Float cheap = goods.stream()
                .map(Goods::getPrice)
                .sorted(Float::compare)
                .limit(1)
                .findAny()
                .get();
        System.out.println(cheap);
		//1.0

        System.out.println("飲品按名字排序------------------");
        //飲品按名字排序
        List<Goods> sortedDrinks = goods.stream()
                .filter(e -> e.getGoodsType().equals(GType.DRINK))
                .sorted(Comparator.comparing(Goods::getName))
                .collect(Collectors.toList());
        sortedDrinks.forEach(System.out::println);
		//beer - 3 - 8.0 - 100
		//milk - 3 - 6.0 - 100
		//moutai - 3 - 2980.0 - 1

        //食品庫存量
        System.out.println("所有商品庫存量------------------");
        int goodsCount = goods.stream()
                .mapToInt(Goods::getCount)
                .sum();
        System.out.println(goodsCount);
		//766
		
        //是否有免費的商品
        System.out.println("是否有免費的商品------------------");
        boolean freeGoods = goods.stream()
                .anyMatch(e -> e.getPrice() == 0);
        System.out.println(freeGoods);
		//false
		
        //以商品種類分類
        System.out.println("以商品種類分類------------------");
        Map<GType, List<Goods>> goodsMap = goods.stream()
                .collect(Collectors.groupingBy(Goods::getGoodsType));
        System.out.println(goodsMap);
		//{FOODS=[bread - 2 - 3.9 - 15, cookie - 2 - 2.6 - 300, sugar - 2 - 1.0 - 150], FRUITS=[apple - 1 - 5.0 - 30, banana - 1 - 3.5 - 20, orange - 1 - 8.0 - 40, grape - 1 - 18.3 - 10], DRINK=[milk - 3 - 6.0 - 100, beer - 3 - 8.0 - 100, moutai - 3 - 2980.0 - 1]}
		
        //每個商品的餘量
        System.out.println("每個商品的餘量------------------");
        Map<String, Integer> goodsCountMap = goods.stream()
                .collect(Collectors.toMap(Goods::getName, Goods::getCount));
        System.out.println(goodsMap);
        //{orange=40, banana=20, apple=30, bread=15, cookie=300, milk=100, moutai=1, grape=10, sugar=150, beer=100}
    }
           

使用流時要注意一點,如果沒有終端操作的話,流是不會執行的哦。

goods.stream().peek(e -> System.out.print(e.getName() + " "));
//
goods.stream().peek(e -> System.out.print(e.getName() + "-")).count();
//apple-banana-orange-bread-milk-grape-beer-cookie-sugar-moutai-
           

繼續閱讀