首页 > 编程语言 >Java8 函数式编程和Lambda

Java8 函数式编程和Lambda

时间:2024-07-25 14:39:30浏览次数:11  
标签:编程 System Java8 employee Employee new Lambda public out

lambda

初识lambda表达式

一个简单的方法,比较两个Integer:

public void Test1() {
	Comparator<Integer> com = new Comparator<Integer>() {
		@Override
		public int compare(Integer o1, Integer o2) {
			return Integer.compare(o1, o2);
		}
	};
	TreeSet<Integer> treeSet = new TreeSet<>(com);
}

下面的这个方法与上面的方法效果相同:

public void Test2() {
	Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
	TreeSet<Integer> treeSet = new TreeSet<>(com);
}

从上面就可以看出lambda表达式可以极大地简化代码量现在有一个需求,找出所有年龄大于35的员工,Employee的实体类属性如下

String name;
int age;
int salary;

创建一个List保存数据:

List<Employee> employees = Arrays.asList(
		new Employee("张三", 11, 9000),
		new Employee("李四", 33, 3000),
		new Employee("王五", 44, 4000),
		new Employee("p8", 55, 2000)
);

一开始,我们也许会选择一种最简单的方法去实现:

@Test
public List<Employee> Test3() {
	List<Employee> list = new ArrayList<>();
	for (Employee employee : employees) {
		if (employee.age > 35) {
			list.add(employee)
		}
	}
    return list;
}

优化一、使用策略设计模式

自己新建实现类来过滤员工功能

然后有了一个新的需求,找出所有员工中工资高于3500的员工,此时就需要加方法了,然后也许还会有别的需求(姓李的员工)等,我们不可能每次加需求都要加方法,于是这时候可以采用策略设计模式,设计一个接口MyInterface,里面声明一个用于处理过滤员工的方法:

public interface MyInterface<T> {    
    boolean filter(T t);
}

编写一个专门用于过滤员工的方法:

/**
 * 用于过滤 Employee
 *
 * @param list
 * @param myInterface
 * @return
 */
List<Employee> filterEmployees(List<Employee> list, MyInterface<Employee> myInterface) {
    List<Employee> emp = new ArrayList<>();
    for (Employee employee : list) {
        if (myInterface.filter(employee)) {
            emp.add(employee);
        }
    }
    return emp;
}

之后如果需要使用员工过滤的方法时,调用filterEmployees方法时传入MyInterface的具体实现类,这个实现类中重写好所需要的filter方法。

使用内部匿名类来实现过滤员工功能

我们此时在调用的方法中就是传入一个匿名内部类,重写好我们所需要的方法即可:

@Test
public void test4() {
    List<Employee> list = filterEmployees(employees, new MyInterface<Employee>() {

        @Override
        public boolean filter(Employee employee) {
            if (employee.age > 22) {
                return true;
            }
            return false;
        }
    });
    for (Employee employee : list) {
        System.out.println(employee.name + " " + employee.age + " " + employee.salary);
    }
}

优化二、使用lambda表达式

lambda表达式其实就是简化了使用匿名内部类的写法繁琐

@Test
public void test5() {
    List<Employee> list = filterEmployees(employees, (employee -> employee.age > 22));
    for (Employee employee : list) {
        System.out.println(employee.name + " " + employee.age + " " + employee.salary);
    }
}

lambda原理

在讲almbda原理之前先说下函数式接口FunctionalInterface,FunctionalInterface就是接口中只有一个抽象方法的接口,此时可以使用注解@FunctionalInterface,如果注解的接口内部有多个抽象方法,编译器会报错。那么这和lambda有什么关系呢,思考一下,如果使用lambda来替代一个匿名内部类的写法,我们使用lambda的时候是没有指明使用实现接口的哪个方法的,也就是说如果接口内有多个抽象方法,编译器就不知道lambda想要重写哪个方法,这个时候自然会报错。注意事项:

  1. 保持Lambda表达式简短和一目了然,过长的Lambda表达式通常是危险的,因为代码越长越难以读懂,意图看起来也不明,并且代码也难以复用,测试难度也大。
  2. 使用@FunctionalInterface注解,否则他人修改了函数式接口后使用lambda的地方就用不了了。
  3. 不要在Lambda表达中执行有"副作用"的操作,"副作用"是严重违背函数式编程的设计原则,比如在forEach操作里面操作外面的某个List或者设置某个Map这其实是不对的。
  4. 不要把Lambda表达式和匿名内部类同等对待。lambda和匿名内部类是有区别的,主要区别在于匿名内部类中的this和lambda中的this指代的是不同的实例。匿名内部类中的this指向当前匿名内部类的实例(且匿名内部类中的this只能指向内部类的实例,不能指向所在类的实例),而lambda中的this指向所在类的实例,测试如下:
