• 内部类与函数式编程


      首先,我们来看下策略模式,这是网上找到的一个关于策略模式的解释:策略模式是对算法的包装,是把使用算法的责任和算法本身分割开来,委派给不同的对象管理。策略模式通常把一个系列的算法包装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是:“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。

      这里不详细介绍策略模式,只是简单的用策略模式来引入函数式编程的例子。

      通常,传递给方法的数据不同,结果不同。如果我们希望方法在调用时行为不同,该怎么做呢?结论是:只要能将代码传递给方法,我们就可以控制它的行为。

    interface Strategy {
      String approach(String msg);
    }
    
    class DefaultStrategy implements Strategy {
      public String approach(String msg) {
        return msg.toLowerCase() + "?";
      }
    }public class Strategize {
      Strategy strategy = new DefaultStrategy();
      String msg;
      Strategize(String msg) {
        this.msg = msg;
      }
    
      void communicate() {
        System.out.println(strategy.approach(msg));
      }
    
      void changeStrategy(Strategy strategy) {
        this.strategy = strategy;
      }
    }

      Strategize有一个默认的策略,当我们调用communicate时,就是使用默认的策略来处理msg。当然我们也可以更换Strategy从而让communicate表现不同的行为。在java 8之前我们除了像DefaultStrategy那样定义一个类之外,还可以通过定义一个匿名内部类来实现。

    public static void main(String[] args) {
        Strategize s = new Strategize("Hello there");
        Strategy s = new Strategy() {
            public String approach(String msg) {
              return msg.toUpperCase() + "!";
            }
         };
        s.communicate();
      }

      

      在java 8中引入函数式编程后,上面的例子就可以用Lambda 表达式进行改写

    public static void main(String[] args) {
        Strategize s = new Strategize("Hello there");
        Strategy s = msg -> msg.toUpperCase() + "!";
        s.communicate();
      }

      代码少多了,实现的效果和上面一样。

      如果你想使用的策略已经存在其他类或对象中,还可以直接使用方法引用的方式来实现。如下面的例子。

    public static void main(String[] args) {
        Strategize s = new Strategize("Hello there");
        Strategy s = String::toLowerCase;
        s.communicate();
      }

      Lambda 表达式和方法引用产生函数,而不是类。 虽然实际上在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,java 8在幕后执行各种操作使 Lambda 看起来像函数 —— 但作为程序员,你可以高兴地假装它们“只是函数”。意识到这一点很重要,我们使用Lambda 表达式和方法引用进行函数式编程时,不要和类联系起来,仅仅当它们是函数就可以了,这样更容易理解代码。

      如果一个接口只有一个方法,那么就可以称为函数时接口,在java 8之前很容易通过匿名内部类来实现。在java 8中将匿名内部类转成Lambda 表达式也是很简单的,只需要多练习几次就很容易掌握。

      关于方法引用可能就比较难以理解。简单来说就是返回值和方法参数一致,就可以用方法引用,具体细节参考下面的例子。

    import java.util.*;
    
    interface Callable { // [1]
      void call(String s);
    }
    
    class Describe {
      void show(String msg) { // [2]
        System.out.println(msg);
      }
    }
    
    public class MethodReferences {
      static void hello(String name) { // [3]
        System.out.println("Hello, " + name);
      }
      static class Description {
        String about;
        Description(String desc) { about = desc; }
        void help(String msg) { // [4]
          System.out.println(about + " " + msg);
        }
      }
      static class Helper {
        static void assist(String msg) { // [5]
          System.out.println(msg);
        }
      }
      public static void main(String[] args) {
        Describe d = new Describe();
        Callable c = d::show; // [6]
        c.call("call()"); // [7]
    
        c = MethodReferences::hello; // [8]
        c.call("Bob");
    
        c = new Description("valuable")::help; // [9]
        c.call("information");
    
        c = Helper::assist; // [10]
        c.call("Help!");
      }
    }

      [1] 我们从单一方法接口开始(同样,你很快就会了解到这一点的重要性)。

      [2] show() 的签名(参数类型和返回类型)符合 Callable 的 call() 的签名。

      [3] hello() 也符合 call() 的签名。

      [4] help() 也符合,它是静态内部类中的非静态方法。

      [5] assist() 是静态内部类中的静态方法。

      [6] 我们将 Describe 对象的方法引用赋值给 Callable ,它没有 show() 方法,而是 call() 方法。 但是,Java 似乎接受用这个看似奇怪的赋值,因为方法引用符合 Callable 的 call() 方法的签名。

      [7] 我们现在可以通过调用 call() 来调用 show(),因为 Java 将 call() 映射到 show()

      [8] 这是一个静态方法引用。

      [9] 这是 [6] 的另一个版本:对已实例化对象的方法的引用,有时称为绑定方法引用

      [10] 最后,获取静态内部类的方法引用的操作与 [8] 中外部类方式一样。

      上面提到了绑定方法引用,那么对应的还有未绑定方法引用。下面是一个例子

    class X {
      String f() { return "X::f()"; }
    }
    
    interface MakeString {
      String make();
    }
    
    interface TransformX {
      String transform(X x);
    }
    
    public class UnboundMethodReference {
      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()); // 同等效果
      }
    }

      我们已经知道了与接口方法同名的方法引用。 在 [1],我们尝试把 X 的 f() 方法引用赋值给 MakeString。结果:即使 make() 与 f() 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 这是因为实际上还有另一个隐藏的参数:this。 你不能在没有 X 对象的前提下调用 f()。 因此,X :: f 表示未绑定的方法引用,因为它尚未“绑定”到对象。

      要解决这个问题,我们需要一个 X 对象,所以我们的接口实际上需要一个额外的参数的接口,如上例中的 TransformX。 如果将 X :: f 赋值给 TransformX,这在 Java 中是允许的。这次我们需要调整下心里预期——使用未绑定的引用时,函数方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 理由是:你需要一个对象来调用方法。

      [2] 的结果有点像脑筋急转弯。 我接受未绑定的引用并对其调用 transform(),将其传递给 X,并以某种方式导致对 x.f() 的调用。 Java 知道它必须采用第一个参数,这实际上就是 this,并在其上调用方法。

      本文参考 on java 8

  • 相关阅读:
    JSOI2018 战争(凸包+闵可夫斯基和)
    树结构
    Emacs奇技淫巧
    概率期望
    动态DP
    无旋treap大法好
    玩NOILinux
    <虚树+树型DP> HNOI2014世界树
    <虚树+树型DP> SDOI2011消耗战
    日志系统升级之路(.Net客户端的使用)
  • 原文地址:https://www.cnblogs.com/lilinwei340/p/12833847.html
Copyright © 2020-2023  润新知