【Java8新特性】——Lambda表達式

前言

從java8出現以來lambda是最重要的特性之一,它可以讓我們用簡潔流暢的代碼完成一個功能。 很長一段時間java被吐槽是冗餘和缺乏函數式編程能力的語言,隨着函數式編程的流行java8種也引入了 這種編程風格。

一、lambda簡介

什麼是lambda?

lambda表達式是一段可以傳遞的代碼,它的核心思想是將面向對象中的傳遞數據變成傳遞行爲。 我們回顧一下在使用java8之前要做的事,之前我們編寫一個線程時是這樣的:

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("do something.");      
    }
}

我們也可以使用匿名內部類的方式實現該方法,設計匿名內部類的目的,就是爲了方便 Java 程序員將代碼作爲數據傳遞。不過,匿名內部 類還是不夠簡便。 爲了執行一個簡單的任務邏輯,不得不加上 6 行冗繁的樣板代碼。那如果是lambda該怎麼做?

Runnable r = () -> System.out.println("do something.");

嗯,這代碼看起來很酷,你可以看到我們用()和->的方式完成了這件事,這是一個沒有名字的函數,也沒有人和參數,再簡單不過了。 使用->將參數和實現邏輯分離,當運行這個線程的時候執行的是->之後的代碼片段,且編譯器幫助我們做了類型推導; 這個代碼片段可以是用{}包含的一段邏輯。下面一起來學習一下lambda的語法。

基礎語法 

在lambda中我們遵循如下的表達式來編寫:

expression = (variable) -> action
  • variable: 這是一個變量,一個佔位符。像x,y,z,可以是多個變量;
  • action: 這裏我稱它爲action, 這是我們實現的代碼邏輯部分,它可以是一行代碼也可以是一個代碼片段。

可以看到Java中lambda表達式的格式:參數、箭頭、以及動作實現,當一個動作實現無法用一行代碼完成,可以編寫 一段代碼用{}包裹起來。

lambda表達式可以包含多個參數,例如:

int sum = (x, y) -> x + y;

這時候我們應該思考這段代碼不是之前的x和y數字相加,而是創建了一個函數,用來計算兩個操作數的和。 後面用int類型進行接收,在lambda中爲我們省略去了return。

1.無參數,無返回值

() -> System.out.println("Hello Lambda!");
Runnable r1 = () -> System.out.println("Hello Lambda!");

2.有一個參數,並且無返回值

//只有一個參數是,參數括號可省略
(x) -> System.out.println(x)
Consumer<String> con = (x) -> System.out.println(x)

3.有兩個以上的參數,有返回值

Comparator<Integer> com = (x, y) -> {

   System.out.println("函數式接口");

   return Integer.compare(x, y);

  };

4.當 Lambda 體只有一條語句時,return 與大括號可以省略 

Comparator<Integer> com = (x, y) -> Integer.compare(x, y);

函數式接口 

函數式接口是隻有一個方法的接口,用作lambda表達式的類型。前面寫的例子就是一個函數式接口,來看看jdk中的Runnable源碼

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

這裏只有一個抽象方法run,實際上你不寫public abstract也是可以的,在接口中定義的方法都是public abstract的。 同時也使用註解@FunctionalInterface告訴編譯器這是一個函數式接口,當然你不這麼寫也可以,標識後明確了這個函數中 只有一個抽象方法,當你嘗試在接口中編寫多個方法的時候編譯器將不允許這麼幹。

函數式接口大致分爲以下幾類:

1.消費型接口示例

public static void donation(Integer money, Consumer<Integer> consumer){
    consumer.accept(money);  
}
public static void main(String[] args) {
    donation(1000, money -> System.out.println("您一共消費"+money+"元")) ;
}

2.供給型接口示例 

public static List<Integer> supply(Integer num, Supplier<Integer> supplier){
       List<Integer> resultList = new ArrayList<Integer>()   ;
       for(int x=0;x<num;x++)  
           resultList.add(supplier.get());
       return resultList ;
}
public static void main(String[] args) {
    List<Integer> list = supply(10,() -> (int)(Math.random()*100));
    list.forEach(System.out::println);
}

