Translate

Search This Blog

Monday 27 April 2020

About Java 8 Streams


What are Streams in Java?
You probably must have heard about stream before Java 8, in a different context while working with input and output ex. FileInputStream and FileOutputStream allowing us to use the contents in the file as stream of bytes or characters. 

Java 8 Stream works at a different level on a sequence of elements in a data source like Collections or Arrays. Various intermediate operations can then be pipe-lined together on the Stream to transform or filter the elements and finally the results can be collected in a terminal operation.  

Note : Stream maintains the order of the data as it is in the data source.

When was it introduced?
Stream API where introduced as part of Java 8. It provides abstraction on operations that can be performed on sequence of elements. 

How to use Streams?
Stream are available to us on Collections, if you check the Java 8 api for Collection interface, you will find that it has default methods like stream and parallelStream, that gives us the Stream object. 

You would then work with the Stream object using various intermediate operation and terminal operation on them.

Some of the significant intermediate operation available are

  • filter
    • takes a predicate that can be used to filter our elements that match the condition.
  • map
    • takes an function as an argument that helps to get mapped elements out of the stream. 
  • flatMap
    • similar to map it takes function as an argument and after mapping flattens the elements of the result into the output stream. Ex: Stream.of(list,list) could be flattened into a single list for consumption.
  • distinct
    • retrieves distinct values from the stream of elements.
  • sorted
    • sorts the values in the stream.

Some of the significant terminal operations available are
  • reduce
  • count
  • collect
  • forEach
  • match
  • findFirst

Where to use Streams?
Stream classes support functional-style operations on streams of elements, such as map-reduce transformations on collections. The code written is much cleaner and expressive. 

You could achieve performance improvements through the use of parallelStream which help to take advantage of the multi core system.

References:

Thursday 16 April 2020

Why do we need an Immutable key in an HashMap

In my previous posts, I explained the contract of equals and hashCode methods and also explained how the HashMap works in Java. If you have not read it I would suggest you to strongly read it before you read this topic.

To understand this topic we should first know what happens if the key is mutable.

Let's take the put operation for example
Whenever we implement hashCode method, we need to keep in mind that it should not include fields which are mutable.

If it does, then clearly the hashCode implementation will return different value then the previous time it was invoked.  i.e. when the state of the object is modified, then the hashCode will differ.

What happens then, is that during the process of insertion of the key, value pair, the hash value calculated for the key, in order to find the index of the bucket differs from the earlier one (Note: The post on how HashMap works will come in handy here for clear understanding) and the key, value would be place in a different bucket. As a result, the earlier node in the bucket list will be never reached and hence would be dangling forever.

So what happens if the key is modified and we try to retrieve?
Imagine, the key, value pair is already placed in a specific location in the bucket based on the key. Now if the Key is mutated, a new hashCode is formed, it causes indexFor method to return a different index to look for the key and hence won't be found, resulting in null value to be returned.

Hence, it is strongly advised to have they key as immutable so that the HashMap does not loose track of the bucket where they key was placed. This is the case for all Hash based collections.

What are the Interface enhancements in Java 8


Interface enhancements in Java 8

With Java 8, interfaces can now have default method and static method within the interface definition.

Default method, help to add new behaviour in the interface which if otherwise added would break all implementing classes. You can add default method through the use of the keyword default.

For Example,
If you notice the Iterator interface, it has added a new default method named forEachContaining(Consumer c) 

Similarly, Java 8 has modified many of its other interfaces to add new behaviour, but if you realise hasn't broken any functionality in the previous version (i.e. how it has maintained backward compatibility)

Static method, can also be added in the interfaces through the use of keyword static, except that they cant be overridden, exactly for the same reason as to why they weren't possible when associated with classes .

What happens to abstract classes?

Interfaces as earlier, still abstract out behaviour and can relate entities which are unrelated in hierarchy but common in behavioural interfaces.

For example: Sound interface with method makeSound still holds true when modelling Animal hierarchy and Instruments hierarchy.

Where as we would still use abstract classes when we want to abstract out common behaviour when there is strict hierarchy among classes.

What happens when two interfaces have default method with same signature?
Which also brings us to another point, what happens when two interfaces have common default method. Compiler complains saying "inherits unrelated defaults" and forces you to over ride the method.

Java8 - Functions



What are java 8 Functions all about?

  • Functions are a new addition in Java 8.
  • Are used to implement Functional programming in Java
  • Java 8 Function's are Functional interfaces and they have a single abstract method  
    • For example, the Function interface has a single abstract method apply() .
  • There are various flavours of it present in the java.util.function package. 
Why do we need a Function interfaces?
  • Its purpose lies in that, it abstracts out the behaviour of a function. i.e a function takes an input and gives an output.
    • i.e. broadly map object of one type and converts it to another [R apply(T)], 
    • For example, the map function in streams converts/maps object of one type to another.
      • Note: 
        • Not all interfaces in the function package, adhere to this rule. 
          • For example Consumer interface within function package just excepts a value and does not return anything.
How is it used?

  • Functions are functional interface and hence it finds it usages in places where it can be used as Lamda expressions.
What are the various functions present in the functions package?
Following are the main functional interfaces and all others are variations to these interfaces. If you understand these interfaces , you would quickly grasp the rest of the interface functions.

1. Function

  • Represents a function that accepts one argument and produces a result.
  • This interface has the following functions defined
    • R apply(T) - abstract method which takes an argument of type T and returns R
    • andThen(Function after) - its a default method, it combines the Function on which it is applied(T,R)  with another function named after (going from R to V), so that the net affect is a composed function which results in transformation from T to V.
    • compose(Function before) - its again another default method, it first applies the function(V,T) given as parameter and then applies this function(T,R) to give a combined function whose net effect is  a composed function which results in transformation from V to R.
    • identity() - is a static function which always returns a function that always returns its input argument.

2. Consumer
  • Represents an operation that accepts a single input argument and returns no result.
  • This interface has the following methods
    • void accept(T t) - Performs this operation on the given argument.
    • andThen(Consumer after) - Returns a composed Consumer that performs, in sequence, this operation followed by the after operation.
3. Supplier
  • Represents a supplier of results
  • This interface has the following methods.
    • T get() - gets the result.
4. Predicate
  • Represents a predicate (boolean-valued function) of one argument.
  • This interface has the following methods
    • boolean test(T t) - and abstract method that evaluates this predicate on the given argument.
    • and(Predicate other) - default method that returns a composed predicate that represents a short-circuiting logical AND of this predicate and another.
    • or(Predicate other) - default method that returns a composed predicate that represents a short-circuiting logical OR of this predicate and another.
    • isEqual(Object other) - static method that returns a predicate that tests if two arguments are equal according to Objects.equals(Object, Object).


References: