OOPS Unit III-Part I
OOPS Unit III-Part I
Oracle released a new version of Java as Java 8 in March 18, 2014. It was a revolutionary
release of the Java for software development platform. It includes various upgrades to the Java
programming, JVM, Tools and libraries.
o Lambda expressions,
o Method references,
o Functional interfaces,
o Stream API,
o Default methods,
o Base64 Encode Decode,
o Static methods in interface,
o Optional class,
o Collectors class,
o ForEach() method,
o Annotations
Java lambda expression is treated as a function, so compiler does not create .class file.
Functional Interface
• If a Java interface contains one and only one abstract method then it is termed as
functional interface. This only one method specifies the intended purpose of the
interface.
• For example, the Runnable interface from package java.lang; is a functional interface
because it constitutes only one method i.e. run().
• In the above example, the interface MyInterface has only one abstract method
getValue(). Hence, it is a functional interface.
• Here, we have used the annotation @FunctionalInterface. The annotation forces the
Java compiler to indicate that the interface is a functional interface.
• Hence, does not allow to have more than one abstract method. However, it is not
compulsory though.
• In Java 7, functional interfaces were considered as Single Abstract Methods
or SAM type. SAMs were commonly implemented with Anonymous (unnamed)
Classes in Java 7.
Example: Implement SAM with anonymous classes in java
public class FunctionInterfaceTest {
public static void main(String[] args) {
// anonymous class
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("I just implemented the Runnable Functional Interface.");
}
}).start();
}
}
Output:
Here, we can pass an anonymous class to a method. This helps to write programs with fewer
codes in Java 7. However, the syntax was still difficult and a lot of extra lines of code were
required.
• Java 8 extended the power of a SAMs by going a step further. Since we know that a
functional interface has just one method, there should be no need to define the name
of that method when passing it as an argument. Lambda expression allows us to do
exactly that.
The new operator (->) used is known as an arrow operator or a lambda operator
Suppose, we have a method like this:
double getPiValue() {
return 3.1415;
}
() -> 3.1415
Here, the method does not have any parameters. Hence, the left side of the operator includes
an empty parameter. The right side is the lambda body that specifies the action of the lambda
expression. In this case, it returns the value 3.1415.
() -> {
double pi = 3.1415;
return pi;
};
This type of the lambda body is known as a block body. The block body allows the lambda
body to include multiple statements. These statements are enclosed inside the braces and you
have to add a semi-colon after the braces.
Note: For the block body, you can have a return statement if the body returns a value.
However, the expression body does not require a return statement.
As mentioned earlier, a lambda expression is not executed on its own. Rather, it forms the
implementation of the abstract method defined by the functional interface.
import java.lang.FunctionalInterface;
// abstract method
double getPiValue();
}
// lambda expression
ref = () -> 3.1415;
Value of Pi = 3.1415
• Finally, we call the method getPiValue() using the reference interface. When
interface MyFI
{
public String say();
}
public class LambdaExpressionEx{
public static void main(String[] args) {
MyFI s=()->{
return "Hello All";
};
System.out.println(s.say());
}
}
Lambda Expressions with parameters
Till now we have created lambda expressions without any parameters. However, similar
to methods, lambda expressions can also have parameters. For example,
Here, the variable n inside the parenthesis is a parameter passed to the lambda expression.
The lambda body takes the parameter and checks if it is even or odd.
interface MyFI
{
public String say(String name);
}
public class LambdaExpressionEx{
public static void main(String[] args) {
// abstract method
String reverse(String n);
}
}
Output:
Lambda reversed = adbmaL
interface Addable{
int add(int a,int b);
}
Output:
30
300
return places;
}
}
Output:
myPlaces.stream()
.filter((p) -> p.startsWith("Nepal"))
.map((p) -> p.toUpperCase())
.sorted()
.forEach((p) -> System.out.println(p));
Here, we are using the methods like filter(), map() and forEach() of the Stream API. These
methods can take a lambda expression as input.
We can also define our own expressions based on the syntax we learned above. This allows
us to reduce the lines of code drastically as we saw in the above example.
Method References
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
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
In the following example, we have defined a functional interface and referring a static method
to it's functional method say().
interface MyFI{
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
MyFI s = MethodReference::saySomething;
// Calling interface method
s.say();
}
}
Output:
Example
In the following example, we are using predefined functional interface Runnable to refer static
method.
Output:
Thread is running...
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
In the following example, we are referring non-static methods. You can refer methods by class
object and anonymous object.
interface Sayable{
void say();
}
Output:
Example
In the following example, we are referring instance (non-static) method. Runnable interface
contains only one abstract method. So, we can use it as functional interface.
Output:
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
1. 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
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.
Interfaces
Interfaces are like a 100-percent abstract superclass. An interface is a contract.
Definition: An interface is a reference type, similar to a class, that can contain only constants,
method signatures, default methods, static methods.
➢ Using interface, we can specify what a class must do, but not how it does it.
➢ Method bodies exist only for default methods and static methods.
➢ Interfaces cannot be instantiated—they can only be implemented by classes or extended
by other interfaces.
➢ Using interfaces, Java achieves the run time polymorphism - “one interface, multiple
methods” aspect of polymorphism.
Defining an Interface
access interface name {
// constants
type final-varname = value;
//abstract methods
return-type method-name(parameter-list);
//default methods
default type method-name(parameter-list)
{
//body
}
//static methods
static type method-name(parameter-list)
{
//body
}
}
In the following example, Sayable is a functional interface that contains a default and an
abstract method. The concept of default method is used to define a method with default
implementation. You can override default method also to provide more specific
implementation for the method.
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
You can also define static methods inside the interface. Static methods are used to define utility
methods. The following example explain, how to implement static method in interface?
interface Sayable{
// default method
default void say(){
System.out.println("Hello, this is default method");
}
// Abstract method
void sayMore(String msg);
// static method
static void sayLouder(String msg){
System.out.println(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
Sayable.sayLouder("Helloooo..."); // calling static method
}
}
Output:
Hello there
Work is worship
Helloooo...
Annotation Syntax
An annotation is declared using the character @ and the annotation name,
i.e. @AnnotationName. When the compiler goes through this element, it understands that this
is an annotation.
• SOURCE — indicates that this annotation is available only in the source code and
ignored by the Compiler and JVM, and hence not available in runtime.
• CLASS — indicates that this annotation is available to the Compiler but not JVM,
and hence not available during runtime.
• RUNTIME — indicates that the annotation is available to JVM, and hence can be
used in runtime.
@Target: This annotation indicates the target elements an annotation can be applied to:
@Documented: This annotation can be applied to other annotations. It means that the
annotated elements will be documented using the Javadoc tool.
@Deprecated: Indicates that the annotated element should not be used. This annotation gets
the compiler to generate a warning message. It can be applied to methods, classes, and fields.
@SuppressWarnings: Indicates the compiler not to produce warnings for a specific reason
or reasons.
@Override: This annotation informs the compiler that the element is overriding an element
of the superclass. It is not mandatory to use when overriding elements, but it helps the
compiler to generate errors when the overriding is not done correctly, for example, if the
subclass method parameters are different than the superclass ones, or if the return type does
not match.
@SafeVarargs: Asserts that the code of the method or constructor does not perform unsafe
operations on its arguments.
Java Type Annotations
Type annotations are one more feature introduced in Java 8. Even though we had
annotations available before, now we can use them wherever we use a type. 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.
This means that we can use them on:
Tools like IDEs can then read these annotations and show warnings or errors based on the
annotations.
• Java annotations are metadata (data about data) for our program source code. There are
several predefined annotations provided by the Java SE. Moreover, we can also create
custom annotations as per needs.
@NonNull String str; // null value is forbidden to return (for methods), pass to (for
parameters) and hold (for local variables and fields).
When the code is compiled, the compiler checks for potential problems and raised warnings
when any such code is found where the variable may be assigned a null value.
In Java 8, type annotations can be written on any use of a type, such as the following:
@Encrypted String data
List<@NonNull String> strings
MyGraph = (@Immutable Graph) tmpGraph;
@NonNull List<String>
List<@NonNull String> str
Arrays<@NonNegative Integer> sort
@Encrypted File file
@Open Connection connection
void divideInteger(int a, int b) throws @ZeroDivisor ArithmeticException
@Override annotation was introduced so that developers could document methods as
overriding a superclass method.
• The Java compiler then uses the annotations to warn the developer if the program
doesn't match their intentions. Used this way, annotations act as a form of machine-
checked documentation.
Note - Java created type annotations to support improved analysis of Java programs. It
supports way of ensuring stronger type checking.
Local Variable Definition
Let us see how to ensure that our local variable doesn’t end up as a null value:
Constructor Call
We want to make sure that we cannot create an empty ArrayList:
@Notify(email = "[email protected]")
@Notify(email = "[email protected]")
public class UserNotAllowedForThisActionException
extends RuntimeException {
final String user;
}
}
We have our custom exception class that we will throw whenever a user tries to do something
that the user is not allowed. Our annotations to this class say that we want to notify two
emails when code throws this exception.
Custom Annotations
Java allows programmers to define and implement custom annotations. The syntax to define
custom annotations is:
1
public @interface CustomAnnotation { }
This creates a new annotation type called CustomAnnotation. The @interface keyword is
used to define a custom annotation.
When defining custom annotations, two mandatory attributes must be defined for the
annotation. Other attributes can be defined here, but these two are important and mandatory.
These two attributes are the Retention Policy and Target.
These two attributes are declared in the form of annotations to that custom annotation. Also,
properties to the annotations can be defined when defining the custom annotation. For example:
1
@Retention (RetentionPolicy.RUNTIME)
2
@Target (ElementType.ELEMENT)
3
public @interface CustomAnnotation {
4
public String name() default “Mr Bean”;
5
public String dateOfBirth();
6
}
In the above custom annotation, the Retention Policy is RUNTIME, that means it is available
to the JVM at runtime and the Target is ELEMENT, which means it can be annotated to any
element type.
Also, it has two properties: name with the default value Mr Bean and one dateOfBirth with
no default value.
Note that the properties declared as Method don’t have any parameter and throws clause.
Also, the return type is restricted to String, class, enums, and annotations and arrays of the
mentioned return types.
1
@CustomAnnotation (dateOfBirth = “1980-06-25”)
2
public class CustomAnnotatedClass {
3
4
}
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.
@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.
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:
1. @interfaceGames{
2. Game[] value();
3. }
Note - Compiler will throw a compile-time error, if you apply the same annotation to a
declaration without first declaring it as repeatable.
OUTPUT:
Cricket on Sunday
Hockey on Friday
Football on Saturday
Let us imagine we have an application with fully implemented security. It has different levels
of authorization. Even though we implemented everything carefully, we want to make sure
that we log every unauthorized action. On each unauthorized action, we are sending an email
to the owner of the company and our security admin group email. Repeating annotations are
our way to go on this example.
@Repeatable(Notifications.class)
public @interface Notify {
String email();
}
public @interface Notifications {
Notify[] value();
}
}
We create @Notify as a regular annotation, but we add the @Repeatable (meta-)annotation to
it. Additionally, we have to create a “container” annotation Notifications that contains an
array of Notify objects. An annotation processor can now get access to all
repeating Notify annotations through the container annotation Noifications.
Please note that this is a mock annotation just for demonstration purposes. This annotation
will not send emails without an annotation processor that reads it and then sends emails.
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.
Base64 encoding converts the binary data into text format, which is passed through
communication channel where a user can handle text safely.
This class provides three different encoders and decoders to encrypt information at each level.
You can use these methods at the following levels.
• When you encode text in ASCII, you start with a text string and convert it to a sequence
of bytes.
• When you encode data in Base64, you start with a sequence of bytes and convert it to
a text string.
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.
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.
Base64.Decoder This class implements a decoder for decoding byte data using the Base64 encoding scheme as specified
RFC 4648 and RFC 2045.
Base64.Encoder This class implements an encoder for encoding byte data using the Base64 encoding scheme as specified
in RFC 4648 and RFC 2045.
Base64 Methods
Methods Description
public static It returns a Base64.Decoder that decodes using the Basic type base64 encoding scheme.
Base64.Decoder
getDecoder()
public static It returns a Base64.Encoder that encodes using the Basic type base64 encoding scheme.
Base64.Encoder
getEncoder()
public static It returns a Base64.Decoder that decodes using the URL and Filename safe type base64 enc
Base64.Decoder scheme.
getUrlDecoder()
public static It returns a Base64.Decoder that decodes using the MIME type base64 decoding scheme.
Base64.Decoder
getMimeDecoder()
public static It Returns a Base64.Encoder that encodes using the MIME type base64 encoding scheme.
Base64.Encoder
getMimeEncoder()
public static It returns a Base64.Encoder that encodes using the MIME type base64 encoding scheme wit
Base64.Encoder specified line length and line separators.
getMimeEncoder(int
lineLength, byte[]
lineSeparator)
public static It returns a Base64.Encoder that encodes using the URL and Filename safe type base64 enco
Base64.Encoder scheme.
getUrlEncoder()
Base64.Decoder Methods
Methods Description
public byte[] decode(byte[] src) It decodes all bytes from the input byte array using the Base64 encoding scheme, writ
results into a newly-allocated output byte array. The returned byte
array is of the length of the resulting bytes.
public byte[] decode(String src) It decodes a Base64 encoded String into a newly-allocated byte array using
the Base64 encoding scheme.
public int decode(byte[] src, It decodes all bytes from the input byte array using the Base64 encoding
byte[] dst) scheme, writing the results into the given output byte array, starting at offset
0.
public ByteBuffer It decodes all bytes from the input byte buffer using the Base64 encoding
decode(ByteBuffer buffer) scheme, writing the results into a newly-allocated ByteBuffer.
public InputStream It returns an input stream for decoding Base64 encoded byte stream.
wrap(InputStream is)
Base64.Encoder Methods
Methods Description
public byte[] encode(byte[] src) It encodes all bytes from the specified byte array into a newly-allocated byte
array using the Base64 encoding scheme. The returned byte array is of the
length of the resulting bytes.
public int encode(byte[] src, byte[] It encodes all bytes from the specified byte array using the Base64 encoding
dst) scheme, writing the resulting bytes to the given output byte array,
starting at offset 0.
public String It encodes the specified byte array into a String using the Base64 encoding
encodeToString(byte[] src) scheme.
public ByteBuffer It encodes all remaining bytes from the specified byte buffer into a
encode(ByteBuffer buffer) newly-allocated ByteBuffer using the Base64 encoding scheme. Upon return,
the source buffer's position will be updated to its limit; its limit will not
have been changed. The returned output buffer's position will be zero and
its limit will be the number of resulting encoded bytes.
public OutputStream It wraps an output stream for encoding byte data using the Base64 encoding scheme
wrap(OutputStream os)
public Base64.Encoder It returns an encoder instance that encodes equivalently to this one, but without addi
withoutPadding() padding character at the end of the encoded byte data.
Java Base64 Example: Basic Encoding and Decoding
import java.util.Base64;
public class Base64BasicEncryptionExample {
public static void main(String[] args) {
// Getting encoder
Base64.Encoder encoder = Base64.getEncoder();
// Creating byte array
Byte byteArr[] = {1,2};
// encoding byte array
Byte byteArr2[] = encoder.encode(byteArr);
System.out.println("Encoded byte array: "+byteArr2);
bytebyteArr3[] = newbyte[5]; // Make sure it has enough size to store copie
d bytes
intx = encoder.encode(byteArr,byteArr3); // Returns number of bytes written
System.out.println("Encoded byte array written to another array: "+byteArr3);
System.out.println("Number of bytes written: "+x);
// Encoding string
String str = encoder.encodeToString("JavaTpoint".getBytes());
System.out.println("Encoded string: "+str);
// Getting decoder
Base64.Decoder decoder = Base64.getDecoder();
// Decoding string
String dStr = new String(decoder.decode(str));
System.out.println("Decoded string: "+dStr);
}
}
Output:
Output:
Encoded URL:
aHR0cDovL3d3dy5qYXZhdHBvaW50LmNvbS9qYXZhLXR1dG9yaWFsLw==
Decoded URL: http://www.mychannel.com.com/java-tutorial/