3.函數型接口示例

轉換字符串爲Integer

public static Integer convert(String str, Function<String, Integer> function) {
    return function.apply(str);
}
public static void main(String[] args) {
    Integer value = convert("28", x -> Integer.parseInt(x));
}

4.斷言型接口示例

篩選出只有2個字的水果

public static List<String> filter(List<String> fruit, Predicate<String> predicate){
    List<String> f = new ArrayList<>();
    for (String s : fruit) {
        if(predicate.test(s)){
            f.add(s);
        }
    }
    return f;
}
public static void main(String[] args) {
    List<String> fruit = Arrays.asList("香蕉", "哈密瓜", "榴蓮", "火龍果", "水蜜桃");
    List<String> newFruit = filter(fruit, (f) -> f.length() == 2);
    System.out.println(newFruit);
}

默認方法

在Java語言中,一個接口中定義的方法必須由實現類提供實現。但是當接口中加入新的API時, 實現類按照約定也要修改實現,而Java8的API對現有接口也添加了很多方法,比如List接口中添加了sort方法。 如果按照之前的做法,那麼所有的實現類都要實現sort方法,JDK的編寫者們一定非常抓狂。

幸運的是我們使用了Java8,這一問題將得到很好的解決,在Java8種引入新的機制,支持在接口中聲明方法同時提供實現。 這令人激動不已,你有兩種方式完成 1.在接口內聲明靜態方法 2.指定一個默認方法。

我們來看看在JDK8中上述List接口添加方法的問題是如何解決的

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

這個List接口的源碼,其中加入一個默認方法default void sort(Comparator<? super E> c)。 在返回值之前加入default關鍵字,有了這個方法我們可以直接調用sort方法進行排序。

List<Integer> list = Arrays.asList(2, 7, 3, 1, 8, 6, 4);
list.sort(Comparator.naturalOrder());
System.out.println(list);

Comparator.naturalOrder()是一個自然排序的實現,這裏可以自定義排序方案。你經常看到使用Java8操作集合的時候可以直接foreach的原因也是在Iterable接口中也新增了一個默認方法:forEach,該方法功能和 for 循環類似,但是允許 用戶使用一個Lambda表達式作爲循環體。

方法引用和構造器引用 

方法引用:

當要傳遞給Lambda體的操作,已經有實現的方法了,可以使用方法引用!(實現抽象方法的參數列表,必須與方法引用方法的參數列表保持一致! )

方法引用:使用操作符 “::” 將方法名和對象或類的名字分隔開來。

先定義一個人的實體類:有以下屬性:

public class Person {

    private String firstName, lastName, job, gender;
    private int salary, age;
    //省略構造方法/get/set
}

如下三種主要使用情況:

(1)對象::實例方法

Consumer<String> consumer = System.out::println;

Supplier<String> sup = () -> person.getFirstName();
System.out.println(sup.get());
        
Supplier<String> sup2 = person::getFirstName;
System.out.println(sup2.get());

(2)類::靜態方法

BiFunction<Double, Double, Double> fun2 = Math::max;
System.out.println(fun2.apply(1.2, 1.5));

Comparator<Integer> com = Integer::compare;

(3)類::實例方法  

BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abc", "abc"));

構造器引用:構造器的參數列表,需要與函數式接口中參數列表保持一致! 

Function<Integer, Integer[]> fun1 = Integer[]::new;
//Person要有一個只有String參數的構造方法
Function<String, Person> fun2 = Person::new;

二、lambda實戰

1.線程啓動

new Thread( () -> {
      System.out.println("In Java8, Lambda expression rocks !!");
}).start();

2.對列表進行迭代

// 使用 lambda 表達式以及函數操作(functional operation)
list.forEach((obj) -> System.out.println(obj));
// 在 Java 8 中使用雙冒號操作符(double colon operator)
list.forEach(System.out::println);

3.排序集合

omparator<String> sortByName = (String s1, String s2) -> (s1.compareTo(s2));
list.sort(sortByName);
list.forEach(System.out::println);

