Java 8 Pattern Matching?

I suppose you are not talking about pattern matching in the sense of applying a regular expression on a string, but as applied in Haskell. For instance using wildcards:

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

Java 8 will not support that natively, with Lambda expression there are, however, ways to do so, like this for computing the factorial:

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

How to implement that you will find more information in this blog post: Towards Pattern Matching in Java.


It's possible to implement pattern matching as a library in Java 8 (taking advantage of lambda expressions), but unfortunately we will still be missing the compiler exhaustiveness check that languages such as Haskell or Scala have.

Cyclops-react has a powerful Pattern Matching module, which offers both structural pattern matching for Java 8, and pattern matching via guards.

It provides a when / then / otherwise DSL and matching, including deconstruction is based on standard Java Predicates (so matching can be used to filter a Stream for example).

Matching by guards

For matching via guards we use whenGuard / then / otherwise to clearly show the case is driving the test and not the structure of the Object under test.

e.g. For guard based matching, If we implement a Case class that implements the Matchable interface

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

(btw, Lombok can come in very handy for creating sealed case class hierarchies)

We can match on it's internal values (recursively if neccessary, or by type among various other options).

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

If we have an Object that doesn't implement [Matchable][3], we can coerce it to Matchable anyway, our code would become

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

If we don't care about one of the values we can use wildcards

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

Or recursively de-structure a nested set of classes

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

Where NestedCase looks something like this -

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

Users can also compose pattern matching expressions using 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"));

Structual pattern matching

We can also match on the exact structure of the Object being tested. That is rather than use if / then tests to see if the structure happens to match our cases, we can have the compiler ensure that our cases match the structure of the provided Objects. The DSL to do this is almost identical as per guard based matching, but we use when / then / otherwise to clearly show the Objects structure drives the test cases and not vice versa.

  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"

Structurally matching on an Address Object extracted from a customer. Where the Customer and Address classes look this this

@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 provides a Matchables class which allows structural pattern matching against common JDK types.


I am aware that this question has already been answered, furthermore I am new to functional programming but, after much hesitation, I finally decided to get invloved in this discussion to have feedback on what follows.

I would suggest the (too ?) simple implementation below. It is slightly different from the (nice) article cited in the accepted answer ; but in my (short) experience, it was a bit more flexible to use and easy to maintain (which is of course also a matter of taste).

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