Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 41

INTERFACE IN JAVA

An interface in Java is a reference type, similar to a class, that can contain only constants, method
signatures, default methods, static methods, and nested types. It represents a contract for what a class
can do, without specifying how it does it.

 Declaration: Interfaces are declared using the interface keyword, followed by the interface
name and its body.

// Interface definition

interface MyInterface {

// Method signatures

void method1();

int method2();

// Constant declaration (implicitly public, static, and final)

int CONSTANT = 10;

// Default method (with method body)

default void defaultMethod() {

System.out.println("Default method implementation");

// Static method (with method body)

static void staticMethod() {

System.out.println("Static method implementation");

// Class implementing the interface

class MyClass implements MyInterface {


// Implementing interface methods

public void method1() {

System.out.println("method1 implementation");

public int method2() {

System.out.println("method2 implementation");

return 0;

// Main class to run the program

public class Main {

public static void main(String[] args) {

MyClass obj = new MyClass(); // Creating an object of implementing class

obj.method1(); // Invoking method1

int result = obj.method2(); // Invoking method2 and storing its result

System.out.println("Result of method2: " + result); // Printing the result of method2

System.out.println("Constant value: " + MyInterface.CONSTANT); // Accessing the constant

obj.defaultMethod(); // Invoking the default method

MyInterface.staticMethod(); // Invoking the static method directly from interface

OUTPUT:

method1 implementation

method2 implementation
Result of method2: 0

Constant value: 10

Default method implementation

Static method implementation

 Implementing Interfaces: A class can implement one or more interfaces by providing


implementations for all the methods declared in the interface(s) it implements.

class MyClass implements MyInterface {

// Implementation of method1 declared in MyInterface

public void method1() {

System.out.println("Method 1 implementation");

// Implementation of method2 declared in MyInterface

public int method2() {

System.out.println("Method 2 implementation");

return 0;

public class Main {

public static void main(String[] args) {

// Creating an instance of MyClass

MyClass myObj = new MyClass();


// Calling methods defined in MyInterface

myObj.method1();

int result = myObj.method2();

System.out.println("Result: " + result);

// Calling default method defined in MyInterface

myObj.defaultMethod();

// Calling static method defined in MyInterface

MyInterface.staticMethod();

// Accessing constant defined in MyInterface

System.out.println("Constant value: " + MyInterface.CONSTANT);

OUTPUT::

Method 1 implementation

Method 2 implementation

Result: 0

Default method implementation

Static method implementation

Constant value: 10

 Multiple Inheritance: Unlike classes, Java allows a class to implement multiple


interfaces. This feature enables a form of multiple inheritance through interfaces.
 Default Methods: Starting from Java 8, interfaces can have default methods, which
provide default implementations for methods. Default methods allow interfaces to
evolve without breaking existing implementations.
 Static Methods: Java interfaces can also contain static methods, which are associated
with the interface itself rather than with any instance of the interface.
 Constants: Interfaces can contain constant declarations. All constant variables declared
in an interface are implicitly public, static, and final.

ABSTRACT METHOD
An abstract method in Java is a method defined in an abstract class or interface that doesn't
have an implementation. Abstract methods serve as placeholders within a class or interface,
defining a method signature (name, return type, and parameters) without providing the actual
implementation.

Here are the key points about abstract methods:

1. Declaration: Abstract methods are declared using the abstract keyword, and they don't
have a method body. They end with a semicolon (;) rather than a block of code enclosed
in braces ({ }).
2. Abstract Classes: Abstract classes can contain both abstract and concrete (non-abstract)
methods. However, if a class contains even a single abstract method, it must be
declared as abstract itself. Abstract classes cannot be instantiated directly; they serve as
blueprints for subclasses to provide implementations for abstract methods
3. Interfaces: In interfaces, all methods are implicitly abstract unless marked as default or
static. Prior to Java 8, interfaces could only contain abstract method declarations. From
Java 8 onward, default and static methods can also be defined in interfaces.
4. Subclass Implementation: Any concrete subclass (a regular class that extends an
abstract class or implements an interface) must provide implementations for all abstract
methods declared in the superclass or interface. Failure to do so will result in a
compilation error.

abstract class Shape {

// Abstract method to calculate area

abstract double calculateArea();

class Circle extends Shape {

private double radius;

public Circle(double radius) {

this.radius = radius;
}

double calculateArea() {

return Math.PI * radius * radius;

public class Main {

public static void main(String[] args) {

Circle circle = new Circle(5);

System.out.println("Area of circle: " + circle.calculateArea());

Output: Area of circle: 78.53981633974483

FUNCTIONAL INTERFACE
In Java, a functional interface is an interface that contains only one abstract method. These
interfaces are a key component of functional programming in Java, particularly with the
introduction of lambda expressions in Java 8.

Here are the key points about functional interfaces:

 Single Abstract Method (SAM): A functional interface can have only one abstract
method. It may contain any number of default methods or static methods, but it must
have exactly one abstract method.
 Lambda Expressions: Functional interfaces are designed to be used with lambda
expressions, which provide a concise way to represent anonymous functions. Lambda
expressions allow you to pass functions as arguments, similar to other data types.
 @FunctionalInterface Annotation: While not strictly required, it's a good practice to
annotate functional interfaces with @FunctionalInterface annotation. This annotation
ensures that the interface has only one abstract method. If you try to add another
abstract method, the compiler will produce an error.
 Functional Interface Examples: Examples of functional interfaces in Java include
Runnable, Comparator, Callable, etc. Java 8 also introduced several functional interfaces
in the java.util.function package, such as Predicate, Function, Consumer, Supplier, etc.,
which are commonly used in functional-style programming.

@FunctionalInterface

interface MyFunctionalInterface {

void myMethod(String str);

public class Main {

public static void main(String[] args) {

// Using lambda expression to implement MyFunctionalInterface

MyFunctionalInterface myInterface = (str) -> System.out.println("Hello, " + str);

// Calling the method using the functional interface

myInterface.myMethod("World");

Output : Hello, World

LAMBDA EXPRESSION
A lambda expression in Java is a concise way to represent an anonymous function, i.e., a
function without a name. It provides a clear and compact syntax for writing code that requires
the implementation of functional interfaces.

Here are the key characteristics of lambda expressions:

1. Concise Syntax: Lambda expressions provide a shorter syntax compared to traditional


anonymous classes. They allow you to express instances of single-method interfaces (functional
interfaces) more compactly.
2. Functional Interfaces: Lambda expressions are primarily used in the context of functional
interfaces, which are interfaces containing only one abstract method. Lambda expressions
provide a way to implement the abstract method of a functional interface without explicitly
declaring a new class.
3. Parameter List: A lambda expression can have a comma-separated list of zero or more
parameters enclosed in parentheses. If there are no parameters, empty parentheses are used.
4. Arrow (->) Operator: The arrow operator -> separates the parameter list from the body of the
lambda expression. It is sometimes referred to as the "lambda operator".
5. Body: The body of the lambda expression can be a single expression or a block of code enclosed
in curly braces {}. If it's a single expression, it is evaluated and returned. If it's a block of code,
you need to use the return keyword explicitly if the return type is not void.

Here's a general syntax of a lambda expression:

(parameters) -> expression

Or

(parameters) -> { statements; }

Example:

// Lambda expression to add two numbers

Calculator adder = (a, b) -> a + b;

EXAMPLE:

interface Sayable

public String say();

public class LambdaExpression

public static void main(String[] args)

Sayable s=()->{
return "I have nothing to say.";

};

System.out.println(s.say());

Output : I have nothing to say.

Java Method References


Java provides a new feature called method reference in Java 8. Method reference is used to
refer method of functional interface. It is compact and easy form of lambda expression. Each
time when you are using lambda expression to just referring a method, you can replace your
lambda expression with method reference.

Types of Method References


There are following types of method references in java:
1. Reference to a static method.
2. Reference to an instance method.
3. Reference to a constructor.

1) Reference to a Static Method


You can refer to static method defined in the class. Following is the syntax and example which
describe the process of referring static method in Java.
Syntax:
ContainingClass::staticMethodName
Example:
interface Sayable{
void say();
}
public class MethodReference {
public static void saySomething(){
System.out.println("Hello, this is static method.");
}
public static void main(String[] args) {
// Referring static method
Sayable sayable = MethodReference::saySomething;
// Calling interface method
sayable.say();
}
}
Output: Hello, this is static method.

2) Reference to an Instance Method


like static methods, you can refer instance methods also. In the following example, we are
describing the process of referring the instance method.
Syntax
containingObject::instanceMethodName
Example:
interface Sayable{
void say();
}
public class InstanceMethodReference {
public void saySomething(){
System.out.println("Hello, this is non-static method.");
}
public static void main(String[] args) {
InstanceMethodReference methodReference = new InstanceMethodReference();
// Creating object
// Referring non-static method using reference
Sayable sayable = methodReference::saySomething;
// Calling interface method
sayable.say();
// Referring non-static method using anonymous object
Sayable sayable2 = new InstanceMethodReference()::saySomething;
// You can see anonymous object also
// Calling interface method
sayable2.say();
}
}
Output :
Hello, this is non-static method.
Hello, this is non-static method.

3) Reference to a Constructor
You can refer a constructor by using the new keyword. Here, we are referring constructor with
the help of functional interface.
Syntax:
ClassName::new

Example
interface Messageable{
Message getMessage(String msg);
}
class Message{
Message(String msg){
System.out.print(msg);
}
}
public class ConstructorReference {
public static void main(String[] args) {
Messageable hello = Message::new;
hello.getMessage("Hello");
}
}
Output : Hello

STREAM API
In Java, the Stream API was introduced in Java 8 as a part of the java.util.stream
package. It provides a powerful way to process collections of objects in a
functional programming style. Streams allow you to perform aggregate
operations on a collection of data, such as filtering, mapping, reducing, and
sorting, with concise and expressive syntax.
Stream provides following features:
o Stream does not store elements. It simply conveys elements from a source such as a
data structure, an array, or an I/O channel, through a pipeline of computational
operations.
o Stream is functional in nature. Operations performed on a stream does not modify it's
source. For example, filtering a Stream obtained from a collection produces a new
Stream without the filtered elements, rather than removing elements from the source
collection.
o Stream is lazy and evaluates code only when required.
o The elements of a stream are only visited once during the life of a stream. Like an
Iterator, a new stream must be generated to revisit the same elements of the source.
Here's a brief overview of how you can use the Stream API:
 Creating Streams: You can create streams from various sources like collections, arrays,
or by using static methods provided by the Stream interface.
List<String> list = Arrays.asList("apple", "banana", "cherry");

Stream<String> streamFromCollection = list.stream();

Stream<String> streamFromArray = Stream.of("apple", "banana", "cherry");

Stream<Integer> streamFromIterate = Stream.iterate(0, n -> n + 2).limit(5); // Generates 0, 2, 4,


6, 8

 Intermediate Operations: These operations are used to transform or filter the elements
of the stream. Intermediate operations return a new stream, so they can be chained
together.

stream.filter(s -> s.startsWith("a")) // Filter elements starting with "a"

.map(String::toUpperCase) // Convert elements to uppercase

.sorted() // Sort the elements


.forEach(System.out::println); // Print each element

 Terminal Operations: These operations produce a result or a side-effect. They are


typically called after intermediate operations, and they trigger the processing of the
stream.

long count = stream.count(); // Count the number of elements in the stream

Optional<String> firstElement = stream.findFirst(); // Find the first element of the stream

List<String> resultList = stream.collect(Collectors.toList()); // Collect elements into a List

 Parallel Streams: Streams can be processed in parallel to take advantage of multi-core


processors, which can improve performance for large datasets.

List<String> list = Arrays.asList("apple", "banana", "cherry");

list.parallelStream().forEach(System.out::println); // Process elements in parallel

 Reduction: Reduction operations combine elements of a stream to produce a single


result.

int sum = stream.mapToInt(Integer::parseInt).sum(); // Calculate the sum of integers in the


stream

Optional<String> longest = stream.reduce((s1, s2) -> s1.length() > s2.length() ? s1 : s2); // Find
the longest string in the stream

The Stream API provides a more declarative and concise way to work with collections in Java,
promoting immutability, and facilitating parallel processing. It's a powerful addition to the Java
language, especially for handling data manipulation tasks.

Java Default Methods


Java provides a facility to create default methods inside the interface. Methods which are
defined inside the interface and tagged with default are known as default methods. These
methods are non-abstract methods.
interface Sayable{
// Default method
default void say(){
System.out.println("Hello, this is default method");
}
// Abstract method
void sayMore(String msg);
}
public class DefaultMethods implements Sayable{
public void sayMore(String msg){
// implementing abstract method
System.out.println(msg);
}
public static void main(String[] args) {
DefaultMethods dm = new DefaultMethods();
dm.say(); // calling default method
dm.sayMore("Work is worship"); // calling abstract method

}
}
OUTPUT:
Hello, this is default method
Work is worship

Static Method in Java


The static keyword is used to construct methods that will exist regardless of
whether or not any instances of the class are generated. Any method that uses the
static keyword is referred to as a static method.
Features of static method:
 A static method in Java is a method that is part of a class rather than an
instance of that class.
 Every instance of a class has access to the method.
 Static methods have access to class variables (static variables) without
using the class’s object (instance).
 Only static data may be accessed by a static method. It is unable to access
data that is not static (instance variables).
 In both static and non-static methods, static methods can be accessed
directly.
class Student{
int rollno;
String name;
static String college = "ITS";
//static method to change the value of static variable
static void change(){
college = "BBDIT";
}
//constructor to initialize the variable
Student(int r, String n){
rollno = r;
name = n;
}
//method to display values
void display(){
System.out.println (rollno+" "+name+" "+college);
}
}
//Test class to create and display the values of object
public class TestStaticMethod{
public static void main(String args[]){
Student.change(); //calling change method
//creating objects
Student s1 = new Student(111,"Karan");
Student s2 = new Student(222,"Aryan");
Student s3 = new Student(333,"Sonoo");
//calling display method
s1.display();
s2.display();
s3.display();
}
}
Output:
111 Karan BBDIT
222 Aryan BBDIT
333 Sonoo BBDIT

Java forEach loop


Java provides a new method forEach() to iterate the elements. It is defined in Iterable and
Stream interface. It is a default method defined in the Iterable interface. Collection classes
which extends Iterable interface can use forEach loop to iterate elements.
This method takes a single parameter which is a functional interface. So, you can pass lambda
expression as an argument.
default void forEach(Consumer<super T>action)
Example:

import java.util.ArrayList;

import java.util.List;

public class ForEachExample {

public static void main(String[] args) {

// Create a list of strings

List<String> names = new ArrayList<>();

names.add("Alice");

names.add("Bob");

names.add("Charlie");

names.add("David");

// Using forEach to print each element in the list


names.forEach(name -> System.out.println(name));

// Using method reference

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

OUTPUT:

Alice

Bob

Charlie

David

Alice

Bob

Charlie

David

The try-with-resources statement


In Java, the try-with-resources statement is a try statement that declares one or more
resources. The resource is as an object that must be closed after finishing the program. The try-
with-resources statement ensures that each resource is closed at the end of the statement
execution.
You can pass any object that implements java.lang.AutoCloseable, which includes all objects
which implement java.io.Closeable.
EXAMPLE:

import java.io.FileOutputStream;
public class TryWithResources {
public static void main(String args[]){
// Using try-with-resources
try(FileOutputStream fileOutputStream =newFileOutputStream("/java7-new-features/
src/abc.txt")) // file path provided represents the location where the data will be written
{
String msg = "Welcome to javaTpoint!";
byte byteArray[] = msg.getBytes(); //converting string into byte array
fileOutputStream.write(byteArray); //resource
System.out.println("Message written to file successfuly!");
}
catch(Exception exception){
System.out.println(exception);
}
}
}

Output:

Message written to file successfuly!


Output of file:

Welcome to javaTpoint!

The following example writes a string into a file. It uses an instance of


FileOutputStream to write data into the file. FileOutputStream is a resource that
must be closed after the program is finished with it. So, in this example, closing of
resource is done by itself try.

ANNOTATIONS
Think of annotations like little notes you can attach to your Java code to tell the
computer and other programs more about what your code is doing. These notes
start with the @ symbol. For example, let's say you have a method in your code
that's really important, and you want to make sure it's clear to other developers.
You can attach an annotation like @ImportantMethod above it. This way, when
other developers look at your code, they'll immediately know that this method is
crucial.

Annotations can also tell the computer to do certain things with your code. For
instance, if you mark a method as @Override, you're telling the computer that
you're intentionally replacing a method from a parent class. This helps catch
errors if you make a mistake in overriding methods.

In simpler words, annotations are like sticky notes for your code. They provide
extra information to both humans and programs, making it easier to understand
and work with your Java code.

In Java, annotations are a form of metadata that can be added to Java code
elements, such as classes, methods, fields, parameters, and packages.
Annotations provide data about the program to the compiler or at runtime, and
they have no direct effect on the operation of the code itself (unless explicitly
designed to do so by frameworks or tools).

They are widely used for various purposes, including:

Code Documentation: Annotations can be used to add documentation to code


elements, providing additional information for developers or for generating
documentation.

Compiler Instructions: Annotations can provide hints or instructions to the


compiler, influencing how it processes the annotated code.

Runtime Behavior: Annotations can affect the behavior of the program at


runtime, such as enabling frameworks to perform certain actions based on the
presence of specific annotations.

Tool Integration: Annotations are often used by tools and frameworks to


automate tasks or perform static analysis on code.

Java Type Annotations

Java 8 has included two new features repeating and type annotations in its prior
annotations topic. In early Java versions, you can apply annotations only to
declarations. After releasing of Java SE 8 , annotations can be applied to any type
use. It means that annotations can be used anywhere you use a type.

For example, if you want to avoid NullPointerException in your code, you can
declare a string variable like this:

@NonNull String str;

EXAMPLE:

import javax.annotation.Nullable;

public class Example {

public void process(@Nullable String data) {

if (data != null) {

// process data

Java Repeating Annotations

In Java 8 release, Java allows you to repeating annotations in your source code. It
is helpful when you want to reuse annotation for the same class. You can repeat
an annotation anywhere that you would use a standard annotation.

For compatibility reasons, repeating annotations are stored in a container


annotation that is automatically generated by the Java compiler. In order for the
compiler to do this, two declarations are required in your code.
1. Declare a repeatable annotation type

Declaring of repeatable annotation type must be marked with the @Repeatable


meta-annotation. In the following example, we have defined a custom @Game
repeatable annotation type.

@Repeatable(Games.class)
@interfaceGame{
String name();
String day();
}
2. Declare the containing annotation type

Containing annotation type must have a value element with an array type. The
component type of the array type must be the repeatable annotation type. In the
following example, we are declaring Games containing annotation type:

@interfaceGames{

Game[] value();

EXAMPLE:

// Importing required packages for repeating annotation

import java.lang.annotation.Repeatable;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

// Declaring repeatable annotation type

@Repeatable(Games.class)

@interfaceGame{

String name();
String day();

// Declaring container for repeatable annotation type

@Retention(RetentionPolicy.RUNTIME)

@interfaceGames{

Game[] value();

// Repeating annotation

@Game(name = "Cricket", day = "Sunday")

@Game(name = "Hockey", day = "Friday")

@Game(name = "Football", day = "Saturday")

public class RepeatingAnnotationsExample {

public static void main(String[] args) {

// Getting annotation by type into an array

Game[] game =
RepeatingAnnotationsExample.class.getAnnotationsByType(Game.class);

for (Gamegame2 : game)

{ // Iterating values

System.out.println(game2.name()+" on "+game2.day());

}
OUTPUT:

Cricket on Sunday

Hockey on Friday

Football on Saturday

Java 9 Module System


Java Module System is a major change in Java 9 version. Java added this feature
to collect Java packages and code into a single unit called module.

In earlier versions of Java, there was no concept of module to create modular Java
applications, that why size of application increased and difficult to move around.
Even JDK itself was too heavy in size, in Java 8, rt.jar file size is around 64MB.

To deal with situation, Java 9 restructured JDK into set of modules so that we can
use only required module for our project.

Apart from JDK, Java also allows us to create our own modules so that we can
develop module based application.

The module system includes various tools and options that are given below.

o Includes various options to the Java tools javac, jlink and java where we can specify
module paths that locates to the location of module.
o Modular JAR file is introduced. This JAR contains module-info.class file in its root folder.
o JMOD format is introduced, which is a packaging format similar to JAR except it can
include native code and configuration files.
o The JDK and JRE both are reconstructed to accommodate modules. It improves
performance, security and maintainability.
o Java defines a new URI scheme for naming modules, classes and resources.

Java 9 Module
Module is a collection of Java programs or softwares. To describe a module, a Java file module-
info.java is required. This file also known as module descriptor and defines the following
o Module name: It is a name of module and should follow the reverse-domain-pattern. Like we
name packages, e.g. com.javatpoint.
o What does it export
o What does it require

How to create Java module


Creating Java module required the following steps.
o Create a directory structure
o Create a module declarator
o Java source code
The Java module system, introduced in Java 9, revolutionized the way Java applications are
built, packaged, and executed. It allows developers to organize code into modules, which are
self-describing units of functionality with explicit dependencies. This brings modularity to the
Java platform, enabling better code encapsulation, reuse, and maintainability.
Key concepts of the Java module system include:
1. Module Declaration (module-info.java): Each module contains a module declaration file
named module-info.java. This file specifies metadata about the module, including its name,
dependencies on other modules, and the packages it exports.
2. Module Path: Unlike the classpath used in previous versions of Java, Java modules are
resolved through the module path. The module path explicitly lists the directories or JAR
files containing modules.
3. Module Visibility: Modules enforce encapsulation by default. Only packages explicitly
exported by a module are accessible to other modules. Internal packages are hidden,
preventing unintended access.
4. Explicit Dependencies: Modules declare their dependencies on other modules in their
module-info.java file. This allows the Java runtime to resolve dependencies at compile time
and runtime.
5. Strong Encapsulation: With the module system, encapsulation is enforced at a stronger
level compared to traditional Java packages. Private members of classes are inaccessible
outside the declaring module, even if they are declared as public.
6. Module Path and Classpath Compatibility: Java 9 and later versions support running
applications that use both the module path and the classpath. This ensures backward
compatibility with existing libraries and frameworks that have not yet been
modularized.
7. Jlink: The jlink tool, introduced in Java 9, allows developers to create custom runtime
images containing only the modules required by their application. This reduces the size
of the runtime environment and improves deployment.
Overall, the Java module system provides a powerful mechanism for building modular, scalable,
and maintainable Java applications. It encourages better software design practices, facilitates
dependency management, and enhances application security.
Anonymous Inner Class in Java
Nested Classes in Java is prerequisite required before adhering forward to grasp
about anonymous Inner class. It is an inner class without a name and for which
only a single object is created. An anonymous inner class can be useful when
making an instance of an object with certain “extras” such as overriding methods
of a class or interface, without having to actually subclass a class.

Syntax:

Test t = new Test()

// data members and methods

public void test_method()

........

........

};

The difference between regular class(normal classes) and Anonymous Inner class
 A normal class can implement any number of interfaces but the
anonymous inner class can implement only one interface at a time.
 A regular class can extend a class and implement any number of
interfaces simultaneously. But anonymous Inner class can extend a class
or can implement an interface but not both at a time.
 For regular/normal class, we can write any number of constructors but
we can’t write any constructor for anonymous Inner class because the
anonymous class does not have any name and while defining constructor
class name and constructor name must be same.
DIAMOND OPERATOR
The diamond operator in Java, introduced in Java 7, is a syntactic feature that
simplifies the use of generics when creating objects. It allows you to omit the type
arguments of a generic class instance creation if the compiler can infer those
types from the context.

List<String> myList = new ArrayList<>();

Here, the diamond operator (<>) tells the compiler to infer the type arguments
(String) from the left-hand side of the assignment.

The diamond operator improves code readability and reduces redundancy by


eliminating the need to repeat the type arguments when creating instances of
generic classes. It's especially handy when dealing with complex generic types or
nested generics.

Diamond Operator in Anonymous Class

In Java 9, the diamond operator can be used with an anonymous class as well to
simplify code and improve readability.

Handler<Integer> intHandler = new Handler<>(1) {

@Override

public void handle() {

System.out.println(content);

};

Example :

public class Tester {

public static void main(String[] args) {

// create an Anonymous class to handle 1


// Here we do not need to pass Type arguments in diamond operator

// as Java 9 compiler can infer the type automatically

Handler<Integer> intHandler = new Handler<>(1) {

@Override

public void handle() {

System.out.println(content);

};

intHandler.handle();

Handler<? extends Number> intHandler1 = new Handler<>(2) {

@Override

public void handle() {

System.out.println(content);

};

intHandler1.handle();

Handler<?> handler = new Handler<>("test") {

@Override

public void handle() {

System.out.println(content);

};
handler.handle();

abstract class Handler<T> {

public T content;

public Handler(T content) {

this.content = content;

abstract void handle();

Output:

Test

What is type inference?


Type inference refers to the automatic detection of the datatype of a variable,
done generally at the compiler time.

What is Local Variable type inference?


Local variable type inference is a feature in Java 10 that allows the developer to
skip the type declaration associated with local variables (those defined inside
method definitions, initialization blocks, for-loops, and other blocks like if-else),
and the type is inferred by the JDK. It will, then, be the job of the compiler to
figure out the datatype of the variable.

Local Variable Type Inference (also known as 'var') is a feature introduced in Java
10 that allows you to declare local variables without explicitly stating their type.
Instead, the compiler infers the type of the variable based on the initializer
expression used to initialize the variable.

Before Local Variable Type Inference:

String message = "Hello, World!";

With Local Variable Type Inference:

var message = "Hello, World!";

Here are some key points to note about Local Variable Type Inference:

 It can only be used for local variables with initializer expressions (variables
initialized when declared).
 It does not change the way Java handles types at runtime; it's purely a
compile-time feature for convenience.
 It does not make Java a dynamically typed language like JavaScript or
Python. Java is still statically typed; the type of var is determined at compile
time.
 It enhances code readability and conciseness, especially in cases where the
type is obvious from the context.

However, it's essential to use var judiciously. While it can improve readability in
some cases, overuse may reduce code clarity and maintainability, particularly
when the inferred type isn't obvious or when the variable name doesn't convey
enough information about its type.

EXAMPLE

public class LocalVariableTypeInferenceExample {


public static void main(String[] args) {

// Before Java 10

String message1 = "Hello, World!";

System.out.println("Message 1: " + message1);

// With Local Variable Type Inference (Java 10 and later)

var message2 = "Hello, World!";

System.out.println("Message 2: " + message2);

// Inferred type is still String

System.out.println("Type of message2: " + message2.getClass().getName());

// You can also use var with other types

var number = 42;

System.out.println("Number: " + number);

System.out.println("Type of number: " + number.getClass().getName());

// var can also be used with complex types

var list = new ArrayList<String>();

list.add("Apple");

list.add("Banana");

list.add("Orange");

System.out.println("List: " + list);

System.out.println("Type of list: " + list.getClass().getName());

}
OUTPUT:

Message 1: Hello, World!

Message 2: Hello, World!

Type of message2: java.lang.String

Number: 42

Type of number: java.lang.Integer

List: [Apple, Banana, Orange]

Type of list: java.util.ArrayList

SWITCH EXPRESSIONS
Switch expressions, introduced in Java 12 and enhanced in later versions, provide
a more concise and expressive way to write switch statements. They allow you to
use switch as an expression, returning a value directly from each case block.
Here's an explanation with an example:

Traditional Switch Statement:


int day = 3;

String dayString;

switch (day) {

case 1:

dayString = "Monday";

break;

case 2:

dayString = "Tuesday";
break;

case 3:

dayString = "Wednesday";

break;

default:

dayString = "Invalid day";

Switch Expression:

int day = 3;

String dayString = switch (day) {

case 1 -> "Monday";

case 2 -> "Tuesday";

case 3 -> "Wednesday";

default -> "Invalid day";

};

In the switch expression:

 The switch keyword is followed by the expression whose value is being


switched on (day in this case).
 Each case label (case 1 ->, case 2 ->, etc.) specifies a value to compare
against.
 The arrow -> separates the case label from the expression to be evaluated
if the case matches.
 The default -> label is used for the default case.
 The value of the expression after the arrow is assigned to the variable
dayString.

Switch expressions have several advantages over traditional switch statements:

 They are more concise and readable, especially for simple switch cases.
 They can be used as expressions, allowing you to assign their result directly
to a variable.
 They are more flexible, allowing for more complex logic within each case
block.

Switch expressions are a powerful addition to Java that streamline code and
make it easier to write and maintain.

YIELD KEYWORD
In Java, the yield keyword is used in combination with switch expressions to
return a value from a switch block. It's used within switch expressions to
return a value from a case without terminating the entire switch block. The
yield statement essentially acts as a "return" statement within a switch
expression.

Here's a simple example to illustrate its usage:

public class YieldExample {

public static void main(String[] args) {

int day = 3;

String dayString = switch (day) {

case 1 -> "Monday";

case 2 -> "Tuesday";

case 3 -> {
yield "Wednesday"; // return "Wednesday" without terminating the
switch block

default -> "Invalid day";

};

System.out.println("Day: " + dayString);

OUTPUT:

Day: Wednesday

In this example:

 The switch expression is used to evaluate the value of day.


 When day is 3, it matches the case 3 label.
 Instead of immediately assigning a value to dayString, the yield keyword is
used to return "Wednesday" without exiting the switch block.
 The value "Wednesday" is assigned to dayString, and the switch expression
completes.

The yield keyword enhances the flexibility of switch expressions, allowing for
more complex control flow and enabling the expression of logic that requires
returning a value from a specific case without breaking out of the switch block
entirely.

TEXT BLOCKS
Text blocks, introduced in Java 13, provide a way to write multi-line strings more
conveniently and with improved readability. They allow you to include line breaks
and special characters directly within the string literal without the need for
escape sequences or concatenation.

Here's how you can use text blocks in Java:

public class TextBlocksExample {

public static void main(String[] args) {

String html = """

<html>

<body>

<p>Hello, world!</p>

</body>

</html>

""";

System.out.println(html);

OUTPUT:

<html>

<body>

<p>Hello, world!</p>

</body>

</html>
In this example:

 The """ delimiters mark the beginning and end of the text block.
 You can include line breaks and indentation directly within the text block,
improving the readability of the string.
 Escape sequences are interpreted literally within text blocks, so you can
include characters like \n or \t without escaping them.

Text blocks are particularly useful for embedding HTML, XML, JSON, SQL, or any
other multi-line text directly within Java code. They eliminate the need for messy
concatenation or escape characters, resulting in cleaner and more maintainable
code.

RECORDS
Records, introduced in Java 14, provide a concise way to declare classes that are
immutable data carriers. They combine the simplicity of data classes with the
benefits of immutability and encapsulation. Here's a breakdown of records in
Java:

Declaration Syntax:

Records are declared using the record keyword followed by the name of the
record and a list of components (fields):

public record Person(String name, int age) {}

 Implicit Methods:

Records automatically generate useful methods such as constructors, accessor methods


(getters), equals(), hashCode(), and toString() based on the components declared in the record.

 Immutable by Default:

Record components are implicitly final and immutable. Once a record object is created, its state
cannot be modified.
 Conciseness and Readability:

Records provide a concise way to declare simple data-holding classes, reducing boilerplate code
and improving readability.

EXAMPLE:

public class RecordsExample {

public static void main(String[] args) {

// Create a new record object

Person person = new Person("Alice", 30);

// Access record components using accessor methods

System.out.println("Name: " + person.name());

System.out.println("Age: " + person.age());

// Record objects have a built-in toString() method

System.out.println("Person: " + person);

// Records provide structural equality by default

Person anotherPerson = new Person("Alice", 30);

System.out.println("Equals: " + person.equals(anotherPerson)); // true

// Define a record named Person

record Person(String name, int age) {}

OUTPUT:

Name: Alice
Age: 30

Person: Person[name=Alice, age=30]

Equals: true

In this example, Person is a record that encapsulates data about a person,


including their name and age. Records provide a concise and immutable way to
define such data-holding classes in Java.

SEALED CLASSES
Sealed classes, introduced in Java 15, provide a way to restrict the subclasses that
can extend or implement a particular class or interface. This helps improve the
maintainability and robustness of your code by explicitly specifying which classes
are allowed to extend or implement a sealed class or interface.

Here's how sealed classes work in Java:

Declaration Syntax:

You declare a sealed class or interface using the sealed modifier, followed by the
class or interface name. You also specify the permitted subclasses using the
permits clause:

public sealed class Shape permits Circle, Rectangle, Triangle {}

 Permitted Subclasses:

The permits clause specifies the classes that are allowed to extend the sealed
class. Only these permitted subclasses can directly extend the sealed class. Other
classes attempting to extend the sealed class will result in a compile-time error.

public final class Circle extends Shape {

// class definition
}

 Sealed Interfaces:

Similarly, you can declare sealed interfaces to specify which classes are allowed to
implement them.

 Extending Sealed Classes:

Permitted subclasses of a sealed class must explicitly state which class they
extend. If a subclass further restricts its subclasses, it can use the sealed modifier
as well.

 Non-Sealed Subclasses:

If a permitted subclass does not further restrict its subclasses, it can be extended
by any class, whether sealed or non-sealed.

public non-sealed class Shape permits Circle, Square, Triangle, CustomShape {

// class definition

 Rules for Subclasses: A subclass of a sealed class or interface must be in the


same module as the sealed class or in the same package if the sealed class
is not in a module.
 Finality: Sealed classes and interfaces can be final, but all permitted
subclasses must be in the same module or package as the sealed class.

Sealed classes provide more control over the inheritance hierarchy, making it
easier to manage and reason about class hierarchies, especially in library APIs.
They help to ensure that the set of subclasses or implementations is known
and fixed, which can be useful for pattern matching and exhaustiveness
checking in switch expressions.

EXAMPLE:
// Sealed class
public sealed class Shape permits Circle, Square, Triangle {
public abstract void draw();
}

// Subclasses
final class Circle extends Shape {
@Override
public void draw() {
System.out.println("Drawing Circle");
}
}

final class Square extends Shape {


@Override
public void draw() {
System.out.println("Drawing Square");
}
}

final class Triangle extends Shape {


@Override
public void draw() {
System.out.println("Drawing Triangle");
}
}
// Main class
public class Main {
public static void main(String[] args) {
Shape circle = new Circle();
Shape square = new Square();
Shape triangle = new Triangle();

circle.draw();
square.draw();
triangle.draw();
}
}
OUTPUT:
Drawing Circle
Drawing Square
Drawing Triangle

You might also like