【问题标题】:Java 8 Pattern Matching?Java 8 模式匹配?
【发布时间】:2012-06-28 12:14:44
【问题描述】:

Java 8 会像 Scala 和其他函数式程序那样支持模式匹配吗?我正在对 Java 8 的 Lambda 功能进行演示。我在这个特殊的函数式编程概念上找不到任何东西。

我记得让我对函数式编程感兴趣的是快速排序实现,尤其是与命令式编程的实现相比。

【问题讨论】:

  • 不,它不支持这样的东西。
  • 模式匹配与函数式编程无关。它主要以函数式语言提供,这不过是巧合。

标签: java lambda java-8 matching


【解决方案1】:

我想你不是在谈论在字符串上应用正则表达式的意义上的模式匹配,而是applied in Haskell。例如使用通配符:

head (x:_)  = x
tail (_:xs) = xs

Java 8 本身不支持这一点,但是使用 Lambda 表达式有一些方法可以做到这一点,例如计算阶乘:

public static int fact(int n) {
     return ((Integer) new PatternMatching(
          inCaseOf(0, _ -> 1),
          otherwise(  _ -> n * fact(n - 1))
     ).matchFor(n));
}

如何实现,您将在这篇博文中找到更多信息:Towards Pattern Matching in Java

【讨论】:

    【解决方案2】:

    在 Java 8 中可以将模式匹配实现为一个库(利用 lambda 表达式),但不幸的是,我们仍然会缺少 Haskell 或 Scala 等语言所具有的编译器详尽性检查。

    Cyclops-react 有一个强大的 Pattern Matching 模块,它提供 Java 8 的结构模式匹配和通过守卫的模式匹配。

    它提供了when/then/otherwise DSL 和匹配,包括基于标准Java Predicates 的解构(例如可以使用匹配来过滤一个Stream)。

    守卫匹配

    为了通过守卫进行匹配,我们使用 whenGuard / then / else 来清楚地显示案例正在驱动测试,而不是被测对象的结构。

    例如对于基于守卫的匹配,如果我们实现一个实现 Matchable 接口的 Case 类

     static class MyCase  implements Matchable{ int a; int b; int c;}
    

    (顺便说一句,Lombok 可以非常方便地创建密封案例类层次结构)

    我们可以匹配它的内部值(必要时递归,或按类型以及其他各种选项)。

      import static com.aol.cyclops.control.Matchable.otherwise;
      import static com.aol.cyclops.control.Matchable.whenGuard;
    
      new MyCase(1,2,3).matches(c->c.is(whenGuard(1,2,3)).then("hello"),
                                   .is(whenGuard(4,5,6)).then("goodbye")
                                   ,otherwise("goodbye")
                               );
    

    如果我们有一个没有实现 [Matchable][3] 的 Object,无论如何我们都可以强制它为 Matchable,我们的代码会变成

    Matchable.ofDecomposable(()->new MyCase(1,2,3)))
             .matches(c->c.is(whenGuard(1,2,3)).then("hello"),
                          .is(whenGuard(4,5,6)).then("goodbye")
                          ,otherwise("hello"));
    

    如果我们不关心其中一个值,我们可以使用通配符

    new MyCase(1,2,3).matches(c->c.is(whenGuard(1,__,3)).then("hello"),
                                  .is(whenGuard(4,__,6)).then("goodbye")
                                  ,otherwise("hello)
                               );
    

    或者递归地解构一组嵌套的类

    Matchable.of(new NestedCase(1,2,new NestedCase(3,4,null)))
                    .matches(c->c.is(whenGuard(1,__,has(3,4,__)).then("2")
                     ,otherwise("default");
    

    NestedCase 看起来像这样 -

    class NestedCase implemends Decomposable { int a; int b; NestedCase c; }
    

    用户还可以使用 hamcrest 编写模式匹配表达式

     import static com.aol.cyclops.control.Matchable.otherwise;
     import static com.aol.cyclops.control.Matchable.then;
     import static com.aol.cyclops.control.Matchable.when;
    
     Matchable.of(Arrays.asList(1,2,3))
                    .matches(c->c.is(when(equalTo(1),any(Integer.class),equalTo(4)))
                            .then("2"),otherwise("default"));
    

    结构模式匹配

    我们还可以匹配被测对象的确切结构。这不是使用 if / then 测试来查看结构是否恰好匹配我们的案例,我们可以让编译器确保我们的案例与提供的对象的结构相匹配。执行此操作的 DSL 与基于保护的匹配几乎相同,但我们使用 when / then / else 来清楚地显示对象结构驱动测试用例,反之亦然。

      import static com.aol.cyclops.control.Matchable.otherwise;
      import static com.aol.cyclops.control.Matchable.then;
      import static com.aol.cyclops.control.Matchable.when;
    
      String result =  new Customer("test",new Address(10,"hello","my city"))
                                .match()
                                .on$_2()
                                .matches(c->c.is(when(decons(when(10,"hello","my city"))),then("hello")), otherwise("miss")).get();
    
      //"hello"
    

    对从客户中提取的地址对象进行结构匹配。 Customer 和 Address 类看起来是这样的

    @AllArgsConstructor
    static class Address{
        int house;
        String street;
        String city;
    
        public MTuple3<Integer,String,String> match(){
            return Matchable.from(()->house,()->street,()->city);
        }
    }
    @AllArgsConstructor
    static class Customer{
        String name;
        Address address;
        public MTuple2<String,MTuple3<Integer,String,String>> match(){
            return Matchable.from(()->name,()->Maybe.ofNullable(address).map(a->a.match()).orElseGet(()->null));
        }
    }
    

    cyclops-react 提供了一个Matchables 类,它允许对常见的JDK 类型进行结构模式匹配。

    【讨论】:

    • This link 在这个答案中似乎被打破了。模式匹配的文档是否在其他地方可用?
    【解决方案3】:

    Derive4J 是一个库,旨在支持对 java(甚至更多)的 sum 类型和结构模式匹配的近乎原生支持。 以一个小型计算器 DSL 为例,使用 Derive4J 可以编写如下代码:

    import java.util.function.Function;
    import org.derive4j.Data;
    import static org.derive4j.exemple.Expressions.*;
    
    @Data
    public abstract class Expression {
    
        interface Cases<R> {
            R Const(Integer value);
            R Add(Expression left, Expression right);
            R Mult(Expression left, Expression right);
            R Neg(Expression expr);
        }
    
        public abstract <R> R match(Cases<R> cases);
    
        private static Function<Expression, Integer> eval = Expressions
            .match()
                .Const(value        -> value)
                .Add((left, right)  -> eval(left) + eval(right))
                .Mult((left, right) -> eval(left) * eval(right))
                .Neg(expr           -> -eval(expr));
    
        public static Integer eval(Expression expression) {
            return eval.apply(expression);
        }
    
        public static void main(String[] args) {
            Expression expr = Add(Const(1), Mult(Const(2), Mult(Const(3), Const(3))));
            System.out.println(eval(expr)); // (1+(2*(3*3))) = 19
        }
    }
    

    【讨论】:

      【解决方案4】:

      我知道这个问题已经有人回答了,而且我是函数式编程的新手,但在犹豫了很久之后,我终于决定参与到这个讨论中来对接下来的内容提出反馈。

      我会建议(也是?)下面的简单实现。它与已接受答案中引用的(好)文章略有不同;但根据我的(短暂的)经验,它使用起来更灵活且易于维护(当然这也是个人喜好问题)。

      import java.util.Optional;
      import java.util.function.Function;
      import java.util.function.Predicate;
      
      final class Test
      {
          public static final Function<Integer, Integer> fact = new Match<Integer>()
                  .caseOf( i -> i == 0, i -> 1 )
                  .otherwise( i -> i * Test.fact.apply(i - 1) );
      
          public static final Function<Object, String> dummy = new Match<Object>()
                  .caseOf( i -> i.equals(42), i -> "forty-two" )
                  .caseOf( i -> i instanceof Integer, i -> "Integer : " + i.toString() )
                  .caseOf( i -> i.equals("world"), i -> "Hello " + i.toString() )
                  .otherwise( i -> "got this : " + i.toString() );
      
          public static void main(String[] args)
          {
              System.out.println("factorial : " + fact.apply(6));
              System.out.println("dummy : " + dummy.apply(42));
              System.out.println("dummy : " + dummy.apply(6));
              System.out.println("dummy : " + dummy.apply("world"));
              System.out.println("dummy : " + dummy.apply("does not match"));
          }
      }
      
      final class Match<T>
      {
          public <U> CaseOf<U> caseOf(Predicate<T> cond, Function<T, U> map)
          {
              return this.new CaseOf<U>(cond, map, Optional.empty());
          }
      
          class CaseOf<U> implements Function<T, Optional<U>>
          {
              private Predicate<T> cond;
              private Function<T, U> map;
              private Optional<CaseOf<U>> previous;
      
              CaseOf(Predicate<T> cond, Function<T, U> map, Optional<CaseOf<U>> previous)
              {
                this.cond = cond;
                this.map = map;
                this.previous = previous;
              }
      
              @Override
              public Optional<U> apply(T value)
              {
                  Optional<U> r = previous.flatMap( p -> p.apply(value) );
                  return r.isPresent() || !cond.test(value) ? r
                      : Optional.of( this.map.apply(value) );
              }
      
              public CaseOf<U> caseOf(Predicate<T> cond, Function<T, U> map)
              {
                return new CaseOf<U>(cond, map, Optional.of(this));
              }
      
              public Function<T,U> otherwise(Function<T, U> map)
              {
                  return value -> this.apply(value)
                      .orElseGet( () -> map.apply(value) );
              }
          }
      }
      

      【讨论】:

      • 我比使用地图的命令模式更喜欢这种方法,并且比程序 if else if ..else 或命令式 swich 语句要好得多。这应该是 jvm 原生接口的一部分,大学里的教授应该使用这个特性教 java
      • 这个Match类属于哪个API?
      【解决方案5】:

      JMPL 是一个简单的 java 库,它可以模拟一些特征模式匹配,使用 Java 8 特性。

         matches(data).as(
            new Person("man"),    () ->  System.out.println("man");
            new Person("woman"),  () ->  System.out.println("woman");
            new Person("child"),  () ->  System.out.println("child");        
            Null.class,           () ->  System.out.println("Null value "),
            Else.class,           () ->  System.out.println("Default value: " + data)
         );
      
      
         matches(data).as(
            Integer.class, i  -> { System.out.println(i * i); },
            Byte.class,    b  -> { System.out.println(b * b); },
            Long.class,    l  -> { System.out.println(l * l); },
            String.class,  s  -> { System.out.println(s * s); },
            Null.class,    () -> { System.out.println("Null value "); },
            Else.class,    () -> { System.out.println("Default value: " + data); }
         );
      
         matches(figure).as(
            Rectangle.class, (int w, int h) -> System.out.println("square: " + (w * h)),
            Circle.class,    (int r)        -> System.out.println("square: " + (2 * Math.PI * r)),
            Else.class,      ()             -> System.out.println("Default square: " + 0)
         );
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-04-28
        • 1970-01-01
        • 1970-01-01
        • 2020-07-08
        • 1970-01-01
        • 2015-01-10
        • 1970-01-01
        相关资源
        最近更新 更多