Session 14, 15, 16|Interfaces¶
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
interfacekeyword - The interface name
- A comma-separated list of parent interfaces (if any)
- The body of the interface
Note: Only
publicand package-private (default) modifiers are allowed for interfaces.protectedandprivatemodifiers 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...
public interface Bird {
void fly();
}
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.
public class Hen implements Bird {
@Override
public void fly() {
System.out.println("Hen Can't fly");
}
}
Bird obj1 = new Eagle(); // Bird as data type
Bird obj2 = new Hen();
obj1.fly();
Eagle Can fly
obj2.fly();
Hen Can't fly
3. Multiple Inheritance:¶
In Java, multiple inheritance is not allowed with classes, but it is possible through interfaces.
public class WaterAnimal {
public boolean canBreathe() {
return true;
}
}
public class LandAnimal {
public boolean canBreathe() {
return true;
}
}
public class Crocodile extends LandAnimal, WaterAnimal {}
| public class Crocodile extends LandAnimal, WaterAnimal {} '{' expected
A class can implement multiple interfaces.
public interface LandAnimal {
public void canBreathe();
}
public interface WaterAnimal {
public void canBreathe();
}
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
privateorprotected.
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.
public interface Bird {
public void fly();
}
public class Sparrow implements Bird {
@Override
public void fly() {
System.out.println("Flying");
}
}
// 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¶
public interface Bird {
public void canfly();
public void noOfLegs();
}
// 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();
}
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.
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¶
public interface Bird {
public void canFly();
public interface NonFlyingBird {
public void canRun();
}
}
// implements only outer interface
public class Sparrow implements Bird {
@Override
public void canFly() {}
}
// implements only nested interface
public class Sparrow implements Bird.NonFlyingBird {
@Override
public void canRun() {
System.out.println("Sparrow can Run");
}
}
Bird.NonFlyingBird obj = new Sparrow(); // Sparrow obj = new Sparrow();
obj.canRun();
Sparrow can Run
// 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");
}
}
Bird.NonFlyingBird obj = new Sparrow();
obj.canRun();
Sparrow can Run
Bird obj = new Sparrow();
obj.canFly();
Sparrow can Fly
Sparrow obj = new Sparrow();
obj.canFly();
obj.canRun();
Sparrow can Fly Sparrow can Run
Interface within a Class¶
public class Bird {
// modifier can be anything
protected interface NonFlyingBird {
void canRun();
}
}
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.
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.
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;
}
}
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();
100
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¶
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
// you've to provide default method implementation
public class Human implements Bird, Plane {
public boolean canFly() {
Bird.super.canFly();
return false;
}
}
public interface LivingThing {
default boolean canBreadth() {
return true;
}
}
public interface Bird extends LivingThing {}
public class Eagle implements Bird {}
Eagle eagle = new Eagle();
eagle.canBreadth();
true
Method 2¶
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();
true
Method 3¶
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();
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
publicby default.
public interface Bird {
static boolean canBreathe() {
return true;
}
}
public class Eagle implements Bird {
public void test() {
if (Bird.canBreathe()) {}
}
}
interface A {
static void m1() {
System.out.println("Interface Static Method");
}
}
A.m1();
Interface Static Method
// 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
@FunctionalInterfaceannotation 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.
@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.
@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.
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;
The Square of 5 is 25
Function<Integer, Integer> square = (i) -> i * i;
square.apply(n);
25
Predicate<Integer> isEven = (num) -> num % 2 == 0;
isEven.test(square.apply(n));
false
BiFunction<Integer, Integer, Integer> sumOfNumbers = (a, b) -> a + b;
sumOfNumbers.apply(10, 15);
25
Function<String, Integer> lengthOfStr = (string) -> string.length();
lengthOfStr.apply("Hello, World");
12
interface Interf {
void m1();
}
class Demo implements Interf {
@Override
public void m1() {
System.out.println("Hello, World");
}
}
Interf i = new Demo(); // old way
i.m1();
Hello, World
Interf i = () -> System.out.println("Hello"); // lambda
i.m1();
Hello
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.
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
// 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
ArrayList<Integer> l = new ArrayList<>();
l.add (20);
l.add(30);
l.add(5);
l.add(0);
l.add (50);
l;
[20, 30, 5, 0, 50]
Sorting with a Custom Comparator¶
Rules:
- The
Comparatorinterface defines thecompare(T o1, T o2)method, which:- Returns a negative integer if
o1should come beforeo2. - Returns a positive integer if
o1should come aftero2. - Returns
0ifo1ando2are considered equal.
- Returns a negative integer if
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;
[0, 5, 20, 30, 50]
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
[50, 30, 20, 5, 0]
Comparator<Integer> c = (l1, l2) -> (l1 < l2) ? -1: (l1 > l2) ? 1 : 0;
Collections.sort(l, c);
l;
[0, 5, 20, 30, 50]
Different ways to implement Functional Interface...¶
// 1) using "implements" keyword
@FunctionalInterface
public interface Bird {
public void canFly(String val);
}
public class Eagle implements Bird {
@Override
public void canFly(String val) {}
}
// 2) using anonymous class
@FunctionalInterface
public interface Bird {
public void canFly();
}
Bird eagle = new Bird() {
@Override
public void canFly() {}
};
eagle.canFly();
// 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.functionpackage.
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {}
}
Consumer<Integer> consumer = val -> {
if (val > 10) {
System.out.println("Logging");
}
};
consumer.accept(20);
Logging
Consumer<String> c = (s) -> System.out.println(s);
c.accept("Hello, World");
Hello, World
// 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.functionpackage.
@FunctionalInterface
public interface Supplier<R> {
R get();
}
Supplier<String> supplier = () -> "Hello";
supplier.get();
Hello
Supplier<Double> randomValue = () -> Math.random() * 10;
randomValue.get();
4.898178622410803
import java.util.Date;
Supplier<Date> d = () -> new Date();
d.get();
Mon Dec 30 06:40:44 UTC 2024
Supplier<String> s = () -> {
String otp = "";
for (int i = 0; i < 6; i++) {
otp += (int)(Math.random() * 10);
}
return otp;
};
s.get();
867303
Function¶
- Represents a function that accepts one argument type
Tand produces a result of typeR. - Found in the
java.util.functionpackage.
@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) {}
}
Function<String, String> function = (String name) -> "Hello " + name;
function.apply("John");
Hello John
Function<Integer, Integer> square = (n) -> n * n;
square.apply(5);
25
Function<String, String> strUpper = (str) -> str.toUpperCase();
strUpper.apply("hello-world");
HELLO-WORLD
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.functionpackage.
@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() {}
}
Predicate<Integer> isEven = (number) -> number % 2 == 0;
isEven.test(5555);
false
Predicate<String> isNotEmpty = (string) -> string.isEmpty();
isNotEmpty.test("");
true
Predicate<String> strLength = str -> str.length() == 5;
strLength.test("Hello");
true
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.functionpackage.
@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) {}
}
BiFunction<Integer, Integer, Integer> sum = (a, b) -> a + b;
sum.apply(10, 20);
30
BiPredicate¶
- Represents a function that takes two arguments and returns a
boolean. - Found in the
java.util.functionpackage.
@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() {}
}
BiPredicate<Integer, Integer> isEqual = (a, b) -> a.equals(b);
isEqual.test(10, 20);
false
BiPredicate<Integer, Integer> isSumEven = (a, b) -> (a+b) % 2 == 0;
isSumEven.test(10, 20);
true
BiConsumer¶
- Represents a function that takes two arguments and returns
void. - Found in the
java.util.functionpackage.
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {}
}
BiConsumer<String, Integer> printPair = (str, num) -> System.out.println(str + ": " + num);
printPair.accept("Hello", 5);
Hello: 5
Other Interfaces¶
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
DoubleFunction d = n -> Double.toString(n);
d.apply(10);
10.0
DoubleToIntFunction doubleToInt = dd -> (int)dd;
doubleToInt.applyAsInt(10.00);
10
Custom Interface¶
@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);
110
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
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¶
@FunctionalInterface
public interface Living {
public boolean breadth();
}
// ok
public interface Bird extends Living {
public void fly();
}
Case 3: Functional Interface Extending Another Functional Interface¶
@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
@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
class Printer {
public static void printMessage(String message) {
System.out.println(message);
}
public void printMessage2(String message) {
System.out.println(message);
}
}
Consumer<String> msg = Printer::printMessage; // static reference
msg.accept("Hello from Static Reference");
Hello from Static Reference
Printer printer = new Printer();
Consumer<String> msg = printer::printMessage2; // non-static reference
msg.accept("Hello from Non-static Reference");
Hello from Non-static Reference
import java.util.List;
List<String> messages = List.of("Hello", "World");
messages.forEach(String::toUpperCase);
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
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
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 Method and Private Static Method¶
- You can define methods with the
privateaccess 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
staticornon-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.
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() {}
}
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
}
}