1. 程式人生 > 實用技巧 >Java8 Lambda表示式的使用

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
@AllArgsConstructor
public 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);

    }
}

如有錯誤,懇請批評指正!