private String value = "Enclosing scope value";
@Test
public void Test6() {
    int num = 333;
    MyInterface<Employee> myInterface = new MyInterface<Employee>() {

        String value = "Inner class value";
        @Override
        public boolean filter(Employee employee) {
            System.out.println("resultIC(this):"+this.value+num);
            System.out.println("resultIC(normal):"+value+num);
            return false;
        }
    };

    Employee employee = employees.get(0);
    myInterface.filter(employee);

    MyInterface<Employee> myInterface1 = employee1 -> {
        String value = "Lambda value";
        System.out.println("resultLambda(normal):"+value+num);
        System.out.println("resultLambda(this):"+this.value+num);
        this.value = "show";
        return true;
    };
    System.out.println("this.value(before filter):"+this.value);
    myInterface1.filter(employee);
    System.out.println("this.value(after filter):"+this.value);
}
打印结果:
resultIC(this):Inner class value333
resultIC(normal):Inner class value333
this.value(before filter):Enclosing scope value
resultLambda(normal):Lambda value333
resultLambda(this):Enclosing scope value333
this.value(after filter):show
  1. 多使用方法引用, 在Lambda表达式中 a -> a.toLowerCase()和String::toLowerCase都能起到相同的作用,但两者相比,后者通常可读性更高并且代码会简短。
  2. 尽量避免在Lambda的方法体中使用{}代码块:
优先使用
Foo foo = parameter -> buildString(parameter);
private String buildString(String parameter) {
    String result = "Something " + parameter;
    //many lines of code
    return result;
}
而不是
Foo foo = parameter -> { String result = "Something " + parameter;
    //many lines of code
    return result;
};

lambda语法

lambda表达式的语法主要可以参考github on java8

java8四大内置核心函数式接口

消费型接口(Consumer)

消费型接口源码:

/**
 * Represents an operation that accepts a single input argument and returns no
 * result. Unlike most other functional interfaces, {@code Consumer} is expected
 * to operate via side-effects.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #accept(Object)}.
 *
 * @param <T> the type of the input to the operation
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}

从上述注释中可以看出,消费型接口就是接收一个参数,然后利用这个参数完成一系列操作。消费型接口使用:

public void happy(double money, Consumer<Double> consumer){
    consumer.accept(money);
}
@Test
public void test1(){
    happy(10, (m)-> System.out.println(m));
    // 打印:10.0
}
@Test
public void test5(){
	// 与使用lambda表达式的效果相同
    happy(10, new Consumer<Double>() {
        @Override
        public void accept(Double aDouble) {
            System.out.println(aDouble);
        }
    });
}

我们必须有一个方法A调用了消费型接口的accept方法,然后运行的时候调用方法A并传入一个消费者对象(可以通过匿名内部类完成),使用lambda则是简化了这段代码。

供给型接口(Supplier)

供给型接口源码:

/**
 * Represents a supplier of results.
 *
 * <p>There is no requirement that a new or distinct result be returned each
 * time the supplier is invoked.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #get()}.
 *
 * @param <T> the type of results supplied by this supplier
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Supplier<T> {

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

供给型接口在get方法完成一系列操作后返回一个结果。供给型接口使用:

public List<Integer> getNumList(int length, Supplier<Integer> integerSupplier){
        List<Integer> list = new ArrayList<>(length);
        for(int i = 0; i < length; i++){
            list.add(integerSupplier.get());
        }
        return list;
    }
    @Test
    public void test2(){
        List<Integer> list = getNumList(10, ()->(int)(Math.random()*100));
        for(Integer in:list){
            System.out.print(in + " ");
        }
        // 打印:41 13 90 13 23 54 69 98 22 25
    }

和消费者接口一样,必有有一个方法A调用供给型接口中的get方法,然后调用这个方法A并提供一个供给型接口参数。

函数型接口(Function)

函数型接口源码:

/**
 * Represents a function that accepts one argument and produces a result.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #apply(Object)}.
 *
 * @param <T> the type of the input to the function
 * @param <R> the type of the result of the function
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}

函数型接口使用:

@Test
public void test3(){
    System.out.println(strHandler("test", (s)->s.toUpperCase()));
    // 打印:TEST
}

public String strHandler(String str, Function<String, String> function){
    return function.apply(str);
}

断言型接口(Predicate)

断言型接口源码:

/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 *
 * @param <T> the type of the input to the predicate
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

断言型接口使用:

@Test
public void test4(){
    List<String> list = Arrays.asList("Hello", "Lambda", "Go", "java");
    list = filterStr(list, (s)->s.contains("o"));
    for(String str:list){
        System.out.print(str+" ");
    }
    // 打印:Hello Go
}
public List<String> filterStr(List<String> list, Predicate<String> predicate){
    List<String> stringList = new ArrayList<>(16);
    for(String str: list){
        if(predicate.test(str)){
            stringList.add(str);
        }
    }
    return stringList;
}

方法引用

什么是方法引用

若lambda体中的内容已经有方法已经实现了,那么就可以使用方法引用

其实方法引用只是在lambda的基础上进一步简化编程的繁重工作,上面这句话就很好地说明了这一点,在使用lambda的过程中,也许我们会自己实现所需要的方法,但是如果已经有实现好的方法时,我们直接调用就好了,此时java就使用一种叫做方法引用的东西来简化调用的代码。使用方法引用主要有如下形式:

  • 引用静态方法 Class::staticMethodName
  • 引用某个对象的实例方法 object::instanceMethodName
  • 引用某个类型的任意对象的实例方法 type::methodName
  • 引用构造方法 ClassName::new

引用某个对象的实例方法

废话不多说,直接看代码:

@Test
public void test1(){
    // 使用lambda
    PrintStream ps = System.out;
    Consumer<String> con = (x)->ps.println(x);
    con.accept("haha");

    // 使用方法引用(对象::实例方法名)
    PrintStream ps1 = System.out;
    Consumer<String> con1 = ps::println;
    con1.accept("dd");

    Consumer<String> con2 = System.out::println;
    con2.accept("iop");
    /**
     * 打印结果:
     * haha
     * dd
     * iop
     */
}

