Java8 Lambda表示式的使用
參考
官網文件:https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
部落格:https://www.cnblogs.com/franson-2016/p/5593080.html
菜鳥教程:https://www.runoob.com/java/java8-functional-interfaces.html
為什麼要用Lambda表示式?
Lambda表示式可以讓我們用較少的程式碼來實現更多的功能,尤其在Java這種相對來說比較“囉嗦”的語言中,是一把提高效率的利器。
演變過程
把官網上提供的demo拿過來
實體類
@Data @AllArgsConstructorpublic class Person { public enum Sex { MALE,FEMALE; } private String name; private LocalDate birthday; private Sex gender; private String emailAddress; private Integer age; public void printPerson() { System.out.println(this.toString()); } }
需求
現在我們需要實現一個功能,按照某種標準,從Person的集合中,挑選出符合條件的物件。
寫法一
/** * 第一種寫法,所有的判斷方法耦合在一起 */ public class PrintTest { //大於指定年齡即可 public static void printPersonOlderTan(List<Person> roster, int age) { for (Person person : roster) { if(person.getAge() > age) { person.printPerson(); } } }//在某個年齡區間內 public static void printPersonWithinAgeRange (List<Person> roster, int low, int high) { for (Person person : roster) { if(low <= person.getAge() && person.getAge() < high) { person.printPerson(); } } } }
根據不同的要求寫多個方法,不夠靈活,重複程式碼較多。
寫法二
上述程式碼存在的問題是,如果篩選的條件改變了,我們就需要寫一個新的方法。
所以,思考一下我們可不可以把變化的部分單獨拿出來。
public interface CheckPerson { boolean test(Person person); }
public class CheckPersonEligibleForSelectiveService implements CheckPerson{ @Override public boolean test(Person person) { return person.getGender() == Person.Sex.MALE && person.getAge() >= 17 && person.getAge() <= 25; } }
/** * 將判斷條件抽象成方法 */ public class PrintPerson { public static void printPerson(List<Person>roster, CheckPerson tester) { for (Person person : roster) { if(tester.test(person)) { person.printPerson(); } } }
將篩選條件封裝成表示演算法的類,當有新的需求下來的時候,可以根據不同的需要編寫對應的類,實現CheckPerson介面,重寫test方法即可。
將不同的演算法封裝起來,實現同一個介面,利用java的多型,根據不同的需求指定對應的演算法,這就是策略設計模式。
呼叫該方法
public class PrintPersonTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "[email protected]", 12); Person person2 = new Person("test2", null, Person.Sex.MALE, "[email protected]", 13); Person person3 = new Person("test3", null, Person.Sex.MALE, "[email protected]", 14); List<Person> rosters = Arrays.asList(person1, person2, person3); //例項化演算法類 CheckPersonEligibleForSelectiveService checkService = new CheckPersonEligibleForSelectiveService(); PrintPerson.printPerson(rosters, checkService) } }
寫法三
有一種演算法就需要定義一個類,如果說這個演算法經常使用,倒還是值得的。
但如果這個演算法只用一次,還要特地定義一個類,未免有些太不划算了。
所以這個時候就想起了jdk1.8之前就存在的匿名內部類。
public class PrintPersonTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "[email protected]", 12); Person person2 = new Person("test2", null, Person.Sex.MALE, "[email protected]", 13); Person person3 = new Person("test3", null, Person.Sex.MALE, "[email protected]", 14); List<Person> rosters = Arrays.asList(person1, person2, person3); //匿名內部類 PrintPerson.printPerson(rosters, new CheckPerson() { @Override public boolean test(Person p) { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } }); } }
寫法四
Lambda表示式可以在匿名類的基礎上,進一步簡化寫法。
public class PrintPersonTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "[email protected]", 12); Person person2 = new Person("test2", null, Person.Sex.MALE, "[email protected]", 13); Person person3 = new Person("test3", null, Person.Sex.MALE, "[email protected]", 14); List<Person> rosters = Arrays.asList(person1, person2, person3); //lambda表示式 PrintPerson.printPerson(rosters, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25); } }
Lambda寫法彙總
沒有引數
public interface SayHello0 { void say(); }
/** * 函式沒有引數 */ public class SayTest { public static void main(String[] args) { //寫法一 SayHello0 sayHello0 = ()->{ System.out.println("hello"); }; //寫法二,可以省略大括號 SayHello0 sayHello01 = () -> System.out.println("hello2") ; //方法裡面的內容多餘一行的時候,必須要加大括號 SayHello0 sayHello02 = () -> { System.out.println("hello3"); System.out.println("hello3"); }; } }
一個引數
public interface SayHello1 { void say(String msg); }
/** * 函式有1個引數 */ public class SayTest1 { public static void main(String[] args) { //寫法一 SayHello1 sayHello1 = (msg)->{ System.out.println(msg); }; //寫法二,簡寫 SayHello1 sayHello11 = msg -> System.out.println(msg); //寫法三:再簡寫(這種寫法的解釋在後面) SayHello1 sayHello12 = System.out::println; } }
兩個及以上
public interface SayHello2 { void say(String name, String msg); }
/** * 函式有2個引數 */ public class SayTest2 { public static void main(String[] args) { //多個引數時,小括號不能省略 SayHello2 sayHello2 = (name, msg) -> { System.out.println("name" + "msg"); }; } }
System.out::println
雙冒號(::)也是1.8才有的運算子,用於lambda表示式中。
看一個demo
public class LambdaTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "[email protected]", 10); Person person2 = new Person("test2", null, Person.Sex.MALE, "[email protected]", 40); Person person3 = new Person("test3", null, Person.Sex.MALE, "[email protected]", 35); Person person4 = new Person("test4", null, Person.Sex.MALE, "[email protected]", 28); Person person5 = new Person("test5", null, Person.Sex.MALE, "[email protected]", 91); List<Person> personList = Arrays.asList(person1, person2, person3, person4, person5); // personList.forEach(person -> System.out.println(person)); personList.forEach(System.out::println); } }
順序輸出集合中的所有元素
personList.forEach(person -> System.out.println(person));
personList.forEach(System.out::println);
這兩種寫法的效果是等價的,
先看forEach()方法需要的引數是什麼
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }
Consumer是java8的內建函式式介面
@FunctionalInterface public interface Consumer<T> { void accept(T t); default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
標註了@FunctionalInterface介面,符合函式式介面的規範。函式式介面要求介面只能有一個方法,這裡的介面為啥會有兩個呢?
第二個方法有個default關鍵字,所以不需要給他初始化,當你不實現他的時候,jvm會採用預設的實現方式。
。。。。。。
所以這個forEach方法是幹嘛的
forEach遍歷集合,把每一個元素都傳給一個方法處理,而這個方法是用Consumer來接收的,所以必須符合Lambda表示式的規範。
所以
personList.forEach(System.out::println);
含義是,將personList裡面的每一個元素都傳給System.out這個物件的println方法,供這個方法消費(consumer)。
自定義一個類,來說明這麼寫的含義,可能會更好一點。
public class ShowService { public void show(Person person) { System.out.println(person); } }
測試類
public class LambdaTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "[email protected]", 10); Person person2 = new Person("test2", null, Person.Sex.MALE, "[email protected]", 40); Person person3 = new Person("test3", null, Person.Sex.MALE, "[email protected]", 35); Person person4 = new Person("test4", null, Person.Sex.MALE, "[email protected]", 28); Person person5 = new Person("test5", null, Person.Sex.MALE, "[email protected]", 91); List<Person> personList = Arrays.asList(person1, person2, person3, person4, person5);
ShowService showService = new ShowService(); personList.forEach(showService::show); //或者把show改為靜態方法 public static void show(Person person) // personList.forEach(ShowService::show); } }
至於forEach以及Stream等java8新特性,後面會陸續總結。
內建函式式介面
1.8新增的內建函式介面有很多,在java.util.function這個包下。
可以參考:.https://www.runoob.com/java/java8-functional-interfaces.html
我們可以根據具體的需求,去使用不同的介面
例如
消費型介面(Consumer),有引數,返回值
public void test01(){ //Consumer Consumer<Integer> consumer = (x) -> System.out.println("消費型介面" + x); //test consumer.accept(100); }
提供型介面(supplier),無引數,有返回值
public void test02(){ List<Integer> list = new ArrayList<>(); List<Integer> integers = Arrays.asList(1,2,3); list.addAll(integers); //Supplier<T> Supplier<Integer> supplier = () -> (int)(Math.random() * 10); list.add(supplier.get()); System.out.println(supplier); for (Integer integer : list) { System.out.println(integer); } }
函式型介面 Function<K,V>,有引數,有返回值
public void test03(){ //Function<T, R> String oldStr = "abc123456xyz"; Function<String, String> function = (s) -> s.substring(1, s.length()-1); //test System.out.println(function.apply(oldStr)); }
斷言型介面 (Predicate),有引數,返回值為布林型
public void test04(){ //Predicate<T> Integer age = 35; Predicate<Integer> predicate = (i) -> i >= 35; if (predicate.test(age)){ System.out.println("你該退休了"); } else { System.out.println("我覺得還OK啦"); } }
本節demo來自:https://blog.csdn.net/weixin_45225595/article/details/106203264
實戰
public class CompareTest { public static void main(String[] args) { Person person1 = new Person("test1", null, Person.Sex.MALE, "[email protected]", 10); Person person2 = new Person("test2", null, Person.Sex.MALE, "[email protected]", 40); Person person3 = new Person("test3", null, Person.Sex.MALE, "[email protected]", 35); Person person4 = new Person("test4", null, Person.Sex.MALE, "[email protected]", 28); Person person5 = new Person("test5", null, Person.Sex.MALE, "[email protected]", 91); //匿名內部類寫法 List<Person> personList = Arrays.asList(person1, person2, person3, person4, person5); Collections.sort(personList, new Comparator<Person>() { //從小到大排序 @Override public int compare(Person p1, Person p2) { return p1.getAge() - p2.getAge(); } }); System.out.println(personList); //正則表示式寫法 Collections.sort(personList, (p1, p2)->{ return p1.getAge() - p2.getAge(); }); //1.8以後的Comparator提供了專門的方法 Collections.sort(personList, Comparator.comparingInt(Person::getAge)); //還可以這麼用,BiFunction也是內建函式 BiFunction<Person,Person, Integer> function= (p1, p2)-> p1.getAge() - p2.getAge(); function.apply(person1, person2); } }
如有錯誤,懇請批評指正!