精华内容
下载资源
问答
  • Java中的 函数式 编程范式(翻译+汇总)
    2021-03-04 10:01:24

    Java中的 函数式 编程范式(翻译+汇总)

    函数式编程范式 略讲

    每种编程语言均有自己的语法和结构规范定义。 这些规范即称为编程范式。 编程范式的英语是 Programming Paradigm,范即模范之意,范式即模式、方法,是一类典型的编程风格,是指从事软件工程的一类典型的风格。

    现在的可用编程范式有:命令式,面向对象,函数式,逻辑式等等。

    现代编程语言非常复杂,无法建立在一个或多个范例上。

    函数式编程作为编程范式之一,着重于编程的声明性方面,其中业务逻辑由纯函数组成,这种想法在某种程度上与面向对象方法的本质形成了对比。 有趣的是,Java将该技术很好地吸收到了该语言的语法结构中。 现在,开发人员可以使用lambda无缝编织代码的功能方面,而无需破坏现有体系结构,甚至不必担心向后兼容性。

    d993e828f34f

    The evolution of various programming languages

    图中的 Imperative为 命令式,命令式的编程范式是命令驱动的,必须指明任务是什么,并阐述如何完成。

    Declarative为陈述式

    面向对象与函数式

    在OOP中,所有事物都表示为一个对象。 因此,即使只需要实现某一行为,例如简单的加法,也必须定义成一个类及其属性组成的格式。

    在函数式编程中,我们通过函数定义行为,而不通过对象定义行为。 这意味着在函数式编程中,我们直接实现函数,而不是包含函数的类。(This means in functional programming we directly implement a function rather than an class that contains a function)

    这是OOP和功能编程之间的基本区别。

    函数式编程与纯函数式编程(Functional programming vs Purely Functional programming)

    纯函数式编程,本质上不允许任何的可变性

    函数式风格的语言提供了高阶函数,但是也引入了可变性,可能会导致错误

    因此,如果一种语言提供了高阶函数(higher-order function ),那他就是一种函数式编程语言。如果一种语言除了高阶函数外,还控制了其中的可变性(mutability ),那么它就是一种纯函数式语言。

    Java是一种函数式语言,Haskell是一种纯函数式语言

    以下是一些函数式编程的基本概念:

    高阶函数(high-order function): 在函数式编程中,函数为一等公民。在传统的编码风格中,我们可以对对象(Object)做如下几种操作

    将对象作为参数传递给一个函数

    在函数内创建一个对象

    函数返回一个对象

    而在高阶函数中,我们可以做如下处理:

    将函数作为参数传递给一个函数

    在函数内创建一个函数

    函数的返回值为一个函数

    纯函数(Pure function): 如果函数始终为相同的参数值返回相同的结果,并且没有诸如修改参数(或全局变量)或输出某些内容之类的副作用,则该函数称为纯函数。

    lambda 表达式: lambda 表达式是一种匿名方法,有且只有一个参数列表和一个方法主体,并根据上下文推断函数返回类型。另外,Lambda expressions work in parallel with the functional interface.

    Lambda表达式的语法为 (parameter) -> body

    Java7 的匿名方法 对比 Java 8的 lambda表达式

    java 7 :

    // Java program to demonstrate

    // anonymous method

    import java.util.Arrays;

    import java.util.List;

    public class GFG {

    public static void main(String[] args)

    {

    // Defining an anonymous method

    Runnable r = new Runnable() {

    public void run()

    {

    System.out.println(

    "Running in Runnable thread");

    }

    };

    r.run();

    System.out.println(

    "Running in main thread");

    }

    }

    JAVA 8

    import java.util.Arrays;

    import java.util.List;

    public class GFG {

    public static void main(String[] args)

    {

    Runnable r

    = ()

    -> System.out.println(

    "Running in Runnable thread");

    r.run();

    System.out.println(

    "Running in main thread");

    }

    }

    原文:

    更多相关内容
  • java函数式编程入门

    2020-12-21 01:43:18
    以前写过一篇java8的流操作,人们都说流操作是函数式编程,但函数式编程是什么呢? 什么是函数式编程 什么是函数式编程?它是一种编程范式,即一切都是数学函数。函数式编程语言里也可以有对象,但通常这些对象都是...
  • 参考:<-------------------------------------廖雪峰学Java--------------------------------------------> 目录1. 函数式编程基本概念2....函数式编程最早是数学家阿隆佐·邱奇研究的一套函数变换逻辑,又称Lam

    参考:<-------------------------------------廖雪峰学Java-------------------------------------------->

    1. 函数式编程基本概念

    • 函数式编程最早是数学家阿隆佐·邱奇研究的一套函数变换逻辑,又称Lambda Calculus(λ-Calculus),所以也经常把函数式编程称为Lambda计算
    • 计算机(Computer)和计算(Compute)的概念:计算机的层次上,CPU执行的是加减乘除的指令代码,以及各种条件判断和跳转指令;计算则指数学意义上的计算,越是抽象的计算,离计算机硬件越远;对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言
    • 函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的
    • 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数
    • Java不支持单独定义函数,但可以把静态方法视为独立的函数,把实例方法视为自带this参数的函数
    • Java平台从Java 8开始,支持函数式编程(引入lambda表达式)

    2. Lambda基础

    • 函数式编程(Functional Programming)把函数作为基本运算单元,函数可以作为变量,可以接收函数,还可以返回函数。历史上研究函数式编程的理论是Lambda演算,所以我们经常把支持函数式编程的编码风格称为Lambda表达式
    • 从Java 8开始,我们可以用Lambda表达式替换单方法接口
    public class Main {
        public static void main(String[] args) {
            String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
            Arrays.sort(array, (s1, s2) -> {
                return s1.compareTo(s2);
            }); // Arrays.sort(_, _)第二个参数需要接受一个Comparator对象,这里用lambda表达式代替
            System.out.println(String.join(", ", array));
        }
    }
    
    • 什么是单方法接口,这里以Comparator<T>接口为例:
    @FunctionalInterface
    public interface Comparator<T> {
    	......
    }
    

    首先需要说明的是 @FunctionalInterface注解:(文档)
    An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification. Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.
    总结包括三个要点:

    1. 由FunctionalInterface包裹的接口只有一个 “ 抽象方法 ”(单方法接口)
    2. 定义在接口中的default方法不算做 “ 抽象方法 ”
    3. 重写java.lang.Objects中的方法而形成的抽象方法,不算做“抽象方法”,因总是为会有来自Object或者其他类中的实现
      Comparator
    • 观察Lambda表达式的写法,它只需要写出方法定义:
    (s1, s2) -> {
        return s1.compareTo(s2);
    }
    

    参数是(s1, s2),参数类型可以省略,因为编译器可以自动推断出String类型。-> { ... }表示方法体,所有代码写在内部即可。Lambda表达式没有class定义,因此写法非常简洁

    • 如果只有一行return xxx的代码,完全可以用更简单的写法:
    Arrays.sort(array, (s1, s2) -> s1.compareTo(s2));
    

    返回值的类型也是由编译器自动推断的,这里推断出的返回值是int,因此,只要返回int,编译器就不会报错

    3. 方法引用

    • 解决上面的问题,除了Lambda表达式,我们还可以直接传入方法引用:
    public class Main {
        public static void main(String[] args) {
            String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
            Arrays.sort(array, Main::cmp);
            System.out.println(String.join(", ", array));
        }
    
        static int cmp(String s1, String s2) {
            return s1.compareTo(s2);
        }
    }
    

    所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用(除了方法名不相同之外,方法参数以及方法返回值都相同)

    • 注意:在这里,方法签名只看参数类型和返回类型,不看方法名称,也不看类的继承关系
    • 引用实例方法:
    public class Main {
        public static void main(String[] args) {
            String[] array = new String[] { "Apple", "Orange", "Banana", "Lemon" };
            Arrays.sort(array, String::compareTo);
            System.out.println(String.join(", ", array));
        }
    }
    

    这里String::compareTo的定义为:

    public final class String {
        public int compareTo(String o) {
            ...
        }
    }
    

    明明只接受了一个String参数为什么却可以编译通过并且顺利执行呢?这是因为实例方法隐含了一个this参数

    • 除了引用静态方法以及实例方法,还可以对构造方法进行引用,例如可以在Stream的map处理过程中传入构造方法的引用(关于Stream的更详细信息可以参考下一小节)

    4. 使用Stream

    • Java8不但引入了Lambda表达式,还引入了一个全新的流式API:Stream API。它位于java.util.stream包中
    • Stream的特点总结
      1. 不同于java.io的InputStream和OutputStream,Stream代表的是任意Java对象的序列
      2. Stream可以存储有限个或者“无限个”元素,因为Stream并不是将所有元素都真实地保存在内存中,Stream可以通过实时计算来得到相应的结果
      3. Stream实现了惰性计算;可以将对Stream的操作分为两个部分:一、逻辑算子;二、执行算子;在添加逻辑算子的过程中,Stream并不会直接对表示的各个元素进行相应的运算,而是存储计算的逻辑,只有当执行算子需求当前元素的最终结果时才会依据存储的计算逻辑,实时地计算得到相应的结果并交还
      4. Stream API支持函数式编程和链式操作
      具体实现方式,下面介绍

    4.1 创建Stream

    • 方式一:Stream.of()方法
    public class Main {
        public static void main(String[] args) {
            Stream<String> stream = Stream.of("A", "B", "C", "D");
            // forEach()方法相当于内部循环调用,
            // 可传入符合Consumer接口的void accept(T t)的方法引用:
            stream.forEach(System.out::println);
        }
    }
    

    这种方式没有办法体现Stream的强大,适用于测试情景

    • 方式二:基于数组或者Collection:
    public class Main {
        public static void main(String[] args) {
            Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
            Stream<String> stream2 = List.of("X", "Y", "Z").stream();
            stream1.forEach(System.out::println);
            stream2.forEach(System.out::println);
        }
    }
    

    前两种方法创造出来的元素值都是固定的

    • 方式三:基于Supplier,创建Stream还可以通过Stream.generate()方法,它需要传入一个Supplier对象:
    Stream<String> s = Stream.generate(Supplier<String> sp);
    

    基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列
    示例:自然数序列

    public class Main {
        public static void main(String[] args) {
            Stream<Integer> natual = Stream.generate(new NatualSupplier());
            // 注意:无限序列必须先变成有限序列再打印:
            natual.limit(20).forEach(System.out::println);
        }
    }
    
    class NatualSupplier implements Supplier<Integer> {
        int n = 0;
        public Integer get() {
            n++;
            return n;
        }
    }
    

    对于无限序列,如果直接调用forEach()或者count()这些最终求值操作,会进入死循环,因为永远无法计算完这个序列,所以正确的方法是先把无限序列变成有限序列,例如,用limit()方法可以截取前面若干个元素,这样就变成了一个有限序列

    • 其他方式:
      • 基于一些API接口:例如Files类的lines()方法可以把一个文件变成一个Stream,每个元素代表文件的一行内容:
      try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
      	...
      }
      
      • 基本类型:因为Java的范型不支持基本类型,所以我们无法用Stream这样的类型,会发生编译错误。为了保存int,只能使用Stream,但这样会产生频繁的装箱、拆箱操作。为了提高效率,Java标准库提供了IntStream、LongStream和DoubleStream这三种使用基本类型的Stream,它们的使用方法和范型Stream没有大的区别,设计这三个Stream的目的是提高运行效率:
      // 将int[]数组变为IntStream:
      IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
      // 将Stream<String>转换为LongStream:
      LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);
      

    4.2 使用map方法

    • Stream.map()是Stream最常用的一个转换方法,它把一个Stream转换为另一个Stream
    • map操作,把一个Stream的每个元素一一对应到应用了目标函数的结果上:
    Stream<Integer> s = Stream.of(1, 2, 3, 4, 5);
    Stream<Integer> s2 = s.map(n -> n * n);
    

    map效果

    • map()方法接收的对象是Function接口对象,它定义了一个**apply()**方法,负责把一个T类型转换成R类型:
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    

    Function的定义为:

    @FunctionalInterface
    public interface Function<T, R> {
        // 将T类型转换为R:
        R apply(T t);
    }
    

    因此我们可以直接传入Lambda表达式来代替匿名类

    • 示例:利用map完成对字符串的操作
    public class Main {
        public static void main(String[] args) {
            List.of("  Apple ", " pear ", " ORANGE", " BaNaNa ")
                    .stream()
                    .map(String::trim) // 去空格
                    .map(String::toLowerCase) // 变小写
                    .forEach(System.out::println); // 打印
        }
    }
    

    4.3 使用filter

    • filter() 操作是对一个Stream的所有元素一一进行测试,不满足条件的元素就会被“过滤”,剩下的满足条件的元素则组成一个新的Stream
    • 示例:过滤偶数
    public class Main {
        public static void main(String[] args) {
            IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
                    .filter(n -> n % 2 != 0)
                    .forEach(System.out::println);
        }
    }
    
    • filter()方法接收的对象是Predicate接口对象,它定义了一个**test()**方法,负责判断元素是否符合条件:
    @FunctionalInterface
    public interface Predicate<T> {
        // 判断元素t是否符合条件:
        boolean test(T t);
    }
    

    用Lambda表达式替代

    • filter()除了常用于数值外,也可应用于任何Java对象,过滤规则需要自己定义

    4.4 使用reduce

    • Stream.reduce() Stream的一个聚合方法(执行方法),它可以把一个Stream的所有元素按照聚合函数聚合成一个结果
    • 示例:累加
    public class Main {
        public static void main(String[] args) {
        	// 第一个参数表示起始值,acc表示当前的累加值,n表示当前位置的元素
            int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).reduce(0, (acc, n) -> acc + n);  // 单行操作(且是返回语句)可以省略return
            System.out.println(sum); // 45
        }
    }
    

    可以解析为:

    // 计算过程:
    acc = 0 // 初始化为指定值
    acc = acc + n = 0 + 1 = 1 // n = 1
    acc = acc + n = 1 + 2 = 3 // n = 2
    acc = acc + n = 3 + 3 = 6 // n = 3
    acc = acc + n = 6 + 4 = 10 // n = 4
    acc = acc + n = 10 + 5 = 15 // n = 5
    acc = acc + n = 15 + 6 = 21 // n = 6
    acc = acc + n = 21 + 7 = 28 // n = 7
    acc = acc + n = 28 + 8 = 36 // n = 8
    acc = acc + n = 36 + 9 = 45 // n = 9
    

    实际上reduce()操作是一个求和操作

    • reduce()方法传入的对象是BinaryOperator接口,它定义了一个apply()方法,负责把上次累加的结果和本次的元素 进行运算,并返回累加的结果:
    @FunctionalInterface
    public interface BinaryOperator<T> {
        // Bi操作:两个输入,一个输出
        T apply(T t, T u);
    }
    
    • 如果去掉初始值,我们会得到一个Optional<Integer>
    Optional<Integer> opt = stream.reduce((acc, n) -> acc + n);
    if (opt.isPresent()) {
        System.out.println(opt.get());
    }
    

    Stream的元素有可能是0个,Optional对象用于进一步判断结果是否存在

    • 示例:灵活运用map方法以及reduce方法
    public class Main {
        public static void main(String[] args) {
            // 按行读取配置文件:
            List<String> props = List.of("profile=native", "debug=true", "logging=warn", "interval=500");
            Map<String, String> map = props.stream()
                    // 把k=v转换为Map[k]=v:
                    .map(kv -> { // 这里的kv是一个字符串
                        String[] ss = kv.split("\\=", 2);
                        return Map.of(ss[0], ss[1]);  // 将一个字符串转换为Map<k, v>的形式
                    })  
                    // 一个字符串对应一个Map因此这里有多个Map,下一步实现把所有Map聚合到一个Map:
                    .reduce(new HashMap<String, String>(), (m, kv) -> {
                    // 初始值为一个空Map, m表示一个逐渐增加元素的总Map对象,kv表示每一个单元素Map
                        m.putAll(kv);
                        return m;
                    });
            // 打印结果:
            map.forEach((k, v) -> {
                System.out.println(k + " = " + v);
            });
        }
    }
    

    4.5 输出集合

    • reduce()只是一种聚合操作,如果我们希望把Stream的元素保存到集合,例如List,因为List的元素是确定的Java对象,因此,把Stream变为List不是一个转换操作,而是一个聚合操作,它会强制Stream输出每个元素
    • 输出为List
    public class Main {
        public static void main(String[] args) {
            Stream<String> stream = Stream.of("Apple", "", null, "Pear", "  ", "Orange");
            List<String> list = stream.filter(s -> s != null && !s.isBlank()).collect(Collectors.toList());
            System.out.println(list);
        }
    }
    

    调用collect()并传入Collectors.toList()对象,它实际上是一个Collector实例,通过类似reduce()的操作,把每个元素添加到一个收集器中(实际上是ArrayList)

    • 输出为数组
    List<String> list = List.of("Apple", "Banana", "Orange");
    String[] array = list.stream().toArray(String[]::new);
    
    • 输出为Map
    public class Main {
        public static void main(String[] args) {
            Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
            Map<String, String> map = stream
                    .collect(Collectors.toMap(
                            // 把元素s映射为key:
                            s -> s.substring(0, s.indexOf(':')),
                            // 把元素s映射为value:
                            s -> s.substring(s.indexOf(':') + 1)));
            System.out.println(map);
        }
    }
    
    • 分组输出
    public class Main {
        public static void main(String[] args) {
            List<String> list = List.of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
            Map<String, List<String>> groups = list.stream()
                    .collect(Collectors.groupingBy(s -> s.substring(0, 1), Collectors.toList()));
            System.out.println(groups);
        }
    }
    
    

    Collectors.groupingBy()需要提供两个函数:一个是分组的key,这里使用s -> s.substring(0, 1),表示只要首字母相同的String分到一组;第二个是分组的value,这里直接使用Collectors.toList(),表示输出为List

    4.6 其他操作

    • 对Stream的元素进行排序十分简单,只需调用sorted()方法:
    public class Main {
        public static void main(String[] args) {
            List<String> list = List.of("Orange", "apple", "Banana")
                .stream()
                .sorted()
                .collect(Collectors.toList());
            System.out.println(list);
        }
    }
    

    此方法要求Stream的每个元素必须实现Comparable接口。如果要自定义排序,在sourted方法中传入指定的Comparator即可

    • 对一个Stream的元素进行去重,没必要先转换为Set,可以直接用distinct():
    List.of("A", "B", "A", "C", "B", "D")
        .stream()
        .distinct()
        .collect(Collectors.toList()); // [A, B, C, D]
    
    • 截取操作常用于把一个无限的Stream转换成有限的Stream,skip()用于跳过当前Stream的前N个元素,limit()用于截取当前Stream最多前N个元素:
    List.of("A", "B", "C", "D", "E", "F")
        .stream()
        .skip(2) // 跳过A, B
        .limit(3) // 截取C, D, E
        .collect(Collectors.toList()); // [C, D, E]
    
    • 将两个Stream合并 为一个Stream可以使用Stream的静态方法concat():
    Stream<String> s1 = List.of("A", "B", "C").stream();
    Stream<String> s2 = List.of("D", "E").stream();
    // 合并:
    Stream<String> s = Stream.concat(s1, s2);
    System.out.println(s.collect(Collectors.toList())); // [A, B, C, D, E]
    
    • 扁平化操作:如果Stream的元素是集合Stream<List<Integer>>,而我们希望把上述Stream转换为Stream<Integer>,就可以使用flatMap()
    Stream<List<Integer>> s = Stream.of(
            Arrays.asList(1, 2, 3),
            Arrays.asList(4, 5, 6),
            Arrays.asList(7, 8, 9));
    Stream<Integer> i = s.flatMap(list -> list.stream());
    

    flatMap(),是指把Stream的每个元素(这里是List)映射为Stream,然后合并成一个新的Stream
    扁平化

    • 把一个普通Stream转换为可以并行处理的Stream只需要用parallel()进行转换:
    Stream<String> s = ...
    String[] result = s.parallel() // 变成一个可以并行处理的Stream
                       .sorted() // 可以进行并行排序
                       .toArray(String[]::new);
    

    经过parallel()转换后的Stream只要可能,就会对后续操作进行并行处理。我们不需要编写任何多线程代码就可以享受到并行处理带来的执行效率的提升

    • 除了reduce()和collect()外,Stream还有一些常用的聚合方法:
    1. count():用于返回元素个数;
    2. max(Comparator<? super T> cp):找出最大元素;
    3. min(Comparator<? super T> cp):找出最小元素。
    
    • 针对IntStream、LongStream和DoubleStream,还额外提供了以下聚合方法:
    1. sum():对所有元素求和;
    2. average():对所有元素求平均数。
    
    • 测试Stream的元素是否满足以下条件:
    1. boolean allMatch(Predicate<? super T>):测试是否所有元素均满足测试条件;
    2. boolean anyMatch(Predicate<? super T>):测试是否至少有一个元素满足测试条件。
    
    • forEach()方法可以循环处理Stream的每个元素,我们经常传入System.out::println来打印Stream的元素
    展开全文
  • 0202年了,还没有用上Java函数式编程!!!——Lambda表达式函数式编程是什么命令式编程(Imperative)声明式编程(Declarative)函数式编程(Functional)总结函数式编程的好处Lambda表达式函数式接口五种形式1.无参数2....
  • java8函数式编程 pdf

    2021-02-12 22:02:58
    java8函数式编程 pdf的重要性就不言而喻了吧。对于每一个JAVA开发者,这都是一个必过的坎。虽然现在的主流编程方式还是面向对象式。当然也不能否认这种方式的健壮性,可扩展性。但是随着编程技术的发展。走在时代...

    java8函数式编程 pdf的重要性就不言而喻了吧。对于每一个JAVA开发者,这都是一个必过的坎。虽然现在的主流编程方式还是面向对象式。当然也不能否认这种方式的健壮性,可扩展性。但是随着编程技术的发展。走在时代前列的函数式编程就粉墨登场了。而这本java8函数式编程就全面介绍了函数式编程的精华,对于很多这种全新的编程理念,大家绝对有必要阅读学习一下。对于JAVA的发展道路非常的有帮助。而这本书主要围绕函数式编程理念、Lambda表达式、高级集合类和收集器、数据并行化等方向进行深入挖掘。对于有一点JAVA基础的朋友,仍然需要花点功夫进行深入学习。最后需要主要的是,本书面向的是JAVA 8版本。也是最新的成熟版本。它的最新特性就是Java 8 引入的一个核心概念是函数式接口。所以,想要赶上JAVA技术前沿的朋友,一定不要错过这本java8函数式编程。

    2a2ea1f8d13ee454a67f4f203f0d037b.png

    图书简介多年以来,函数式编程被认为是少数人的游戏,不适合推广给普罗大众。写作此书的目的就是为了挑战这种思想。本书将探讨如何编写出简单、干净、易读的代码;如何简单地使用并行计算提高性能;如何准确地为问题建模,并且开发出更好的领域特定语言;如何写出不易出错,并且更简单的并发代码;如何测试和调试Lambda表达式。

    如果你已经掌握Java SE,想尽快了解Java 8新特性,写出简单干净的代码,那么本书不容错过。

    80df6ba27a227ff5aed39f74274d262f.png

    函数式编程的核心思想思考问题时,使用不可变值和函数,函数对一个值进行处理,映射成另一个值。类似于数学中的函数求值。

    我们对这种思想的完全定义还为时尚早。我们现在想要的是如何通过使用函数式编程风格的代码来写出好代码。

    本系列将讲述如何实用的使用函数式编程。帮助程序媛们(恩,没打错!!!)能写出易读、易维护的代码。

    函数式编程的好处减少了可变量(Immutable Variable)的声明

    能够更好的利用并行(Parallelism)

    代码更加简洁和可读

    当然,Java 8中对于函数式编程风格的引入,并不是为了要颠覆已经根深蒂固面向对象编程风格。而是让它们和谐共处,取长补短。比如,使用面向对象对实体进行建模,对实体之间的关系进行表述;而使用函数式编码来实现实体中的各种状态改变,业务流程以及数据处理。

    核心特色声明式的代码风格(Declarative Style) : 这需要提高代码的抽象层次,比如在前面的例子中,将从集合中搜索一个元素的操作封装到contains方法中。

    更多的不变性(Promote Immutability) : 能不声明变量就不要声明,需要变量时尽量使用final来修饰。因为变量越多,就意味着程序越难以并行。实现了不变性的方法意味着它不再有副作用,不会因为调用而改变程序的状态。

    使用表达式来代替语句(Prefer Expression to Statement) : 使用语句也就意味着不变性的破坏和程序状态的改变,比如赋值语句的使用。

    使用高阶函数(High-Order Function) : 在Java 8以前,重用是建立在对象和类型系统之上。而Java 8中则将重用的概念更进一步,使用函数也能够实现代码的重用。所谓高阶函数,不要被其名字唬住了,实际上很简单:

    将函数作为参数传入到另外一个函数中

    函数的返回值可以是函数类型

    在函数中创建另一个函数

    图书目录前言

    第1章 简介

    1.1为什么需要再次修改Java

    1.2什么是函数式编程

    1.3示例

    第2章 Lambda表达式

    2.1第一个Lambda表达式

    2.2如何辨别Lambda表达式

    2.3引用值,而不是变量

    2.4函数接口

    2.5类型推断

    2.6要点回顾

    2.7练习

    第3章 流

    3.1从外部迭代到内部迭代

    3.2实现机制

    3.3常用的流操作

    3.3.1collect(toList())

    3.3.2map

    3.3.3filter

    3.3.4flatMap

    3.3.5max和min

    3.3.6通用模式

    3.3.7reduce

    3.3.8整合操作

    3.4重构遗留代码

    3.5多次调用流操作

    3.6高阶函数

    3.7正确使用Lambda表达式

    3.8要点回顾

    3.9练习

    3.10进阶练习

    第4章 类库

    4.1在代码中使用Lambda表达式

    4.2基本类型

    4.3重载解析

    4.4@FunctionalInterface

    4.5二进制接口的兼容性

    4.6默认方法

    4.7多重继承

    4.8权衡

    4.9接口的静态方法

    4.10Optional

    4.11要点回顾

    4.12练习

    4.13开放练习

    第5章 高级集合类和收集器

    5.1方法引用

    5.2元素顺序

    5.3使用收集器

    5.3.1转换成其他集合

    5.3.2转换成值

    5.3.3数据分块

    5.3.4数据分组

    5.3.5字符串

    5.3.6组合收集器

    5.3.7重构和定制收集器

    5.3.8对收集器的归一化处理.

    5.4一些细节

    5.5要点回顾

    5.6练习

    第6章 数据并行化

    6.1并行和并发

    6.2为什么并行化如此重要

    6.3并行化流操作

    6.4模拟系统

    6.5限制

    6.6性能

    6.7并行化数组操作

    6.8要点回顾

    6.9练习

    第7章 测试、调试和重构

    7.1重构候选项

    7.1.1进进出出、摇摇晃晃

    7.1.2孤独的覆盖

    7.1.3同样的东西写两遍

    7.2Lambda表达式的单元测试

    7.3在测试替身时使用Lambda表达式

    7.4惰性求值和调试

    7.5日志和打印消息

    7.6解决方案:peak

    7.7在流中间设置断点

    7.8要点回顾

    第8章 设计和架构的原则

    8.1Lambda表达式改变了设计模式

    8.1.1命令者模式

    8.1.2策略模式

    8.1.3观察者模式

    8.1.4模板方法模式

    8.2使用Lambda表达式的领域专用语言

    8.2.1使用Java编写DSL

    8.2.2实现

    8.2.3评估

    8.3使用Lambda表达式的SOLID原则

    8.3.1单一功能原则

    8.3.2开闭原则

    8.3.3依赖反转原则

    8.4进阶阅读

    8.5要点回顾

    第9章 使用Lambda表达式编写并发程序

    9.1为什么要使用非阻塞式I/O

    9.2回调

    9.3消息传递架构

    9.4末日金字塔

    9.5Future

    9.6CompletableFuture

    9.7响应式编程

    9.8何时何地使用新技术

    9.9要点回顾

    9.10练习

    第10章 下一步该怎么办

    关于Java8函数式编程你需要了解的几点函数式编程与面向对象的设计方法在思路和手段上都各有千秋,在这里,我将简要介绍一下函数式编程与面向对象相比的一些特点和差异。

    b5dfbbdd093365b1c1be46dce95df97c.png

    1.函数作为一等公民

    在理解函数作为一等公民这句话时,让我们先来看一下一种非常常用的互联网语言JavaScript,相信大家对它都不会陌生。JavaScript并不是严格意义上的函数式编程,不过,它也不是属于严格的面向对象。但是,如果你愿意,你既可以把它当做面向对象语言,也可以把它当做函数式语言,因此,称之为多范式语言,可能更加合适。

    如果你使用jQuery,你可能会经常使用如下的代码:

    $("button").click(function(){

    $("li").each(function(){

    alert($(this).text())

    });

    });

    注意这里each()函数的参数,这是一个匿名函数,在遍历所有的li节点时,会弹出li节点的文本内容。将函数作为参数传递给另外一个函数,这是函数式编程的特性之一。

    再来考察另外一个案例:

    复制代码

    function f1(){

    var n=1;

    function f2(){

    alert(n);

    }

    return f2;

    }

    var result=f1();

    result(); // 1

    复制代码

    这也是一段JavaScript代码,在这段代码中,注意函数f1的返回值,它返回了函数f2。在倒数第2行,返回的f2函数并赋值给result,实际上,此时的result就是一个函数,并且指向f2。对result的调用,就会打印n的值。

    函数可以作为另外一个函数的返回值,也是函数式编程的重要特点。

    2.无副作用

    函数的副作用指的是函数在调用过程中,除了给出了返回值外,还修改了函数外部的状态,比如,函数在调用过程中,修改了某一个全局状态。函数式编程认为,函数的副用作应该被尽量避免。可以想象,如果一个函数肆意修改全局或者外部状态,当系统出现问题时,我们可能很难判断究竟是哪个函数引起的问题。这对于程序的调试和跟踪是没有好处的。如果函数都是显式函数,那么函数的执行显然不会受到外部或者全局信息的影响,因此,对于调试和排错是有益的。

    注意:显式函数指函数与外界交换数据的唯一渠道就是参数和返回值,显式函数不会去读取或者修改函数的外部状态。与之相对的是隐式函数,隐式函数除了参数和返回值外,还会读取外部信息,或者可能修改外部信息。

    然而,完全的无副作用实际上做不到的。因为系统总是需要获取或者修改外部信息的。同时,模块之间的交互也极有可能是通过共享变量进行的。如果完全禁止副作用的出现,也是一件让人很不愉快的事情。因此,大部分函数式编程语言,如Clojure等,都允许副作用的存在。但是与面向对象相比,这种函数调用的副作用,在函数式编程里,需要进行有效的限制。

    申明式的(Declarative)

    函数式编程是申明式的编程方式。相对于命令式(imperative)而言,命令式的程序设计喜欢大量使用可变对象和指令。我们总是习惯于创建对象或者变量,并且修改它们的状态或者值,或者喜欢提供一系列指令,要求程序执行。这种编程习惯在申明式的函数式编程中有所变化。对于申明式的编程范式,你不在需要提供明确的指令操作,所有的细节指令将会更好的被程序库所封装,你要做的只是提出你要的要求,申明你的用意即可。

    请看下面一段程序,这一段传统的命令式编程,为了打印数组中的值,我们需要进行一个循环,并且每次需要判断循环是否结束。在循环体内,我们要明确地给出需要执行的语句和参数。

    复制代码

    public static void imperative(){

    int[]iArr={1,3,4,5,6,9,8,7,4,2};

    for(int i=0;i

    System.out.println(iArr[i]);

    }

    }

    复制代码

    与之对应的申明式代码如下:

    public static void declarative(){

    int[]iArr={1,3,4,5,6,9,8,7,4,2};

    Arrays.stream(iArr).forEach(System.out::println);

    }

    可以看到,变量数组的循环体居然消失了!println()函数似乎在这里也没有指定任何参数,在此,我们只是简单的申明了我们的用意。有关循环以及判断循环是否结束等操作都被简单地封装在程序库中。

    3.尾递归优化

    递归是一种常用的编程技巧。使用递归通常可以简化程序编码,大幅减少代码行数。但是递归有一个很大的弊病——它总是使用栈空间。但是,程序的栈空间是非常有限的,与堆空间相比,可能相差几个数量级(栈空间大小通常只有几百K,而堆空间则通常达到几百M甚至上百G)。因此,大规模的递归操作有可能发生栈空间溢出错误,这也限制了递归函数的使用,并给系统带来了一定的风险。

    而尾递归优化可以有效地避免这种状况。尾递归指递归操作处于函数的最后一步。在这种情况下,该函数的工作其实已经完成(剩余的工作就是再次调用它自己),此时,只需要简单得将中间结果传递给后继调用的递归函数即可。此时,编译器就可以进行一种优化,使当前的函数调用返回,或者用新函数的帧栈覆盖老函数的帧栈。总之,当递归处于函数操作的最后一步时,我们总是可以想方设法避免递归操作不断申请栈空间。

    大部分函数式编程语言直接或者间接支持尾递归优化。

    4.不变模式

    如果读者熟悉多线程程序设计,那么一定对不变模式有所有了解。所谓不变,是指对象在创建后,就不再发生变化。比如,java.lang.String就是不变模式的典型。如果你在Java中创建了一个String实例,无论如何,你都不可能改变整个String的值。比如,当你使用String.replace()函数试图进行字符串替换时,实际上,原有的字符串对象并不会发生变化,函数本身会返回一个新的String对象,作为给定字符替换后的返回值。不变的对象在函数式编程中被大量使用。

    请看以下代码:

    static int[] arr={1,3,4,5,6,7,8,9,10};

    Arrays.stream(arr).map((x)->x=x+1).forEach(System.out::println);

    System.out.println();

    Arrays.stream(arr).forEach(System.out::println);

    代码第2行看似对每一个数组成员执行了加1的操作。但是在操作完成后,在最后一行,打印arr数组所有的成员值时,你还是会发现,数组成员并没有变化!在使用函数式编程时,这种状态是一种常态,几乎所有的对象都拒绝被修改。

    5.易于并行

    由于对象都处于不变的状态,因此函数式编程更加易于并行。实际上,你甚至完全不用担心线程安全的问题。我们之所以要关注线程安全,一个很大的原因是当多个线程对同一个对象进行写操作时,容易将这个对象“写坏”,更专业的说法是“使得对象状态不一致”。但是,由于不变模式的存在,对象自创建以来,就不可能发生改变,因此,在多线程环境下,也就没有必要进行任何同步操作。这样不仅有利于并行化,同时,在并行化后,由于没有同步和锁机制,其性能也会比较好。读者可以关注一下java.lang.String对象。很显然,String对象可以在多线程中很好的工作,但是,它的每一个方法都没有进行同步处理。

    6.更少的代码

    通常情况下,函数式编程更加简明扼要,Clojure语言(一种运行于JVM的函数式语言)的爱好者就宣称,使用Clojure可以将Java代码行数减少到原有的十分之一。一般说来,精简的代码更易于维护。而Java代码的冗余性也是出了名的,大部分对于Java语言的攻击都会直接针对Java繁琐,而且死板的语法(但我认为这也是Java的优点之一,正如本书第一段提到的“保守的设计思想是Java最大的优势”),然而,引入函数式编程范式后,这种情况发生了改变。我们可以让Java用更少的代码完成更多的工作。

    请看下面这个例子,对于数组中每一个成员,首先判断是否是奇数,如果是奇数,则执行加1,并最终打印数组内所有成员。

    数组定义:

    static int[] arr={1,3,4,5,6,7,8,9,10};

    传统的处理方式:

    for(int i=0;i

    if(arr[i]%2!=0){

    arr[i]++;

    }

    System.out.println(arr[i]);

    }

    使用函数式方式:

    Arrays.stream(arr).map(x->(x%2==0?x:x+1)).forEach(System.out::println);

    可以看到,函数式范式更加紧凑而且简洁。

    函数式接口(Functional Interface)

    为了在Java中引入函数式编程,Java 8中引入了函数式接口这一概念。

    函数式接口就是仅声明了一个方法的接口,比如我们熟悉的Runnable,Callable,Comparable等都可以作为函数式接口。当然,在Java 8中,新添加了一类函数式接口,如Function,Predicate,Consumer,Supplier等。

    在函数式接口中,可以声明0个或者多个default方法,这些方法在接口内就已经被实现了。因此,接口的default方法也是Java 8中引入的一个新概念。

    函数式接口使用@FunctionalInterface注解进行标注。虽然这个注解的使用不是强制性的,但是使用它的好处是让此接口的目的更加明确,同时编译器也会对代码进行检查,来确保被该注解标注的接口的使用没有语法错误。

    如果一个方法接受一个函数式接口作为参数,那么我们可以传入以下类型作为参数:

    匿名内部类(Anonymous Inner Class)

    Lambda表达式

    方法或者构造器的引用(Method or Constructor Reference)

    第一种方式是Java的以前版本中经常使用的方式,在Java 8中不再被推荐。 第二种方式中,Lambda表达式会被编译器转换成相应函数式接口的一个实例。 第三种方式会在后文中详细介绍。

    免责声明:来源于网络,仅用于分享知识,学习和交流!请下载完在24小时内删除。

    禁用于商业用途!如果您喜欢《图解cio工作指南第4版》,请购买正版,谢谢合作。

    爱学习,请到3322软件站查找资源自行下载!

    下载说明:方法一:

    1、下载并解压,得出pdf文件

    2、如果打不开本文件,别着急,这时候请务必在3322软件站选择一款阅读器下载哦

    3、安装后,再打开解压得出的pdf文件

    4、以上都完成后,接下来双击进行阅读就可以啦,朋友们开启你们的阅读之旅吧。

    方法二:

    1、可以在手机里下载3322软件站中的阅读器和百度网盘

    2、接下来直接将pdf传输到百度网盘

    3、用阅读器打开即可阅读

    下载地址

    请描述一下您遇到的错误,我们将用火箭般的速度修复

    软件无法下载

    下载后无法使用

    与描述不一致

    其他问题

    网友评论

    0条评论

    4d18d56e3c5d496323de8c5ac6000e1b.png评论需审核后才能显示

    展开全文
  • JAVA函数式编程

    千次阅读 2019-02-17 22:40:55
    JAVA函数式编程背景常见的编程范式函数式编程的优劣JAVA8中为函数式编程引入的变化JAVA函数式编程可以简单概括基本函数Lambda表达式方法引用Stream流API创建操作中间操作终止从操作并行流级联表达式与柯里化收集器...

    背景

    JAVA版本最新的目前已经发布到11了,但目前市面上大多数公司依然在使用Java7之前版本的语法,然而这些编程模式已经渐渐的跟不上新时代的技术了。比如时下潮流前沿spring framework5中的响应式编程就是使用到了函数式编程的风格。

    当时有一段时间了解了一下大数据领域生态发现对于写spark中的程序来说如果你使用java7前的命令式编程会使得整体代码相当臃肿,又臭又长匿名类大几百行的代码其中写到的逻辑其实不过几十行,大部分的代码浪费在语法上。使得代码的可读性变得非常差。spark本身是使用Scala编写的对于本身就支持函数式编程的语言,使得代码简洁而又易于理解。当然spark也支持jdk8相对于jdk7来说8加入了函数式编程的支持使得整体优雅了许多。

    在编程历史的长河中java向来可维护性、可扩展性是一直受业界所推崇的。但由于java语法臃肿,也流失了一部分转向了python、go等。在学习大数据的时候发现大部分的教程更倾向Scala(如果大家有java8的教程麻烦连接评论哦!),而Scala本身依赖与jvm,所以若不是公司技术栈限制的话我相信大家更倾向与使用Scala来编程。OK!那么java也推出了函数式编程也通过本文来了解一下。

    常见的编程范式

    • 命令式编程:命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做什么。这种风格我相信对于传统程序员来说都不陌生。甚至一些大牛闭着眼睛都可以敲了。
      代表语言有:C, C++, Java, Javascript, BASIC,Ruby等多为老牌语言
    • 声明式编程:声明式编程是以数据结构的形式来表达程序执行的逻辑。它的主要思想是告诉计算机应该做什么,但不指定具体要怎么做。乍一看似乎没有见过但其实日常来说大家也都在用。
      代表语言有:SQL,HTML,CSS
    • 函数式编程:函数式编程将函数作为编程中的“一等公民”,关注于流程而非具体实现。可以将函数作为参数或返回值。所有数据的操作都通过函数来实现。可以理解为数学中的函数。较新的语言基本上追求语法上的简洁基本都有支持。
      代表语言有:JAVA(8以上),js(ES6),C#,Scala,python等

    函数式编程的优劣

    优点:

    1. 代码简洁可读性强,逻辑结构清晰。
    2. 线程安全,内部API屏蔽了coder对多线程的关注。
    3. 更好的复用性,往往一个函数适用于各种计算场景。

    缺点:

    1. 由于函数内数据不变原则,导致的资源占用
    2. 调试上相对于命令式的困难

    JAVA8中为函数式编程引入的变化

    • 函数式接口,函数式接口中只能有一个抽象方法
      @FunctionInterface,这也是为了函数调用时避免带来二义性。@FunctionInterface并不是一定要标注但若是标注可以在编译时就给你提示错误。
    • 静态方法,静态方法目的完全出于编写类库,对某些行为进行抽象,但是接口中的静态方法不能被继承。
    • 默认实现,是不得已而为之,因为Java8引入了函数式接口,许多像Collection这样的基础接口中增加了方法,如果还是一个传统的抽象方法的话,那么可能很多第三方类库就会变得完全无法使用。新增一个方法所有实现类都要实现一次。被default修饰的方法–默认实现

    JAVA函数式编程可以简单概括

    lambda + 方法引用 + stream API = java函数式编程

    基本函数

    在这里插入图片描述
    以上是在函数式编程中的基本函数模型,我们大可以将其与数学函数做关联:y = x +1,我们仅仅需要关注这个函数的输入输出即可。
    以predicte函数举例:该函数输入一个表达式,输出一个布尔类型
    一元函数function:输入一个类型参数输出为另一个类型,当然这两个类型可以是相同的,当相同时也可以使用unaryOperator来代替。具体下面有给出实际场景的代码断:

    public class FunctionDemo {
    
        public static void main(String[] args) {
            //断言型
    //        predicate();
            //消费型
    //        consumer();
            //一元函数 输入输出不同
    //        function();
            //提供型
    //        supplier();
            //一元函数 输入输出类型相同
    //        unaryOperator();
            //二元函数 输入输出不同
    //        biFunction();
            //二元函数 输入输出相同
            binaryOperator();
        }
    
        /**
         *
         */
        public static void predicate(){
            Predicate<Integer> predicate = i -> i > 0;
            IntPredicate intPredicate = i -> i > 0;
            System.out.print(predicate.test(6));
            System.out.print(intPredicate.test(-1));
        }
    
        public static void consumer(){
            Consumer<String> consumer = s -> System.out.println(s);
            consumer.accept("我是一个消费者");
        }
    
        public static void function(){
            Function<Integer,String> function = x -> "数字是:"+ x;
            System.out.println(function.apply(88));
        }
    
        public static void supplier(){
            Supplier<String> supplier = () -> "我是一个提供者";
            System.out.println(supplier.get());
        }
    
        public static void unaryOperator(){
            UnaryOperator<Integer> unaryOperator = x -> ++x;
            System.out.println(unaryOperator.apply(1));
        }
    
        public static void biFunction(){
            BiFunction<Integer,Double,Double> biFunction = (x,y) -> {
                ++x;
                ++y;
                return x+y;
            };
            System.out.println(biFunction.apply(1,2.3));
        }
    
        public static void binaryOperator(){
            IntBinaryOperator intBinaryOperator = (x,y) -> x + y;
            System.out.println(intBinaryOperator.applyAsInt(2,3));
        }
    }
    

    Lambda表达式

    相信大家在写一些匿名内部类时编译器会提示你,该处可以转换为lambda,例如:

        public static void main(String[] args){
            IMethod iMethod = () -> {};
        }
    
        public static void useLambda(){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.print("123");
                }
            });
            thread.start();
    
            //lambda
            new Thread(() -> System.out.print("123")).start();
        }
    

    定义一个线程并启动他,传统写法我们用了7行,但其实只有 System.out.print(“123”);这句话是有用的,而lambda表达式我们仅仅一行就达到了相同目的。‘

    lambda表达式主要用于实例化一些函数式接口,替换匿名类的写法,主要形式有:
    (输入) -> {输出} // 我们仅仅关注输入输出即可,若代码有多行大括号里面可以写多行,多行有返回值要显示声明return。输入可以为空即()。

    在lambda表达式中无需像传统写法那样声明参数和返回值类型,它会根据你的上下文通过类型推导你实现的是哪一个接口,从而跟具这个接口的定义知道你变量的类型。这也是为什么函数式接口只能声明一个方法的原因。

    文末链接有附带工程demo链接可以参考哦!

    方法引用

    我们可以直接使用两个冒号::来调用方法

    • 静态方法引用
    • 非静态 实例方法引用
    • 非静态 类方法引用
    • 构造函数方法引用
    public class MethodReferenceDemo {
        public static void main(String[] args) {
            //消费者 方法引用模式
    //        consumer();
            //静态方法引用
    //        callStaticMethod();
            //非静态 实例方法引用
    //        callMethod();
            //非静态 类方法引用
    //        callMethodByClass();
            //构造函数方法引用
    //        callConstructorMethod();
            //数据不变模式
            callMethod2();
        }
    
        public static void consumer(){
            Consumer<String> consumer = System.out::println;
            consumer.accept("我是一个消费者");
        }
    
        private static void callStaticMethod() {
            Consumer<Dog> consumer = Dog::bark;
            consumer.accept(new Dog());
        }
    
        private static void callMethod() {
            Dog dog = new Dog();
            Function<Integer,Integer> function = dog::eat;
            System.out.println("还剩[" + function.apply(3) + "]斤狗粮");
        }
    
        private static void callMethodByClass() {
            BiFunction<Dog,Integer,Integer> biFunction  = Dog::eat;
            System.out.println("还剩[" + biFunction.apply(new Dog(),4) + "]斤狗粮");
        }
    
        private static void callConstructorMethod() {
            Supplier<Dog> supplier = Dog::new;
            System.out.println("new 了一个对象" + supplier.get());
        }
    
        private static void callMethod2() {
            Dog dog = new Dog();
            Function<Integer,Integer> function = dog::eat; //函数声明
            dog = null;
            System.out.println("还剩[" + function.apply(3) + "]斤狗粮");
        }
    
    }
    

    Stream流API

    Stream API是Java 8中加入的一套新的API,主要用于处理集合操作。Stream流API是函数式编程的核心所在,它以一种流式编程来对数据进行各种加工运算。形象的来说你可以把它看作工业中的流水线,将原料放入流中经过操作1、操作2…操作N输出一个产品。Stream也是如此它分为创建操作、中间操作、终止操作。业务逻辑清晰简单、代码看上去优雅不少。

    流通常是由三个部分组成:

    1. 数据源:流的获取,比如list.stream()方法;
    2. 中间处理:中间处理是对流元素的一系列处理。比如过滤filter,排序sorted,映射map;
    3. 终端处理:终端处理会生成结果,结果可以是任何不是流值。

    创建操作

    在这里插入图片描述
    在jdk8中集合数组加入了不少流的方法其中就有直接通过实例或是工具类创建流。如:list.stream(),而数据没有自身API需要借助工具类Arrays来创建。这里通过parallelStream()并行流的模式来创建就可以透明的使用到多线程了。

    注:通过阅读源码可以知Stream类与IntStream、LongStream并没有继承关系

    public class CreateStream {
    
        public static void main(String[] args) {
    //        collectionCreate();
    //        arrayCreate();
    //        numCreate();
            selfCreate();
        }
    
        /**
         * 集合创建
         */
        public static void collectionCreate(){
            List<String> list = new ArrayList<>();
            Stream<String> stream = list.stream();
            Stream<String> parallelStream = list.parallelStream();
        }
    
        /**
         * 数组创建
         */
        public static void arrayCreate(){
            Integer[] array = new Integer[5];
            Stream<Integer> stream = Arrays.stream(array);
        }
    
        /**
         * 数字创建
         */
        public static void numCreate(){
            IntStream.of(1,2,3);
            IntStream.rangeClosed(1,10);
            new Random().ints().limit(10);
        }
    
        /**
         * 自己创建
         */
        public static void selfCreate(){
            Random random = new Random();
            Stream.generate(random::nextInt).limit(20);
            Stream.iterate(2, (x) -> x*2).limit(10).forEach(System.out::println);
        }
    }
    

    中间操作

    中间操作分为有状态操作、无状态操作。无状态操作即该中间操作不依赖与另外的空间来存放临时结果。有状态即需要。这么说还是比较抽象,我们不妨来举个栗子0.0。

    比如说:你的排序操作传统我们要进行排序是否需要依赖额外空间来进行大小的比较。去重操作需要额外空间来存放未重复的值。而像是filter只是单纯返回过滤后的结果无需额外空间。

    这是一种说法,另一种说法该操作与其他操作,没有依赖关系即为无状态,反正则为有状态。这么说也没错,你看像是order操作不是就要等前面操作都执行完才可以执行吗。后面会提到一点就是Stream的操作模式实际上是每一条数据通过A操作B操作C操作来进行的,而到了中间有有状态操作是,必须停下等所有数据都操作到这一步时一起进行,否则你让他如何进行排序呢?

    public class MiddleStream {
        public static void main(String[] args) {
    //        mapOrMapToXXX();
    //        flatMap();
    //        peek();
    //        distinct();
    //        sort();
            limitSkip();
        }
    
        /**
         * map操作    A -> B
         * filter操作
         */
        public static void mapOrMapToXXX(){
            String s = "my name is 007";
            Stream.of(s.split(" ")).map(String::length).forEach(System.out::println);
            System.out.println("-------------");
            Stream.of(s.split(" ")).filter(x -> x.length() > 2).mapToDouble(x -> x.length()).forEach(System.out::println);
        }
    
        /**
         * flatMap操作 A -> B list
         * IntStream/LongStream 并不是stream的子类需要进行装箱
         */
        public static void flatMap(){
            String s = "my name is 007";
            Stream.of(s.split(" ")).flatMap(x -> x.chars().boxed()).forEach(x -> System.out.println((char)x.intValue()));
        }
    
        /**
         * peek 要类型对应
         */
        public static void peek(){
            IntStream.of(new int[]{1,2,3,4,5}).peek(System.out::println).forEach(x->{});
        }
    
        /**
         * distinct
         */
        public static void distinct(){
            IntStream.of(new int[]{1,3,3,4,5}).distinct().forEach(System.out::println);
        }
    
        /**
         * sort
         */
        public static void sort(){
            IntStream.of(new int[]{5,4,3,1,2}).sorted().forEach(System.out::println);
        }
    
        /**
         * limitSkip
         */
        public static void limitSkip(){
            IntStream.of(new int[]{1,2,3,4,5,6,7,8}).skip(2).limit(2).forEach(System.out::println);
        }
    }
    

    这里提到Stream有个特性叫做:惰性求值。什么意思呢?就是当stream没有调用到终止操作时,实际上是不会执行之前的所有过程的。这一点可以在demo工程中有相应的证明方法。 有接触过spark的同学可以将这一特性类比为Transformation和Action。

    终止从操作

    终止操作即流水线的最后一个操作,往往就是返回你所要的产品。
    这里分为短路操作和非短路操作:

    非短路操作:从流中获取所有数据进行运算返回,有可能返回一个或多个值,但必定运用到了所有数据
    短路操作:从流中截取部分数据返回。
    在这里插入图片描述

    public class FinalStream {
        public static void main(String[] args){
    //        forEachOrdered();
    //        collect();
    //        reduce();
    //        minMixCount();
            findFirst();
        }
    
        /**
         * forEachOrdered
         */
        public static void forEachOrdered(){
            IntStream.of(new int[]{1,2,3,4,5,6,7}).parallel().forEach(System.out::println);
    //        IntStream.of(new int[]{1,2,3,4,5,6,7}).parallel().forEachOrdered(System.out::println);
        }
    
        /**
         * collect、toArray
         */
        public static void collect(){
            String s = "hello world!";
            List<String> collect = Stream.of(s.split(" ")).collect(Collectors.toList());
            System.out.println(collect);
        }
    
        /**
         * reduce
         */
        public static void reduce(){
            Integer[] intArr = new Integer[]{1,2,3,4,5,6,7,8,9,10};
            Optional<Integer> optional = Stream.of(intArr).reduce((x, y) -> x + y);
            System.out.println(optional.get());
        }
    
        /**
         * minMixCount
         */
        public static void minMixCount(){
            Integer[] intArr = new Integer[]{1,2,3,4,5,6,7,8,9,10};
            Optional<Integer> optional = Stream.of(intArr).max(Comparator.comparingInt(x -> x));
            System.out.println(optional.get());
        }
    
        //短路操作--------------------------------
        /**
         * findFirst
         */
        public static void findFirst(){
            Optional<Integer> first = Stream.generate(() -> new Random().nextInt()).findFirst();
            System.out.println(first.get());
        }
    }
    
    

    并行流

    这里注释已经很全了就不做冗余说明。

    public class ParallelStream {
    
        public static void main(String[] args) {
    //        createParallelStream();
    //        feature2();
    //        feature3();
            feature4();
    
        }
    
        /**
         * 特性一 并行流线程数
         * 并行流线程数默认为cpu个数
         * 默认线程池
         */
        public static void createParallelStream(){
            IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug);
        }
    
        /**
         * 特性二 并行再串行 以最后一个流为准
         */
        private static void feature2(){
            IntStream.range(1, 100).parallel().peek(ParallelStream::printDebug).sequential().peek(ParallelStream::printDebug2).count();
        }
    
        /**
         * 特性三 默认线程池与设置默认线程数
         */
        private static void feature3(){
            System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","3");
            IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug);
        }
    
        /**
         * 特性四 自定义线程池 防止线程被阻塞
         */
        private static void feature4(){
            ForkJoinPool forkJoinPool = new ForkJoinPool();
            forkJoinPool.submit(() -> IntStream.range(1, 100).parallel().forEach(ParallelStream::printDebug));
            forkJoinPool.shutdown();
    
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    
        private static void printDebug(int i){
    //        System.out.println(i);
            System.out.println(Thread.currentThread().getName() + "debug:" + i);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        private static void printDebug2(int i){
            System.err.println(i);
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    级联表达式与柯里化

    简单来说就是将一个复杂表达式拆解为多个简单表达式,比如数学中的:
    y=5! 可以等价为 y = 1 * 2 * 3 * 4 * 5

    注意:这里涉及一个基础概念数据不变性,说白了就是匿名类中运用到外部变量时,外部变量需要是常量。细心的你会发现在级联表达式中外部变量均为常量。

    /**
     * 级联表达式和柯里化
     *
     * @author 旭旭
     * @create 2018-08-12 1:09
     **/
    public class CurryDemo {
    
        public static void main(String[] args) {
            //级联表达式
            Function<Integer,Function<Integer,Integer>> fun = x -> y -> x + y;
            //柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数
            //柯里化的意义:函数标准化
            //高阶函数:返回函数的函数
            System.out.println(fun.apply(2).apply(3));
    
            Function<Integer,Function<Integer,Function<Integer,Integer>>> fun2 = x -> y -> z -> x + y + z;
    
        }
    }
    

    收集器(终止操作因为内容较多提出来说明)

    终止操作中将数据以集合方式回收,可以对数据进行分类统计等。

    /**
     * 收集器
     *
     * @author 旭旭
     * @create 2018-08-18 23:43
     **/
    public class CollectorsStream {
    
        public static void main(String[] args) {
            List<Student> students = new ArrayList<>();
            students.add(new Student("一号",7,true,"一年级"));
            students.add(new Student("二号",8,true,"二年级"));
            students.add(new Student("三号",8,false,"二年级"));
            students.add(new Student("四号",9,true,"三年级"));
            students.add(new Student("五号",7,false,"一年级"));
            students.add(new Student("六号",8,true,"二年级"));
            students.add(new Student("七号",10,true,"四年级"));
    
    //        dataToList(students);
    //        summary(students);
    //        partitioning(students);
            group(students);
        }
    
        /**
         * 获取某一数据的集合
         */
        public static void dataToList(List<Student> students){
            List<Integer> list = students.stream().map(Student::getAge).collect(Collectors.toList());
            System.out.println(list);
        }
    
        /**
         * 获取某一数据的汇总值
         */
        public static void summary(List<Student> students){
            IntSummaryStatistics collect = students.stream().collect(Collectors.summarizingInt(Student::getAge));
            System.out.println(collect);
        }
    
        /**
         * 根据某一数据分类
         */
        public static void partitioning(List<Student> students){
            Map<Boolean, List<Student>> collect = students.stream().collect(Collectors.partitioningBy(x -> x.isGender()));
            System.out.println(collect);
        }
    
        /**
         * 根据某一数据分组
         */
        public static void group(List<Student> students){
            Map<String, Long> collect = students.stream().collect(Collectors.groupingBy(Student::getGrade, Collectors.counting()));
            System.out.println(collect);
        }
    }
    

    Stream特性

    /**
     * 流运行机制,基本特性
     *
     * @author 旭旭
     * @create 2018-08-19 22:51
     **/
    public class FeatureStream {
        public static void main(String[] args){
    //        feature123();
            feature46();
    //        feature5();
        }
    
        /**
         * 特性一 所有操作都是链式调用一个操作只迭代一次
         * 特性二 每一个中间流返回一个新的流,里面的sourceStage都指向同一个地方就是Head
         * 特性三 Head -> NextStage -> NextStage -> null
         */
        public static void feature123(){
            Random random = new Random();
            Stream<Integer> integerStream = Stream.generate(random::nextInt)
                    .limit(500)
                    .peek(x -> System.out.println("peek -> " + x))
                    .filter(x -> {System.out.println("filter -> " + x);return x > 100000;});
            integerStream.count();
        }
    
        /**
         * 特性四 有状态操作(多个参数操作),会把无状态操作阶段分隔,单独处理。
         * parallel /  sequetial 这个2个操作也是中间操作,但是他们不创建新的流,而是修改
         * Head的并行状态,所以多次调用时只会生效最后一个。
         */
        public static void feature46(){
            Random random = new Random();
            Stream<Integer> integerStream = Stream.generate(random::nextInt)
                    .limit(500)
                    .peek(x -> System.out.println("peek -> " + x))
                    .filter(x -> {System.out.println("filter -> " + x);return x > 100000;})
                    .sorted((x,y) -> {System.out.println("sorted -> " + x);return x - y;})
                    .filter(x -> {System.out.println("filter -> " + x);return x > 100000;})
    //                .parallel()
                    ;
            integerStream.count();
        }
    
    
        /**
         * 特性五 有状态操作并行环境下不一定能并行操作
         */
        public static void feature5(){
            Random random = new Random();
            Stream<Integer> integerStream = Stream.generate(random::nextInt)
                    .limit(500)
                    .peek(x -> print("peek -> " + x))
                    .filter(x -> {print("filter -> " + x);return x > 100000;})
                    .sorted((x,y) -> {print("sorted -> " + x);return x - y;})
                    .filter(x -> {print("filter -> " + x);return x > 100000;})
                    .parallel();
            integerStream.count();
        }
    
        private static void print(String x){
            System.out.println(Thread.currentThread().getName() + " " + x);
        }
    }
    
    

    工程地址

    俗话说的好:光说不练假把式,这里附上工程连接拉,有需要的童靴可以拉下来玩玩。
    https://gitee.com/softcx/functional_programming

    展开全文
  • Java函数式编程

    2021-10-24 18:55:53
    函数式编程是一种编程范式,即一切都是数学函数。在Java面向对象编程中,程序是一系列相互作用(方法)的对象,而在函数式编程中,程序会是一个无状态的函数组合序列。 函数是“第一等公民” “第一等公民”指的是...
  • 浅析基于函数式的编程范式_胡志英
  • 在尝试将范式转换为函数式编程时,我将尝试迭代我发现最重要的几个原因。请记住,这绝不是一个巨大的创新,我相信FP自70年代以来一直存在,但仅在最近几年它才获得吸引力并增加了人们的兴趣。我们来看看为什么!并发...
  • java8 函数式编程 我最近一直在研究Java 8,并掌握了Manning出版的“ Java 8 In Action” 。 让我印象深刻的第一件事是Java 8独特的销售主张是函数式编程。 函数现在是一流的变量,您可以像int或String一样在代码中...
  • 函数式编程 一种编程范式,它将电脑运算视为函数运算,并且避免使用程序状态以及易变对象。其中,λ演算(lambda calculus)为该语言最重要的基础。 特性 函数是“第一等公民” 函数与其他数据类型一样,可以赋值...
  • 函数式编程(Functionnal Programming,FP)是一种编程范式。我们平时接触的最多的是面向对象式的编程范式,所以我们在学习JS或者是JAVA的时候最开始都是从面向对象的编程思维开始学习。 面向对象编程的思维方式是:...
  • 啥是函数式编程? 概述: 是相较于 命令式编程 的一种编程范式; 是一种如何 搭建应用程序 的方法论,不是一种具体的技术; 具体的,能够熟练应用Stream流相关的API ,lamdba 表达式 到工作中,就说明你会函数式...
  • Java函数式接口

    2020-02-28 22:27:59
    Java函数式接口与函数式编程有着重要的联系。函数式编程是一种编程范式Java是一款完全面向对象的编程语言。面向对象和函数式编程一样它们都是一种编程范式。所谓编程范式,就是我们如何去组织我们的代码。 函数...
  • 想要学好Java必须要一步一个脚印打好基础、积攒实战经验,接下来小编就给大家分享Java函数式编程知识。 ​什么是函数式编程? 函数式编程是Java8的一大特色,也就是将函数作为一个参数传递给指定方法。函数式编程的...
  • 函数式编程语言是那些方便于使用函数式编程范式的语言。简单来说,如果具备函数式编程所需的特征, 它就可以被称为函数式语言。在多数情况下,编程的风格实际上决定了一个程序是否是函数式的。 是什么让一个语言具有...
  • 编程范式主要有三种:命令式编程(Imperative)、声明式编程(Declarative)和函数式编程(Functional)。 1、命令式编程(Imperative): 命令式编程的主要思想是关注计算机执行的步骤,即一步一步告诉计算机先做什么再做...
  • 今天我们就先来讲一下Java8引入的Lambda表达式,以及由此引入的函数式编程,以及函数式接口。 什么是函数式编程 函数式编程并不是Java新提出的概念,其与指令编程相比,强调函数的计算比指令的计算更重要;与过程...
  • 第一节 函数式范式 1. 什么是函数式编程 函数式编程(英语:functional programming)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。...
  • lambda表达式是java函数式编程范式的一个表现形式,将单方法类或接口的实现,直接看成一个可直接定义的函数方法,以更紧凑简洁形式,提高可读性,减少代码量; Lambda表达式允许您更紧凑地表达单方法类的实例。(这...
  • 仅仅是用实例表达函数式方法的理解这个系列文章不是分析java 8 stream中的方法源码,而是对java 8 stream特性,结合Kotlin, Rxjava之类的理解, 使用纯java的方式实现类似的函数式方法。需要对java 中的泛型以及...
  • 一文带你深入了解 java函数式编程

    千次阅读 2021-01-14 15:14:15
    不变性是函数式编程的核心原则之一。例如Clojure,默认上变量是不可变的,如果你要改变变量,你需要把变量copy出去修改。这对于并行程序来说,bug会大大减少。java中可以通过final来实现。 不可变的数据结构所有...
  • 计算机编程中存在许多编程范式,如命令编程、声明编程、面向对象编程以及结构化编程等等。其中面向对象编程范式认为程序是由一系列相互作用的对象组成,而结构化编程范式认为程序采用子程序、代码区块、for循环...
  • 在面向函数式编程中,一个程序会被看作是一个无状态的函数计算的序列,函数可以作为参数,返回值。在响应式编程中,程序被看作是数据流和以及其传播的过程。 面向过程编程 使用过C语言,我们就能深刻体会面向过程...
  • 函数式编程

    2021-03-07 04:28:28
    本章我们介绍Java函数式编程。我们先看看什么是函数。函数是一种最基本的任务,一个大型程序就是一个顶层函数调用若干底层函数,这些被调用的函数又可以调用其他函数,即大任务被一层层拆解并执行。所以函数就是...
  • 函数式变成思维

    2018-05-04 09:21:43
    本书脱离特定的语言特性,...知名软件架构师Neal Ford为我们展示不同的编程范式,帮我们完成从Java命令式编程人员,到使用Java、Clojure、Scala函数式编程人员的完美转变,建立对函数式语言的语法和语义的良好理解。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,995
精华内容 7,998
关键字:

java 函数式范式

java 订阅