从lambda表达式到方法引用

比如我们有一个员工类:

package com.company.lambda;

public class Employee {
    private String name;
    private int age;
    private double salary;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Employee(String name, int age, double salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    public Employee(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", salary=" + salary +
                '}';
    }
}

然后在Main类中new出一些员工:

public class Main {

    private List<Employee> emps = Arrays.asList(new Employee("zs",20,3000)
    ,new Employee("ls",30,4000),new Employee("ws",35,5000),
            new Employee("zl",33,6000),new Employee("tq",40,8000));

}

现在的需求比方说是:

找出工资大于4000的员工。

我们写一个删选方法,把工资大于4000的员工删选出来:

   //select the employees whose salary is bigger than 4000
    public List<Employee> selectEmpsBySalary(List<Employee> list){
        List<Employee> selectedEmps = new ArrayList<>();
        for(Employee emp : list){
            if(emp.getSalary() > 4000){
                selectedEmps.add(emp);
            }
        }

        return selectedEmps;
    }

测试一下:


    @Test
    public void test02(){

        List<Employee> result = selectEmpsBySalary(emps);
        Iterator<Employee> iterator = result.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }

结果没有问题:

Employee{name='ws', age=35, salary=5000.0}
Employee{name='zl', age=33, salary=6000.0}
Employee{name='tq', age=40, salary=8000.0}

如果我现在的需求变成:

删选年龄大于34岁的员工信息。

这不又要写一个方法来实现这个需求吗?

    //select the employees whose age is bigger than 34
    public List<Employee> selectEmpsByAge(List<Employee> list){
        List<Employee> selectedEmps = new ArrayList<>();
        for(Employee emp : list){
            if(emp.getAge() > 34){
                selectedEmps.add(emp);
            }
        }

        return selectedEmps;
    }

再测试一下:

 @Test
    public void test01(){

       List<Employee> result = selectEmpsByAge(emps);
        Iterator<Employee> iterator = result.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }

    }

也没有问题:

Employee{name='ws', age=35, salary=5000.0}
Employee{name='tq', age=40, salary=8000.0}

有没有什么思想能够避免每来一个需求就要写一个方法来实现呢?

我们可以用策略模式。

搞一个过滤接口:

package com.company.lambda;

@FunctionalInterface
public interface FilterInterface<T> {


    public boolean test(T t);
}

@FunctionalInterface表明这是个函数接口,里面只能有一个方法。

实现类:

package com.company.lambda;

public class FilterClass implements FilterInterface<Employee> {
    @Override
    public boolean test(Employee employee) {
        return employee.getAge()>34;
    }
}

这里我们筛选出年龄大于34岁的员工。

然后我们写一个筛选方法:

//strategy design mode
    public List<Employee> filterEmpoyees(List<Employee> emps, FilterInterface<Employee> filter){
        List<Employee> selectedEmps = new ArrayList<>();

        for (Employee employee : emps){
            if(filter.test(employee)){
                selectedEmps.add(employee);
            }
        }

        return selectedEmps;
    }

传入源数据和筛选策略,然后将符合条件的员工放到一个新的集合。

测试方法:

@Test
    public void test03(){
       List<Employee> selectedEmps = filterEmpoyees(emps,new FilterClass());

       for(Employee emp: selectedEmps){
           System.out.println(emp);
       }
    }

如果有另外一个需求,比如要找工资大于5000元的员工,那么再写一个策略即可:

package com.company.lambda;

public class AnotherFilterClass implements FilterInterface<Employee>{
    @Override
    public boolean test(Employee employee) {
        return employee.getSalary()>5000;
    }
}

我们这时不用再写筛选方法了,但是要写策略类,也是同样的麻烦:

 @Test
    public void test03(){
       List<Employee> selectedEmps = filterEmpoyees(emps,new AnotherFilterClass());

       for(Employee emp: selectedEmps){
           System.out.println(emp);
       }
    }

接下来,考虑到新建一个类,然后里面实在没有什么逻辑,所以我们就用匿名内部类,也就是说,这种类并不重要,连名字都不给取了,我们要的是接口中的方法的实现。

下面的逻辑是找到工资小于4000的员工:

 @Test
    public void test04(){
        List<Employee> employees = filterEmpoyees(emps, new FilterInterface<Employee>() {
            @Override
            public boolean test(Employee employee) {
                return employee.getSalary()<4000;
            }
        });

        for(Employee employee : employees){
            System.out.println(employee);
        }

    }

这是不是很简化了?


