Skip to content

Comparison with Streams and Lambdas in JDK8

Glen K. Peterson edited this page Jan 21, 2022 · 13 revisions
  • When you process data with a Java8+ stream, you have to add .stream(). You're no longer forced to end up with a mutable collection, but even if you choose .stream().collect(Collectors.toUnmodifiableList()) it still implements plain old List so there are no IDE hints or compiler warnings if you try to call mutator methods like .add(x). Paguro's .toImList() is not just briefer, it also returns an ImList with the mutator methods deprecated.

  • There are dead-ends to Java's stream evaluation. For instance Java's Optional<T>.ifPresent() and ifPresentOrElse() methods return void. Paguro's Option<T> returns a value so you can keep evaluating as long as you like:

    x = myOption.match(it -> it.doOrReturnSomething()
                       () -> /* do or return something else */)

    Plus you can throw checked exceptions from Paguro's lambdas without any fuss (see below).

  • If you later add or remove a few items, Java's Unmodifiable collections require an expensive defensive copy of the entire collection. The Clojure-derived collections in Paguro only duplicate the tiny area of the collection that was changed. As immutable collections go, they have excellent performance.

  • The java.util.function interfaces do nothing to help you with Exceptions. Paguro wraps checked exceptions in unchecked ones for you, so that you can write anonymous functions without worrying about exceptions (as you would in Kotlin, Scala, or Clojure).

  • For up to 2-argument functions, java.util.function has 43 different interfaces. The functional methods on these interfaces are named differently, with a total of 11 different names for apply(). Paguro has 3 equivalent interfaces, named by number of arguments (like Scala). All have an applyEx() that you override and an apply() method that callers can use if they want to ignore checked exceptions (they do). If you don't want to return a result, declare the return type as ? and return null. For example: Fn1<Integer,?> takes an Integer and the return value is ignored.

  • I had an enum, MyEnum and wanted to pass MyEnum::values as a function reference to a Java 8 stream. The return-type of MyEnum.values() is Enum<MyEnum>[]. An array of a parameterized type. Between the Arrays.asList(), the cast from MyEnum[] to Enum<MyEnum>[], the checked exception in the method body - what a nightmare! In Paguro, all you need is xformArray(MyEnum.values()) and you're in a happy functional world.

  • If you want to define data in Java 8, you end up learning the difference between Arrays.asList() and Collections.singletonList(), or defining one-off classes for every kind of data you might need before you start writing any code. Paguro has a tiny data-definition language (like a type-safe JSON) with extensible Tuples to give your immutable data structures meaningful type-safe names with a minimum of code (see: Usage Example). With Paguro, you can define your data first (using tuples) and name it later. Even the initial anonymous definition is checked by the type system.

  • You can still use non-destructive Java 8 stream methods on immutable (or unmodifiable) collections if you want to. Paguro doesn't affect what you do with java.util mutable collections at all.