详解JAVA 函数式编程

1.函数式接口

1.1概念:

java中有且只有一个抽象方法的接口。

1.2格式:

修饰符 interface 接口名称 {
public abstract 返回值类型 方法名称(可选参数信息);
// 其他非抽象方法内容
 }

//或者

public interface MyFunctionalInterface {
void myMethod();
 }

1.3@FunctionalInterface注解:

与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注
解可用于一个接口的定义上:

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

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

1.4自定义函数式接口

public class Demo09FunctionalInterface {
// 使用自定义的函数式接口作为方法参数
private static void doSomething(MyFunctionalInterface inter) { inter.myMethod(); // 调用自定义的函数式接口方法
}
public static void main(String[] args) {
// 调用使用函数式接口的方法 doSomething(() ‐> System.out.println("Lambda执行啦!"));
} }

2.函数式编程

2.1 Lambda的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。

性能浪费的日志案例

注:日志可以帮助我们快速的定位问题,记录程序运行过程中的情况,以便项目的监控和优化。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:

public class Demo01Logger {
    private static void log(int level, String msg) {
      if (level == 1) {
        System.out.println(msg);
      }
    }

    public static void main(String[] args) {
      String msgA = "Hello";
      String msgB = "World";
      String msgC = "Java";
      log(1, msgA + msgB + msgC);
    }
  }

这段代码存在问题:无论级别是否满足要求,作为 log 方法的第二个参数,三个字符串一定会首先被拼接并传入方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。

备注:

SLF4J是应用非常广泛的日志框架,它在记录日志时为了解决这种性能浪费的问题,并不推荐首先进行字符串的拼接,而是将字符串的若干部分作为可变参数传入方法中,仅在日志级别满足要求的情况下才会进行字符串拼接。

例如: LOGGER.debug("变量{}的取值为{}。", "os", "macOS") ,其中的大括号 {} 为占位符。
如果满足日志级别要求,则会将“os”和“macOS”两个字符串依次拼接到大括号的位置;否则不会进行字符串拼接。这也是一种可行解决方案,但Lambda可以做到更好。

体验Lambda的更优写法

使用Lambda必然需要一个函数式接口:

  @FunctionalInterface
  public interface MessageBuilder {
    String buildMessage();
  }
public class Demo02LoggerLambda {
    private static void log(int level, MessageBuilder builder) {
      if (level == 1) {
        System.out.println(builder.buildMessage());
      }
    }
    public static void main(String[] args) {
      String msgA = "Hello";
      String msgB = "World";
      String msgC = "Java";
      log(1, () ‐ > msgA + msgB + msgC );
    }
  }

这样一来,只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。

证明Lambda的延迟

下面的代码可以通过结果进行验证

public class Demo03LoggerDelay {
    private static void log(int level, MessageBuilder builder) {
      if (level == 1) {
        System.out.println(builder.buildMessage());
      }
    }

    public static void main(String[] args) {
      String msgA = "Hello";
      String msgB = "World";
      String msgC = "Java";
      log(2, () ‐ > {System.out.println("Lambda执行!"); return msgA + msgB + msgC; });
    }
  }

从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。
扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法
来完成。而是否调用其所在方法是在条件判断之后才执行的。

2.2 使用Lambda作为参数和返回值

如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。

例如 java.lang.Runnable 接口就是一个函数式接口,假设有一个 startThread 方法使用该接口作为参数,那么就可以使Lambda进行传参。这种情况其实和 Thread 类的构造方法参数为 Runnable 没有本质区别。

public class Demo04Runnable {
    private static void startThread(Runnable task) {
      new Thread(task).start();
    }

    public static void main(String[] args) {
      startThread(() ‐ > System.out.println("线程任务执行!"));
    }
  }

类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个 java.util.Comparator 接口类型的对象作为排序器时,就可以调该方法获取。

