Java8特性——Lambda表達式

  • 引言

    lambda表達式,能夠理解爲簡潔的表示可傳遞的匿名函數的一種方式,它沒有名稱,但它有參數列表、函數主體、返回類型。java

    下面是一個比較器的傳統寫法:編程

//之前定義一個比較器的寫法
        Comparator<Apple> comparator = new Comparator<Apple>() {
            @Override
            public int compare(Apple a1, Apple a2) {
                return a1.getWeight() - a2.getWeight();
            }
        };

    使用lambda表達式後:json

(a1,a2) -> a1.getWeight() - a2.getWeight();

   對比先後的寫法,第一種寫法顯得十分的繁瑣和冗長,第二種寫法就顯得十分的簡潔和清晰。app

  • 定義

函數式接口只定義了一個抽象方法的接口,在Java8以前,Java已經有包括Comparator、Runnable、Callable函數式接口,Java8在java.util.function包中提供了以下經常使用的函數式接口:iphone

函數式接口 函數描述符
Predicate<T> T -> boolean
Consumer<T> T -> void
Function<T,R> T -> R
Supplier<T> void -> T
UnaryOperator<T> (T,T) -> T

函數式描述符就是函數式接口的抽象方法的簽名。把lambda函數做爲函數式接口的參數傳遞給一個方法時,必須知足lambda表達式的簽名要和函數式接口的抽象方法同樣。ide

方法簽名:方法簽名由方法名稱和一個參數列表(方法的參數的順序和類型)組成(注意,方法簽名不包括方法的返回類型。不包括返回值和訪問修飾符。)。函數的重寫和重載就是根據方法簽名去判斷的。函數式編程

@FunctionalInterface:函數式接口的註解,用於檢查接口是否爲函數式接口,和override的做用相似的註解。函數

  • 使用和原理

    一個方法的入參若是定義的是一個函數式接口,就可使用lambda表達式,其中lambda表達式的描述符或者說方法簽名應該和函數式接口的同樣,說的更明白點,函數式接口入參定義的是一個入參規範,lambda表達式必須知足這個入參規範,能夠用下面的僞代碼表示:ui

    函數式接口 = ()->{}this

在定義中羅列出了經常使用的函數式接口,下面介紹其中的幾個函數式接口的原理和應用。

    先來一段用Stream API去操做集合的代碼,而後分析其中用的lambda表達式:

@Test
    public void testStream() {
        List<Dish> menu = Arrays.asList(
                new Dish("pork", false, 800, Type.MEAT),
                new Dish("beef", false, 700, Type.MEAT),
                new Dish("chicken", false, 400, Type.MEAT),
                new Dish("french fries", true, 530, Type.OTHER),
                new Dish("rice", true, 350, Type.OTHER),
                new Dish("season fruit", true, 120, Type.OTHER),
                new Dish("pizza", true, 550, Type.OTHER),
                new Dish("prawns", false, 300, Type.FISH),
                new Dish("salmon", false, 450, Type.FISH)
        );

        menu.stream()
                .filter(dish -> dish.getCalories() > 300)
                .map(dish -> dish.getName()).
                forEach(name ->{
                    System.out.println("name:" + name);
                });

    }

    @Data
    class Dish {
        private String name;
        private boolean vegetarian;
        private int calories;
        private Type type;

        public Dish(String name, boolean vegetarian, int calories, Type type) {
            this.name = name;
            this.vegetarian = vegetarian;
            this.calories = calories;
            this.type = type;
        }
    }

    public enum Type {MEAT, FISH, OTHER}

    輸出結果爲:

name:pork
name:beef
name:chicken
name:french fries
name:rice
name:pizza
name:salmon

Predicate

    java.util.function.Predicate<T>接口定義了一個名叫test的抽象方法,它可以接受泛型T對象,並返回一個boolean。

    Stream API中對集合操做的filter接受的就是一個Predicate函數式接口:

Stream<T> filter(Predicate<? super T> predicate);

    內部實現相似於:

public static List<T> filter(List<T> list, Predicate<T> p){
        List<T> results = new ArrayList<>();
        for (T s : list) {
            if(p.test(s)){
                results.add(s);
            }
        }
        return results;
    }

    經過test方法看是否知足條件,而後知足條件的元素放進一個新的容器中,並返回,實現了過濾。

Consumer

    java.util.function.Consumer<T>定義了一個名爲accept的抽象方法,它接收泛型T的對象,沒有返回。如同名字描述的那樣是一種消費,在accept中定義消費動做,即lambda表達式中的表達式主體。

    Stream API中的forEach接受的就是一個Consumer函數式接口:

void forEach(Consumer<? super T> action);

    內部實現相似於:

public static void forEach(List<T> list, Consumer<T> c){
        for (T s : list) {
            c.accept(s);
        }
    }

 

Function

    java.util.function.Function<T,R>定義了一個名爲apply的抽象方法,它接收泛型T的對象,返回一個R型對象。

    Stream API中的map接受的就是一個Function函數式接口:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

    內部實現相似於:

public static List<R> forEach(List<T> list, Function<T,R> f){
        List<R> results = new ArrayList<>();
        for (T s : list) {
            results.add(f.apply(s));
        }
        return results;
    }

注意

    lambda表達式除了能夠用主體裏面的參數之外,也容許使用自由變量(做用域外的變量),就像匿名類同樣。也和匿名類同樣,對實例變量和靜態變量能夠沒有限制使用,可是局部變量必須顯示聲明爲final。主要緣由是兩種變量的存放位置的差異,形成線程共享和不共享的問題。

方法引用

    方法引用可讓你重複使用現有的方法定義,並像lambda同樣傳遞他們,例如咱們可使用方法引用篩選出素菜:

List<Dish> menuNames = menu.stream().
                filter(Dish::isVegetarian).collect(toList());

    不用方法引用咱們須要這樣寫lambda表達式:

List<Dish> menuNames1 = menu.stream().
                filter(dish -> dish.isVegetarian()).collect(toList());

    咱們還能夠經過關鍵字new來建立一個對象:className:new。

//無參的構造函數
        Supplier<Apple> supplier = Apple::new;
        Apple a1 = supplier.get();

        //一個參數的構造函數
        Function<String,Apple> function = Apple::new;
        Apple a2 = function.apply("my iphone");
        
        //兩個參數的構造函數的獲取
        BiFunction<String,Integer,Apple> biFunction = Apple::new;
        Apple a3 = biFunction.apply("my ipad",999);

    若是構造函數式三個入參的時候,就須要本身定義一個函數式接口了。

總結

    lambda表達式的引入極大的提升了代碼的效率和編程思想向函數式編程的轉變,本文只是簡要的介紹了lambda表達式使用和簡單原理,更詳細的解讀能夠參閱《Java8 in action》。

相關文章
相關標籤/搜索