java — 函数式编程

函数式编程

面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是怎么做
有时只是为了做某事情而不得不创建一个对象,而传递一段代码才是我们真正的目的。

Lambda

Lambda是一个匿名函数,可以理解为一段可以传递的代码。
当需要启动一个线程去完成任务时, 通常会通过java.lang.Runnable接口来定义任务内容,并使用java.lang.Thread类来启动该线程
传统写法,代码如下:

public class Demo {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("多线程任务执行!");
            }
        }).start();
    }
}

借助Java 8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到同样的效果:

public class Demo04LambdaRunnable {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程
    }
}

Lambda的优点 简化匿名内部类的使用,语法更加简单。

前提条件

必须是接口, 接口中有且只有一个抽象方法

有且仅有一个抽象方法的接口,称为函数式接口

格式

Lambda表达式的标准格式为:

() -> {}
() 参数列表,无参数留空
-> 固定写法, 代表指向动作
{} 方法体

省略规则
在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 参数类型可省略
  2. 如果只有一个参数 ()可以省略
  3. 如果方法体只有一句话 return 大括号 分号都可省略, 但必须同时省略
new Thread(() -> System.out.println("省略格式开启线程")).start();

原理

  1. 在匿名方法所在类中新增一个方法,方法体为Lambda表达式中的代码
  2. 运行时形成一个新的类,并实现对应接口
  3. 重写方法的方法体中调用匿名方法所在类中新生成的方法.

函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。

从应用层面来讲,Java中的Lambda可以看做是匿名内部类的简化格式,但是二者在原理上不同。

格式

只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称 {
    public abstract 返回值类型 方法名称(可选参数信息);
}

由于接口当中抽象方法的public abstract是可以省略的,所以定义一个函数式接口很简单:

public interface MyFunctionalInterface {
    void myMethod();
}

@FunctionalInterface

@FunctionalInterface 该注解可用于一个接口的定义上:

@FunctionalInterface
public interface MyFunctionalInterface {
    void myMethod();
}

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口

常用函数式接口

Supplier接口

java.util.function.Supplier<T>接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
抽象方法:
T get() 用来获取一个泛型参数指定类型的对象数据
求数组元素最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值

public class supplierInterface {
    public static void main(String[] args) {
        int[] arr = {3,24,346,4,13};
        method(() -> {
            Arrays.sort(arr);
            return arr[arr.length - 1];
        });
    }

    public static void method(Supplier<Integer> s) {
        Integer max = s.get();
        System.out.println(max);
    }
}
Consumer接口

java.util.function.Consumer<T> 接口不生产数据,而是消费一个数据,其数据类型由泛型参数决定
抽象方法
void accept(T t),意为消费一个指定泛型的数据
默认方法
default Consumer<T> andThen(Consumer<? super T> after)

public class _4_consumerInterface {
    public static void main(String[] args) {
        method("Hello World", (String s) -> {
            System.out.println(s.toUpperCase());
        });

        method("HEllO WorlD", s -> System.out.println(s.toLowerCase()));

        System.out.println("==========================");
        method("HEllO WorlD", (String s) -> {
            System.out.println(s.toUpperCase());
        }, (String s) -> {
            System.out.println(s.toLowerCase());
        });
        method("HEllO WorlD",
                s -> System.out.println(s.toUpperCase()),
                s -> System.out.println(s.toLowerCase())
        );
    }

    public static void method(String s, Consumer<String> c) {
        c.accept(s);
    }
    public static void method(String s, Consumer<String> c1, Consumer<String> c2) {
//        c1.accept(s);
//        c2.accept(s);
        // andThen c1.accept(s)后执行c2.accept(s) 等同于上面的写法
        c1.andThen(c2).accept(s);
    }
}
Function接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
抽象方法:
R apply(T t) 根据类型T的参数获取类型R的结果

public class Test {
    public static void main(String[] args) {
        Function<String,Integer> f = new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.parseInt(s);
            }
        };

        Integer apply = f.apply("100");
        System.out.println(apply);

        Function<String,Integer> f2 = s -> Integer.parseInt(s);
        System.out.println(f2.apply("200"));
    }
}

默认方法:andThen

Function接口中有一个默认的andThen方法,用来进行组合操作,与Consumer接口相同

