函数式编程

lamda表达式

尽可能少的语法编写的函数定义,产生的是函数,而不是类
image.png
-> 可以认为是“产生”
只有一个参数,可以只写这个参数,不写括号
没有参数,必须用括号表示空的参数列表
如果表达式需要多行,需要将这些代码放入{}

方法引用

interface Callable{
    void call(String s);
}

class Describe{
    // show的签名(参数类型和返回类型)和Callable中call的签名一致
    void show(String  msg){
        System.out.println(msg);
    }
}

public class test1 {
    // hello()的签名和call一致
    static void hello(String name){
        System.out.println("Hello "+name);
    }

    static class Description{
        String about;

        Description (String desc){
            about = desc;
        }

        // help是静态内部类中的一个非静态方法
        void help(String msg){
            System.out.println(about + " " + msg);
        }
    }


    static class Helper{
        // assist是静态内部类中的一个静态方法
        static void assist(String msg){
            System.out.println(msg);
        }
    }

    public static void main(String[] args){
        Describe d = new Describe();

        // 将Describe对象的一个方法引用赋值给了Callable, Callable中没有show()方法,只有一个Call方法.
        // 然而,java对这种奇怪的赋值没有意见,因为这个方法引用的签名和Callable中的call方法一致
        Callable c = d::show;
        c.call("call()");   // java将call映射到了show上

        // 静态方法引用
        c = test::hello;
        c.call("Hinton");

        // 这是d::show的另一个版本,对某个活跃对象上的方法的引用,有事叫做"绑定方法引用"
        c = new Description("valuable: " )::help;
        c.call("细节");

        // 获得静态内部类中的静态方法的方法引用
        c = Helper::assist;
        c.call("Help!");
    }
}

image.png

Runnable

Runnable接口在java,lang中,遵循特殊的单方法接口格式,其Run方法没有参数,也没有返回值

package com.example.demo;

class  Go{
    static void go(){
        System.out.println("Go::go()");
    }
}

public class test1 {
   public static void main(String[] args){

       new Thread(new Runnable() {
           public void run(){
               System.out.println("Anonymous");
           }
       }).start();

       new Thread(
               ()->System.out.println("lamda")
       ).start();

       new Thread(Go::go).start();
   }
}

image.png
Thread对象接受一个Runnable作为其构造器参数,它有一个start()方法会调用run()

未绑定方法引用

尚未关联到某个对象的普通(非静态)方法
未绑定引用必须先提供对象,然后才能使用

class  X{
    String f(){
        return "X::f()";
    }
}

interface MakeString{
    String make();
}

interface TransformX{
    String transform(X x);
}

public class test1 {
   public static void main(String[] args){

        // MakeString ms = x::f;                 //[1]
        TransformX sp = X::f;
        X x = new X();
        System.out.println(sp.transform(x));     //[2]
        System.out.println(x.f());
   }
}
// 输出
// X::f()
// X::f()

到目前为止,我们看到的对方法的引用,与其关联接口的签名是相同的。在 [1]处, 我们尝试对X中的f()做同样的事情,将其赋值给MakeString。编译器会报错,提示“无效方法引用,即使make()的签名和f()相同。

问题在于,这里事实上还涉及另一个(隐藏的)参数:我们的老朋友this
如果没有一个可供附着的X 对象.就无法调用f()。因此,X::f 代表的是一个未绑定方法引用,因为它没有“绑定到” 某个对象。
为解决这个冋题,我们需要一个X对象,所以我们的接口事实上还需要一个额外的参数,如TransformX中所示。如果将X::f赋值给一个TransformX, Java会开心地接受。
我们必须再做一次心理调节:在未绑定引用的情况下,函数式方法(接口中的单一方法)的 签名与方法引用的签名不再完全匹配。这样做有一个很好的理由,那就是我们需要一个对象,让方法在其上调用。

在[2]处的结果有点儿像“脑筋急转弯”。我们接受了未绑定引用,然后以X为参数在 其上调用了 transformO,最终以某种方式调用了 x.f(),Java知道它必须接受第一个参数, 事实上就是this,并在它的上面调用该方法。

class This{
    void two(int i, double d){}
    void three(int i, double d, String s){}
    void four(int i, double d, String s, char c){}
}

interface TwoArgs{
    void call2(This athis, int i, double  d);
}

interface ThreesArgs{
    void call3(This athis, int i, double  d, String s);
}

interface FourArgs{
    void call4(This athis, int i, double  d, String s,char c);
}
public class test1 {
    public static void main(String[] args){
        TwoArgs twoArgs = This::two;
        ThreesArgs threesArgs = This::three;
        FourArgs fourArgs = This::four;
        This athis = new This();

        twoArgs.call2(athis, 11, 3.14);
        threesArgs.call3(athis, 11, 3.14, "hello");
        fourArgs.call4(athis, 11, 3.14, "hello", 'H');
    }
}

构造器方法引用