下面通過示範來加深熟悉lambda表達式的理解:測試類開始:

定義兩個人的集合:

List<Person> javaProgrammers = new ArrayList<Person>() {
            {
                add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
                add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
                add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
                add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));
                add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));
                add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));
                add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));
                add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));
                add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
                add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));
            }
        };
        List<Person> phpProgrammers = new ArrayList<Person>() {
            {
                add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550));
                add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200));
                add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600));
                add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000));
                add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100));
                add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300));
                add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100));
                add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000));
                add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600));
                add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800));
            }
        };

輸出php程序員名稱:

System.out.println("所有php程序員的姓名:");
phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));

給java程序員加薪(兩種方式):

System.out.println("給程序員加薪 5% :");
javaProgrammers.forEach(e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary()));
System.out.println("給程序員加薪 5% :");
Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());
javaProgrammers.forEach(giveRaise);

過濾月薪超過1400元的java程序員(兩種方式): 

System.out.println("過濾月薪超過 $1,400 的JAVA程序員:");
phpProgrammers.stream()
        .filter((p) -> (p.getSalary() > 1400))
        .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getSalary()));
System.out.println("過濾月薪超過 $1,400 的JAVA程序員:");
Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);
phpProgrammers.stream()
        .filter(salaryFilter)
        .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getSalary()));

 

過濾年齡大於 24歲且月薪在$1,400以上的女PHP程序員:

System.out.println("下面是年齡大於 24歲且月薪在$1,400以上的女PHP程序員:");
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);
Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));
phpProgrammers.stream()
        .filter(ageFilter)
        .filter(salaryFilter)
        .filter(genderFilter)
        .forEach((p) -> System.out.printf("%s %s %s; ", p.getGender(), p.getAge(), p.getFirstName()));

 

將java程序員根據工資排序:

System.out.println("Java programmers根據 salary 排序:");
javaProgrammers.stream()
         .sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
         .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getSalary()));

找出工資最低和最高的java程序員:

System.out.println("工資最低的 Java programmer:");
Person personMin = javaProgrammers
           .stream()
           .min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
           .get();
System.out.printf("Name: %s; Salary: $%,d.", personMin .getFirstName(), pers.getSalary());
System.out.println("工資最高的 Java programmer:");
Person person = javaProgrammers
          .stream()
          .max((p1, p2) -> (p1.getSalary() - p2.getSalary()))
          .get();
System.out.printf("Name: %s; Salary: $%,d.", person.getFirstName(), person.getSalary());

將PHP程序員的姓拼接成字符串:

System.out.println("將 PHP programmers 的 first name 拼接成字符串:");
String phpDevelopers = phpProgrammers
          .stream()
          .map(Person::getFirstName)
          .collect(Collectors.joining(" ; "));
System.out.println(phpDevelopers);

將java程序員的姓放到set中:

System.out.println("將 Java programmers 的 first name 存放到 Set:");
Set<String> javaDevFirstName = javaProgrammers
           .stream()
           .map(Person::getFirstName)
           .collect(Collectors.toSet());

將java程序員的姓放到Treeset中: 

System.out.println("將 Java programmers 的 first name 存放到 TreeSet:");
TreeSet<String> javaDevLastName = javaProgrammers
         .stream()
         .map(Person::getLastName)
         .collect(Collectors.toCollection(TreeSet::new));

計算所有java程序員的工資 :

System.out.println("計算付給 Java programmers 的所有money:");
int totalSalary = javaProgrammers
          .parallelStream()
          .mapToInt(p -> p.getSalary())
          .sum();

獲得各種彙總數據。 

// 接下來,我們可以訪問這些方法,比如getMax, getMin, getSum或getAverage:
//計算 count, min, max, sum, and average for numbers
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = numbers
          .stream()
          .mapToInt((x) -> x)
          .summaryStatistics();

System.out.println("List中最大的數字 : " + stats.getMax());
System.out.println("List中最小的數字 : " + stats.getMin());
System.out.println("所有數字的總和   : " + stats.getSum());
System.out.println("所有數字的平均值 : " + stats.getAverage())

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章