Session 14, 15, 16|Interfaces¶

  • Java Interfaces
  • Java Interface Naming Conventions
  • Implements vs. Extends in Java

What is an Interface?¶

An interface is a construct that allows two systems to interact with each other without one system needing to know the details of the other. In simpler terms, it helps achieve abstraction.

How to Define an Interface?¶

The declaration of an interface consists of the following components:

  • Modifiers
  • The interface keyword
  • The interface name
  • A comma-separated list of parent interfaces (if any)
  • The body of the interface

Note: Only public and package-private (default) modifiers are allowed for interfaces. protected and private modifiers are not allowed.

// public interface
public interface BigBird {
  public void fly();
}

// default interface
interface SmallBird {
  public void fly();
}

// comma separated list of parent interfaces (can't extend from class)
public interface NonflyingBird extends SmallBird, BigBird {
  public void canRun();
}

Why Do We Need an Interface?¶

1. Abstraction:¶

Using an interface, we can achieve full abstraction, meaning we can define what a class must do, but not how it will do it...

In [1]:
public interface Bird {
    void fly();
}
In [2]:
public class Eagle implements Bird {
    @Override
    public void fly() {
        System.out.println("Eagle Can fly");
    }
}

2. Polymorphism:¶

  • An interface can be used as a data type.
  • We cannot create an object of an interface, but it can hold references to any class that implements it. At runtime, the JVM decides which method to invoke based on the actual object's type.
In [3]:
public class Hen implements Bird {
    @Override
    public void fly() {
        System.out.println("Hen Can't fly");
    }
}
In [4]:
Bird obj1 = new Eagle(); // Bird as data type
Bird obj2 = new Hen();

obj1.fly();
Eagle Can fly
In [5]:
obj2.fly();
Hen Can't fly

3. Multiple Inheritance:¶

In Java, multiple inheritance is not allowed with classes, but it is possible through interfaces.

In [6]:
public class WaterAnimal {
    public boolean canBreathe() {
        return true;
    }
}
In [7]:
public class LandAnimal {
    public boolean canBreathe() {
        return true;
    }
}
In [8]:
public class Crocodile extends LandAnimal, WaterAnimal {}
|   public class Crocodile extends LandAnimal, WaterAnimal {}
'{' expected

A class can implement multiple interfaces.

In [9]:
public interface LandAnimal {
    public void canBreathe();
}
In [10]:
public interface WaterAnimal {
    public void canBreathe();
}
In [11]:
public class Crocodile implements LandAnimal, WaterAnimal{
    @Override
    public void canBreathe() {
        System.out.println("Crocodile can breadth");
    }
}

Methods in an Interface¶

  • All methods are implicitly public abstract.
  • Methods cannot be declared as final.
public interface Bird {
  void fly();
}

==

public interface Bird {
  public abstract void fly();
}

Fields/Variables in an Interface¶

  • Fields are implicitly public static final.
  • Fields cannot be declared as private or protected.
public interface Bird {
  int MAX_HEIGHT_IN_FEET = 2000;
}

==

public interface Bird {
  public static final int MAX_HEIGHT_IN_FEET = 2000;
}

Interface Implementation¶

  • Overridden methods cannot have more restrictive access specifiers than those in the interface.
  • A concrete class must override all methods declared in the interface.
  • Abstract classes are not required to override all the methods of an interface.
  • A class can implement multiple interfaces.
In [12]:
public interface Bird {
    public void fly();
}
In [13]:
public class Sparrow implements Bird {
    @Override
    public void fly() {
        System.out.println("Flying");
    }
}
In [14]:
// can't be anything except public
public class Sparrow implements Bird {
    @Override
    protected void fly() {
        System.out.println("Flying");
    }
}
|       @Override
|       protected void fly() {
|           System.out.println("Flying");
|       }
fly() in Sparrow cannot implement fly() in Bird
  attempting to assign weaker access privileges; was public

Example of an Abstract Class Implementing an Interface¶

In [15]:
public interface Bird {
    public void canfly();
    public void noOfLegs();
}
In [16]:
// in an abstract class, it is not mandatory to implement all interface methods, 
// but a concrete class must implement them.
public abstract class Eagle implements Bird {
    @Override
    public void canfly() {}
    
    public abstract void beakLength();
}
In [17]:
public class WhiteEagle extends Eagle {
    @Override
    public void noOfLegs() {}
    
    @Override
    public void beakLength() {}
}

