0% found this document useful (0 votes)
21 views24 pages

Resolving LinkedList Method Errors in Java

Uploaded by

rickyvanith7
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views24 pages

Resolving LinkedList Method Errors in Java

Uploaded by

rickyvanith7
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

UNIT-II:

Multithreading and Reactive Programming in JAVA

What Is Concurrency?
Concurrency is used by all the top Java development companies, and refers to the ability of a
program to execute multiple tasks simultaneously. It enables the efficient utilization of system
resources and can improve the overall performance and responsiveness of the application.
Java concurrency’s concepts, classes, and the interfaces used for multithreading, such as `Thread`,
`Runnable`, `Callable`, `Future`, `ExecutorService`, and the classes in `[Link]`, are
part of the standard Java libraries so there shouldn’t be too much difference across the
various Java frameworks. But before we get into the nitty gritty, first a very basic question.

Multiple Threads in Java?


Multithreading refers to a programming technique where multiple threads of execution exist within a
single application.
Multithreading is just one way to achieve concurrency in Java. Concurrency can also be achieved
through other means, such as multiprocessing, asynchronous programming, or event-driven
programming.
But just for the uninitiated, a ‘thread’ is a single stream of processes that can be executed
independently by a computer’s processor.

Why Should You Use Java Concurrency?


Concurrency is a great solution for building high-performance modern applications for a variety of
reasons.
Improved Performance
Concurrency allows for the division of complex and time-consuming tasks into smaller parts that can
be executed simultaneously, resulting in improved performance. This makes the most of today’s
multi-core CPUs and can make applications run much faster.
Better Resource Utilization
Concurrency enables optimal utilization of system resources, resulting in improved resource
efficiency. By implementing asynchronous I/O operations, the system can avoid blocking a single
thread and allow other tasks to run concurrently, thus maximizing resource utilization and system
efficiency.
Improved Responsiveness
Improved Responsiveness: Concurrency can enhance the user experience in interactive applications
by guaranteeing that the application remains responsive. During the execution of a computationally
intensive task by one thread, another thread can concurrently handle user inputs or UI updates.
Simplified Modeling
In certain scenarios such as simulations or game engines, concurrent entities are inherent to the
problem domain, and thus a concurrent programming approach is both more intuitive and more
performant. This is commonly referred to as simplified modeling.
Robust Concurrency API
Java offers a comprehensive and adaptable concurrency API that includes thread pools, concurrent
collections, and atomic variables to ensure robustness. These concurrency tools streamline the
development of concurrent code and mitigate prevalent concurrency problems.
Concurrency Drawbacks
It is important to understand that concurrent programming isn’t for the beginner. It brings an
increased level of intricacy to your applications and entails a distinct set of difficulties, such as
managing synchronization, preventing deadlocks, and guaranteeing thread safety and that’s not all.
Here are a few things to consider before diving in.
Complexity: Writing concurrent programs can be more difficult and time-consuming than writing
single-threaded programs. It is imperative for developers to have a grasp of synchronization,
memory visibility, atomic operations, and thread communication.
Debugging Difficulties: The non-deterministic nature of concurrent programs can pose a
challenge when debugging. The occurrence of race conditions or deadlocks can be inconsistent,
which poses a challenge in reproducing and resolving them.
Error Potential: Improper handling of concurrency can lead to errors like race conditions,
deadlocks, and thread interference. This issue may pose difficulties in identification and resolution.
Resource Contention: Poorly designed concurrent applications can cause resource contention, in
which many threads fight for the same resource, resulting in performance loss.
Overhead: Creating and maintaining threads increases the CPU and memory use on your machine.
Insufficient management may result in suboptimal performance or resource depletion.
Complicated to Test: Because thread execution is unpredictable and non-deterministic, testing
concurrent programs can be challenging.
So whilst concurrency is a great option, it’s not all plain sailing.

Java Concurrency Tutorial: Thread Creation


Threads can be created in three ways. Here, we will create the same thread using different
methods.
By Inheriting From Thread Class
One way to create a thread is by inheriting it from the thread class. Then, all you have to do is
override the thread object’s run() method. The run() method will be invoked when the thread is
started.
public class ExampleThread extends Thread {
@Override
public void run() {
// contains all the code you want to execute
// when the thread starts

// prints out the name of the thread


// which is running the process
[Link]([Link]().getName());
}
}
To start a new thread, we create an instance of the above class and call the start() method on it.
public class ThreadExamples {
public static void main(String[] args) {
ExampleThread thread = new ExampleThread();
[Link]();
}
}
One common mistake is to call the run() method to start the thread. It might seem correct as
everything works just fine, but calling the run() method does not start a new thread. Instead, it
executes the code of the thread inside the parent thread. We use the start() method to execute a
new thread.
You can test this by calling “[Link]()” instead of “[Link]()” in the above code. You’ll see
that “main” is printed in the console, which means we are not creating any threads. Instead, the
task is executed in the main thread. For more on thread class, be sure to check the docs.
By Implementing Runnable Interface
Another way of creating a thread is by implementing the Runnable interface. Similar to the previous
method, you need to override the run() method, which will contain all the tasks you want the
runnable thread to execute.
public class ExampleRunnable implements Runnable {
@Override
public void run() {
[Link]([Link]().getName());
}
}

