Unit 3
Unit 3
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();
System.out.println("method1 implementation");
System.out.println("method2 implementation");
return 0;
OUTPUT:
method1 implementation
method2 implementation
Result of method2: 0
Constant value: 10
System.out.println("Method 1 implementation");
System.out.println("Method 2 implementation");
return 0;
myObj.method1();
myObj.defaultMethod();
MyInterface.staticMethod();
OUTPUT::
Method 1 implementation
Method 2 implementation
Result: 0
Constant value: 10
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.
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.
this.radius = radius;
}
double calculateArea() {
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.
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 {
myInterface.myMethod("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.
Or
Example:
EXAMPLE:
interface Sayable
Sayable s=()->{
return "I have nothing to say.";
};
System.out.println(s.say());
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");
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.
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.
}
}
OUTPUT:
Hello, this is default method
Work is worship
import java.util.ArrayList;
import java.util.List;
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.add("David");
names.forEach(System.out::println);
OUTPUT:
Alice
Bob
Charlie
David
Alice
Bob
Charlie
David
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:
Welcome to javaTpoint!
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).
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:
EXAMPLE:
import javax.annotation.Nullable;
if (data != null) {
// process data
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.
@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:
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Repeatable(Games.class)
@interfaceGame{
String name();
String day();
@Retention(RetentionPolicy.RUNTIME)
@interfaceGames{
Game[] value();
// Repeating annotation
Game[] game =
RepeatingAnnotationsExample.class.getAnnotationsByType(Game.class);
{ // Iterating values
System.out.println(game2.name()+" on "+game2.day());
}
OUTPUT:
Cricket on Sunday
Hockey on Friday
Football on Saturday
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
Syntax:
........
........
};
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.
Here, the diamond operator (<>) tells the compiler to infer the type arguments
(String) from the left-hand side of the assignment.
In Java 9, the diamond operator can be used with an anonymous class as well to
simplify code and improve readability.
@Override
System.out.println(content);
};
Example :
@Override
System.out.println(content);
};
intHandler.handle();
@Override
System.out.println(content);
};
intHandler1.handle();
@Override
System.out.println(content);
};
handler.handle();
public T content;
this.content = content;
Output:
Test
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.
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
// Before Java 10
list.add("Apple");
list.add("Banana");
list.add("Orange");
}
OUTPUT:
Number: 42
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:
String dayString;
switch (day) {
case 1:
dayString = "Monday";
break;
case 2:
dayString = "Tuesday";
break;
case 3:
dayString = "Wednesday";
break;
default:
Switch Expression:
int day = 3;
};
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.
int day = 3;
case 3 -> {
yield "Wednesday"; // return "Wednesday" without terminating the
switch block
};
OUTPUT:
Day: Wednesday
In this example:
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.
<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):
Implicit Methods:
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:
OUTPUT:
Name: Alice
Age: 30
Equals: true
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.
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:
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.
// class definition
}
Sealed Interfaces:
Similarly, you can declare sealed interfaces to specify which classes are allowed to
implement them.
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.
// class definition
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");
}
}
circle.draw();
square.draw();
triangle.draw();
}
}
OUTPUT:
Drawing Circle
Drawing Square
Drawing Triangle