Nested Interface¶

  • An interface can be declared within another interface.
  • An interface can be declared within a class.

It is generally used to group logically related interfaces.

  • A Guide to Inner Interfaces in Java

Rules:¶

  • A nested interface declared within an interface must be public.
  • A nested interface declared within a class can have any access modifier.
  • When you implement an outer interface, implementing the inner interface is not required, and vice versa.

Interface within Another Interface¶

In [18]:
public interface Bird {
    public void canFly();
    
    public interface NonFlyingBird {
        public void canRun();
    }
}
In [19]:
// implements only outer interface
public class Sparrow implements Bird {
    @Override
    public void canFly() {}
}
In [20]:
// implements only nested interface
public class Sparrow implements Bird.NonFlyingBird {
    @Override
    public void canRun() {
       System.out.println("Sparrow can Run");
    }
}
In [21]:
Bird.NonFlyingBird obj = new Sparrow(); // Sparrow obj = new Sparrow();
obj.canRun();
Sparrow can Run
In [22]:
// implements both (inner and outer) interfaces
public class Sparrow implements Bird, Bird.NonFlyingBird {
    @Override
    public void canRun() {
       System.out.println("Sparrow can Run");
    }
    
    @Override
    public void canFly() {
       System.out.println("Sparrow can Fly");
    }
}
In [23]:
Bird.NonFlyingBird obj = new Sparrow();
obj.canRun();
Sparrow can Run
In [24]:
Bird obj = new Sparrow();
obj.canFly();
Sparrow can Fly
In [25]:
Sparrow obj = new Sparrow();
obj.canFly();
obj.canRun();
Sparrow can Fly
Sparrow can Run

Interface within a Class¶

In [26]:
public class Bird {
    // modifier can be anything
    protected interface NonFlyingBird {
        void canRun();
    }
}
In [27]:
public class Eagle implements Bird.NonFlyingBird {
    @Override
    public void canRun() {}
}

Interface vs Abstract¶

  • Using an Interface vs. Abstract Class in Java
  • Java interface FAQs by Hari Krishna
No Abstract Class Interface
1 The keyword used here is abstract. The keyword used here is interface.
2 Child classes must use the extends keyword. Child classes must use the implements keyword.
3 It can have both abstract and non-abstract methods. It can have only abstract methods (prior to Java 8).
4 It can extend another class and implement multiple interfaces. It can extend only other interfaces.
5 Variables can be static, non-static, or final. Variables are by default constants (static and final).
6 Methods and variables can have private, protected, public, or package-private access modifiers. Methods and variables are by default public.
7 Multiple inheritance is not supported. Multiple inheritance is supported (through interfaces).
8 It can provide implementations for interfaces. It cannot provide implementations for other interfaces or abstract classes.
9 It can have constructors. It cannot have constructors.
10 The abstract keyword is required to declare a method as abstract. It can be public, protected, default. Methods are implicitly abstract and do not require the abstract keyword

Java 8 Interface Features¶

  • Static and Default Methods in Interfaces in Java
  • Interface With Default Methods vs Abstract Class
  • Java 8 Interview Questions(+ Answers)

Key Features Introduced in Java 8 Interfaces¶

  • Default Methods
  • Static Methods
  • Functional Interfaces and Lambda Expressions

Default Method¶

Until Java 7, interfaces could only have public abstract methods, and implementing classes had to provide their implementations. Java 8 introduced default methods, which are methods with predefined implementations. Classes can override these methods if needed.

In [28]:
public interface Bird {
    public void canFly(); // same as public abstract void canFly();
}

public class Eagle implements Bird {
    @Override
    public void canFly() {}
}

public class Sparrow implements Bird {
    @Override
    public void canFly() {}
}

Adding a new method to an interface requires updating all its implementing classes.

In [29]:
public interface Bird {
    public int getMiniumFlyWeight();
    public void canFly();
}

public class Eagle implements Bird {
    @Override
    public void canFly() {}
    
    @Override
    public int getMiniumFlyWeight() {
        return 100;
    }
}

public class Sparrow implements Bird {
    @Override
    public void canFly() {}
    
    @Override
    public int getMiniumFlyWeight() {
        return 100;
    }
}
In [30]:
public interface Bird {
    default public int getMiniumFlyWeight() { // implementation of this method in child classes is optional
        return 100;
    }
    
    public void canFly();
}

public class Sparrow implements Bird {
    @Override
    public void canFly() {}
}