import java.util.Arrays;
  import java.util.Comparator;

  public class Demo06Comparator {
    private static Comparator<String> newComparator() {
      return (a,b) ‐>b.length() ‐a.length();
    }

    public static void main(String[] args) {
      String[] array = {"abc", "ab", "abcd"};
      System.out.println(Arrays.toString(array));
      Arrays.sort(array, newComparator());
      System.out.println(Arrays.toString(array));
    }
  }

其中直接return一个Lambda表达式即可。

3.常用函数式接口

JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在 java.util.function 包中被提供。

下面是最简单的几个接口及使用示例。

3.1 Supplier接口(求数组元素最大值)

java.util.function.Supplier<T> 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。

求数组元素最大值

使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用java.lang.Integer 类。

public class Demo02Test {
    //定一个方法,方法的参数传递Supplier,泛型使用Integer
    public static int getMax(Supplier<Integer> sup) {
      return sup.get();
    }

    public static void main(String[] args) {
      int arr[] = {2, 3, 4, 52, 333, 23}; //调用getMax方法,参数传递Lambda
      int maxNum = getMax(()‐ > {
      //计算数组的最大值
      int max = arr[0];
      for (int i : arr) {
        if (i > max) {
          max = i;
        }
      }
      return max; });
      System.out.println(maxNum);
    }
  }

3.2 Consumer接口

java.util.function.Consumer<T> 接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。

抽象方法:accept

Consumer 接

import java.util.function.Consumer;

  public class Demo09Consumer {
    private static void consumeString(Consumer<String> function) {
      function.accept("Hello");
    }

    public static void main(String[] args) {
      consumeString(s ‐ > System.out.println(s));
    }
  }

默认方法:andThen

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。下面是JDK的源代码:口中包含抽象方法 void accept(T t) ,意为消费一个指定泛型的数据。基本使用如:

格式化打印信息

下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。 ”的格式将信息打印出来。要求将打印姓名的动作作为第一个 Consumer 接口的Lambda实例,将打印性别的动作作为第二个 Consumer 接口的Lambda实例,将两Consumer 接口按照顺序“拼接”到一起。

import java.util.function.Consumer;

  public class DemoConsumer {
    public static void main(String[] args) {
      String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男"};
      printInfo(s ‐ > System.out.print("姓名:" + s.split(",")[0]), s ‐>
      System.out.println("。性别:" + s.split(",")[1] + "。"), array);
    }

    private static void printInfo(Consumer<String> one, Consumer<String> two, String[] array) {
      for (String info : array) {
        one.andThen(two).accept(info); // 姓名:迪丽热巴。性别:女。
      }
    }
  }

3.3 Predicate接口

有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T> 接口。

抽象方法:test

Predicate 接口中包含一个抽象方法: boolean test(T t) 。用于条件判断的场景:

import java.util.function.Predicate;

  public class Demo15PredicateTest {
    private static void method(Predicate<String> predicate) {
      boolean veryLong = predicate.test("HelloWorld");
      System.out.println("字符串很长吗:" + veryLong);
    }

    public static void main(String[] args) {
      method(s ‐ > s.length() > 5);
    }
  }

默认方法:and

既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and 。其JDK源码为

import java.util.function.Predicate;

  public class Demo16PredicateAnd {
    private static void method(Predicate<String> one, Predicate<String> two) {
      boolean isValid = one.and(two).test("Helloworld");
      System.out.println("字符串符合要求吗:" + isValid);
    }

    public static void main(String[] args) {
      method(s ‐ > s.contains("H"), s ‐>s.contains("W"));
    }
  }

默认方法:or

与 and 的“与”类似,默认方法 or 实现逻辑关系中的“或”。JDK源码为:

import java.util.function.Predicate;

  public class Demo16PredicateAnd {
    private static void method(Predicate<String> one, Predicate<String> two) {
      boolean isValid = one.or(two).test("Helloworld");
      System.out.println("字符串符合要求吗:" + isValid);
    }

    public static void main(String[] args) {
      method(s ‐ > s.contains("H"), s ‐>s.contains("W"));
    }
  }

默认方法:negate

