Java 8 - Default Methods


Java 8 introduces “Default Method” or (Defender methods) new feature, which allows a developer to add new methods to the
Interfaces without breaking the existing implementation of these Interface.

For example, ‘List’ or ‘Collection’ interfaces do not have ‘forEach’ method declaration. Thus, adding such method will simply break the collection framework implementations.

See how it works :

public interface studentClassOne{

   default void classTest() {
      System.out.println("classTest in studentClassOne");
   }
}

public interface studentClassTwo{

   default void classTest() {
      System.out.println("classTest in studentClassOne");
   }
}
public class student implements studentClassOne, studentClassTwo{
}

The above code will fail to compile with the following error,
java: class student inherits unrelated defaults for classTest() from types studentClassOne and studentClassTwo

In order to fix this class, we need to provide default method implementation with super Interface:

public class student implements studentClassOne, studentClassTwo{

   default void classTest() {
      vehicle.super.classTest();
   }
}

Why we needed to implements the default method?

Re-engineering an existing JDK framework is always very complex.If we modify one interface in JDK interface in JDK
framework breaks all classes and extends the interface ,when we want to adding any new method could be break millions of code.
Therefore Defaults method has introduced in Java 8.


Default methods can be provide to an interface without affecting implementaing calsses as it includes an implementation.

Java 8 Method-References

In java 8 new feature introduce method Reference.Method Reference is used to refer method of functional interface.
when we used Lambda expression to just referring a method.we can replace our lambda expression with method Reference.

Type of Method Referencess

  1. Reference to static method. (Example :ContainingClass::staticMethodName)
  2. Reference to instance method of particular object (Example :containingObject::instanceMethodName)
  3. Reference to constructor (example :ClassName:: new)


1)Reference to static method:-

  Used to refer static methods from a class

          interface TestMethod{  
              void shop();  
          }  


          public class SampleExample{  
           public static void testReferance(){  
               System.out.println("Hello,world !");  
          }  

          public static void main(String[] args) {  
          TestMethod testMethod= SampleExample::testReferance; // Referring static method  
          testMethod.shop(); // Calling interface method    
    }  


2) Reference to instance method of particular object

 In this type we are referring non-static methods. You can refer methods by class object and anonymous object.

interface TestMethod{  
    void say();  
}  

public class InstanceMethodRef {  
    public void testReferance(){  
        System.out.println("Hello, this is non-static method.");  
    }  

    public static void main(String[] args) {  
        InstanceMethodRef methodRef = new InstanceMethodRef();  
        TestMethod testMethod = methodRef::saySomething;  // Referring non-static method using reference   
            testMethod.shop();  // Calling interface method 
            // Referring non-static method using anonymous object  
            TestMethod sayable2 = new InstanceMethodRef()::testReferance;  
            // Calling interface method  
            testMethod2.shop();  
    }  
}  

3) Reference to a Constructor

 In this type , we are referring constructor with the help of functional interface.

interface TestMethod{  
    ConsMethod getMessage(String msg);  
}  
class ConsMethod{  
    Message(String msg){  
        System.out.print(msg);  
    }  
}  
public class ConstructorRef {  
    public static void main(String[] args) {  
        TestMethod hello = ConsMethod::new;  
        hello.getMessage("Hello world");  
    }  
}  

Java 8 Streams

Java 8 Streams are designed in such a way that most of its stream operations returns Streams only. This help us creating chain of various stream operations. This is called as pipelining.

A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.

Streams vs. Collections:

A Collection is an in-memory data structure, which holds all the values that the data structure currently has—every element in the Collection has to be computed before it can be added to the Collection. A Stream is a conceptually fixed data structure, in which elements are computed on demand. This gives rise to significant programming benefits.

In java, java.util.Stream represents a stream on which one or more operations can be performed. Stream operations are either intermediate or terminal. While terminal operations return a result of a certain type, intermediate operations return the stream itself so you can chain multiple method calls in a row. Streams are created on a source, e.g. a java.util.Collection like lists or sets (maps are not supported). Stream operations can either be executed sequential or parallel.

If we list down the various characteristics of Stream, they will be as follows:

  • Not a data structure
  • Designed for lambdas
  • Do not support indexed access
  • Can easily be outputted as arrays or lists
  • Lazy access supported
  • Parallelizable

Build streams functions:

1) Using Stream.of(val1, val2, val3….)

         Stream stream = Stream.of(1,2,3,4,5);
         stream.forEach(p -> System.out.println(p));