public class Test {
    public static void main(String[] args) {
        method5("10", (String s) -> {
            return Integer.parseInt(s);
        }, (Integer i) -> {
            return i * 10;
        });
        method5("100", s -> Integer.parseInt(s), i -> i * 10);
        method5("1000", Integer::parseInt, i -> i * 10);
    }
    private static void method5(String s, Function<String, Integer> f1, Function<Integer, Integer> f2) {
//        Integer i = f1.apply(s);
//        Integer n = f2.apply(i);
        Integer n = f1.andThen(f2).apply(s);
        System.out.println(n);
    }
}

Function的前置条件泛型和后置条件泛型可以相同

Predicate接口

java.util.function.Predicate 判断型接口
抽象方法: boolean test(T t) 返回boolean

public class predicateInterface {
    public static void main(String[] args) {
        method("HelloWorld.java", (String s) -> {
            return s.toLowerCase().endsWith(".java");
        });

        method("Hello.java", s -> s.toLowerCase().endsWith(".java"));

    }
    public static void method(String filename, Predicate<String> p) {
        boolean b = p.test(filename);
        System.out.println(b);
    }
}

默认方法
Predicate<T> and(Predicate<? super T> other) 并且, 底层使用 &&
Predicate<T> or(Predicate<? super T> other) 或者, 底层使用 ||
Predicate<T> negate() 取反, 底层使用 !

public class Test {
    public static void main(String[] args) {
        method("Helloworld" ,s -> s.contains("H"), s -> s.contains("W"));
    }
    private static void method(String str ,Predicate<String> one, Predicate<String> two) {
        boolean b1 = one.test(str);
        boolean b2 = two.test(str);
        System.out.println("字符串符合要求吗:" + (b1 && b2));

        boolean isValid = one.and(two).test(str);
        System.out.println("字符串符合要求吗:" + isValid);
    }
}

public class Test {
    public static void main(String[] args) {
        method("Helloworld" ,s -> s.contains("H"), s -> s.contains("W"));
    }
    private static void method(String str ,Predicate<String> one, Predicate<String> two) {
        boolean b1 = one.test(str);
        boolean b2 = two.test(str);
        System.out.println("字符串符合要求吗:" + (b1 || b2));

        boolean isValid = one.or(two).test(str);
        System.out.println("字符串符合要求吗:" + isValid);
    }
}


public class Test {
    public static void main(String[] args) {
       isLong("aaa", new Predicate<String>() {
           @Override
           public boolean test(String s) {
               return  s.length()<5;
           }
       });
       isLong("bbbaa",s -> s.length()>=5);
    }
    public static void isLong(String s , Predicate<String> p){
        boolean test = p.test(s);
        System.out.println(!test);
        boolean b2 =  p.negate().test(s);
        System.out.println(b2);
    }
}

方法引用

前提

Lambda表达式中只有一句话时 可能使用

格式

符号表示 : ::
符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用
推导与省略 : ** 如果使用Lambda,那么根据“可推导就是可省略**”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导

应用Lambda表达式 , 在accept方法中接收字符串 , 目的就是为了打印显示字符串 , 那么通过Lambda来使用它的代码很简单:

public class DemoPrintSimple {
    private static void printString(Consumer<String> data, String str) {
        data.accept(str);
    }
    public static void main(String[] args) {
      	printString(s -> System.out.println(s), "Hello World");
    }
}

使用方法引用进行简化

public class DemoPrintRef {
    private static void printString(Consumer<String> data, String str) {
        data.accept(str);
    }
    public static void main(String[] args) {
      	printString(System.out::println, "HelloWorld");
    }
}

其他引用

public class _5_functionInterface {
    public static void main(String[] args) {
        method("100", (String s) -> {
            return Integer.parseInt(s);
        });
        method("10", s -> Integer.parseInt(s));
        /*
            类名引用静态方法
                类名::方法名
         */
        method("1000", Integer::parseInt);
        /*
            类名引用构造方法
                类名::new
         */
        method2("张三", Person::new);
        method2("李四", Person::new);
        method2("王五", s -> new Person(s));
        method3(Person::new);
         /*
            数组引用构造方法
                数据类型[]::new
         */
        method4(5,int[]::new);
        method4(3, (Integer i) -> {
            return new int[i];
        });
        method4(1, i -> new int[i]);

    }

    public static void method(String s, Function<String, Integer> f) {
        Integer n = f.apply(s);
        System.out.println(n);
    }
    private static void method2(String s, Function<String, Person> f) {
        Person p = f.apply(s);
        System.out.println(p);
    }
    private static void method3(Supplier<Person> su) {
        Person p = su.get();
        System.out.println(p);
    }
    private static void method4(Integer i, Function<Integer, int[]> f) {
        int[] arr = f.apply(i);
        System.out.println(Arrays.toString(arr));
    }
}
class Person {
    private String name;