“与”、“或”已经了解了,剩下的“非”(取反)也会简单。默认方法 negate 的JDK源代码为:从实现中很容易看出,它是执行了test方法之后,对结果boolean值进行“!”取反而已。一定要在 test 方法调用之前调用 negate 方法,正如 and 和 or 方法一样:

import java.util.function.Predicate;

  public class Demo17PredicateNegate {
    private static void method(Predicate<String> predicate) {
      boolean veryLong = predicate.negate().test("HelloWorld");
      System.out.println("字符串很长吗:" + veryLong);
    }

    public static void main(String[] args) {
      method(s ‐ > s.length() < 5);
    }
  }

信息筛选

数组当中有多条“姓名+性别”的信息如下,请通过 Predicate 接口的拼装将符合要求的字符串筛选到集合ArrayList 中,需要同时满足两个条件:

1. 必须为女生;

2. 姓名为4个字。

import java.util.ArrayList; import java.util.List; import java.util.function.Predicate;

  public class DemoPredicate {
    public static void main(String[] args) {
      String[] array = {"迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女"};
      List<String> list = filter(array, s ‐ > "女".equals(s.split(",")[1]), s ‐>s.split(",")[0].length() == 4);
      System.out.println(list);
    }

    private static List<String> filter(String[] array, Predicate<String> one, Predicate<String> two) {
      List<String> list = new ArrayList<>();
      for (String info : array) {
        if (one.and(two).test(info)) {
          list.add(info);
        }
      }
      return list;
    }
  }

3.4 Function接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

抽象方法:apply

Function 接口中最主要的抽象方法为: R apply(T t) ,根据类型T的参数获取类型R的结果。使用的场景例如:将 String 类型转换为 Integer 类型。

import java.util.function.Function;

  public class Demo11FunctionApply {
    private static void method(Function<String, Integer> function) {
      int num = function.apply("10");
      System.out.println(num + 20);
    }

    public static void main(String[] args) {
      method(s ‐ > Integer.parseInt(s));
    }
  }

默认方法:andThen

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

练习:自定义函数模型拼接

题目
请使用 Function 进行函数模型的拼接,按照顺序需要执行的多个函数操作为:

String str = "赵丽颖,20";

1. 将字符串截取数字年龄部分,得到字符串;
2. 将上一步的字符串转换成为int类型的数字;
3. 将上一步的int数字累加100,得到结果int数字。

import java.util.function.Function;

  public class DemoFunction {
    public static void main(String[] args) {
      String str = "赵丽颖,20";
      int age = getAgeNum(str, s ‐ > s.split(",")[1], s ‐>Integer.parseInt(s), n ‐>n += 100);
      System.out.println(age);
    }

    private static int getAgeNum(String str, Function<String, String> one, Function<String, Integer> two, Function<Integer, Integer> three) {
      return one.andThen(two).andThen(three).apply(str);
    }
  }