上面的代码就展示了从lambda到方法引用的改变过程。当然,这里只是引用某个对象的实例方法。接下来进行一点改变:

@Test
public void test2(){
    Employee employee = new Employee("李四",33,24);
    Supplier<String> supplier = ()-> employee.getName();
    System.out.println(supplier.get());
    // 对象::实例方法名
    Supplier<Integer> supplier1 = employee::getAge;
    System.out.println(supplier1.get());
    /**
     * 打印结果:
     * 李四
     * 33
     */
}

上面的test2()使用供应者接口,通过供应者接口拿到一个Integer,再输出到cmd,使用的还是 **对象::实例方法名 **。

引用静态方法

@Test
public void test3(){
    Comparator<Integer> comparator = (x,y)->Integer.compare(x,y);

    // 类名::静态方法名
    Comparator<Integer> comparator1 = Integer::compare;

    System.out.println(comparator.compare(10,11));
    System.out.println(comparator1.compare(10,11));
    /**
     * 打印结果:
     * -1
     * -1
     */
}

上面的test3()是通过 Integer::compare 来生成一个Comparator的实例,再调用这个实例的compare方法来获取比较的结果,并将其输出到打印台。

引用某个类型的任意对象的实例方法

这个引用讲道理我还不会用,主要是看到网上有人说有这个,自己对这些的研究还不够,暂时先写到这里,后面有机会再来更新一下。

构造器引用

构造器引用,顾名思义,其实就是通过类名::new来获取一个引用,过程中使用了构造器。

@FunctionalInterface
// 取名这么随意主要是测试了一些今天看到的泛型使用时泛型的名称随意使用
interface MyFunction<T,U,R,J>{
    J apply(T t,U u,R r);
}
@Test
public void test5(){
    Supplier<Employee> supplier = ()->new Employee("dd",22,33);
    System.out.println(supplier.get());
    // 其中new使用的Employee构造器与函数式接口中传入的相同,如下,
    // Supplier<Employee>只是说明要获取一个Employee对象,但是没有传入参数,则调用的为无参构造器
    Supplier<Employee> supplier1 = Employee::new;
    System.out.println(supplier1.get());
    System.out.println("-------------");

    // Function<Integer, Employee> function = Employee::new;
    // 由于使用了lombok,这里就不能这样写了,得传入三个参数,然后返回一个Employee
    // 于是自定义了一个函数式接口,传入三个参数,返回一个值(这里返回Employee)
    MyFunction<String, Integer, Integer, Employee> function = Employee::new;
    System.out.println(function.apply("dd",33,900));
    /**
     * 打印结果:
     * Employee(name=dd, age=22, salary=33)
     * Employee(name=null, age=0, salary=0)
     * -------------
     * Employee(name=dd, age=33, salary=900)
     */
}

其实很多在上面代码中的注释中已经写清楚了,这里就不再赘述。这里还有学到的一个例子,如下

@Test
public void test6(){
    Function<Integer, String[]> function = (x)->new String[x];
    String[] strings = function.apply(10);
    System.out.println(strings.length);

    Function<Integer, Integer[]> function1 = Integer[]::new;
    System.out.println(function1.apply(20).length);
    /**
     * 打印结果:
     * 10
     * 20
     */
}