Bird sparrow = new Sparrow();
sparrow.getMiniumFlyWeight();
Out[30]:
100
In [31]:
interface I {
    default void m1() {
        System.out.println("Hello");
    }

    // Object class method can't be default method, not allowed to override
    default int hashCode() {
        return 10;
    }
}

I.hashCode();
|       default int hashCode() {
|           return 10;
|       }
default method hashCode in interface I overrides a member of java.lang.Object

Default Methods and Multiple Inheritance¶

In [32]:
public interface Bird {
    default boolean canFly() {
        return true;
    }
}

public interface Plane {
    default boolean canFly() {
        return true;
    }
}

public class Human implements Bird, Plane {}
|   
|   
|   public class Human implements Bird, Plane {}
types Bird and Plane are incompatible;
  class Human inherits unrelated defaults for canFly() from types Bird and Plane
In [33]:
// you've to provide default method implementation
public class Human implements Bird, Plane {
    public boolean canFly() {
        Bird.super.canFly();
        return false;
    }
}

Extend an interface that contains a default method¶

Method 1¶

In [34]:
public interface LivingThing {
    default boolean canBreadth() {
        return true;
    }
}

public interface Bird extends LivingThing {}
public class Eagle implements Bird {}

Eagle eagle = new Eagle();
eagle.canBreadth();
Out[34]:
true

Method 2¶

In [35]:
public interface LivingThing {
    default boolean canBreadth() {
        return true;
    }
}

public interface Bird extends LivingThing {
    // LivingThing.canBreadth becomes abstract now
    boolean canBreadth();
}

public class Eagle implements Bird {
    // now child class has to implement it
    @Override
    public boolean canBreadth() {
        return true;
    }
}

Eagle eagle = new Eagle();
eagle.canBreadth();
Out[35]:
true

Method 3¶

In [36]:
public interface LivingThing {
    default boolean canBreadth() {
        return true;
    }
}

public interface Bird extends LivingThing {
    // override parent implementation
    default boolean canBreadth() {
        return LivingThing.super.canBreadth();
    }
}

public class Eagle implements Bird {}

Eagle eagle = new Eagle();
eagle.canBreadth();
Out[36]:
true

Static Method¶

  • Interfaces can include static methods with implementations.
  • Static methods cannot be overridden by the classes that implement the interface.
  • Static methods in an interface are not inherited by the implementing class and must be called using the interface name.
  • They are implicitly public by default.
In [37]:
public interface Bird {
    static boolean canBreathe() {
        return true;
    }
}

public class Eagle implements Bird {
    public void test() {
        if (Bird.canBreathe()) {}
    }
}
In [38]:
interface A {
    static void m1() {
        System.out.println("Interface Static Method");
    }
}

A.m1();
Interface Static Method
In [39]:
// it is also possible to declare a main method inside an interface
interface A {
    public static void main(String[] args) {
        System.out.println("Static Main Method");
    }
}

A.main(new String[]{}); // tested in .java file
Static Main Method

Functional Interfaces and Lambda Expressions¶

  • Functional Interfaces in Java 8
  • Lambda Expressions and Functional Interfaces: Tips and Best Practices

What is a Functional Interface?¶

  • A functional interface is an interface with exactly one abstract method.
  • It can have multiple default or static methods.
  • It is also referred to as a SAM (Single Abstract Method) interface.
  • The @FunctionalInterface annotation is used to mark an interface as a functional interface.
@FunctionalInterface
public interface Bird {
  void canFly();
}

==

public interface Bird {
  void canFly();
}

The @FunctionalInterface annotation ensures that an interface can have only one abstract method. If more than one abstract method is added, a compilation error will occur.

In [40]:
@FunctionalInterface
public interface Bird {
    void canFly();
    
    void sayHi();
}
|   @FunctionalInterface
Unexpected @FunctionalInterface annotation
  Bird is not a functional interface
    multiple non-overriding abstract methods found in interface Bird

A functional interface can have only one abstract method, but it can include other methods such as default methods, static methods, or methods inherited from Object class.

In [41]:
@FunctionalInterface
public interface Bird {
    void canFly(); // abstract method

    default void getWeight() {}
    static void canEat() {}

    String toString(); // Object class method
}

What is a Lambda Expression?¶

A lambda expression is an anonymous function that does not have a name, modifiers, or a return type, and it is one of the ways to implement a functional interface.

