Java 8 模式匹配?

31

Java 8是否支持像Scala和其他函数式编程语言一样的模式匹配?我正在准备Java 8 Lambda特性的演示文稿,但在这个特定的函数式编程概念上找不到任何信息。

我记得让我对函数式编程感兴趣的是快速排序算法的实现,特别是与命令式编程的实现相比较而言。


2
不,它不会支持任何类似的功能。 - Marko Topolnik
2
模式匹配与函数式编程无关。它只是一个巧合,大多数情况下它在函数式语言中可用。 - SK-logic
5个回答

32

我想你所说的模式匹配并不是指在字符串上应用正则表达式,而是指如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


10

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

Cyclops-react拥有一个强大的Pattern Matching模块,为Java 8提供了结构化模式匹配和通过guards进行模式匹配的功能。

它提供了when / then / otherwise DSL和匹配,包括基于标准Java谓词的解构(因此匹配可以用于过滤流,例如)。

通过guards进行匹配

对于通过guards进行匹配,我们使用whenGuard / then / otherwise来清楚地显示情况正在驱动测试,而不是被测试对象的结构所驱动。

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

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

(btw,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]的对象,我们仍然可以将其强制转换为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/otherwise 来清楚地展示对象的结构驱动测试案例,而不是相反。

  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"

从客户中提取的地址对象进行结构匹配。其中,客户和地址类如下所示

@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类型进行结构模式匹配。


这个答案中的链接似乎已经失效了。有没有其他地方可以找到模式匹配的文档? - Anderson Green

3

我知道这个问题已经有答案了,而且我对函数式编程还很陌生,但是经过了很多犹豫之后,我最终决定参与讨论并获得反馈。

我建议使用以下(可能太)简单的实现方式。它与被接受的答案中提到的(好的)文章略有不同,但在我的(短暂)经验中,它更加灵活易用且易于维护(当然也是品味的问题)。

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) );
        }
    }
}

1
我更喜欢这种方法,而不是使用映射的命令模式,比起过程式的if else if...else或命令式的switch语句要好得多。这应该成为JVM本地接口的一部分,大学教授应该使用这个特性来教授Java。 - Gabriel Hernandez
这个Match类属于哪个API? - Yashpal

0

Derive4J 是一个旨在为 Java 提供接近本地支持的和结构化模式匹配的库(甚至更多)。 以小型计算器 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
    }
}

0

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)
   );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接