捕获对某个构造器的引用,之后通过该引用来调用哪个构造器

class Dog{
    String name;
    int age = -1;

    Dog(){name = "旺财";}
    Dog(String nm){name = nm; }
    Dog(String nm, int a){name = nm; age = a;}
}

interface MakeNoArgs{
    Dog make();
}

interface Make1Arg{
    Dog make(String nm);
}

interface Make2Arg{
    Dog make(String nm, int a);
}


public class test1 {
   public static void main(String[] args){
        MakeNoArgs mna = Dog::new;
        Make1Arg m1a = Dog::new;
        Make2Arg m2a = Dog::new;

        Dog dn = mna.make();
        Dog d1 = m1a.make("修斯");
        Dog d2 = m2a.make("修斯", 2);

   }
}

这三个构造器都只有一个名字 ::new
这时,构造器引用被赋值给了不同的接口,编译器可以从接口来推断使用哪个构造器

函数式接口

Java 8引入了包含一组接口的java.util.function,这些接口是 lambda表达式和方法引用的目标类型。每个接口都只包含一个抽象方法,叫作函数式方法



@FunctionalInterface
interface Functional{
    String goodbye(String arg);
}

interface FunctionalNoAnn {
    String goodbye(String arg);
}

/*
@FunctionalInterface
interface NotFunctional {
    String goodbye(String arg);
    String hello(String arg);
}
产生报错信息:
在接口 com.example.demo.NotFunctional 中找到多个非重写 abstract 方
*/
public class myTest {
    public String goodbye(String arg) {
        return "Goodbye, " + arg;
    }
    public static void main(String[] args) {
        myTest fa = new myTest();

        Functional f = fa::goodbye;
        FunctionalNoAnn fna = fa::goodbye;
        // Functional fac = fa; // 不兼容
        Functional fl = a -> "Goodbye, " + a;
        FunctionalNoAnn fnal = a -> "Goodbye, " + a;
    }
}

如果我们将一个方法引用或lambda表达式赋值给某个函数式接口(而且类型可以匹配), 那么Java会调整这个赋值,使其匹配目标接口。而在底层,Java编译器会创建一个实现 了目标接口的类的实例,并将我们的方法弓I用或lambda表达式包果在其中。

java.util.function中的目标类型

高阶函数

只是一个能接受函数作为参数或能把函数当返回值的函数。

import java.util.function.*;

 // 使用继承,可以轻松地为专门的接口创建一个别名
interface FuncSS extends Function<String, String> {}    

public class myTest {
    static FuncSS produce(){
        return s -> s.toLowerCase();                     // [2]
    }

    public static void main(String[] args) {
       FuncSS f = produce();
       System.out.println(f.apply("YELLING"));
    }
}
/*输出:
* yelling
* */
class One {}

class Two {}

public class myTest {
    static Two consume(Function<One, Two> oneTwo) {
        return  oneTwo.apply(new One());
    }
    public static void main(String[] args){
        Two two = consume(one -> new Two());
    }
}
class I {
    @Override
    public String toString(){
        return "I";
    }
}

class O {
    @Override
    public String toString(){
        return "O";
    }
}


public class myTest {
    static Function<I, O> transform(Function<I, O> in){
        /*
        * andThen方法是专门为操作函数而设计的默认方法
        * 会在in函数调用之后调用(还有一个compose方法, 它会在in函数之前应用新函数
        * 要附加一个andThen函数, 只需要将该函数作为参数传递
        * 从transform传出的是一个新函数,将in的动作和andThen参数的动作结合了起来
        */
        return  in.andThen(o -> {
            System.out.println(o);
            return o;
        });
    }

    public static void main(String[] args){
        Function <I,O> f2 = transform(i -> {
            System.out.println(i);
            return new O();
        });

        O o = f2.apply(new I());
    }
}
/*输出
* I
* O
* */

闭包

如果有一个比之前更复杂的lambda表达式,它使用了其函数作用域之外的变量。当返回该函数时,会发生什么呢? 外部变量会变成什么呢?如果语言能自动处理这个问题,那就说它是支持闭包的

// functional/Closurel.java
public class Closure1 {
    int i;
    IntSupplier makeFun(int x){
        return () -> x + i++;
    }
}

在调用makeFun之后,这个对象仍然还存在

// functional/myTest.java
public class myTest {
    public static void main(String[] args){
        Closure1 c1 = new Closure1();

        IntSupplier f1 = c1.makeFun(0);
        System.out.println(f1.getAsInt());

        IntSupplier f2 = c1.makeFun(0);
        System.out.println(f2.getAsInt());

        IntSupplier f3 = c1.makeFun(0);
        System.out.println(f3.getAsInt());
    }
}
/*输出:
* 0
* 1
* 2
* */
// functional/Closure2.java
public class Closure2 {

    IntSupplier makeFun(int x){
        int i = 0;
        return () -> x + i;
    }
}