n -> return n*n; // invalid
n -> {return n*n;}; // valid
n -> {return n*n}; // invalid
n -> {n*n;}; // invalid
n -> n*n; // valid

Without curly braces, you cannot use the return keyword; the compiler will automatically consider the returned value. However, when using curly braces, if you want to return a value, you must explicitly use the return statement.

To invoke a lambda expression, you need to use a FunctionalInterface, which contains a single abstract method.

  • Exceptions in Java Lambda Expressions
In [42]:
import java.util.function.*;

class Test {
    public static int squareOfN(int n) {
        return n * n;
    }
}

int n = 5;
Test t = new Test();
String str = String.format("The Square of %d is %d", n, t.squareOfN(n));

str;
Out[42]:
The Square of 5 is 25
In [43]:
Function<Integer, Integer> square = (i) -> i * i; 
square.apply(n);
Out[43]:
25
In [44]:
Predicate<Integer> isEven = (num) -> num % 2 == 0;
isEven.test(square.apply(n));
Out[44]:
false
In [45]:
BiFunction<Integer, Integer, Integer> sumOfNumbers = (a, b) -> a + b;
sumOfNumbers.apply(10, 15);
Out[45]:
25
In [46]:
Function<String, Integer> lengthOfStr = (string) -> string.length();
lengthOfStr.apply("Hello, World");
Out[46]:
12
In [47]:
interface Interf {
    void m1();
}

class Demo implements Interf {
    @Override
    public void m1() {
        System.out.println("Hello, World");
    }
}
In [48]:
Interf i = new Demo(); // old way
i.m1();
Hello, World
In [49]:
Interf i = () -> System.out.println("Hello"); // lambda
i.m1();
Hello
In [50]:
Interf i = new Demo() { // anonymous class
    @Override
    public void m1() {
        System.out.println("Hello, World");
    }
};

i.m1();
Hello, World

We can implement a thread in two ways:

  • By implementing the Runnable interface.
  • By extending the Thread class.
In [51]:
class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Child Thread");
        }
    }
}

MyRunnable r = new MyRunnable(); // there is only one thread
Thread t = new Thread(r);
t.start(); // after there are 2 threads

for (int i = 0; i < 10; i++) {
    System.out.println("Main Thread");
}
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Child Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
In [52]:
// via lambda
Runnable r = () -> {
    for (int i = 0; i < 10; i++) {
        System.out.println("Child Thread-2");
    }
};

Thread t = new Thread(r);
t.start();

for (int i = 0; i < 10; i++) {
    System.out.println("Main Thread-2");
}
Child Thread-2
Child Thread-2
Child Thread-2
Child Thread-2
Child Thread-2
Child Thread-2
Child Thread-2
Child Thread-2
Child Thread-2
Child Thread-2
Main Thread-2
Main Thread-2
Main Thread-2
Main Thread-2
Main Thread-2
Main Thread-2
Main Thread-2
Main Thread-2
Main Thread-2
Main Thread-2
In [53]:
ArrayList<Integer> l = new ArrayList<>();
l.add (20);
l.add(30);
l.add(5);
l.add(0);
l.add (50);

l;
Out[53]:
[20, 30, 5, 0, 50]

Sorting with a Custom Comparator¶

Rules:

  • The Comparator interface defines the compare(T o1, T o2) method, which:
    • Returns a negative integer if o1 should come before o2.
    • Returns a positive integer if o1 should come after o2.
    • Returns 0 if o1 and o2 are considered equal.
In [54]:
class MyComparator implements Comparator<Integer> {
    public int compare(Integer i, Integer j) {
        if (i < j) {
            return -1;
        } else if (i > j) {
            return 1;
        } else {
            return 0;
        }
    }
}

Collections.sort(l, new MyComparator());
l;
Out[54]:
[0, 5, 20, 30, 50]
In [55]:
class MyComparator implements Comparator<Integer> {
    public int compare(Integer i, Integer j) {
        if (i > j) { // reverse the operation
            return -1;
        } else if (i < j) {
            return 1;
        } else {
            return 0;
        }
    }
}

// the `compare` method is called automatically by the sort function during the sorting process.
Collections.sort(l, new MyComparator());
l; // descending order
Out[55]:
[50, 30, 20, 5, 0]
In [56]:
Comparator<Integer> c = (l1, l2) -> (l1 < l2) ? -1: (l1 > l2) ? 1 : 0;