public class ThreadExamples {


public static void main(String[] args) {
ExampleRunnable runnable = new ExampleRunnable();
Thread thread = new Thread(runnable);
[Link]();
}
}
Both the methods work exactly the same with no difference in performance. However, the Runnable
interface leaves the option of extending the class with some other class since you can inherit only
one class in Java. It’s also easier to create a thread pool using runnables.
By Using Anonymous Declarations
This method is very similar to the above method. But instead of creating a new class that
implements the runnable method, you create an anonymous function that contains the task you
want to execute.
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
// task you want to execute
[Link]([Link]().getName());
});
[Link]();
}
}
Thread Methods
If we call the [Link]() method inside threadTwo, it will put threadTwo into a state of waiting
until the threadOne has finished execution.
Calling [Link](long timeInMilliSeconds) static method will put the current thread into a state
of timed waiting.
Thread Lifecycle
A thread can be in one of the following states. Use [Link]() to get the current state of the
thread.
1. NEW: created but has not started execution
2. RUNNABLE: started execution
3. BLOCKED: waiting to acquire a lock
4. WAITING: waiting for some other thread to perform a task
5. TIMED_WAITING: waiting for a specified time period
6. TERMINATED: completed execution or aborted
Executors and Thread Pools
Threads require some resources to start, and they are stopped after the task is done. For
applications with many tasks, you would want to queue up tasks instead of creating more threads.
Wouldn’t it be great if we could somehow reuse existing threads while also limiting the number of
threads you can create?
The ExecutorService class allows us to create a certain number of threads and distribute tasks
among the threads. Since you are creating a fixed number of threads, you have a lot of control over
the performance of your application.
import [Link];
import [Link];

