Sunday, 8 June 2025

Nth highest salary from a given list using Java 8 Streams or second highest salary

 

Nth highest salary from a given list using Java 8 Streams. Below is a step-by-step program that demonstrates this using distinct sorting and skipping elements.


Java Program: Find the Nth Highest Salary


import java.util.Arrays;

import java.util.List;

import java.util.Optional;


public class NthHighestSalary {

    public static void main(String[] args) {

        List<Integer> salaries = Arrays.asList(50000, 60000, 75000, 40000, 75000, 90000, 60000);

        int n = 4; // Change this value to find the nth highest salary


        Optional<Integer> nthHighest = salaries.stream()

                .distinct() // Remove duplicate salaries

                .sorted((a, b) -> Integer.compare(b, a)) // Sort salaries in descending order

                .skip(n - 1) // Skip (n-1) highest salaries

                .findFirst(); // Get the nth highest salary


        System.out.println(n + "th highest salary: " + nthHighest.orElse(null));

    }

}


Exaplnation 

Create a list of salaries: Some salaries may be duplicate.


Use .stream(): Convert the list into a stream.


Remove duplicates: .distinct() ensures only unique salaries are considered.


Sort in descending order: .sorted((a, b) -> Integer.compare(b, a)) arranges salaries from highest to lowest.


Skip (n-1) highest salaries: .skip(n - 1) moves past the first (n-1) elements.


Get the next element: .findFirst() extracts the Nth highest salary.


Print the result: If the salary exists, it prints, otherwise null is returned




Second highest salary with given list


import java.util.Arrays;

import java.util.List;

import java.util.Optional;


public class SecondHighestNumber {

    public static void main(String[] args) {

        List<Integer> numbers = Arrays.asList(12, 5, 8, 20, 15, 9, 20);


        Optional<Integer> secondHighest = numbers.stream()

                .distinct() // Remove duplicate numbers

                .sorted((a, b) -> Integer.compare(b, a)) // Sort in descending order

                .skip(1) // Skip the highest number

                .findFirst(); // Get the second highest


        System.out.println("Second highest number: " + secondHighest.orElse(null));

    }

}




2 ) 

import java.util.Arrays;

import java.util.List;

import java.util.Optional;


public class SecondHighestNumber {

    public static void main(String[] args) {

        List<Integer> numbers = Arrays.asList(12, 5, 8, 20, 15, 9, 20);


        Optional<Integer> highest = numbers.stream().max(Integer::compare);

        Optional<Integer> secondHighest = numbers.stream()

                .filter(num -> !num.equals(highest.orElse(null))) // Remove the highest

                .max(Integer::compare); // Find the second highest


        System.out.println("Second highest number: " + secondHighest.orElse(null));

    }

}




3 .


public class SecondHighestSalaryWithSet {

    public static void main(String[] args) {

        List<Employee> employees = Arrays.asList(

            new Employee("Alice", 50000),

            new Employee("Bob", 60000),

            new Employee("Charlie", 70000),

            new Employee("David", 80000),

            new Employee("Eve", 90000),

            new Employee("Frank", 90000) // Duplicate salary

        );


        // Find the second highest salary using a Set to eliminate duplicates

        Optional<Double> secondHighestSalary = employees.stream()

            .map(emp -> emp.salary)

            .collect(Collectors.toSet())               // Collect to a Set to remove duplicates

            .stream()

            .sorted(Comparator.reverseOrder())

            .skip(1)

            .findFirst();


        // Print the result

        System.out.println("Second Highest Salary: " + secondHighestSalary.orElse(-1));

    }

}






Spring Transaction Management:

 


Spring's transaction management is a robust abstraction that simplifies handling database transactions, ensuring data integrity and consistency. 


1. What is Transaction Management?

A transaction is a sequence of operations performed as a single logical unit of work. In Spring, transaction management ensures that either all operations in a transaction succeed (commit) or none do (rollback), maintaining data consistency.

ACID Properties: Atomicity, Consistency, Isolation, Durability


Atomicity:

The @Transactional annotation wraps multiple database operations within a single transaction. If all operations complete successfully, the transaction is committed and changes are saved. If any operation throws a runtime exception (or another specified exception), Spring automatically rolls back the transaction, undoing all changes made during that transaction


Consistency:

This approach ensures that the database remains in a valid state, preventing partial updates or data corruption. If a constraint is violated or an error occurs, the transaction is aborted and the database is restored to its previous state.

Example Workflow:


Start a transaction (@Transactional method is called).


Perform several database operations (e.g., insert, update).


If all succeed, Spring commits the transaction.


If any operation fails (throws an exception), Spring rolls back all changes-none of the operations have any effect on the database

2 . Spring Transaction Management Approaches


Declarative: Using annotations like @Transactional (most common).


Programmatic: Using TransactionTemplate or PlatformTransactionManager directly.


3. Setting Up Transaction Management

Step 1: Add Dependencies


Spring Data JPA or JDBC


Database driver (e.g., H2, MySQL)


Step 2: Enable Transaction Management

@Configuration

@EnableTransactionManagement

public class AppConfig {

    @Bean

    public PlatformTransactionManager txManager(DataSource dataSource) {

        return new DataSourceTransactionManager(dataSource);

    }

}