Collections.sort(l, c);
l;
Out[56]:
[0, 5, 20, 30, 50]

Different ways to implement Functional Interface...¶

In [57]:
// 1) using "implements" keyword

@FunctionalInterface
public interface Bird {
    public void canFly(String val);
}

public class Eagle implements Bird {
    @Override
    public void canFly(String val) {}
}
In [58]:
// 2) using anonymous class

@FunctionalInterface
public interface Bird {
    public void canFly();
}

Bird eagle = new Bird() {
    @Override
    public void canFly() {}
};

eagle.canFly();
In [59]:
// 3) using lambda expression

@FunctionalInterface
public interface Bird {
    public void canFly(String val);
}

Bird eagle = (String val) -> {
    System.out.println(val);
};

Types of Functional Interface¶

Package java.util.function doc

  • Predefined Functional Interface
    • Consumer
    • Supplier
    • Function
    • Predicate
  • Two arguments Predefined Functional Interface
    • BiFunction
    • BiConsumer
    • BiPredicate
  • Primitive Functional Interface
    • IntPredicate
    • IntFunction
    • IntConsumer

Consumer¶

  • Represents a function that accepts a single argument and returns no result (void).
  • Found in the java.util.function package.
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {}
}
In [60]:
Consumer<Integer> consumer = val -> {
    if (val > 10) {
        System.out.println("Logging");
    }
};

consumer.accept(20);
Logging
In [61]:
Consumer<String> c = (s) -> System.out.println(s);
c.accept("Hello, World");
Hello, World
In [62]:
// chaining
class Movie {
    String name;

    Movie(String name) {
        this.name = name;
    }
}

Consumer<Movie> m1 = m -> System.out.println(m.name + " is ready to release");
Consumer<Movie> m2 = m -> System.out.println(m.name + " is released but flopped");
Consumer<Movie> m3 = m -> System.out.println(m.name + " storing information in database");

Movie[] movies = {new Movie("Squid Game Season 2"), new Movie("Baby John"), new Movie("Tum bin")};
// Consumer<Movie> cc = m1.andThen(m2).andThen(m3);
Consumer[] c = {m1, m2, m3};

int x = 0;
for (Movie movie: movies) {
    c[x].accept(movie);
    x += 1;
}
Squid Game Season 2 is ready to release
Baby John is released but flopped
Tum bin storing information in database

Supplier¶

  • Represents a function that return/supplies a value of type R, but takes no input.
  • Found in the java.util.function package.
@FunctionalInterface
public interface Supplier<R> {
    R get();
}
In [63]:
Supplier<String> supplier = () -> "Hello";
supplier.get();
Out[63]:
Hello
In [64]:
Supplier<Double> randomValue = () -> Math.random() * 10;
randomValue.get();
Out[64]:
4.898178622410803
In [65]:
import java.util.Date;

Supplier<Date> d = () -> new Date();
d.get();
Out[65]:
Mon Dec 30 06:40:44 UTC 2024
In [66]:
Supplier<String> s = () -> {
    String otp = "";

    for (int i = 0; i < 6; i++) {
        otp += (int)(Math.random() * 10);
    }

    return otp;
};

s.get();
Out[66]:
867303

Function¶

  • Represents a function that accepts one argument type T and produces a result of type R.
  • Found in the java.util.function package.
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {}
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {}
}
In [67]:
Function<String, String> function = (String name) -> "Hello " + name;
function.apply("John");
Out[67]:
Hello John
In [68]:
Function<Integer, Integer> square = (n) -> n * n;
square.apply(5);
Out[68]:
25
In [69]:
Function<String, String> strUpper = (str) -> str.toUpperCase();
strUpper.apply("hello-world");
Out[69]:
HELLO-WORLD
In [70]:
Function<Integer, Integer> f1 = i -> 2 * i;
Function<Integer, Integer> f2 = i -> i * i * i;

// it's used to combine two functions (chaining), executing one after the other. the result of the first function becomes the input to the second function.
System.out.println(f1.andThen(f2).apply(2));

// it is used to combine two functions, but with the order of execution reversed compared to .andThen().
System.out.println(f1.compose(f2).apply(2));
64
16