public class Main {


public static void main(String[] args) {
ExecutorService executor = [Link](2);

for (int i = 0; i < 20; i++) {


int finalI = i;
[Link](() -> [Link]([Link]().getName() + " is
executing task " + finalI));
}
[Link]();
}
}
Race Conditions
A race condition is a condition of a program where its behavior depends on the relative timing or
interleaving of multiple threads or processes. To better understand this, let’s look at the example
below.
public class Increment {
private int count = 0;

public void increment() {


count += 1;
}

public int getCount() {


return [Link];
}
}

public class RaceConditionsExample {


public static void main(String[] args) {
Increment eg = new Increment();
for (int i = 0; i < 1000; i++) {
Thread thread = new Thread(eg::increment);
[Link]();
}
[Link]([Link]());
}
}

Here, we have an Increment class which stores a variable count and a function that increments the
count. In the RaceConditionsExample, we’re starting a thousand threads, each of which will invoke
the increment() method. Finally, we’re waiting for all the threads to finish executing and then print
out the value of the count variable.
If you run the code multiple times, you’ll notice that sometimes the final value of count is less than
1,000. To understand why this happens, let’s take two threads, Thread-x and Thread-y, as
examples. The threads can execute the read write operation in any order. So, there will be a case
when the order of execution is as follows.
Thread-x: Reads [Link] (which is 0)
Thread-y: Reads [Link] (which is 0)
Thread-x: Increments [Link] by 1
Thread-y: Increments [Link] by 1
Thread-x: Updates [Link] (which becomes 1)
Thread-y: Updates [Link] (which becomes 1)
In this case, the final value of the count variable is 1 and not 2. This is because both the threads are
reading the count variable before any of them can update the value. This is known as a race
condition. More specifically, a “read-modify-write” race condition.

Synchronization Strategies
In the previous section, we examined what race conditions are. In order to avoid race conditions,
we need to synchronize tasks. In this section, we’ll look at different ways to synchronize different
processes across multiple threads.
Lock
There will be cases when you’d want a task to be executed by a single thread at a time. But how
would you make sure a task is being executed by only one thread?
One way to do so is by using locks. The idea is that you create a lock object that can be “acquired”
by a single thread at a time. Before performing a task, the thread tries to acquire the lock. If it’s
successful in doing so, it proceeds with the task. Once it’s done performing the task, it releases the
lock. If the thread fails to acquire the lock, it means the task is being executed by another thread.
Here’s an example using the ReentrantLock class, which is an implementation of the lock interface.
import [Link];

public class LockExample {


private final ReentrantLock lock = new ReentrantLock();
private int count = 0;

public int increment() {


[Link]();
try {
return [Link]++;
} finally {
[Link]();
}
}
}
When we call the lock() method in a thread, it tries to acquire the lock. If it’s successful, it executes
the task. However, if it’s unsuccessful, the thread is blocked until the lock is released.
The isLocked() returns a boolean value depending on whether lock can be acquired or not.
The tryLock() method tries to acquire the lock in a nonblocking way. It returns true if it’s successful
and false otherwise.
The unlock() method releases the lock.

ReadWriteLock
When working with shared data and resources, usually, you’d want two things:
1. Multiple threads should be able to read the resource at a time if it’s not being written.
2. Only one thread can write the shared resource at a time if no other thread is reading or
writing it.
ReadWriteLock Interface achieves this by using two locks instead of one. The read lock can be
acquired by multiple threads at a time if no thread has acquired the write lock. The write lock can be
acquired only if both read and write lock have not been acquired.
Here’s an example to demonstrate. Suppose we have a SharedCache class that simply stores key-
value pairs as shown below.
public class SharedCache {
private Map<String, String> cache = new HashMap<>();

public String readData(String key) {


return [Link](key);
}

public void writeData(String key, String value) {


[Link](key, value);
}
}
We want multiple threads to read our cache at the same time (while it’s not being written). But only
one thread can write our cache at a time. To achieve this, we will use the ReentrantReadWriteLock
which is an implementation of the ReadWriteLock interface.
public class SharedCache {
private Map<String, String> cache = new HashMap<>();
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public String readData(String key) {
[Link]().lock();
try {
return [Link](key);
} finally {
[Link]().unlock();
}
}

public void writeData(String key, String value) {


[Link]().lock();
try {
[Link](key, value);
} finally {
[Link]().unlock();
}
}
}
Synchronized Blocks And Methods
Synchronized blocks are pieces of Java code that can be executed by only one thread at a time.
They are a simple way to implement synchronization across threads.
// SYNTAX
synchronized (Object reference_object) {
// code you want to be synchronized
}
When you create a synchronized block, you need to pass a reference object. In the above example
”this” or the current object is the reference object, which means if multiple instances of the are
created, they won’t be synchronized.
You can also make a method synchronized by using the synchronized keyword.
public synchronized int increment();

Deadlocks

Deadlock occurs when two or more threads are unable to proceed because each of them is waiting
for the other to release a resource or take a specific action. As a result, they remain stuck
indefinitely, unable to make progress.
Consider this, you have two threads and two locks (let’s call them threadA, threadB, lockA and
lockB). ThreadA will try to acquire lockA first and if it’s successful, it will try to acquire lockB.
ThreadB, on the other hand, tries to acquire lockB first and then lockA.
import [Link];

public class Main {


public static void main(String[] args) {
ReentrantLock lockA = new ReentrantLock();
ReentrantLock lockB = new ReentrantLock();

Thread threadA = new Thread(() -> {


[Link]();
try {
[Link]("Thread-A has acquired Lock-A");
[Link]();
try {
[Link]("Thread-A has acquired Lock-B");
} finally {
[Link]();
}
} finally {
[Link]();
}
});

Thread threadB = new Thread(() -> {


[Link]();
try {
[Link]("Thread-B has acquired Lock-B");
[Link]();
try {
[Link]("Thread-B has acquired Lock-A");
} finally {
[Link]();
}
} finally {
[Link]();
}
});

[Link]();
[Link]();
}
}
Here, ThreadA acquires lockA and is waiting to lockB. ThreadB has acquired lockB and is waiting to
acquire lockA. Here, threadA will never acquire lockB as it is held by threadB. Similarly, threadB can
never acquire lockA as it is held by threadA. This kind of situation is called a deadlock.
Here are some points to keep in mind to avoid deadlocks.
1. Define a strict order in which resources should be acquired. All threads must follow the same
order when requesting resources.
2. Avoid nesting of locks or synchronized blocks. The cause for the deadlock in the previous
example was that the threads were not able to release one lock without acquiring the other
lock.
3. Ensure that threads do not acquire multiple resources simultaneously. If a thread holds one
resource and needs another, it should release the first resource before attempting to acquire
the second. This prevents circular dependencies and reduces the likelihood of deadlocks.
4. Set timeouts when acquiring locks or resources. If a thread fails to acquire a lock within a
specified time, it releases all acquired locks and tries again later. This prevents a situation
where a thread holds a lock indefinitely, potentially causing a deadlock.

Concurrent Collections in Java (Concurrent Data Structures)

Concurrent collection classes are used to ensure safe and concurrent access to data by
multiple threads (which is not the case in normal collection classes like list, set, map etc).
The concurrent collection classes :
1. CopyOnWriteArrayList
2. CopyOnWriteArraySet
3. ConcurrentHashmap
Concurrent collection classes are meant for synchronized access to threads. Although, we already
have some classes like Vector and Hashtable, which are synchronized or thread-safe, but they have
some cons associated with them, and they are not the best solutions. Therefore, a new types of
classes have been introduced as part of concurrent collections in java which serve this purpose.
These classes are part of [Link] package. Let’s first understand the need of concurrent
collection classes.
Need of concurrent collections
1. Most of the classes in collections framework like Arraylist, LinkedList, HashSet, HashMap,
LinkedHashMap etc are not thread safe. If we use these collections in a multithreaded
environment, then we will not get the right results. This raises the requirement to have
thread safe classes.
2. Although, we can make make a list, or set synchronized, for that matter, using methods like
[Link](list), but it is not an efficient way.
3. If we use [Link]() method, then the entire list gets locked by one
thread. Even if all threads are readers threads, then also, the they can access the list in a
synchronized way. This increases the response time significantly.
Concurrent collection classes in java
1. CopyOnWriteArrayList
2. CopyOnWriteArraySet
3. ConcurrentHashmap
CopyOnWriteArrayList
CopyOnWriteArrayList is part of collections framework with some differences with ArrayList.
CopyOnWriteArrayList is thread safe and can be used in multithreaded environment. Let’s have a
look at the features of CopyOnWriteArrayList
1. The CopyOnWriteArrayList is a thread safe version of ArrayList. If we are making
modifications like adding, removing elements in CopyOnWriteArrayList, then JVM does so by
creating a new copy of it by the use of Cloning.
2. CopyOnWriteArrayList is costly if used in case of more update operations. Because when
changes are made, JVM has to create a cloned copy of the underlying array and add/update
elements to it.
3. Multiple threads can read the data from CopyOnWriteArrayList, but only one thread can write
data at a particular time.
4. We can add duplicate elements in it.
5. CopyOnWriteArrayList is the best choice in multithreading, if there are more read operations.

Class hierarchy

CopyOnWriteArrayListExample

import [Link];
import [Link];
import [Link];
import [Link];

public class CopyOnWriteArrayListExample {

public static void main(String[] args) {


List<String> list = [Link]("CV Raman", "Homi Bhabha", "Ramanujan");
CopyOnWriteArrayList<String> cowArrayList = new
CopyOnWriteArrayList<String>(list);

[Link]("List = " + cowArrayList);

Iterator<String> iterator1 = [Link]();

// adding another element


[Link]("Vikram Sarabhai");

while([Link]()) {
[Link]("Element from iterator1 : " + [Link]());
}

Iterator<String> iterator2 = [Link]();

while([Link]()) {
[Link]("Element from iterator2 : " + [Link]());
}
}
}

Output :

List = [CV Raman, Homi Bhabha, Ramanujan]


Element from iterator1 : CV Raman
Element from iterator1 : Homi Bhabha
Element from iterator1 : Ramanujan
Element from iterator2 : CV Raman
Element from iterator2 : Homi Bhabha
Element from iterator2 : Ramanujan
Element from iterator2 : Vikram Sarabhai

CopyOnWriteArraySet

CopyOnWriteArraySet is like CopyOnWriteArrayList.


It is thread safe version of HashSet.
CopyOnWriteArrayListExample

import [Link];
import [Link];
import [Link];
import [Link];

public class CopyOnWriteArrayListExample {

public static void main(String[] args) {


List<String> list = [Link]("CV Raman", "Homi Bhabha", "Ramanujan",
"Ramanujan", "Homi Bhabha");
CopyOnWriteArraySet<String> cowArraySet = new
CopyOnWriteArraySet<String>(list);

[Link]("Set = " + cowArraySet);

Iterator<String> iterator1 = [Link]();

// adding another element


[Link]("Vikram Sarabhai");

while([Link]()) {
[Link]("Element from iterator1 : " + [Link]());
}

Iterator<String> iterator2 = [Link]();

while([Link]()) {
[Link]("Element from iterator2 : " + [Link]());
}
}
}

Output :

Set = [CV Raman, Homi Bhabha, Ramanujan]


Element from iterator1 : CV Raman
Element from iterator1 : Homi Bhabha
Element from iterator1 : Ramanujan
Element from iterator2 : CV Raman
Element from iterator2 : Homi Bhabha
Element from iterator2 : Ramanujan
Element from iterator2 : Vikram Sarabhai

ConcurrentHashMap

CosurrentHashMap, as its name suggests, provides concurrent access to multiple threads.


It is thread safe version of HashMap. We know, HashMap is not synchronized and therefore is
not thread safe. To overcome this issue, collections framework provides ConcurrentHashMap.
ConcurrentHashMap provides similar functionalities like HashMap. The only difference is that
it internally maintains concurrency.
It is concurrent version of HashMap. It internally maintains a HashTable, which is divided into
segments. The number of segments depend on the level of concurrency specified in the
ConcurrentHashMap. By default, it divides it into 16 segments.
It doesn’t lock the entire map unlike HashTable or Synchronized HashMap.
It only locks the particular segment of HashMap where write operation is going on.

ConcurrentHashMapExample

import [Link];

public class ConcurrentHashMapExample {

public static void main(String[] args) {

ConcurrentHashMap<Integer, String> cMap = new ConcurrentHashMap<>();


[Link](1, "Taj Mahal");
[Link](2, "Qutab Minar");
[Link](3, "Char Minar");

for(Integer key : [Link]()) {


[Link](key + " : " + [Link](key));
}
}
}

Output :
1 : Taj Mahal
2 : Gateway of India
3 : Char Minar

What is Reactive Programming

• Reactive programming is a programming paradigm where the focus is on developing


asynchronous and non-blocking applications in an event-driven form.
• It is a declarative programming model that enables the processing of data streams in a more
efficient, scalable, and resilient way.
Core Concepts of Reactive Programming :

Data Streams: In Reactive Programming, data is treated as a stream of events. These events can
be anything from user inputs to data fetched from a network. Streams are continuous and can be
transformed, filtered, and combined in various ways.
Asynchronous Processing: Reactive systems handle data asynchronously, meaning operations
don’t block the main thread. This is particularly useful for I/O-bound tasks and can significantly
improve application responsiveness and performance.
Functional Composition: Reactive Programming relies heavily on functional programming
principles. Streams are transformed using functions like map, filter, flatMap, etc. This allows for a
declarative approach to data handling, making code more concise and expressive.
Backpressure Handling: Reactive Programming frameworks often include mechanisms for
managing backpressure, which is crucial when a consumer can't keep up with the rate of incoming
data. This ensures that systems remain stable and responsive even under heavy load.
Error Handling: Reactive frameworks provide robust error handling strategies.
Errors in one part of a stream can be caught and managed without disrupting the entire stream.

Use Cases:

• Web and Mobile Applications: For handling real-time data and user interactions.
• IoT Applications: Managing data streams from numerous sensors and devices.
• Real-Time Data Processing: Systems that need to process and analyze data streams in real time,
such as financial trading systems or monitoring applications.

Popular Frameworks in Java:

• Reactor: Developed by Pivotal, Reactor is a reactive library for building non-blocking applications
on the JVM. It provides a rich set of operators for composing and processing data streams.
• RxJava: A popular reactive library that provides a wide range of operators for composing
asynchronous and event-based programs. RxJava is widely used for building reactive applications in
Java and Android.
• Akka: Akka is a toolkit for building highly concurrent, distributed, and resilient message-driven
applications on the JVM. It provides support for reactive programming through its actors model and
is well-suited for complex systems requiring high scalability.

Reactive Streams Specification in Java

• Reactive Streams Specification is a set of rules or set of guidelines that you need to follow when
designing a reactive stream.
• These specifications introduce four interfaces that should be used and overridden when creating a
reactive stream.
// Reactor Interfaces
• Publisher : The Publisher is a data source that will always publish events.
• Subscriber: The Subscriber will subscribe/consume the events from the Publisher.
• Subscription: The Subscription represents the unique relationship between a Subscriber and
a Publisher interface.
• Processor: It represents a processing stage – which is both a Subscriber and a Publisher and MUST
obey the contracts of both.

Publisher
Subscriber:

Subscription

Processor
Reactive Programming Libraries

• A reactive library is nothing but the implementation of reactive specification interfaces. Here are
some reactive libraries that are available to us:
• Project Reactor
• RxJava
• JDK 9 Flow Reactive Stream

The Project Reactor

• The Project Reactor is a fourth-generation reactive library, based on the Reactive Streams
specification, for building non-blocking applications on the JVM.
• Mono and Flux Implementations
• Project reactor libraries provide two implementations of the Publisher interface:
1. Mono
2. Flux
• Mono: Returns 0 or 1 element.
The Mono API allows producing only one value.

Flux: Returns 0…N elements.
The Flux can be endless, it can produce multiple values.

Mono vs Flux:

• Mono and Flux are both implementations of the Publisher interface.


• In simple terms, we can say that when we're doing something like a computation or making a
request to a database or an external service, and expecting a maximum of one result, then we
should use Mono.

• When we're expecting multiple results from our computation, database, or external service call,
then we should use Flux

Reactive Stream Workflow


1. The Subscriber will call subscribe() method of the Publisher to subscribe or register with the
Publisher.

2. The Publisher creates an instance of Subscription and sends it to Subscriber saying that your
subscription is successful.
3. Next, the Subscriber will call the request(n) method of Subscription to request data from the
Publisher.

4. Next, Publisher call onNext(data) method to send data to the Subscriber. Publisher call
onNext(data) n times. It means if there are 10 items then the Publisher call onNext(data)
method 10 times.

5. Once the Publisher sends all the data to Subscriber, the next Publisher call onComplete()
method to notify Subscriber that all the data has been sent. If there are any errors while
sending the data then the Publisher call onError() method to send error details to the Subscriber.

Creating a simple reactive application in Java:

Step-by-Step Guidezz
1. Set Up Your Eclipse Project
1. Open Eclipse IDE.
2. Create a new Java project:
• Go to File > New > Java Project.
• Enter the project name (e.g., ReactiveExample).
• Click Finish.
3. Add Project Reactor dependency:
• Right-click on your project in the Package Explorer.
• Select Build Path > Configure Build Path.
• Go to the Libraries tab and click Add Library.
• Select Maven and click Next.
• If Maven is not already set up, you may need to add the Maven Integration for Eclipse
plugin via Help > Eclipse Marketplace.
• Click Finish.
4. Add a [Link] file:
• Right-click on your project.
• Select New > File.
• Name the file [Link] and click Finish.
Update Maven dependencies:
Right-click on the project.
Select Maven > Update Project.

[Link]:

<project xmlns="[Link]
xmlns:xsi="[Link]
xsi:schemaLocation="[Link]
[Link]
<modelVersion>4.0.0</modelVersion>
<groupId>[Link]</groupId>
<artifactId>reactiveexample</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter-reactor-netty</artifactId>
<version>3.3.0</version> <!-- Check for the latest version -->
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.3.0</version> <!-- Check for the latest version -->
</dependency>
</dependencies>

Open [Link] and add the following code:

Mono example :

package [Link];
import [Link];
public class ReactiveApp {
public static void main(String[] args) {
// Create a Mono that emits a single item
Mono<String> mono = [Link]("Hello, Reactive World!");
// Subscribe to the Mono and print the item
[Link]([Link]::println);
}
}

Run the application

• Right-click on [Link].
• Select Run As > Java Application.

Flux EXample

package [Link];
import [Link];
public class ReactiveApp {
public static void main(String[] args) {
// Create a Flux that emits a sequence of integers
Flux<Integer> flux = [Link](1, 5);
// Subscribe to the Flux and print each item
flux
.doOnNext(item -> [Link]("Received: " + item)) // Action to perform for each
item
.doOnComplete(() -> [Link]("Sequence complete")) // Action to perform when
complete
.subscribe();
// To ensure the program does not exit immediately
// This is only for demonstration purposes; in a real application, other mechanisms may be
used
try {
[Link](1000); // Wait a bit to ensure all items are processed
} catch (InterruptedException e) {
[Link]();
}
}
}
Output:
• Received: 1
• Received: 2
• Received: 3
• Received: 4
• Received: 5
• Sequence complete

Functional programming in Java is a programming paradigm that treats computation as the


evaluation of mathematical functions and avoids changing state and mutable data. This style of
programming emphasizes immutability, first-class functions, and declarative code. Java,
traditionally an object-oriented language, has incorporated functional programming features
starting with Java 8. Here’s an overview of functional programming concepts and features in
Java:

Core Concepts of Functional Programming:

1. First-Class Functions: Functions can be passed as arguments, returned from other functions,
and assigned to variables.
2. Immutability: Data structures are immutable, meaning once created, they cannot be changed.
3. Pure Functions: Functions that do not have side effects and return the same result given the
same input.
4. Declarative vs. Imperative: Declarative code focuses on what to do rather than how to do it,
contrasting with imperative code that specifies step-by-step instructions.

Functional Programming Features in Java

1. Lambda Expressions
Lambda expressions provide a concise way to represent an anonymous function (a function
without a name). They are used primarily to define the behavior of functional interfaces.

Syntax:

(parameters) -> expression

Example:

// Traditional way of defining a Runnable


Runnable runnable = new Runnable() {
@Override
public void run() {
[Link]("Hello from Runnable");
}
};

// Using lambda expression


Runnable lambdaRunnable = () -> [Link]("Hello from Lambda Runnable");

2. Functional Interfaces:

A functional interface is an interface with exactly one abstract method.


Functional interfaces can have multiple default or static methods,
but only one abstract method.
Common Examples:
 [Link]<T, R>: Takes an argument of type T and returns a result
of type R.
 [Link]<T>: Takes an argument of type T and performs an operation but
does not return a result.
 [Link]<T>: Provides a result of type T with no input.
 [Link]<T>: Takes an argument of type T and returns a boolean value.

@FunctionalInterface
interface MyFunctionalInterface {
void myMethod();
}

// Using the functional interface


MyFunctionalInterface myLambda = () -> [Link]("Hello from MyFunctionalInterface");

3. Streams API

The Streams API allows for functional-style operations on sequences of elements. It provides a high-
level abstraction for working with collections in a more declarative manner.
Basic Operations:
 Creation: [Link](), [Link](), [Link]()
 Intermediate Operations: filter(), map(), flatMap(), distinct(), sorted()
 Terminal Operations: collect(), forEach(), reduce(), count(), anyMatch()

Example:
List<String> names = [Link]("Alice", "Bob", "Charlie", "David");

// Filtering and printing names starting with 'A'


[Link]()
.filter(name -> [Link]("A"))
.forEach([Link]::println);

4. Method References
Method references provide a way to refer to methods without executing them. They are a shorthand
notation for lambda expressions that call a specific method.
Syntax:
java
Copy code
ClassName::methodName
Examples:
java
Copy code
// Using method reference to print elements
List<String> names = [Link]("Alice", "Bob", "Charlie");
[Link]([Link]::println);

// Using method reference with static method


Function<String, Integer> stringToInteger = Integer::parseInt;
5. Optional
The Optional class is a container that may or may not contain a value. It is used to represent the
absence of a value and helps in avoiding null pointer exceptions.
Basic Usage:
java
Copy code
Optional<String> optional = [Link]("Hello");
[Link]([Link]::println); // Prints "Hello"

Optional<String> emptyOptional = [Link]();


String result = [Link]("Default Value"); // Returns "Default Value"
6. Immutability and Final Variables
Immutability is a key principle in functional programming. In Java, you can use final to
declare variables that should not be reassigned. Immutable classes can be created by ensuring all
fields are final and not providing setters.
Example:
java
Copy code
public final class ImmutablePerson {
private final String name;
private final int age;

public ImmutablePerson(String name, int age) {


[Link] = name;
[Link] = age;
}

public String getName() {


return name;
}

public int getAge() {


return age;
}
}
Practical Benefits of Functional Programming in Java
1. Simpler Code: Functional programming often results in more concise and readable code,
especially with the use of lambdas and streams.
2. Parallel Processing: Streams can be processed in parallel easily, which can lead to
performance improvements in multi-core processors.
3. Fewer Bugs: Immutability and pure functions reduce the chances of bugs related to state
changes and side effects.
Object-Oriented versus Functional Programming

Lambda Expressions in Java

What is 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 [Link];

• is a functional interface because it constitutes only one method i.e. run()


Example 1: Define a Functional Interface in java
import [Link]; @FunctionalInterface
public interface MyInterface{
// the single abstract method double getValue();
}

Example 2: 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() {
[Link]("I just implemented the Runnable Functional Interface.");
}
}).start();
}
• }

