Unit-3
Java New Features
Introduction to Default Methods
• Default methods are methods defined in interfaces
with a default implementation. Introduced in Java 8
with the Purpose To allow interfaces to have methods
with concrete implementations.
• Backward Compatibility: Allow adding new methods
to interfaces without breaking existing
implementations.
• Enhanced Functionality: Provide common
functionality to all implementing classes.
interface MyInterface {
//default method Use default keyword
default void myDefaultMethod() {
System.out.println("This is a default
method");
} public class Demo5 {
void testAbstract();//abstract method public static void main(String[] args) {
static void testStatic() {//static method MyInterface mi=new Temp();
System.out.println("This is static mi.testAbstract();
method"); mi.myDefaultMethod();
} MyInterface.testStatic();
} }
class Temp implements MyInterface }
{
@Override
public void testAbstract() {
System.out.println("This is abstract
method");
}
}
FUNCTIONAL INTERFACES
• Functional interfaces a new and important feature included in Java SE 8.
• A functional interface is an interface that contains exactly one abstract
method.
• They enable the use of lambda expressions and method references.
• Annotation: @FunctionalInterface annotation (optional but
recommended).
• Examples of Functional Interfaces
Java Built-in Functional Interfaces
1. Runnable (single run method)
2. Callable<V> (single call method)
3. Comparator<T> (single compare method)
@FunctionalInterface //optinal but recommended
Using functional interfaces
interface Inf
{
Example 1:
public void test(String s);//only one abstract method
}
public class Demo1 {
public static void main(String[] args) {
// USE-CASES OF Functional interfaces
//1. Using for anonymous class
Inf inf1=new Inf() {
@Override
public void test(String s) {
System.out.println("Hello "+s);
}
};
inf1.test("Rinki");
//2. Using for lambda expressions Only one method in interface
Inf inf2=(s)->System.out.println("Namaste "+s);
inf2.test("Rinki ke papa");
}
}
import java.util.*; Using functional interfaces
class Job{
int jobNo; Example 2:
String jobName;
public Job(int jobNo, String jobName) {
super(); Lambda on comparator interface
this.jobNo = jobNo;
this.jobName = jobName;
}
@Override
public String toString() {
return "Job [jobNo=" + jobNo + ", jobName=" + jobName + "]";
}}
public class Demo2 {
public static void main(String[] args) {
List<Job> list=List.of(new Job(11,"Cleaning"),new Job(3,"Dusting"), new Job(5,
"Washing"));
ArrayList<Job> al=new ArrayList<Job>(list);
al.sort((a,b)-> a.jobNo-b.jobNo);
System.out.println(al);
}
}
public class Demo3 {
public static void main(String[] args) {
//First thread(anonymous class)
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++)
{
System.out.println(Thread.currentThread().getName());
}
}
}).start();;
//Using lambda expression
Runnable runner=()->{
for(int i=0;i<10;i++)
{
System.out.println(Thread.currentThread().getName());
}};
new Thread(runner).start();
}
}
public class Demo3 {
public static void main(String[] args) {
//First thread(anonymous class)
new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++)
{
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}}}
}).start();;
//Using lambda expression
Runnable runner=()->{
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}}};
new Thread(runner).start();
}}
Lambda Expressions
• Lambda expressions are a new and important feature included in Java SE 8.
• A lambda expression is a concise way to represent an anonymous function.
• They enable functional programming in Java, simplifying the code.
• Lambda expressions also improve the Collection libraries making it easier
to iterate through, filter, and extract data from a Collection .
• Reduce code length
Benefits of Using Lambda Expressions
• Conciseness: Reduces boilerplate code.
• Readability: Improves code readability and maintainability.
• Functional Programming: Enables functional programming paradigms.
Syntax
Basic syntax
(parameters) -> expression
Syntax Breakdown
No Parameters:
() -> System.out.println("Hello World")
Single Parameter:
x -> x * x
Multiple Parameters:
(a, b) -> a + b
import java.util.Arrays;
import java.util.List;
interface Square
{
int sqr(int a);
}
public class Demo4 {
public static void main(String[] args) {
List<Integer> li=Arrays.asList(1,2,3,4,5);
Square t=a->a*a;
li.forEach((s)->System.out.print(s+" "));
System.out.println("Sqaures of array items");
li.forEach((s)->System.out.print(t.sqr(s)+" "));
}
}
Exception Handling:
Handling exceptions within lambda expressions.
Example:
(a, b) -> { try { return a / b; } catch (ArithmeticException e) { return 0; } }
Method References
• Introduced In: Java 8
• Method references provide a way to refer to methods directly by their
names.
• Purpose: They simplify lambda expressions by reducing boilerplate
code.
Types of Method References
• Four Main Types:
• Reference to a static method
• Reference to an instance method of a particular object
• Reference to an instance method of an arbitrary object of a particular type
• Reference to a constructor
Method References
• Basic Syntax:
1. Static methods: ClassName::methodName
2. Instance methods of a particular object: instance::methodName
3. Instance methods of an arbitrary object: ClassName::methodName
4. Constructors: ClassName::new
import java.util.Arrays;
import java.util.Arrays; import java.util.List;
import java.util.List;
class Show
{
1 2
class Show
public static void display(String s) {
{ public void display(String s)
System.out.println(s); {
} System.out.println(s);
} }
public class Demo6 { }
public static void main(String...strings) public class Demo6 {
{ public static void main(String...strings)
List<String> list = Arrays.asList("Alex", {
"Brian", "Charles"); List<String> list = Arrays.asList("Alex",
list.forEach(Show::display); "Brian", "Charles");
} Show s=new Show();
} list.forEach(s::display);
}
}
import java.util.Arrays; @FunctionalInterface
import java.util.List; interface MyInf {
class Show{ 3 public Student get(String str);
String msg;
Show(String s){msg=s;}
}
class Student {
4
public void display() private String str;
{ public Student(String str) {
System.out.println(this.msg); this.str = str;
} System.out.println("The name of the student
} is: " + str);
public class Demo6 { }
public static void main(String...strings) }
{ public class Demo7 {
List<Show> list = Arrays.asList(new public static void main(String[] args) {
Show("Alex"),new Show("Brian"),new MyInf constructorRef = Student :: new; //
Show("Charles")); constructor reference
list.forEach(Show::display); constructorRef.get("Aditya");
} }
} }
Use of Package java.util.function
• This java.util.function package provides standard
library-based functional interfaces for common
requirements with their corresponding lambda
expression, which can be used by the programmer in
his code instead of creating brand new functional
interfaces.
Function
• This interface has one function apply (), this function takes one input
parameter as T and
• return value as R after performing some kind of operation on the input
parameter.
• Structure of Function interface:
@FunctionalInterface
public interface Function <T, R> {
R apply (T t);
}
This T and R may have any type of value like Integer, Float, Double, String,
etc.
import java.util.List;//CODE 1 EXAMPLE
import java.util.function.Function;
public class Demo8 {
public static void main(String[] args) {
List<Float> ls=List.of(34.0f,42.6f,66.5f, 40.5f);
Function<Float, Float> fn=(f)->f/2;
ls.forEach((s)->System.out.println(fn.apply(s)));
}
}
//CODE 2 EXAMPLE
List<String> list=Arrays.asList("Hello ","Hi ","Welcome ");
System.out.println(list);
Function<String, String> msg=s->s+"Rinki";
list.forEach(s->System.out.println(msg.apply(s)));
BiFunction
• This interface also has one function apply (), this function takes two
input parameters as T and
• U and returns a value as R after performing some kind of operation
on given input parameters.
• Structure of BiFunction interface:
@FunctionalInterface
public interface BiFunction <T, U, R> {
R apply (T t, U u);
}
• This T, U, and R may have any type of value like Integer, Float, Double,
String, etc.
import java.util.Set;//EXAMPLE 1
import java.util.function.BiFunction;
public class Demo9 {
public static void main(String...strings)
{
BiFunction<String, String, String> bi=
(s1,s2)->s1+":"+s2.toUpperCase();
Set<String> set=Set.of("Lion","Tiger","Jagaur");
set.forEach((s)->System.out.println(bi.apply(s,
s)));
}
}
BiFunction<Integer, Integer, Integer> bi1=(a1,a2)->a1*a2; //EXAMPLE 2
System.out.println(bi1.apply(5, 6));
Java Stream API
• Stream API is another new feature of Java 8.
• All the classes and interfaces of this Stream API is in the
java.util.stream package. By using this package, the programmer can
perform various aggregate operations on the data returned from
collections, arrays, and I/O operations.
• The Stream API provides a functional approach to processing
collections of objects.
• To perform complex data processing operations in a concise and
readable manner.
Key Concepts of Streams
• Streams: Sequences of elements supporting sequential and parallel
aggregate operations.
• Not a Data Structure: Streams do not store elements; they convey
elements from a source through a pipeline of operations.
• Functional Programming: Emphasizes operations such as map-reduce
transformations.
• Simplifies data processing
• Enhances code readability and maintainability
• Supports parallel processing
• Efficient performance with lazy evaluation
Stream API Components
Stream Interface: The main interface for stream operations
Source: Where streams originate (collections, arrays, I/O
channels)
Intermediate Operations: Transform a stream into another
stream (e.g., filter, map)
Terminal Operations: Produce a result or a side-effect (e.g.,
collect, forEach)
Creating Streams
• from Collections: collection.stream()
• From Arrays: Arrays.stream(array)
• From Files: Files.lines(path)
• Using Stream.of(), Stream.iterate(), Stream.generate()
Intermediate Operations
•filter(): Select elements based on a condition
•map(): Transform elements
•flatMap(): Flatten nested structures
•distinct(): Remove duplicates
•sorted(): Sort elements
•peek(): Perform an action on each element
•limit(): Truncate the stream to a specified size
•skip(): Skip the first N elements
Terminal Operations
•forEach(): Perform an action for each element
•collect(): Gather the elements into a collection
•reduce(): Aggregate elements
•toArray(): Convert the stream to an array
•min(), max(): Find the minimum or maximum value
•count(): Count the elements
•anyMatch(), allMatch(), noneMatch(): Test elements
against a predicate
•findFirst(), findAny(): Find elements
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Demo10 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(12, 10, 30, 3,70,-30);
List<Integer> li=list.stream().filter(x->x>10).collect(Collectors.toList());
System.out.println(li);
}
}
List<String> al=Arrays.asList("Bob","Alex","Cloe","Ethen","Astor");
al.stream().map((s)->s+" thomason").forEach(System.out::println);
A map operation applies a function to each element of the input stream to produce another output stream. The number
of elements in the input and output streams is the same. The operation does not modify the elements of the input stream.
Filter : Lesser number Map : Same number Reduce: One element
of elements of elements
import java.util.List;
public class SimpleMapReduceDemo {
public static void main(String[] args) {
List<Integer> li=List.of(1,2,3,4,5,6,7,8,9,10,11,12,13,12,12,13,14,15);
//sum of odd numbers
int sum=li.stream().filter(x->(x%2!=0)?true:false).reduce(0,Integer::sum);
System.out.println(sum);
//sum of all numbers greater than 10 and less than 14
int sum1=li.stream().filter(x->x>10).filter(x->x<14).reduce(0, (x,y)->x+y);
System.out.println(sum1);
}
}
import java.util.*;
class Person{ //REMEMBER THIS CLASS FOR FUTURE PROGRAMS
int age; String name;
public Person(int age, String name) {
super();
Program To sort the list using stream
this.age = age;
this.name = name;
api(order=age of person)
}
@Override
public String toString() {
return "Person [age=" + age + ", name=" + name + "]";
}
}
public class Demo {
public static void main(String[] args) {
List<Person> people = List.of(new Person(26,"Shiva"),new Person(32,"Amit"),new
Person(32,"Amit"),
new Person(23,"Sanjay"),new Person(24,"Vinod"),new Person(32,"Narendra"));
List<Person> list=people.stream()
.sorted((a,b)->(a.age-b.age))
.collect(Collectors.toList());
System.out.println(list);
}
}
Find the sum of all the Person having their names starting
With “S”
Output:49
import java.util.Comparator;
import java.util.List;
public class Demo11 {
public static void main(String[] args) {
List<Person> people = List.of(new Person(26,"Shiva"),new Person(32,"Amit"),new
Person(32,"Amit"),
new Person(23,"Sanjay"),new Person(24,"Vinod"),new Person(32,"Narendra"));
int age=people.stream().filter(p->(p.name).startsWith("S")).map(p-
>p.age).reduce(0,Integer::sum);
System.out.println(age);
}}
Method referencing
import java.util.*;
import java.util.stream.Collectors; Program to group the Persons according to age
public class Demo11 {
public static void main(String[] args) {
List<Person> people = List.of(new Person(26,"Shiva"),new Person(32,"Amit"),new
Person(32,"Amit"),new Person(23,"Sanjay"),new Person(24,"Vinod"),new
Person(32,"Narendra"));
Map<Integer, List<Person>> peopleByAge = people.stream()
.filter(p -> p.age > 20)//INTERMEDIATE
.sorted(Comparator.comparing(p->p.age)) //INTERMEDIATE
.collect(Collectors.groupingBy(p->p.age));//TERMINAL
peopleByAge.forEach((k,v)->System.out.println(k+" "+v));
}}
Base64 Encode and Decode
• Java SE provides built-in support for Base64 encoding and decoding
• java.util.Base64 class in Java 8 and later versions.
Real-world applications of Base64 encoding in Java:
• Integrating with web services.
• Storing binary data in databases.
• Handling attachments in email clients.
Base64.Decoder This class implements a decoder for decoding byte data using the Base64 encoding scheme as
specified in 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.
import java.util.Base64;
public class Base64Ex {
public static void main(String[] args) {
// Java 8 and later
//Encoding
String originalInput = "Hello";
String encodedString = Base64.getEncoder().encodeToString(originalInput.getBytes());
System.out.println("Encoded string: " + encodedString);
//Decoding
String encodedStringnow = "SGVsbG8=";
byte[] decodedBytes = Base64.getDecoder().decode(encodedStringnow);
String decodedString = new String(decodedBytes);
System.out.println("Decoded string: " + decodedString);
}
}
forEach Method in Java
• in Java 8 forEach was introduced as a method for iterating over
collections.
• It simplifies iteration and enhances code readability.
• Iterates over each element in the collection.
• Executes the specified action (lambda expression or method
reference) for each element.
•Advantages: •Limitations
•Cleaner and more expressive code. •Performance implications with
•Encourages functional programming heavy computations.
style. •Limited control over iteration (e.g.,
•Suitable for use with lambdas and cannot break out of the loop).
method references.
public class ForEachDemo {
public static void main(String[] args) {
Consumer<Integer> consumer=(x)->System.out.print(x+" ");
List<Integer> list=List.of(3,5,1,6,7,8,7);
list.forEach(consumer);//with java.util.Consumer
list.forEach(System.out::print);//method referencing
}
}
Try-with-resources
• Simplifies resource management by automatically closing resources.
• Resources declared in the try block are automatically closed at the
end of the block.(For example, a File resource or a Socket connection
resource.)
• No explicit finally block needed for resource cleanup.
• any object as a resource that implements java.lang.AutoCloseable,
which includes all objects which implement java.io.Closeable can be
passed to try
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class Demo {
public static void main(String[] args) {
try(FileWriter fw=new FileWriter("temp.txt"); BufferedWriter bw=new
BufferedWriter(fw))
{
bw.write("try with resource demo");
System.out.println("written successfully");
}
catch(IOException io)
{
io.printStackTrace();
}
}
}
Pass single or multiple resources to try
Type Annotations
• Type Annotations were introduced in Java 8.
• Type Annotations purpose is to extend the Java type system to
provide additional type information.
• Improved readability, Reduced errors, Improved IDE support
• Examples of Type Annotations are: (Inbuilt)
@Repeatable, @FunctionalInterface
@SuppressWarnings: Suppress warnings at specific code locations.
@Deprecated: Marks that a method or class is deprecated.
Type annotations can be used to annotate a variety of different
elements in a Java program, including:
Classes: Type annotations can be used to annotate the fields and
methods of a class.
Methods: Type annotations can be used to annotate the parameters
and return type of a method.
Variables: Type annotations can be used to annotate the type of a
variable.
import java.util.ArrayList;
import java.util.List;
@Deprecated
class OldClass {// Example 1 #class type annotation
// Class implementation
//Mark classes, methods, or fields as deprecated.
//Encourage developers to migrate to alternative solutions.
}
public class TypeAnnoDemo {
public static void main(String[] args) {
@SuppressWarnings("unchecked") //Example 2 #variable annotation
List<String> list = new ArrayList<String>();
//Suppress specific compiler warnings at a local scope.
}
}
CREATE CUSTOM TYPE ANNOTATIONS IN JAVA
• Use the @interface keyword followed by the annotation name.
• Define the elements or attributes of the annotation.
• Use the annotation on the program elements, such as constructors,
methods, variables, and more.
//Example 1 //Example 2
@interface MyMessage//User defined @interface MyMessage//User defined
Annotation Annotation
{ {
String msg(); String msg();
} }
@MyMessage(msg = "Hello There") class Speaker
class Speaker {
{ void speak(@MyMessage(msg = "hello")
void speak() String s)
{ {
System.out.println("Welcome"); System.out.println(s);
} }
} }
public class TypeAnnoSimpleExample { public class TypeAnnoSimpleExample {
public static void main(String[] public static void main(String[] args)
args) { {
Speaker s=new Speaker(); Speaker s=new Speaker();
s.speak(); s.speak("Welcome");
} }
} }
Repeating Annotations
• Repeating annotations in Java allow you to apply the same annotation
to a declaration or type use more than once.
• @Repeatable meta-annotation indicates that the marked annotation
can be applied more than once to the same declaration or type use.
@Repeatable(Roles.class)//Example 1 import java.lang.annotation.Repeatable;
@interface Role { import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Retention;
String value();
@Repeatable(Colors.class)
} @interface Color {
@interface Roles { String name();
Role[] value(); }
@Retention(RetentionPolicy.RUNTIME)
} @interface Colors {
@Role("admin") Color [] value();
@Role("manager") }
@Color(name="Red")//repeat @Color
class AccountResource
@Color(name="Green")
{ public class TypeAnnotationEx {//Example 2
} public static void main(String[] args) {
}
}
Java Module System
• The Java Module System is a feature introduced in Java 9 that allows
developers to create modular applications.
• The Java Module System provide better modularity, encapsulation,
and dependency management for Java applications.
• A Java module is a set of related Java packages that can be used by
other Java modules.
• Modules can be used to encapsulate code, data, and resources, and
to control how they are accessed by other modules.
• Improved encapsulation
• Reduced dependencies
• Increased flexibility
Java Module System
• 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
• Module name
• What does it export
• What does it require
package abc;//CALLING MODULE
public class Call {
public void call() {
System.out.println("i am called");
}
}
package cdf;//CALLER MODULE
import abc.Call;
public class Caller {
public static void main(String[] args) {
new Call().call();
}
}
Diamond Syntax with Inner Anonymous Class
• Introduced in Java 7.
• It simplifies the instantiation of generic classes by omitting redundant
type declarations.
List<String> list = new ArrayList<>();
Benefits:
• Improves code readability by reducing verbosity.
• Reduces potential errors in type declarations.
• Enhances maintainability by focusing on intent rather than implementation
details.
Inner Anonymous Class
• A nested class without a named type.
• It is instantiated inline where it is defined. For example
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Executing runnable");
}
};
Diamond Syntax with Inner Anonymous Class
Example
List<String> names = new ArrayList<>() {
{
add("Alice");
add("Bob");
add("Charlie");
}
};
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.
• var is not a keyword
public class VarEx {
//var num=10; not allow
public static void main(String[] args) {
// Local Variable type inference
var num=10;
System.out.println(num);
var name=new String("KIET");
System.out.println(name);
var var=5.0f;// var is not a keyword
System.out.println(var);
}
}
Switch expressions
Switch expressions are an extension of switch statements that allow
developers to return values.
Break and yield statements
Switch statements can be targeted by break statements, while switch
expressions can be targeted by yield statements.
Switch expression cases must be exhaustive(must have default),
meaning there must be a matching switch label for all possible values.
Switch expression Switch Statement
int value = 3; int n=5;
String result = switch (value) { switch (n) {//EXAMPLE 3
case 1, 2, 3, 4:
yield "i will not tell you"; case 1:
case 5, 6, 7: System.out.println("do");
yield "5-7"; //EXAMPLE 1 break;
case 10: case 2:
yield "10"; System.out.println("don't");
default://required break;
yield "Not Found"; }
};
System.out.println(result); //DEFAULT-CASE not necessary
//does not return anything
int value=25; //EXAMPLE 2
String result=switch(value) {
case 1,2,3,4-> "1-4";
case 5,6,7-> "5-7";
Yield keyword
case 10-> "10";
default->"Not Found"; //required
};
System.out.println(result);
Text Block
• To get pre-formatted public class TextBlockEx {
public static void main(String[] args) {
text in // Text Block Example....
output.(similar to String data="""
This is my original Data
<pre> tag inhtml) My data is good
• Use triple quotes Another line""";
System.out.println(data);
""“text""“ }
}
Sealed Classes
• Sealed classes were introduced in Java 15.
• They are a new feature that allows developers to control which classes can
extend a particular class.
• Enforcing a particular type hierarchy
abstract sealed class Shape permits Circle, Rectangle{
abstract void getArea();
}
final class Rectangle extends Shape
{
void getArea() {
System.out.println("Rectangle");
}
}
non-sealed class Circle extends Shape
{
void getArea() {
System.out.println("Circle");
}
}
public class SealedEx {
public static void main(String[] args) {
Circle c = new Circle();
c.getArea();
Rectangle r=new Rectangle();
r.getArea();
}
}
When you declare a sealed class, you restrict which classes can extend it
using the permits clause.
Subclasses of a sealed class can be declared as:
final (cannot be subclassed further),
sealed (restrict further subclassing),
or non-sealed (allow unrestricted subclassing, effectively "breaking" the
sealing).
So, a non-sealed class is a subclass of a sealed class that opens up
inheritance again, allowing any other classes to extend it freely.
Java Records
• Records are a new feature in Java 16 that provide a concise and
convenient way to create immutable data classes.
• Records require much less code to define than traditional classes.
Public final class Person { Is equivalent to
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age; record Person(String name, int age) {}
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
• Records are immutable by default, which means that their state
cannot be changed once they are created.
• Records are compiled into smaller, more efficient code than
traditional classes.
• Records are final classes, meaning that they cannot be extended.
• Records can implement interfaces.
• Records can have static methods.
• Records can have instance methods, but they cannot modify the state
of the record.
• Records can be generic.
Person p=new Person("Shyam", 21);
// you can not modify the attributes now
//p.age=31; NOT POSSIBLE FOR RECORDS