2) Using Stream.of(arrayOfElements)

         Stream stream = Stream.of( new Integer[]{1,2,3,4,5} );
         stream.forEach(p -> System.out.println(p));

3) Using someList.stream()

         List list = new ArrayList();
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
         Stream stream = list.stream();
         stream.forEach(p -> System.out.println(p));

4) Using Stream.generate() or Stream.iterate() functions

public class StreamBuilders {
     public static void main(String[] args){
         Stream stream = Stream.generate(() -> { return new Date();});
         stream.forEach(p -> System.out.println(p));
5) Using String chars or String tokens

        IntStream stream = "12345_abcdefg".chars();
        stream.forEach(p -> System.out.println(p));
         
        //OR
         
        Stream stream = Stream.of("A$B$C".split("\\$"));
        stream.forEach(p -> System.out.println(p));

Converting streams to collections:


1) Convert Stream to List using stream.collect(Collectors.toList())

        List list = new ArrayList();
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
         Stream stream = list.stream();
         List evenNumList = stream.filter(i -> i%2 == 0).collect(Collectors.toList());
         System.out.print(evenNumList);
2) Convert Stream to array using stream.toArray(EntryType[]::new)

        List list = new ArrayList();
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
         Stream stream = list.stream();
         Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
         System.out.print(evenNumbersArr);
  

Stream operations and pipelines

Stream operations are divided into intermediate and terminal operations, and are combined to form stream pipelines
public class SampleExample {. 
  public static void main(String arg[]){
  List studentName=new ArrayList();
  studentName.add("Sonali");
  studentName.add("Monali");
  studentName.add("Saanvi");
  studentName.add("Kiansh");
  studentName.add("Surya");
 }
}

A) filter()

memberNames.stream().filter((s) -> s.startsWith("S"))
                    .forEach(System.out::println); //print  Sonali Saaanvi
B) map()
memberNames.stream().filter((s) -> s.startsWith("A"))
                     .map(String::toUpperCase)
                     .forEach(System.out::println); //print SONALI SAANVI

Terminal operations :

Terminal operations return a result of a certain type instead of again a Stream.

A) forEach()

This method helps in iterating over all elements of a stream and perform some operation on each of them. The operation is passed as lambda expression parameter.

memberNames.forEach(System.out::println);

Java 8 - Interview Questions

Q1.  What are new features introduced with Java 8 ?
Ans. Lambda Expressions , Interface Default and Static Methods , Method Reference , Parameters Name , Optional , Streams, Concurrency.

Q2.  What is a Lambda Expression ? What's its use ?
Ans. Its an anonymous method without any declaration. Lambda Expression are useful to write shorthand Code and hence saves the effort of writing lengthy Code.    It promotes Developer productivity, Better Readable and Reliable code.

Q3. What is a method reference?
Ans:  method reference is a Java 8 construct that can be used for referencing a method without invoking it. It is used for treating methods as Lambda Expressions,This way, the following code:

         1)      (o) -> o.toString();
         2)      Object::toString();
A method reference can be identified by a double colon separating a class or object name and the name of the method.
         1)      String::new;  (constructor reference)
         2)      String::valueOf;  (Static method reference)
         3)      str::toString;  ( bounded instance method reference)
         4)      String::toString; (Unbound instance method reference)

Q4.  What are Default Methods ?
Ans. With Java 8, We can provide method definitions in the Interfaces that gets carried down the classes implementing that interface in case they are not overridden by the Class. Keyword "default" is used to mark the default method.

Q5. What is StringJoiner ?
Ans. StringJoiner is a util method to construct a string with desired delimiter. This has been introduced with wef from Java 8.
Sample Code :
StringJoiner strJoiner = new StringJoiner(".");
strJoiner.add("Hello").add("Java 8");
System.out.println(strJoiner);
Result:- prints Hello Java8

Q6. Describe some of the functional interfaces in the standard library.

There are a lot of functional interfaces in the java.util.function package, the more common ones include but not limited to:

Function – it takes one argument and returns a result
Consumer – it takes one argument and returns no result (represents a side effect)
Supplier – it takes not argument and returns a result
Predicate – it takes one argument and returns a boolean
BiFunction – it takes two arguments and returns a result
BinaryOperator – it is similar to a BiFunction, taking two arguments and returning a result. The two arguments and the result are all of the same types

UnaryOperator – it is similar to a Function, taking two arguments and returning a result. The argument and the result are all of the same types.