Introduction to lambda expressions

• Lambda expression is, essentially, an anonymous or unnamed method.

• The lambda expression does not execute on its own.

• Instead, it is used to implement a method defined by a functional interface.

How to define lambda expression in Java?

(parameter list) -> lambda body Suppose, we have a method like this:
double getPiValue() { return 3.1415;
}

We can write this method using lambda expression as: () -> 3.1415

Types of Lambda Body


1. A body with a single expression

() -> [Link]("Lambdas are great");

This type of lambda body is known as the expression body.

2. A body that consists of a block of code.

() -> {
double pi = 3.1415; return pi;
};

Example
import [Link];
// this is functional interface @FunctionalInterface interface MyInterface{

// abstract method double getPiValue();


}
public class Main {
public static void main( String[] args ) {
// declare a reference to MyInterface MyInterface ref;
// lambda expression ref = () -> 3.1415;
[Link]("Value of Pi = " + [Link]());
}
}

Lambda Expressions with parameters


@FunctionalInterface
interface MyInterface {
// abstract method String reverse(String n);
}
public class Main {
public static void main( String[] args ) {
// declare a reference to MyInterface
// assign a lambda expression to the reference MyInterface ref = (str) -> {
String result = "";
for (int i = [Link]()-1; i >= 0 ; i--) result += [Link](i);
return result;
};
// call the method of the interface
[Link]("Lambda reversed = " + [Link]("Lambda")); }}