既然能写匿名内部类,当然就能写成lambda表达式,因为java8中出现lambda表达式的目的就是简化匿名内部类:

 @Test
    public void test05(){
        List<Employee> lists = filterEmpoyees(emps,(employee -> employee.getSalary()<4000));
        lists.forEach(System.out::println);
    }

这是不是更简便了?

->左右两边,左边是参数,就是传入方法的参数,没有就别写了,右边是方法的具体逻辑。

想一想,因为我们只要筛选员工的某个条件逻辑,所以lambda表达式的写法是最简单的。

上面的输出语句,我还用到了方法引用。


方法引用是lambda表达式的另一种写法。

与lambda表达式要实现接口中的方法不同,方法引用指向一个类或者对象的某个具体方法,也就是说,这个方法里面是有东西的,是个具体的方法。

方法引用的类型:

  • 类名::静态方法名
  • 对象::实例方法名
  • 类名::实例方法名
  • 类名::new

最后一种是构造器引用,是一种特殊的方法引用。

静态方法引用例子:

package com.company.methodref;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

public class MethodRefDemo {

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {

                doSomething();
            }
        }).start();


        new Thread(()->doSomething()).start();

        new Thread(MethodRefDemo::doSomething).start();
    }


    static synchronized void doSomething(){

        String threadName = Thread.currentThread().getName();
        System.out.println("****************************************************************************");
        for(int i = 0; i < 50; i++){
            System.out.printf("%s:%d%n", threadName,i);
            try {
                Thread.sleep((int) (Math.random()*100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

这里列出了匿名内部类、lambda表达式和静态方法引用的语法区别。


对象::实例方法的例子:

package com.company.methodref;

import java.util.function.Supplier;

public class MethodRef2 {

    public static void main(String[] args) {
        System.out.println("----------匿名内部类----------------");
        String s = "hello method ref";
        print(new Supplier<String>() {
            @Override
            public String get() {
                return s.toString();
            }
        });

        System.out.println("----------lambda表达式----------------");
        print(()->s.toString());

        System.out.println("----------方法引用--------------------");
        print(s::toString);

    }

    static void print(Supplier<String> supplier){
        System.out.println(supplier.get());
    }
}

上面的s就是String类型的一个实例。

要注意的是,引用的方法的参数个数和返回值与接口中方法的参数个数和返回值要保持一致:

引用的方法:

    /**
     * This object (which is already a string!) is itself returned.
     *
     * @return  the string itself.
     */
    public String toString() {
        return this;
    }

无参,返回String。

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

无参,返回范型,但是我们指定了类型为String。


类名::实例方法的例子:

package com.company.methodref;

import java.util.function.BiPredicate;

public class MethodRef3 {
    public static void main(String[] args) {
        System.out.println("*************匿名内部类*******************");
        BiPredicate<String, String> predicate2 = new BiPredicate<String, String>() {
            @Override
            public boolean test(String s, String s2) {
                return s.equals(s2);
            }
        };
        boolean test2 = predicate2.test("aa", "ab");
        System.out.println(test2);


        System.out.println("*************lambda表达式*******************");
        BiPredicate<String,String> predicate = (x, y) -> x.equals(y);
        boolean test = predicate.test("b", "b");
        System.out.println(test);


        System.out.println("*************方法引用*******************");
        BiPredicate<String,String> predicate1 = String::equals;
        boolean test1 = predicate1.test("a", "b");
        System.out.println(test1);

    }
}


构造器引用的例子:

员工类:

package com.company.methodref;

public class Employee {
    String name;
    Integer age;

    public Employee(){
        name="ocean";
        age=20;
    }

    public Employee(String name,Integer age){
        this.name = name;
        this.age = age;

    }
}

里面有两个构造器。

函数接口:

package com.company.methodref;

@FunctionalInterface
public interface EmployeeProvider {
    Employee getEmployee(String name, Integer age);
}

测试:

package com.company.methodref;

public class MethodRef4 {

    public static void main(String[] args) {
        EmployeeProvider provider = Employee::new;
        Employee emp = provider.getEmployee("John", 23);
        System.out.printf("Name: %s%n",emp.name);
        System.out.printf("Age:%d%n",emp.age);


    }
}

当编译器遇到Employee::new,他会去找无参构造。

当运行到Employee emp = provider.provide("John", 23);时,编译器会先去找两个参数的构造器,new出员工对象之后,他再调用getEmployee方法拿到对象。

发布了25 篇原创文章 · 获赞 26 · 访问量 1139

猜你喜欢

转载自blog.csdn.net/weixin_43810802/article/details/103824848