Predicate¶

  • Represents a function that accepts one argument and returns a boolean.
  • Found in the java.util.function package.
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {}
    default Predicate<T> or(Predicate<? super T> other) {}
    default Predicate<T> negate() {}
}
In [71]:
Predicate<Integer> isEven = (number) -> number % 2 == 0;
isEven.test(5555);
Out[71]:
false
In [72]:
Predicate<String> isNotEmpty = (string) -> string.isEmpty();
isNotEmpty.test("");
Out[72]:
true
In [73]:
Predicate<String> strLength = str -> str.length() == 5;
strLength.test("Hello");
Out[73]:
true
In [74]:
int x[] = {0, 5, 10, 15, 20, 25};
Predicate<Integer> isEvenNumber = num -> num % 2 == 0;
for (int n: x) {
    if (isEvenNumber.test(n)) {
        System.out.println("Even");
    }
}
Even
Even
Even

BiFunction¶

  • Represents a function that takes two arguments and produces a result of type R.
  • Found in the java.util.function package.
@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);

    default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {}
    default <V> BiFunction<V, U, R> compose(Function<? super V, ? extends T> before) {}
}
In [75]:
BiFunction<Integer, Integer, Integer> sum = (a, b) -> a + b;
sum.apply(10, 20);
Out[75]:
30

BiPredicate¶

  • Represents a function that takes two arguments and returns a boolean.
  • Found in the java.util.function package.
@FunctionalInterface
public interface BiPredicate<T, U> {
    boolean test(T t, U u);

    default BiPredicate<T, U> and(BiPredicate<? super T, ? super U> other) {}
    default BiPredicate<T, U> or(BiPredicate<? super T, ? super U> other) {}
    default BiPredicate<T, U> negate() {}
}
In [76]:
BiPredicate<Integer, Integer> isEqual = (a, b) -> a.equals(b);
isEqual.test(10, 20);
Out[76]:
false
In [77]:
BiPredicate<Integer, Integer> isSumEven = (a, b) -> (a+b) % 2 == 0;
isSumEven.test(10, 20);
Out[77]:
true

BiConsumer¶

  • Represents a function that takes two arguments and returns void.
  • Found in the java.util.function package.
@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);

    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {}
}
In [78]:
BiConsumer<String, Integer> printPair = (str, num) -> System.out.println(str + ": " + num);
printPair.accept("Hello", 5);
Hello: 5

Other Interfaces¶

In [79]:
int[] nums = {0, 5, 10, 15, 20, 25, 30};
Predicate<Integer> p = i -> i % 2 == 0; // auto boxing (int to Integer) happens 7 times
IntPredicate evenNums = i -> i % 2 == 0; // 0 times

for (int n: nums) {
    if (p.test(n)) {
        System.out.println(n);
    }
}
0
10
20
30
In [80]:
DoubleFunction d = n -> Double.toString(n);
d.apply(10);
Out[80]:
10.0
In [81]:
DoubleToIntFunction doubleToInt = dd -> (int)dd;
doubleToInt.applyAsInt(10.00);
Out[81]:
10

Custom Interface¶

In [82]:
@FunctionalInterface
public interface VarArgsFunction<T, R> {
    public R accept(T... t);
}

VarArgsFunction<Integer, Integer> sumOfNumbers = (numbers) -> {
    int total = 0;
    for (int num: numbers) {
        total += num;
    }

    return total;
};

sumOfNumbers.accept(10, 20, 30, 40, 10);
Out[82]:
110

Handling the Use Case When a Functional Interface Extends Another Interface¶

Case 1: Functional Interface Extending a Non-Functional Interface¶

In [83]:
public interface Living {
    public void breadth();
}

// since bird inherited from Living, all living methods also become a part of Bird and 
// functional interface can contain only one abstract method, so compile error occur.
@FunctionalInterface
public interface Bird extends Living {
    void fly();
}
|   @FunctionalInterface
Unexpected @FunctionalInterface annotation
  Bird is not a functional interface
    multiple non-overriding abstract methods found in interface Bird
In [84]:
public interface Living {
    default boolean breadth() {
        return true;
    }
}

// only one abstract method
@FunctionalInterface
public interface Bird extends Living {
   public void fly();
}

Case 2: Non-Functional Interface Extending a Functional Interface¶

In [85]:
@FunctionalInterface
public interface Living {
    public boolean breadth();
}

// ok
public interface Bird extends Living {
   public void fly();
}

Case 3: Functional Interface Extending Another Functional Interface¶

In [86]:
@FunctionalInterface
public interface Living {
    public boolean breathe();
}

