一、Lambda 簡介
Lambda 表示式,也可稱為閉包,是一個匿名函式。我們可以把 Lambda 表示式理解為是一段可傳遞的程式碼(像資料一樣傳遞)。即 Lambda 允許把函式作為一個方法的實參引數(函式當作引數傳遞到方法中)。是Java8的其中一個很重要的新特性。從而可以寫出更簡潔,更靈活的程式碼。作為一種更加緊湊的程式碼風格,使java的語言表達能力得到了提升。
public class LambdaDemo { public static void main(String[] args) { List<String> list = Arrays.asList("A", "B", "C"); //x -> System.out.println(x),就是一個匿名函式,即Lambda表示式,作為實參傳給dealList方法 dealList(list, x -> System.out.println(x)); } public static void dealList(List<String> list, Consumer<String> consumer) { // 遍歷list中的每一個元素,傳給consumer物件的accept函式,進行呼叫 for (String x : list) { consumer.accept(x); } }}1234567891011121314
Lambda 表示式是對某些介面的簡單實現,但不是所有介面都可以使用 Lambda 表示式來實現的,Lambda 規定能被 Lambda 表示式實現的介面中,它只能只有一個需要被實現的方法(函式),但不要求介面中只能只有一個方法。因為Java8中有另外一個新特性,即 default 關鍵字修飾的介面方法有預設實現,這個預設的方法是可以不需要子類實現的,可使用@FunctionalInterface註解來強制使介面只能有一個需要被實現的方法。
二、Lambda 優勢2.1 案例一以前我們要寫一個類才能實現一個介面,並且實現裡面的抽象方法。如方式一:
// 介面public interface Human { // 抽象方法,需要被實現 void speak();}12345
// 實現類,並且實現抽象方法public class Man implements Human { @Override public void speak() { System.out.println("I am man!"); }}1234567
public class Main { public static void main(String[] args) { // 方式一 直接編寫實現類 Human human = new Man(); human.speak(); }}1234567
如果我們不想編寫一個單獨的實現類(Man),則可以用匿名內部類。如方式二:
public class Main { public static void main(String[] args) { // 方式一 直接編寫實現類 Human human = new Man(); human.speak(); // 方式二 匿名內部類 Human human1 = new Human() { @Override public void speak() { System.out.println("I am woman!"); } }; human1.speak(); }}12345678910111213141516
但方式二中,有用的就只有System.out.println("I am woman!");這一行,所以有了Lambda表示式,可以這樣寫,如方式三:
public class Main { public static void main(String[] args) { // 方式一 直接編寫實現類 Human human = new Man(); human.speak(); // 方式二 匿名內部類 Human human1 = new Human() { @Override public void speak() { System.out.println("I am woman!"); } }; human1.speak(); // 方式三 Lambda表示式 Human human2 = () -> System.out.println("I am woman!"); human2.speak(); }}1234567891011121314151617181920
2.2 案例二再假如我們要對一個Student類的陣列按指定條件進行過濾,如下:
public class Student { private String name; private int age; private double score;}12345
public class Main { public static void main(String[] args) { List<Student> students = Arrays.asList(new Student("張三", 18, 89.5), new Student("李四", 20, 60), new Student("王五", 19, 100), new Student("趙六", 22, 89)); // 過濾出年齡大於等於20的學生 List<Student> stus1 = filterStudentByAge(students); System.out.println(stus1); // 過濾出成績大於80的學生 List<Student> stus2 = filterStudentByScore(students); System.out.println(stus2); } // 過濾出年齡大於等於20的學生 private static List<Student> filterStudentByAge(List<Student> students) { List<Student> stus = new ArrayList<>(); for (Student stu : students) { if (stu.getAge() >= 20) { stus.add(stu); } } return stus; } // 過濾出成績大於80的學生 private static List<Student> filterStudentByScore(List<Student> students) { List<Student> stus = new ArrayList<>(); for (Student stu : students) { if (stu.getScore() > 80) { stus.add(stu); } } return stus; }}1234567891011121314151617181920212223242526272829303132333435363738
按上面的方式,如果要按另外一個條件過濾呢,又要寫一個方法。那可以用策略模式處理,編寫一個抽象策略介面,然後編寫多個不同策略類實現它。
// 策略介面public interface MyPredicate<T> { boolean test(T t);}1234
// 過濾出年齡大於等於20的學生public class filterStudentByAge implements MyPredicate<Student> { @Override public boolean test(Student t) { return t.getAge() >= 20; }}1234567
// 過濾出成績大於80的學生public class filterStudentByScore implements MyPredicate<Student> { @Override public boolean test(Student t) { return t.getScore() > 80; }}1234567
public class Main { public static void main(String[] args) { List<Student> students = Arrays.asList(new Student("張三", 18, 89.5), new Student("李四", 20, 60), new Student("王五", 19, 100), new Student("趙六", 22, 89)); // 過濾出年齡大於等於20的學生 List<Student> stus1 = filterStudent(students, new FilterStudentByAge()); System.out.println(stus1); // 過濾出成績大於80的學生 List<Student> stus2 = filterStudent(students, new FilterStudentByScore()); System.out.println(stus2); } // 按myPredicate策略過濾出滿足條件的學生 private static List<Student> filterStudent(List<Student> students, MyPredicate<Student> myPredicate) { List<Student> stus = new ArrayList<>(); for (Student stu : students) { if (myPredicate.test(stu)) { stus.add(stu); } } return stus; }}12345678910111213141516171819202122232425262728
但是以上方式,每增加一個過濾條件,就要編寫一個策略類,太麻煩。所以我們就用匿名內部類方式。
// 匿名內部類形式 過濾出年齡大於等於18的學生List<Student> stus3 = filterStudent(students, new MyPredicate<Student>() { @Override public boolean test(Student t) { return t.getAge() > 18; }});System.out.println(stus3);12345678
但我們會覺得匿名內部類還是太麻煩,無用程式碼太多,有用的程式碼其實就只有return t.getAge() > 18;,於是 Lambda 表示式發揮的作用就來了:
// Lambda形式 過濾出年齡大於等於18的學生List<Student> stus4 = filterStudent(students, t -> t.getAge() > 18);System.out.println(stus4);123
這時還是有人會問,那我們定義的介面MyPredicate和方法filterStudent(),好像沒什麼作用呀。然而官方已經想到這一點,它內建了一些通用介面,我們可以使用它。例如斷言的介面 Predicate,那我們就用如下方式,完全不用寫介面MyPredicate和方法filterStudent(),如下:
// 按myPredicate策略過濾出滿足條件的學生private static List<Student> filterStudent(List<Student> students, Predicate<Student> predicate) { List<Student> stus = new ArrayList<>(); for (Student stu : students) { if (predicate.test(stu)) { stus.add(stu); } } return stus;}1234567891011
當然,如果你會使用Stream(可以看我另外一篇文章),只需要寫下面的程式碼,如下:
public class Main { public static void main(String[] args) { List<Student> students = Arrays.asList(new Student("張三", 18, 89.5), new Student("李四", 20, 60), new Student("王五", 19, 100), new Student("趙六", 22, 89)); // 過濾出年齡大於等於20的學生 students.stream().filter(t -> t.getAge() >= 20).forEach(System.out::println); System.out.println("-------------------------------------"); // 過濾出成績大於80的學生 students.stream().filter(t -> t.getScore() > 80).forEach(System.out::println); }}12345678910111213141516
三、Lambda 語法語法:() -> {}():Lambda的形參列表,也就是接口裡面那個抽象方法的形參列表。->:Lambda的運算子,可以理解為引數和Lambda體的分隔符。{}:實現了介面中的抽象方法的方法體。
我們還是以一個簡單的例子,由淺到深學習 Lambda 語法。按照語法,我們可以寫出如下 Lambda 表示式,(String name, int age) 是引數列表,-> 是分隔符,{} 中的程式碼是方法體。
// 函式介面@FunctionalInterfacepublic interface Human { // 抽象方法,需要被實現 String speak(String name, int age);}123456
public class LambdaDemo { public static void main(String[] args) { Human human = (String name, int age) -> { System.out.println("My name is " + name + " ,I am " + age + " years old."); return name; }; human.speak("Mr.nobody", 18); }}123456789
當然,() 括號內的引數型別還能省略(推薦)。
public class LambdaDemo { public static void main(String[] args) { Human human = (name, age) -> { System.out.println("My name is " + name + " ,I am " + age + " years old."); return name; }; human.speak("Mr.nobody", 18); }}123456789
如果是隻有一個引數,() 也能省略。
@FunctionalInterfacepublic interface Human { // 抽象方法,需要被實現 String speak(String name);}12345
public class LambdaDemo { public static void main(String[] args) { Human human = name -> { System.out.println("My name is " + name + "."); return name; }; human.speak("Mr.nobody"); }}123456789
如果,方法體 {} 中,只有一行語句,{} 也能省略(推薦)。
@FunctionalInterfacepublic interface Human { // 抽象方法,需要被實現 void speak(String name, int age);}12345
public class LambdaDemo { public static void main(String[] args) { Human human = (name, age) -> System.out .println("My name is " + name + " ,I am " + age + " years old."); human.speak("Mr.nobody", 18); }}1234567
如果方法體需要返回值,而且只有一行語句,那 {} 大括號和 return 關鍵字都可以省略(推薦)。
@FunctionalInterfacepublic interface Human { // 抽象方法,需要被實現 String speak(String name, int age);}12345
public class LambdaDemo { public static void main(String[] args) { Human human = (name, age) -> "My name is " + name + " ,I am " + age + " years old."; human.speak("Mr.nobody", 18); }}