以上就是详解JAVA 函数式编程的详细内容,更多关于JAVA 函数式编程的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java函数式编程(十一):遍历目录

    列出目录中的文件 用File类的list()方法可以很容易的列出目录中的所有文件的文件名.如果想要获取文件而不止是文件名的话,可以使用它的listFiles()方法.这很简单,难的是怎么去处理这个返回的列表.我们不再使用传统的冗长的外部迭代器,而是使用优雅的函数式来实遍历这个列表.这里我们还得用到JDK的新的CloseableStream接口以及一些相关的高阶函数. 下面这段代码可以列出当前目录下所有文件的名字. 复制代码 代码如下: Files.list(Paths.get(".")

  • Java函数式编程(十二):监控文件修改

    使用flatMap列出子目录 前面已经看到如何列出指定目录下的文件了.我们再来看下如何遍历指定目录的直接子目录(深度为1),先实现一个简单的版本,然后再用更方便的flatMap()方法来实现. 我们先用传统的for循环来遍历一个指定的目录.如果子目录中有文件,就添加到列表里:否则就把子目录添加到列表里.最后,打印出所有文件的总数.代码在下面--这个是困难模式的. 复制代码 代码如下: public static void listTheHardWay() {      List<File> f

  • 深入了解java 8的函数式编程

    前言 关于"Java 8为Java带来了函数式编程"已经有了很多讨论,但这句话的真正意义是什么? 本文将讨论函数式,它对一种语言或编程方式意味着什么.在回答"Java 8的函数式编程怎么样"之前,我们先看看Java的演变,特别是它的类型系统,我们将看到Java 8的新特性,特别是Lambda表达式如何改变Java的风景,并提供函数式编程风格的主要优势. 函数式编程语言是什么? 函数式编程语言的核心是它以处理数据的方式处理代码.这意味着函数应该是第一等级(First-

  • Java函数式编程(十):收集器

    前面我们已经用过几次collect()方法来将Stream返回的元素拼成ArrayList了.这是一个reduce操作,它对于将一个集合转化成另一种类型(通常是一个可变的集合)非常有用.collect()函数,如果和Collectors工具类里的一些方法结合起来使用的话,能提供极大的便利性,本节我们将会介绍到. 我们还是继续使用前面的Person列表作为例子,来看一下collect()方法到底有哪些能耐.假设我们要从原始列表中找出所有大于20岁的人.下面是使用了可变性和forEach()方法实现

  • Java8深入学习系列(二)函数式编程

    前言 在之前的一篇文章中我们快速学习了lambda和Stream,本章节中我们来回顾和理解函数式编程的思想. 我们不断的提及函数式这个名词,它指的是lambda吗?如果是这样,采用函数式编程能为你带来什么好处呢? 函数式的思考 命令式编程 一般我们实现一个系统有两种思考方式,一种专注于如何实现,比如下厨做菜,通常按照自己熟悉的烹饪方法:首先洗菜, 然后切菜,热油,下菜,然后-- 这看起来像是一系列的命令合集.对于这种"如何做"式的编程风格我们称之为命令式编程, 它的特点非常像工厂的流水

  • 浅析Java8的函数式编程

    前言 本系列博客,介绍的是JDK8 的函数式编程,那么第一个问题就出现了,为什么要出现JDK8?   JAVA不是已经很好,很强大了吗,很多公司用的还是1.6,1.7呀,1.8有必要吗?更不要提即将问世的JDK9了,鲁迅的<拿来主义>说过这么一句话   JAVA如果真的这么完美无缺,那为什么还会有其他语言的兴盛呢?所以说,没有一样东西是绝对完美的,JDK8包括之后的版本,就是不断的完善JAVA语言,让它往更好的方向上去走,面向过程有它的缺点,然而无疑也有它的优点,在JAVA8 之前,JAVA欠

  • 详解JAVA 函数式编程

    1.函数式接口 1.1概念: java中有且只有一个抽象方法的接口. 1.2格式: 修饰符 interface 接口名称 { public abstract 返回值类型 方法名称(可选参数信息); // 其他非抽象方法内容 } //或者 public interface MyFunctionalInterface { void myMethod(); } 1.3@FunctionalInterface注解: 与 @Override 注解的作用类似,Java 8中专门为函数式接口引入了一个新的注解

  • 详解Java函数式编程和lambda表达式

    为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于某种语法或调用API去进行编程.例如,我们现在需要从一组数字中,找出最小的那个数字,若使用用命令式编程实现这个需求的话,那么所编写的代码如下: public static void main(String[] args) { int[] nums = new int[]{1, 2, 3, 4, 5,

  • 详解Java网络编程

    一.网络编程 1.1.概述 1.计算机网络是通过传输介质.通信设施和网络通信协议,把分散在不同地点的计算机设备互连起来,实现资源共享和数据传输的系统.网络编程就就是编写程序使联网的两个(或多个)设备(例如计算机)之间进行数据传输.Java语言对网络编程提供了良好的支持,通过其提供的接口我们可以很方便地进行网络编程. 2.Java是 Internet 上的语言,它从语言级上提供了对网络应用程 序的支持,程序员能够很容易开发常见的网络应用程序. 3.Java提供的网络类库,可以实现无痛的网络连接,联

  • 详解Java并发编程基础之volatile

    目录 一.volatile的定义和实现原理 1.Java并发模型采用的方式 2.volatile的定义 3.volatile的底层实现原理 二.volatile的内存语义 1.volatile的特性 2.volatile写-读建立的happens-before关系 3.volatile的写/读内存语义 三.volatile内存语义的实现 1.volatile重排序规则 2.内存屏障 3.内存屏障示例 四.volatile与死循环问题 五.volatile对于复合操作非原子性问题 一.volati

  • 详解Java事件编程的使用

    Java事件编程 当前在线网店很多,很涉及商品管理和销售的问题,比如: 一,在商品库存管理的商品增加时,我们主要业务时编辑保持商品信息, 同时因商品增加而附带有一些"非主要业务",如: 1,应商品的库存数量等更新, 2,热销产品的推广处理等 二,在商品产生订单时,我们的主要业务(对买家而言)是建立订单业务, 同时因产生订单而附带有一些不是买家关心的"非主要业务",如: 1,库存和已售数量的更新 2,发货的准备处理事宜 3,物流的处理事宜 非主要业务我们可以让程序使用

  • 详解Java并发编程之原子类

    目录 原子数组 AtomicIntegerArray 原子更新器 AtomicIntegerFieldUpdater 原子累加器 LongAdder 原子数组 原子数组有AtomicIntegerArray.AtomicLongArray.AtomicReferenceArray,主要是用来对数组中的某个元素进行原子操作.三个类的方法基本类似,这里只介绍一下AtomicIntegerArray的方法. AtomicIntegerArray 两个构造方法,第一个构造方法传入数组长度初始化一个所有值

  • 详解Python函数式编程—高阶函数

    函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用.而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的. 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数! Python对函数式编程提供部分支持.由于Python允许使用变量,因此,Python不是纯函数式编程语言. 高阶函数 变量

  • 详解用函数式编程对JavaScript进行断舍离

    我和JavaScript 从1997年网景的Navigator 3浏览器开始就开始使用JavaScript.当时,JavaScript还只能做一些很简单的事情.我记得最酷的就是用JavaScript实现mouseover特性,在那个时候已经算得上是高科技了!当鼠标移过去之后,文本内容就神奇的改变了.因为当时都是pre-DHTML,你根本不需要隐藏或则显示DOM元素. 关于DHTML DHTML是Dynamic HTML的简称,就是动态的html(标准通用标记语言下的一个应用),是相对传统的静态的

  • 详解java并发编程(2) --Synchronized与Volatile区别

    1 Synchronized 在多线程并发中synchronized一直是元老级别的角色.利用synchronized来实现同步具体有一下三种表现形式: 对于普通的同步方法,锁是当前实例对象. 对于静态同步方法,锁是当前类的class对象. 对于同步方法块,锁是synchronized括号里配置的对象. 当一个代码,方法或者类被synchronized修饰以后.当一个线程试图访问同步代码块的时候,它首先必须得到锁,退出或抛出异常的时候必须释放锁.那么这样做有什么好处呢? 它主要确保多个线程在同一

  • 详解Java并发编程之内置锁(synchronized)

    简介 synchronized在JDK5.0的早期版本中是重量级锁,效率很低,但从JDK6.0开始,JDK在关键字synchronized上做了大量的优化,如偏向锁.轻量级锁等,使它的效率有了很大的提升. synchronized的作用是实现线程间的同步,当多个线程都需要访问共享代码区域时,对共享代码区域进行加锁,使得每一次只能有一个线程访问共享代码区域,从而保证线程间的安全性. 因为没有显式的加锁和解锁过程,所以称之为隐式锁,也叫作内置锁.监视器锁. 如下实例,在没有使用synchronize

随机推荐