这个test中使用的是Integer[]::new,而不是普通的类名::new,所以我觉得还是有必要拿出来看看的,以后使用的时候可以参考下。

总结

函数式编程就和vim一样,是一个熟能生巧的东西,结合Stream可以极大地方面编程,所以以后还是要多练多使用。

标签:编程,System,Java8,employee,Employee,new,Lambda,public,out
From: https://www.cnblogs.com/luolin-cn/p/18323031

相关文章

  • 防御性编程:让系统坚不可摧
    1.引言面对复杂多变的运行环境、不可预测的用户输入以及潜在的编程错误,如何确保软件在遭遇异常情况时依然能够稳定运行,是每位开发者必须面对的挑战。防御性编程(DefensiveProgramming)正是为解决这一问题而生的一种编程范式,它强调在编程过程中预见并防范潜在的错误和异常情况,从......
  • AWS Lambda 没有名为“regex._regex”的模块
    我一直在尝试使用python3.9通过AWSLambda运行一些代码,但在运行代码时不断遇到问题。我不断收到“没有名为'regex._regex'的模块”错误。几天前,我在通过PyCharm在本地工作时遇到了这个问题,但我想通过安装更新​​版本的正则表达式来解决这个问题(老实说,我不记得到底是什......
  • JavaWeb(7) DOM编程
    目录一、什么是DOM编程二、获取页面元素的API1.在整个文档范围内查找元素结点2.在具体元素节点范围内查找子节点3.查找指定子元素节点的父节点4.查找指定元素节点的兄弟节点5.整体代码演示三、操作元素属性值API1.属性操作2.内部文本操作3.整体代码演示 四、增......
  • 油管视频《编程思维》中的题目,使用C语言编写出来,第二集,反抗
    题目,假设要在人群中找一位领袖,领袖的相关信息有,他的眼睛是绿色的,如果他长着红头发,名字至少两个连续字母相同,如果戴眼镜的话,名字中有且仅有2个元音,否则名字中会有三个元音,只有一人附和以上条件,请下达指令涉及编程的基础原理1,结构体的使用,用于存储每个人的信息2,字符串的处理,......
  • 油管视频《编程思维》中的题目,使用C语言编写出来,第三集,炉膛机器人
    题目:假设起初只有一个机器人,他的炉膛里有一个数字0,和另一个未知的任意生成的编码,随着推移,原始机器人自我复制,制造出更多一样的炉膛机器人,被原始机器人自我复制制造出的每一个子机器人的熔炉内,都继承了原始机器人未知的编码,并且有一个属于自己,独一无二的编码刻在外壳,第二代炉膛......
  • Java编程指南:高级技巧解析 - Excel单元格样式的编程设置
    最新技术资源(建议收藏)https://www.grapecity.com.cn/resources/前言在Java开发中,处理Excel文件是一项常见的任务。在处理Excel文件时,经常需要对单元格进行样式设置,以满足特定的需求和美化要求,通过使用Java中的相关库和API,我们可以轻松地操作Excel文件并设置单元格的样式。在......
  • 学了十几种编程语言后,我终于悟了!
    大家好,我是程序员鱼皮。16~24年,算下来我学编程8年多了,这期间我学过十几种编程语言,比如C、C++、Java、Python、JavaScript、Go、PHP、C#、SQL、Scala等。这么一看,目前排名前10的语言除了Fortran没接触过外,别的语言或多或少都写过点儿东西。VisualBasic是高中考计......
  • C语言面向对象风格编程解惑-全局变量性能分析
    C语言面向对象风格编程解惑-全局变量性能分析如果你是CPP老手,但在软件开发过程中要求采用C语言作为主要语言,首先遇到的是各种设计模式不方便应用了,感到非常困扰,然后就是认命之后走向另外一个极端,常常会有过度使用全局变量和goto语句的问题。CPP既然是CWithClass,自然不会排斥面......
  • 都4202年了为什么大厂程序员还在用java8?
    Java8新特性文章目录Java8新特性接口的默认方法(DefaultMethodsforInterfaces)Lambda表达式(Lambdaexpressions)函数式接口(FunctionalInterfaces)方法和构造函数引用(MethodandConstructorReferences)Lambda表达式作用域(LambdaScopes)访问局部变量访问字......
  • 【React】箭头函数:现代 JavaScript 的高效编程方式
    文章目录一、箭头函数的基本语法二、箭头函数的特性三、在React中的常见用法四、最佳实践在现代JavaScript中,箭头函数(ArrowFunctions)是一种简洁的函数表达方式,并且在React开发中非常常见。箭头函数不仅简化了函数的语法,还带来了与普通函数不同的行为特性。本......