Skip to content
Brendan Cheong Ern Jie edited this page Oct 12, 2021 · 21 revisions

Declarative vs Imperative programming

The stream data structure was introduced in java 8 and is used in functional programming, where data is manipulated in a self contained data pipeline. Streams aim to replace the for loop and are considered a type of declarative programming as opposed to imperative programming, where we write code that tells the program what we want but not how to get it instead of telling the program step by step how to get what we want.

Thus, in streams we completely remove the need for a for loop and avoid needing state management which can cause mutable data structures, leading to bugs (that's why all streams are self-contained).

Lets take a look at some examples

// Imperative Approach
List<Integer> intList = List.<Integer>of(1,2,3,4,5,6,7,8,9,10);

/**
 * Here we have to take note of the "state" of evenList. 
 * Right now its empty, but it will change its state overtime*
 */
List<Integer> evenList = new ArrayList<>(); 

for (Integer i : intList) { // we instruct the program to loop through the list
    if (i % 2 == 0) { // if the number is even, add to evenList
        evenList.add(i);
    }
}

System.out.println(evenList.toString());

// Declarative Approach
List<Integer> intList = List.<Integer>of(1,2,3,4,5,6,7,8,9,10);

/**
 * Here we tell the program to give us everything that is even.
 * Note that the stream is self contained, and there's no "state" to be found 
 */
List <Integer> evenList =  
    intList.
        stream().
        filter((x) -> x % 2 == 0).
        collect(Collectors.toList());

System.out.println(evenList.toString());
                              

Lazy Evaluation

Stream Operations

Here is a non-exhaustive table of some useful stream operations. Feel free to add more to the table and add examples of how these stream operations can be used!

Starting a Stream Intermediate Operations Terminal Operations
Stream.<T>of(...)
Stream.<T>of(T t)
map(Function<? super T, ? extends U> mapper)
.map((x) -> x.method())
collect(Collectors<? super T, U, V> collector)
.collect(Collectors.toList())
Stream.<String>concat(Stream<? extends T> a, Stream<? extends T> b) filter(Predicate<? super T> pred)
.filter((x) -> { if x % 2 == 0 return true;})
forEach(Consumer<? super T> action
.forEach((x) -> System.out.println(x))
flatMap(Function<? super T, ? extends Stream<? extends U>> mapper)
.flatMap((x) -> x.stream())
noneMatch(Predicate<? super T> pred)
peek(Consumer<? super T> action>
.peek((x) -> System.print.ln(x))
allMatch(Predicate<? super T? pred)
limit(long maximumSize)
.limit(5)
anyMatch(Predicate<? super T> pred)
min(Comparator<? super T> comparator)
max(Comparator<? super T> comparator)

Clone this wiki locally