    public Person() {}
    public Person(String name) {
        this.name = name;
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

原文链接:https://www.cnblogs.com/paopaoT/p/17337998.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:java — 函数式编程 - Python技术站

(0)
上一篇 2023年4月22日
下一篇 2023年4月22日

相关文章

  • 将Java项目打包成可执行的jar包

    将Java项目打包成可执行的jar包可以方便地进行部署和发布,本文将介绍完整的打包流程。 1. 准备工作 在打包之前,需要准备好以下内容: 项目代码 Java开发环境(JDK) 指定项目的入口主类 2. 打包操作 下面就开始具体的打包操作步骤。 2.1 编译项目代码 首先需要将项目代码编译,生成class文件。在命令行中进入项目代码的根目录,执行以下命令: …

    Java 2023年5月26日
    00
  • Java有哪些操作字符串的类?区别在哪?

    Java中有多个类可以用于操作字符串,以下是比较常用的几个类: String 类: String 是一个 final 类,字符串是一个对象,一旦被创建,就不能被修改。因为Java中的String对象是可以共享的,所以每次对String进行修改时,都会创建一个新的String对象,影响了性能。 示例1:使用加号操作字符串,每次操作都会创建一个新的 String…

    Java 2023年5月27日
    00
  • java代码实现C盘文件统计工具

    Java代码实现C盘文件统计工具 本攻略介绍如何使用Java编写一个C盘文件统计工具,可以计算C盘某个目录下的文件数量、目录数量、总大小等信息,并输出到控制台。 步骤一:创建Java项目 首先,打开Eclipse,在工作区中创建一个Java项目。 选择菜单栏中的 “File” –> “New” –> “Java Project”。 输入项目的…

    Java 2023年5月19日
    00
  • Maven  pom.xml与settings.xml详解

    Maven pom.xml与settings.xml详解 1. pom.xml 1.1 意义 pom.xml 是 Maven 项目的 XML 形式的配置文件。它存储关于项目的信息,例如它的依赖项,它编译时的类路径,构建插件及其配置,开发者列表,许可证等。 1.2 样例配置 下面是一个标准的pom.xml的例子: <project xmlns=&quot…

    Java 2023年6月2日
    00
  • 魔剑之刃斩魂技能全面介绍及点评

    魔剑之刃斩魂技能全面介绍及点评 什么是斩魂技能? 斩魂技能是魔剑之刃游戏中的一种特殊技能,通过学习和研究斩魂技能,角色可以提升自身战斗力和生存能力。 斩魂技能的分类 魔剑之刃游戏中,斩魂技能分为三大类,分别是: 攻击技能 攻击技能主要用于提高角色的攻击力和输出能力,可以有效地进行单体或群体伤害。举例如下: 感知之刃:消耗30点能量,对单个目标造成大量伤害,并…

    Java 2023年6月16日
    00
  • Ajax添加数据与删除篇实现代码

    下面详细讲解“Ajax添加数据与删除篇实现代码”的完整攻略。 一、准备工作 在正式开始编写Ajax添加数据与删除篇的实现代码前,需要先完成以下准备工作: 确保你已经学习过Ajax基础知识,包括Ajax的基本流程、请求方式、回调函数等等。 确定添加数据与删除篇功能需要操作的数据表格,包括表格名称、字段名称等等。 熟悉服务器端处理Ajax请求的技术,例如PHP、…

    Java 2023年6月15日
    00
  • SpringData JPA中@OneToMany和@ManyToOne的用法详解

    下面我将详细讲解“SpringData JPA中@OneToMany和@ManyToOne的用法详解”的完整攻略。 什么是@OneToMany和@ManyToOne 在关系型数据库中,一个对象与另一个对象之间存在着不同的关系,如一对一、一对多、多对一、多对多等。而在Java中,对象之间的关系可以用多种方式来表示和映射到数据库中。Spring Data JPA…

    Java 2023年5月20日
    00
  • Struts2学习笔记(6)-简单的数据校验

    针对这个话题,下面是一份完整攻略。 Struts2学习笔记(6)-简单的数据校验 前言 在Struts2中,数据校验是开发过程中不可缺少的一部分,而Struts2提供了全面而且灵活的校验机制来实现数据校验。在这篇文章中,我们将介绍Struts2中简单的数据校验。 配置数据校验 Struts2的校验机制主要是通过在Action类中定义方法进行校验,校验方法必须…

    Java 2023年5月20日
    00
合作推广
合作推广
分享本页
返回顶部