OOPS Unit 3 Notes
B.Tech CSE (2nd Year) (Dr. A.P.J. Abdul Kalam Technical University)
Object Oriented Programming with Java
Unit-3
Java Functional Interfaces
A functional interface is an interface that contains only one abstract method. They can
have only one functionality to exhibit. From Java 8 onwards, lambda expressions can be
used to represent the instance of a functional interface. A functional interface can have any
number of default and static methods. Runnable, ActionListener, and Comparable are
some of the examples of functional interfaces.
Functional Interface is additionally recognized as Single Abstract Method Interfaces. In
short, they are also known as SAM interfaces.
Functional interfaces are used and executed by representing the interface with
an annotation called @FunctionalInterface.
Program:
class Test {
public static void main(String args[])
{
// create anonymous inner class object
new Thread(new Runnable() {
@Override public void run()
{
System.out.println("New thread created");
}
}).start();
}
}
Output: New thread created
1
@FunctionalInterface Annotation
@FunctionalInterface annotation is used to ensure that the functional interface can’t have
more than one abstract method. In case more than one abstract methods are present, the
compiler flags an ‘Unexpected @FunctionalInterface annotation’ message. However, it is
not mandatory to use this annotation.
Some Built-in Java Functional Interfaces
Since Java SE 1.8 onwards, there are many interfaces that are converted into functional
interfaces. All these interfaces are annotated with @FunctionalInterface. These interfaces
are as follows –
Runnable –> This interface only contains the run() method.
Comparable –> This interface only contains the compareTo() method.
ActionListener –> This interface only contains the actionPerformed() method.
Callable –> This interface only contains the call() method.
Java Lambda Expressions
Lambda Expressions were added in Java 8.
A lambda expression is a short block of code which takes in parameters and returns a value.
Lambda expressions are similar to methods, but they do not need a name and they can be
implemented right in the body of a method.
Syntax
The simplest lambda expression contains a single parameter and an expression:
parameter ->expression
To use more than one parameter, wrap them in parentheses:
(parameter1, parameter2) ->expression
Expressions are limited. They have to immediately return a value, and they cannot contain
variables, assignments or statements such as if or for. In order to do more complex
2
operations, a code block can be used with curly braces. If the lambda expression needs to
return a value, then the code block should have a return statement.
(parameter1, parameter2) ->{ code block }
Import java.util.ArrayList;
public class Main
{
public static void main(String[] args)
{
ArrayList<Integer> numbers = new ArrayList<Integer>();
numbers.add(5);
numbers.add(9);
numbers.add(8);
numbers.add(1);
numbers.forEach( (n) -> { System.out.println(n); } );
}
}
Output:
5
9
8
1
Method References in Java
In Java 8 we can use the method as if they were objects or primitive values, and we can
treat them as a variable. The example shows the function as a variable in java:
// This square function is a variable getSquare.
Function<Integer, Integer>getSquare = i -> i * i;
SomeFunction(a, b, getSquare);
// Pass function as a argument to other function easily
3
Sometimes, a lambda expression only calls an existing method. In those cases, it looks clear
to refer to the existing method by name. The method references can do this, they are
compact, easy-to-read as compared to lambda expressions. A method reference is the
shorthand syntax for a lambda expression that contains just one method call.
Generic syntax: Method reference
A. To refer to a method in an object
Object :: methodName
B. To print all elements in a list
Following is an illustration of a lambda expression that just calls a single method in its
entire execution:
list.forEach(s ->System.out.println(s));
C. Shorthand to print all elements in a list
To make the code clear and compact, In the above example, one can turn lambda
expression into a method reference:
list.forEach(System.out::println);
The method references can only be used to replace a single method of the lambda
expression.
The following example is about performing some operations on elements in the list and
adding them. The operation to be performed on elements is a function argument and the
caller can pass accordingly.
Illustration:
public int transformAndAdd(List<Integer> l,
Function<Integer, Integer> ops)
{
int result = 0;
for (Integer s : l)
result += f.apply(s);
return results;
}
// Operations utility class
classOpsUtil
{
4
// Method 1
// To half the variable
public static Integer doHalf(Integer x)
{
return x / 2;
}
// Method 2
// Square up the integer number
public static Integer doSquare(Integer x)
{
return x * x;
}
... many more operations...
}
Types of Method References
There are four type method references that are as follows:
1. Static Method Reference.
2. Instance Method Reference of a particular object.
3. Instance Method Reference of an arbitrary object of a particular type.
4. Constructor Reference.
Default Methods in Java
Before Java 8, interfaces could have only abstract methods. The implementation of these
methods has to be provided in a separate class. So, if a new method is to be added in an
interface, then its implementation code has to be provided in the class implementing the
same interface. To overcome this issue, Java 8 has introduced the concept of default
methods which allow the interfaces to have methods with implementation without affecting
the classes that implement the interface.
// A simple program to Test Interface default methods in java
interfaceTestInterface
5
{
// abstract method
public void square(int a);
// default method
default void show()
{
System.out.println("Default Method Executed");
}
}
classTestClass implements TestInterface
{
// implementation of square abstract method
public void square(int a)
{
System.out.println(a*a);
}
public static void main(String args[])
{
TestClass d = new TestClass();
d.square(4);
// default method executed
d.show();
}
}
Public static void main(String args[])
TestClass d = newTestClass();
d.square(4);
// default method executed
6
d.show();
}
}
Output: 16
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 static 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.
Syntax to declare the static method:
Access_modifier static void methodName()
{
// Method body.
}
Java Base64 Encode and Decode
Java provides a class Base64 to deal with encryption. You can encrypt and decrypt your data
by using provided methods. You need to import java.util.Base64 in your source file to use its
methods.
We can use these methods at the following levels:
7
1. Basic Encoding and Decoding
It uses the Base64 alphabet specified by Java in RFC 4648 and RFC 2045 for encoding and
decoding operations. The encoder does not add any line separator character. The decoder
rejects data that contains characters outside the base64 alphabet.
2. URL and Filename Encoding and Decoding
It uses the Base64 alphabet specified by Java in RFC 4648 for encoding and decoding
operations. The encoder does not add any line separator character. The decoder rejects data
that contains characters outside the base64 alphabet.
3. MIME
It uses the Base64 alphabet as specified in RFC 2045 for encoding and decoding operations.
The encoded output must be represented in lines of no more than 76 characters each and uses
a carriage return '\r' followed immediately by a linefeed '\n' as the line separator. No line
separator is added to the end of the encoded output. All line separators or other characters not
found in the base64 alphabet table are ignored in decoding operation.
For-each loop in Java
For-each is another array traversing technique like for loop, while loop, do-while loop
introduced in Java5.
It starts with the keyword for like a normal for-loop.
Instead of declaring and initializing a loop counter variable, you declare a variable that
is the same type as the base type of the array, followed by a colon, which is then
followed by the array name.
In the loop body, you can use the loop variable you created rather than using an indexed
array element.
It’s commonly used to iterate over an array or a Collections class (eg, ArrayList)
Syntax:
for (type var : array)
{
statements using var;
8
}
The above syntax is equivalent to:
for (int i=0; i<arr.length; i++)
{
typevar = arr[i];
statements using var;
}
Simple program with for each loop:
/*package whatever //do not write package name here */
import java.io.*;
class Easy
{
public static void main(String[] args)
{
// array declaration
Int ar[] = { 10, 50, 60, 80, 90 };
for (int element : ar)
System.out.print(element + " ");
}
}
Output
10 50 60 80 90
Try-with resources statement
The try-with-resources statement is a try statement that declares one or more resources.
A resource is an object that must be closed after the program is finished with it.
The try-with-resources statement ensures that each resource is closed at the end of the
statement. Any object that implements java.lang.AutoCloseable, which includes all objects
which implement java.io.Closeable, can be used as a resource.
9
The following example reads the first line from a file. It uses an instance
of FileReader and BufferedReader to read data from the file.
FileReader and BufferedReader are resources that must be closed after the program is
finished with it:
static String readFirstLineFromFile(String path) throws IOException {
try (FileReaderfr = new FileReader(path);
BufferedReaderbr = new BufferedReader(fr)) {
returnbr.readLine();
}
}
In this example, the resources declared in the try-with-resources statement are
a FileReader and a BufferedReader. The declaration statements of these resources appear
within parentheses immediately after the try keyword. The
classes FileReader and BufferedReader, in Java SE 7 and later, implement the
interface java.lang.AutoCloseable. Because the FileReader and BufferedReader instances are
declared in a try-with-resource statement, they will be closed regardless of whether
the try statement completes normally or abruptly (as a result of the
method BufferedReader.readLine throwing an IOException).
Java Type Annotations
Java 8 has included new feature “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;
Following are the examples of type annotations:
@NonNull List<String>
List<@NonNull String> str
10
Arrays<@NonNegative Integer> sort
Note - Java created type annotations to support improved analysis of Java programs. It
supports way of ensuring stronger type checking.
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
2. Declare the containing annotation type
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();
}
The value of the @Repeatable meta-annotation, in parentheses, is the type of the
container annotation that the Java compiler generates to store repeating
annotations. In the following example, the containing annotation type is Games. So,
repeating @Game annotations is stored in an @Games annotation.
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:
11
@interfaceGames
{
Game[] value();
}
Java Repeating Annotations 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(Ga
me.class);
for (Gamegame2 : game) { // Iterating values
System.out.println(game2.name()+" on "+game2.day());
}
}
}
12
OUTPUT:
1. Cricket on Sunday
2. Hockey on Friday
3. 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.
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
o What does it export
o What does it require
Module Name
It is a name of module and should follow the reverse-domain-pattern. Like we name
packages, e.g. com.javatpoint.
13
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
Create a Directory Structure
To create module, it is recommended to follow given directory structure, it is same
as reverse-domain-pattern, we do to create packages / project-structure in Java.
Note: The name of the directory containing a module's sources should be equal to the name
of the module, e.g. com.javatpoint.
Create a file module-info.java, inside this file, declare a module by
using module identifier and provide module name same as the directory name that
contains it. In our case, our directory name is com.javatpoint.
module com.javatpoint{
}
Leave module body empty, if it does not has any module dependency. Save this
file inside src/com.javatpoint with module-info.java name.
Java Source Code
Now, create a Java file to compile and execute module. In our example, we have
a Hello.java file that contains the following code.
1. lass Hello{
2. public static void main(String[] args){
3. System.out.println("Hello from the Java module");
14
4. }
5. }
Save this file inside src/com.javatpoint/com/javatpoint/ with Hello.java name.
Compile Java Module
To compile the module use the following command.
1. javac -d mods --module-source-path src/ --module com.javatpoint
After compiling, it will create a new directory that contains the following structure.
Now, we have a compiled module that can be just run.
Run Module
To run the compiled module, use the following command.
1. java --module-path mods/ --module com.javatpoint/com.javatpoint.Hello
Output:
Hello from the Java module
Well, we have successfully created, compiled and executed Java module.
Nested Classes in Java
In Java, it is possible to define a class within another class, such classes are known
as nested classes. They enable you to logically group classes that are only used in one
place, thus this increases the use of encapsulation and creates more readable and
maintainable code.
15
The scope of a nested class is bounded by the scope of its enclosing class. Thus in the
below example, the class NestedClass does not exist independently of the
class OuterClass.
A nested class has access to the members, including private members, of the class in
which it is nested. But the enclosing class does not have access to the member of the
nested class.
A nested class is also a member of its enclosing class.
As a member of its enclosing class, a nested class can be
declared private, public, protected, or package-private(default).
Nested classes are divided into two categories:
1. static nested class: Nested classes that are declared static are called static nested
classes.
2. inner class: An inner class is a non-static nested class.
Syntax:
class OuterClass
{
...
class NestedClass
{
...
}
}
16
Anonymous Inner Class in Java
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.
Tip: Anonymous inner classes are useful in writing implementation classes for listener
interfaces in graphics programming.
The syntax of an anonymous class expression is like the invocation of a constructor,
except that there is a class definition contained in a block of code.
/ Test can be interface, abstract/concrete class
Test t = new Test()
{
// data members and methods
public void test_method()
{
........
........
}
};
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.
17
Diamond operator for Anonymous Inner Class
Diamond operator was introduced in Java 7 as a new feature. The main purpose of the
diamond operator is to simplify the use of generics when creating an object. It avoids
unchecked warnings in a program and makes the program more readable.
The diamond operator could not be used with Anonymous inner classes in JDK 7. In JDK
9, it can be used with the anonymous class as well to simplify code and improves
readability. Before JDK 7, we have to create an object with Generic type on both side of
the expression like:
// Here we mentioned the generic type on both side of expression while creating object
List<String> geeks = new ArrayList<String>();
When Diamond operator was introduced in Java 7, we can create the object without
mentioning generic type on right side of expression like:
List<String> geeks = new ArrayList<>();
Local Variable Type Inference (LVTI)
What is type inference?
Type inference refers to the automatic detection of the data type 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 data type of the variable.
Instead of mentioning the variable data type on the left-side, before the variable, LVTI
allows you to simply put the keyword ‘var’. For example,
18
// Java code for local variable declaration using LVTI
import java.util.ArrayList;
import java.util.List;
class A {
public static void main(String ap[])
{
var data = new ArrayList<>();
}
}
Switch Expressions
Like all expressions, switch expressions evaluate to a single value and can be used in
statements. They may contain "case L ->" labels that eliminate the need for break statements
to prevent fall through. You can use a yield statement to specify the value of a switch
expression.
"case L ->" Labels
The following is a switch expression that uses the new kind of case label to print the number
of letters of a day of the week:
Day day = Day.WEDNESDAY;
System.out.println(
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
default -> throw new IllegalStateException ("Invalid day: " + day);
}
);
The new kind of case label has the following form:
case label_1, label_2, ..., label_n -> expression;|throw-statement;
yield statements
19
They take one argument, which is the value that the case label produces in
a switch expression.
The yield statement makes it easier for you to differentiate between switch statements
and switch expressions. A switch statement, but not a switch expression, can be the target of
a break statement. Conversely, a switch expression, but not a switch statement, can be the
target of a yield statement.
Program:
Day day = Day.WEDNESDAY;
int numLetters = switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
System.out.println(6);
yield 6;
case TUESDAY:
System.out.println(7);
yield 7;
case THURSDAY:
case SATURDAY:
System.out.println(8);
yield 8;
case WEDNESDAY:
System.out.println(9);
yield 9;
default:
throw new IllegalStateException("Invalid day: " + day);
};
System.out.println(numLetters);
Text Blocks
A text block is an alternative form of Java string representation that can be used anywhere
a traditional double-quoted string literal can be used. Text blocks begin with a “”” (3
double-quote marks) observed through non-obligatory whitespaces and a newline. It is
introduced in Java 15.
// ORIGINAL
20
String message = "A-143, 9th Floor, Sovereign Corporate Tower,\n" +
"Sector-136, Noida,\n" +
"Uttar Pradesh - 201305";
// BETTER : Using text blocks gets rid of lots of the clutter
String message = """
A-143, 9th Floor, Sovereign Corporate Tower,
Sector-136, Noida,
Uttar Pradesh - 201305""";
Records
In Java, a record is a special type of class declaration. Java records were introduced with
the intention to be used as a fast way to create data carrier classes, i.e. the classes whose
objective is to simply contain data and carry it between modules, also known as POJOs
(Plain Old Java Objects) and DTOs (Data Transfer Objects). Record was introduced in
Java SE 14 as a preview feature. Java SE 15 extends the preview feature with additional
capabilities such as local record classes.
Some more Properties of Records
a. You can use nested classes and interfaces inside a record.
b. You can have nested records too, which will implicitly be static.
c. A record can implement interfaces.
d. You can create a generic record class.
e. It is possible to use local record classes (since Java SE 15).
f. Records are serializable.
Sealed Class in Java
A sealed class is a technique that limits the number of classes that can inherit the given
class. This means that only the classes designated by the programmer can inherit from that
particular class, thereby restricting access to it. when a class is declared sealed, the
programmer must specify the list of classes that can inherit it. The concept of sealed
classes in Java was introduced in Java 15.
Steps to Create a Sealed Class
Define the class that you want to make a seal.
21
Add the “sealed” keyword to the class and specify which classes are permitted to
inherit it by using the “permits” keyword.
sealed class Human permits Manish, Vartika, Anjali
{
public void printName()
{
System.out.println("Default");
}
}
non-sealed class Manish extends Human
{
public void printName()
{
System.out.println("Manish Sharma");
}
}
sealed class Vartika extends Human
{
public void printName()
{
System.out.println("Vartika Dadheech");
}
}
final class Anjali extends Human
{
public void printName()
{
System.out.println("Anjali Sharma");
}
}
22