/*输出:
* 0
* 0
* 0
* */
// functional/Closure3.java
public class Closure3 {

    IntSupplier makeFun(int x){
        int i = 0;
        return () -> x++ + i++;
    }

}

此时, 不论是x++或者i++都会报错 -> lambda 表达式中使用的变量应为 final 或有效 final
那为什么Closure2能编译成功呢?这里就引出了final的意义
虽然我们没有显式地声明为最终变量,但是仍然可以以最终变量的方式来对待它,只要不修改它即可
如果一个局部变量的从初始值没有改变,那它实际上就是一个最终变量

// functional/Closure4.java
public class Closure4 {
    IntSupplier makeFun(int x){
        int i = 0;
        i++;
        x++;
        final int iFinal = i;
        final int xFinal = x;
        return () -> iFinal + xFinal;
    }
}
// 这里的两个final是多余的
// functional/Closure5.java
public class Closure5 {
    Supplier<List<Integer>> makeFun(){
        final List<Integer> ai = new ArrayList<>(); //  final可以删去
        ai.add(1);
        return () -> ai;
    }

    public static void main(String[] args){
        Closure5 c = new Closure5();

        List<Integer>
        l1 = c.makeFun().get(),
        l2 = c.makeFun().get();
        System.out.println(l1);
        System.out.println(l2);

        l1.add(42);
        l2.add(96);
        System.out.println(l1);
        System.out.println(l2);
    }
}
/* 输出
[1]
[1]
[1, 42]
[1, 96]
*/

每次调用makeFun时,会创建一个全新的ArrayList
final关键字应用于对象引用,只是说这个对象引用不能被重新赋值,并不是说我们不能修改对象本身。

public class Closure6 {
    IntSupplier makeFun(int x){
        int i = 0;
        // 同样的规则适用于i++, x++ 
        return new IntSupplier() {
            public int getAsInt(){
                return x + i;
            }
        };
    }
}

只要有内部类.就会有闭包

柯里化与部分求值

柯里化定义: 将一个接受多个参数的函数转变为一系列只接受一个参数的函数

public class CurryingAndPartials {
    // 未柯里化
    static String uncurried(String a, String b) {
        return a + b;
    }

    public static void main(String[] args){
        // 柯里化函数
        Function<String, Function<String, String>> sum = a -> b -> a + b;

        System.out.println(uncurried("嘿","哈"));

        // 通过提供一个参数来创建一个新函数
        Function<String, String> hi = sum.apply("你");
        System.out.println(hi.apply("哈"));

        // 部分应用
        Function<String, String> sumHi = sum.apply("我");
        System.out.println(sumHi.apply("哈"));
        System.out.println(sumHi.apply("嘻"));
    }
}
/* 输出:
嘿哈
你哈
我哈
我嘻
 */
public class Curry3Args {
    public static void main(String[] args){
        Function<String,
        Function<String,
        Function<String, String>>> sum = a -> b -> c -> a + b + c;
        Function<String, Function<String, String>> hi = sum.apply("嘿: ");
        Function<String, String> ho = hi.apply("哈: ");

        System.out.println(ho.apply("我"));
    }
}

相关推荐

  1. 函数编程

    2024-07-17 05:28:02       52 阅读
  2. 函数编程

    2024-07-17 05:28:02       46 阅读
  3. 函数编程

    2024-07-17 05:28:02       51 阅读
  4. 函数编程要点

    2024-07-17 05:28:02       52 阅读
  5. 函数编程

    2024-07-17 05:28:02       32 阅读
  6. 7 - 函数编程

    2024-07-17 05:28:02       27 阅读
  7. Python函数编程

    2024-07-17 05:28:02       28 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 05:28:02       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 05:28:02       71 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 05:28:02       58 阅读
  4. Python语言-面向对象

    2024-07-17 05:28:02       69 阅读

热门阅读

  1. Linux的线程

    2024-07-17 05:28:02       21 阅读
  2. Transformer模型在多任务学习中的革新应用

    2024-07-17 05:28:02       25 阅读
  3. 【Qt+opencv】ROI与图像混合

    2024-07-17 05:28:02       25 阅读
  4. Jmeter二次开发Demo

    2024-07-17 05:28:02       23 阅读
  5. ubuntu上通过修改grub启动参数,将串口重定向到sol

    2024-07-17 05:28:02       23 阅读
  6. 【mysql】02在ubuntu24安装并配置mysql

    2024-07-17 05:28:02       26 阅读
  7. 常见云存储服务对比

    2024-07-17 05:28:02       19 阅读
  8. Linux基础 -- 运行安全之ASLR的作用与实现方式

    2024-07-17 05:28:02       28 阅读
  9. Django获取request请求中的参数

    2024-07-17 05:28:02       26 阅读
  10. 上传文件给Ubuntu服务器

    2024-07-17 05:28:02       27 阅读
  11. 0. 前言

    2024-07-17 05:28:02       32 阅读