Generic Functional Interface and Lambda Expressions

• functional interface only accepts String and returns String.


• However, we can make the functional interface generic,
• so that any data type is accepted.

Example
@FunctionalInterface
interface GenericInterface<T> {
// generic method
T func(T t);} public class Main {
public static void main( String[] args ) { GenericInterface<String> reverse = (str) -> {
String result = "";
for (int i = [Link]()-1; i >= 0 ; i--) result += [Link](i);
return result;
}; [Link]("Lambda reversed = " + [Link]("Lambda"));
GenericInterface<Integer> factorial = (n) -> {
int result = 1;
for (int i = 1; i <= n; i++) result = i * result; return result;
};
[Link]("factorial of 5 = " + [Link](5));
}
}

Jva's Date and Time API, introduced in Java 8 with the [Link] package, offers a
comprehensive and modern approach to handling date and time. It addresses many
shortcomings of the old [Link] and [Link] classes and provides a more
consistent and flexible approach to date and time manipulation.

Core Classes in the Date and Time API


1. LocalDate
 Description: Represents a date without time-zone information.
 Usage: Useful for representing dates like birthdays or anniversaries.
 Example:
java
Copy code
LocalDate today = [Link](); // Current date
LocalDate specificDate = [Link](2024, 9, 6); // September 6, 2024
2. LocalTime
 Description: Represents a time without date or time-zone information.
 Usage: Useful for representing times of day, such as office hours or events.
 Example:
LocalTime now = [Link](); // Current time
LocalTime specificTime = [Link](14, 30); // 2:30 PM
3. LocalDateTime
 Description: Represents both date and time without time-zone information.
 Usage: Useful for representing specific moments in time during the day.
 Example:
LocalDateTime now = [Link](); // Current date and time
LocalDateTime specificDateTime = [Link](2024, 9, 6, 14, 30); // September
6, 2024, 2:30 PM
4. ZonedDateTime
 Description: Represents a date and time with time-zone information.
 Usage: Useful for representing specific moments in time across different time zones.
 Example:
ZonedDateTime zonedDateTime = [Link](); // Current date and time
with time zone
ZonedDateTime specificZonedDateTime = [Link](2024, 9, 6, 14, 30, 0, 0,
[Link]("America/New_York")); // Specific date and time with time zone
5. Instant
 Description: Represents a point on the timeline in UTC with nanosecond precision.
 Usage: Useful for timestamps and time calculations.
 Example:
Instant now = [Link](); // Current timestamp
Instant specificInstant = [Link]("2024-09-06T14:30:00Z"); // Specific
timestamp in UTC
6. Duration
 Description: Represents the amount of time between two Temporal objects.
 Usage: Useful for measuring time intervals.
 Example:
Duration duration = [Link]([Link](14, 30), [Link](15, 30)); //
Duration of 1 hour
long hours = [Link](); // Duration in hours
7. Period
 Description: Represents a period of time in years, months, and days.
 Usage: Useful for calculating differences in dates.
 Example:
Period period = [Link]([Link](2023, 9, 6), [Link](2024, 9, 6)); //
Period of 1 year
int months = [Link](); // Number of months in the period
8. DateTimeFormatter
 Description: Provides formatting and parsing of date and time objects.
 Usage: Useful for converting date and time objects to and from string representations.
 Example:
DateTimeFormatter formatter = [Link]("yyyy-MM-dd
HH:mm:ss");
String formattedDate = [Link]().format(formatter); // Format current
date and time
LocalDateTime parsedDate = [Link]("2024-09-06 14:30:00",
formatter); // Parse string to LocalDateTime
Examples of Common Operations
1. Getting Current Date and Time:
LocalDate today = [Link]();
LocalTime now = [Link]();
LocalDateTime nowDateTime = [Link]();
ZonedDateTime zonedNow = [Link]();
2. Creating Specific Date and Time Instances:

LocalDate date = [Link](2024, 9, 6);


LocalTime time = [Link](14, 30);
LocalDateTime dateTime = [Link](2024, 9, 6, 14, 30);
ZonedDateTime zonedDateTime = [Link](2024, 9, 6, 14, 30, 0, 0,
[Link]("America/New_York"));
3. Formatting and Parsing Dates:
DateTimeFormatter formatter = [Link]("yyyy-MM-dd");
String formattedDate = [Link]().format(formatter);
LocalDate parsedDate = [Link]("2024-09-06", formatter);
4. Calculating Durations and Periods:
Duration duration = [Link]([Link](14, 30), [Link](15, 30));
long hours = [Link]();

Period period = [Link]([Link](2023, 9, 6), [Link](2024, 9, 6));


int months = [Link]();
.

Common questions

Powered by AI

Java Streams improve readability and efficiency by providing a functional-style approach to handling sequences of elements. Streams allow for concise and expressive code via operations like map, filter, and reduce, eliminating the verbosity of explicit loops. They encourage a declarative programming style, focusing on what to achieve rather than how, leading to cleaner and more maintainable code .

Race conditions occur when the outcome of a program depends on the non-deterministic ordering of operations by multiple threads, leading to inconsistent results, such as incorrect increment operations demonstrated in the example where multiple threads increment a shared variable. Synchronization is crucial to prevent such conditions by ensuring that only one thread can modify shared data at a time, thus maintaining data consistency and correctness .

Backpressure handling in Reactive Programming is critical because it manages the flow of data between producers and consumers, preventing the consumer from being overwhelmed. This ensures that the application remains stable and responsive under high data loads by throttling or buffering the data and allowing systems to handle varying speeds of data production and consumption effectively .

Creating a thread by inheriting from the Thread class involves overriding the run() method, whereas using the Runnable interface requires implementing the method. The Runnable interface allows extending another class as Java only supports single inheritance. While there's no significant performance difference between these methods, using Runnable is preferable for creating thread pools due to its flexibility .

Immutability enhances robustness in functional programming by preventing state changes and side effects, which reduces bugs associated with data modification. Immutable data structures enhance thread safety, as objects can be shared between threads without concerns about concurrent modifications. This leads to more predictable, reliable code and simplifies debugging .

Reactive Programming enhances performance and scalability by utilizing asynchronous and non-blocking architectures. It treats data as streams and processes them asynchronously, reducing the blocking of threads. This approach efficiently handles I/O-bound tasks and improves responsiveness. Its declarative model simplifies code management, and its backpressure handling ensures stability under high load .

Writing concurrent programs is more complex and time-consuming because developers must understand synchronization, memory visibility, atomic operations, and thread communication. Debugging can be challenging due to the non-deterministic nature of concurrent programs, leading to inconsistent occurrence of race conditions or deadlocks. Errors in concurrency, such as race conditions and thread interference, are harder to identify and resolve. Additionally, poor design can cause resource contention and increased CPU and memory overhead, complicating performance optimization and testing .

The ExecutorService class allows for more efficient thread management by creating fixed numbers of threads that can be reused, improving control over application performance. It queues tasks, preventing unnecessary resource creation, and distributes them among available threads. This results in better resource management and optimized performance, especially important in applications with numerous tasks .

Lambda expressions enable Java to adopt functional programming by providing a concise syntax for implementing functional interfaces. They allow functions to be treated as first-class citizens, facilitating operations like passing behavior as arguments and supporting immutable data processes. This shift helps write more efficient, less error-prone code by reducing boilerplate and promoting a functional, declarative approach .

ConcurrentHashMap optimizes concurrency by dividing the map into segments and ensuring that only the segment where a write operation occurs is locked, allowing multiple threads to operate on different segments simultaneously. This reduces contention and avoids the bottleneck of locking the entire map as seen in HashMap and synchronized Map implementations, resulting in improved performance in multi-threaded environments .

You might also like