// functional interface can only contain one abstract method
@FunctionalInterface
public interface Bird extends Living {
    public boolean eat();
}
|   @FunctionalInterface
Unexpected @FunctionalInterface annotation
  Bird is not a functional interface
    multiple non-overriding abstract methods found in interface Bird
In [87]:
@FunctionalInterface
public interface Living {
    public boolean breathe();
}

// but if the parent abstract method and child abstract method is same then no problem
@FunctionalInterface
public interface Bird extends Living {
    public default boolean fly()  {
       return false;
    }
    
    // both have same abstract method
    public boolean breathe();
}

Bird eagle = () -> {
    return false;
};

Method and Constructor Reference¶

  • Method references provide a shorthand way of writing lambda expressions by referring directly to a method in the class or instance.

Types of Method References¶

  • Static Method Reference ClassName::staticMethodName
  • Instance Method Reference on a Particular Object objectName::instanceMethodName
  • Instance Method Reference on an Arbitrary Object of a Particular Type ClassName::instanceMethodName
  • Constructor Reference ClassName::new
In [88]:
class Printer {
    public static void printMessage(String message) {
        System.out.println(message);
    }

    public void printMessage2(String message) {
        System.out.println(message);
    }
}
In [89]:
Consumer<String> msg = Printer::printMessage; // static reference
msg.accept("Hello from Static Reference");
Hello from Static Reference
In [90]:
Printer printer = new Printer();
Consumer<String> msg = printer::printMessage2; // non-static reference
msg.accept("Hello from Non-static Reference");
Hello from Non-static Reference
In [91]:
import java.util.List;

List<String> messages = List.of("Hello", "World");
messages.forEach(String::toUpperCase);
In [92]:
class Sample {
    Sample() {
        System.out.println("Sample class constructor");
    }
}

interface I {
    Sample get();
}

I i = Sample::new; // constructor reference
Sample s = i.get();
Sample class constructor
In [93]:
interface I {
    public int add(int a, int b);
}

class Reference {
    public static int sum(int a, int b) {
        return a + b;
    }
}

I i = (a, b) -> a + b;
I i2 = Reference::sum;

System.out.println(i.add(10, 20));
System.out.println(i2.add(10, 20));
30
30
In [94]:
class Main {
    protected void m1() {
        for (int i = 0; i < 4; i++) {
            System.out.println("Child Thread -1");
        }
    }

    public int m2() {
        System.out.println(10 + 15);
        return 10;
    }
}

Main main = new Main();
Runnable r = main::m2; // args type should be same run() == m2(), return type doesn't matter
Thread t = new Thread(r);
t.start();

for (int i = 0; i < 4; i++) {
    System.out.println("Main Thread -1");
}
25
Main Thread -1
Main Thread -1
Main Thread -1
Main Thread -1

Java 9 Interface Features¶

  • Private Methods in Java Interfaces

Private Method and Private Static Method¶

  • You can define methods with the private access modifier in interfaces.
  • This feature improves the readability of the code, especially when multiple default methods share common logic.
  • Private methods can be defined as either static or non-static.
    • A private static method can only be called from other static methods within the interface.
    • A private static method can be called from both static and non-static methods within the interface.
  • Private interface methods cannot be abstract, meaning they must provide a method definition.
  • These methods can only be used within the specific interface they are defined in.
In [95]:
public interface Bird {
    public abstract void canFly();

    // java 8
    public default void minimumFlyinfHeight() {
        myStaticPublicMethod(); // static method
        myPrivateMethod(); // private method
        myStaticPrivateMethod(); // private static method
    }
    
    public static void myStaticPublicMethod() {
        // static methods can access only static members.
        myStaticPrivateMethod(); 
    }

    // java 9
    private void myPrivateMethod() {
        // non-static methods can access both static and non-static members.
        myStaticPrivateMethod(); 
    }
    
    private static void myStaticPrivateMethod() {}
}
In [96]:
interface I {
    default void m1() {
        System.out.println("M1 code");
        m3();
    }

    default void m2() {
        System.out.println("M2 code");
        m3();
    }

    // code reusability
    private void m3() {
        System.out.println("Common Code");
    }

    public static void m4() {
        System.out.println("Static Interface method");
        m5();
    }

    // code reusability
    private static void m5() {
        System.out.println("Common Static method");
    }
}

class Main implements I {
    public static void main() {
        Main main = new Main();
        main.m1(); // M1 code, Common Code
        I.m4(); // Static Interface method, Common Static method
    }
}