4.. Using @Transactional Annotation (Declarative Approach)

Basic Usage:

@Service

public class UserService {

    @Autowired

    private UserRepository userRepository;


    @Transactional

    public void createUser(User user) {

        userRepository.save(user);

    }

}


Explanation:

The createUser method runs inside a transaction. If any exception occurs, the transaction is rolled back.


Output:


On successful save: User is persisted.


On exception: No user is saved (rollback).


5. Transaction Propagation

Propagation defines how transactions behave when a transactional method is called by another transactional method. The most common propagation modes are:


REQUIRED (default): Uses the existing transaction if one exists; otherwise, creates a new one.


REQUIRES_NEW: Suspends any existing transaction and starts a new one.


NESTED: Executes within a nested transaction if a current transaction exists (uses savepoints); otherwise, behaves like REQUIRED.

SUPPORTS: Join if exists, else non-transactional.


6. Isolation Levels and Read-Only Transactions


Isolation Levels

Isolation levels control how transaction integrity is maintained when multiple transactions are running concurrently. Common levels include:

READ_UNCOMMITTED: Lowest level; allows dirty reads.

READ_COMMITTED: Default in many databases; prevents dirty reads.

REPEATABLE_READ: Ensures that if a value is read twice in the same transaction, it remains consistent.

SERIALIZABLE: Highest isolation; fully serializes transactions.


7.Exception Handling and Rollback

Unchecked exceptions (RuntimeException): Trigger rollback by default.


Checked exceptions: Do not trigger rollback unless specified.


@Transactional(rollbackFor = IOException.class)

public void method() throws IOException { ... }


Output:

Rolls back on IOException as  well



 How does Spring ensure transactional integrity in a distributed environment ?


Can you provide an example of using @Transactional with multiple database operations ?







Collectors in java8 streams

 


Java 8’s Collectors class provides a suite of static methods for accumulating elements from a stream into collections, strings, maps, and more.


 Collectors are typically used with the Stream.collect() method, making it easy to process and transform data in a functional style.


Why:


Collectors provide ready-made solutions for common reduction operations, such as collecting stream elements into collections, grouping, partitioning, joining, and summarizing data. They make code concise, readable, and expressive.


When:


Use collectors when you want to gather, transform, group, or summarize data from a stream into a desired result format (e.g., List, Set, Map, or a single value).


Common Collectors and Their Use Cases


1. Collectors.toList()

When: 

When you want to collect stream elements into a List.


Example:

List<String> cities = Arrays.asList("Mumbai", "Delhi", "Bangalore", "Chennai");

List<String> cityList = cities.stream().collect(Collectors.toList());

System.out.println(cityList); // Output: [Mumbai, Delhi, Bangalore, Chennai]




Explanation:

Collects all stream elements into a List in encounter order


2. Collectors.toSet()

When:

When you need a Set (no duplicates) from stream elements.


Example:

List<String> cities = Arrays.asList("Mumbai", "Delhi", "Mumbai", "Bangalore");

Set<String> citySet = cities.stream().collect(Collectors.toSet());

System.out.println(citySet); // Output: [Mumbai, Delhi, Bangalore]


Explanation:

Removes duplicates by collecting elements into a Set



Thursday, 15 May 2025

Stream.iterate () vs iterate method

 


Stream.iterate() produces an infinite sequential stream in which each element is generated by applying a function (a unary operator) to the previous element. Because the stream is potentially unbounded, you almost always combine it with a terminal operation such as limit().


1. Generating a Sequence of Numbers

This example starts with zero and adds 1 for each subsequent element. We limit the stream to the first 10 elements.



import java.util.stream.Stream;


public class IterateBasicExample {

    public static void main(String[] args) {

        // Start at 0, with each subsequent element increased by 1.

        Stream<Integer> numbers = Stream.iterate(0, n -> n + 1);

        

        // Limit to the first 10 elements and print each number.

        numbers.limit(10).forEach(System.out::println);

    }

}



Explanation: Here, the initial element is 0. The lambda n -> n + 1 tells the stream to generate the next number by adding one to the current number. Since the stream is infinite, using limit(10) ensures that only 10 numbers are processed.


Output::

0

1

2

3

4

5

6

7

8

9



2 . Generating the Fibonacci Sequence

This example uses Stream.iterate() along with an array to generate pairs representing consecutive Fibonacci numbers. We then map these pairs to extract the first element in each pair (the Fibonacci number).


import java.util.stream.Stream;


public class FibonacciIterate {

    public static void main(String[] args) {

        // Each element in the stream is an array where:

        // - array[0] is the current Fibonacci number,

        // - array[1] is the next Fibonacci number.

        Stream.iterate(new long[]{0, 1}, pair -> new long[]{pair[1], pair[0] + pair[1]})

              .limit(10)              // Get the first 10 Fibonacci pairs.

              .map(pair -> pair[0])   // Extract the first number from each pair.

              .forEach(n -> System.out.print(n + " "));

    }

}



Explanation:


We start with the array {0, 1}.


The lambda pair -> new long[]{pair[1], pair[0] + pair[1]} computes the next Fibonacci pair.


We then limit the stream to 10 elements and map each array to its first element. This produces the Fibonacci numbers.


 Output::

0 1 1 2 3 5 8 13 21 34