Vertx-web – Sample Application


Vert.x-Web is a set of building blocks for building web applications with Vert.x. Vert.x-Web can be used to create classic server-side web applications, RESTful web applications, ‘real-time’ (server push) web applications, or any other kind of web application. Vert.x-Web is a great fit for writing RESTful HTTP micro-services. In this post, we will discuss about writing a developing a basic rest api using vert.x-web.

Here is the sample API using Vert.x-Web:

package com.malliktalksjava.vertx.samples;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.Router;

public class MainVerticle extends AbstractVerticle {

  @Override
  public void start(Promise<Void> startPromise) throws Exception {
    HttpServer server = vertx.createHttpServer();
    Router router = Router.router(vertx);

    router.route().handler(ctx -> {

      // This handler will be called for every request
      HttpServerResponse response = ctx.response();
      response.putHeader("content-type", "application/json");

      // Write to the response and end it
      response.end("Hello from Vert.x-Web!" );
    });

    server.requestHandler(router).listen(8888);
  }
}

Create an HTTP server as before, then create a router. Then create a simple route with no matching criteria so it will match all requests that arrive on the server.
Then specify a handler for that route. That handler will be called for all requests that arrive on the server.
The object that gets passed into the handler is a RoutingContext – this contains the standard Vert.x HttpServerRequest and HttpServerResponse but also various other useful stuff that makes working with Vert.x-Web simpler.
For every request that is routed there is a unique routing context instance, and the same instance is passed to all handlers for that request.
Once the handler is setup, set the request handler of the HTTP server to pass all incoming requests to handle.

Try to access the application on using http://localhost:8888. Here is the output:

Java Streams: A Comprehensive Guide


Java Streams, introduced in Java 8, have revolutionized the way developers work with data collections. They provide a concise and expressive way to perform operations on sequences of data, making code more readable and maintainable. In this detailed tutorial, we’ll explore Java Streams from the ground up, covering everything from the basics to advanced techniques.

Table of Contents

  1. Introduction to Java Streams
  2. Creating Streams
    • 2.1. From Collections
    • 2.2. From Arrays
    • 2.3. Stream.of
    • 2.4. Stream.builder
  3. Intermediate Operations
    • 3.1. Filter
    • 3.2. Map
    • 3.3. FlatMap
    • 3.4. Sorted
    • 3.5. Peek
  4. Terminal Operations
    • 4.1. forEach
    • 4.2. toArray
    • 4.3. collect
    • 4.4. reduce
    • 4.5. min and max
    • 4.6. count
  5. Parallel Streams
  6. Stream API Best Practices
  7. Advanced Stream Techniques
    • 7.1. Custom Collectors
    • 7.2. Stream of Streams
    • 7.3. Grouping and Partitioning
  8. Real-World Examples
    • 8.1. Filtering Data
    • 8.2. Mapping Data
    • 8.3. Aggregating Data
  9. Performance Considerations
  10. Conclusion

1. Introduction to Java Streams

Java Streams are a powerful addition to the Java programming language, designed to simplify the manipulation of collections and arrays. They allow you to perform operations like filtering, mapping, and reducing in a more functional and declarative way.

Key characteristics of Java Streams:

  • Sequence of Data: Streams are a sequence of elements, whether from collections, arrays, or other sources.
  • Functional Style: Operations on streams are expressed as functions, promoting a functional programming paradigm.
  • Lazy Evaluation: Streams are evaluated on demand, making them efficient for large datasets.
  • Parallel Processing: Streams can easily be processed in parallel to leverage multi-core processors.

2. Creating Streams

2.1. From Collections

You can create a stream from a collection using the stream() method:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
Stream<String> nameStream = names.stream();

2.2. From Arrays

Arrays can be converted into streams using Arrays.stream():

String[] colors = { "Red", "Green", "Blue" };
Stream<String> colorStream = Arrays.stream(colors);

2.3. Stream.of

To create a stream from individual elements, use Stream.of():

Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5);

2.4. Stream.builder

For dynamic stream creation, employ a Stream.Builder:

Stream.Builder<String> builder = Stream.builder();
builder.accept("One");
builder.accept("Two");
Stream<String> customStream = builder.build();

3. Intermediate Operations

Intermediate operations are used to transform or filter data within a stream.

3.1. Filter

The filter operation allows you to select elements that meet a specific condition:

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> evenNumbers = numbers.filter(n -> n % 2 == 0);

3.2. Map

map transforms elements by applying a function to each element:

Stream<String> names = Stream.of("Alice", "Bob", "Charlie");
Stream<Integer> nameLengths = names.map(String::length);

3.3. FlatMap

flatMap is used to flatten nested streams into a single stream:

Stream<List<Integer>> nestedStream = Stream.of(Arrays.asList(1, 2), Arrays.asList(3, 4));
Stream<Integer> flattenedStream = nestedStream.flatMap(Collection::stream);

3.4. Sorted

You can sort elements using the sorted operation:

Stream<String> names = Stream.of("Charlie", "Alice", "Bob");
Stream<String> sortedNames = names.sorted();

3.5. Peek

peek allows you to perform an action on each element without modifying the stream:

Stream<Integer> numbers = Stream.of(1, 2, 3);
Stream<Integer> peekedNumbers = numbers.peek(System.out::println);

4. Terminal Operations

Terminal operations produce a result or a side-effect and trigger the execution of the stream.

4.1. forEach

The forEach operation performs an action on each element:

Stream<String> names = Stream.of("Alice", "Bob", "Charlie");
names.forEach(System.out::println);

4.2. toArray

toArray converts a stream into an array:

Stream<Integer> numbers = Stream.of(1, 2, 3);
Integer[] numArray = numbers.toArray(Integer[]::new);

4.3. collect

The collect operation accumulates elements into a collection:

Stream<String> names = Stream.of("Alice", "Bob", "Charlie");
List<String> nameList = names.collect(Collectors.toList());

4.4. reduce

reduce combines the elements of a stream into a single result:

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Optional<Integer> sum = numbers.reduce(Integer::sum);

4.5. min and max

You can find the minimum and maximum elements using min and max:

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Optional<Integer> min = numbers.min(Integer::compareTo);
Optional<Integer> max = numbers.max(Integer::compareTo);

4.6. count

count returns the number of elements in the stream:

Stream<String> names = Stream.of("Alice", "Bob", "Charlie");
long count = names.count();

5. Parallel Streams

Java Streams can be easily parallelized to take advantage of multi-core processors. You can convert a sequential stream to a parallel stream using the parallel method:

Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> parallelNumbers = numbers.parallel();

Be cautious when using parallel streams, as improper usage can lead to performance issues and race conditions.

6. Stream API Best Practices

To write clean and efficient code with Java Streams, follow these best practices:

  • Keep Streams Stateless: Avoid modifying variables from outside the lambda expressions used in stream operations.
  • Choose Appropriate Data Structures: Use the right data structure for your needs to optimize stream performance.
  • Lazy Evaluation: Use intermediate operations to filter and transform data before calling terminal operations to minimize unnecessary work.
  • Avoid Side Effects: Keep terminal operations clean and avoid side effects for better code maintainability.

7. Advanced Stream Techniques

7.1. Custom Collectors

You can create custom collectors to perform advanced data aggregations:

List<Person> people = ...;
Map<Gender, List<Person>> peopleByGender = people.stream()
    .collect(Collectors.groupingBy(Person::getGender));

7.2. Stream of Streams

Streams can be nested, allowing for more complex data processing:

Stream<List<Integer>> listOfLists = ...;
Stream<Integer> flattenedStream = listOfLists.flatMap(List::stream);

7.3. Grouping and Partitioning

The groupingBy and partitioningBy collectors enable advanced data grouping:

Map<Gender, List<Person>> peopleByGender = people.stream()
    .collect(Collectors.groupingBy(Person::getGender));

8. Real-World Examples

Let’s explore some real-world scenarios where Java Streams shine:

8.1. Filtering Data

Filtering a list of products by price and category:

List<Product> filteredProducts = products.stream()
    .filter(p -> p.getPrice() < 50 && p.getCategory().equals("Electronics"))
    .collect(Collectors.toList());

8.2. Mapping Data

Calculating the average salary of employees in a department:

double averageSalary = employees.stream()
    .filter(e -> e.getDepartment().equals("HR"))
    .mapToDouble(Employee::getSalary)
    .average()
    .orElse(0.0);

8.3. Aggregating Data

Finding the most popular tags among a list of articles:

Map<String, Long> tagCounts = articles.stream()
    .flatMap(article -> article.getTags().stream())
    .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

9. Performance Considerations

While Streams offer convenience, improper use can impact performance. Be mindful of:

  • Stream Size: Large data sets may lead to excessive memory usage.
  • Parallel Streams: Use with caution; not all tasks benefit from parallelism.
  • Statelessness: Ensure lambda expressions used in stream operations are stateless.
  • Avoiding Excessive Intermediate Operations: Minimize unnecessary filtering and mapping.

10. Conclusion

Java Streams are a versatile and powerful tool for working with data in a functional and declarative manner. By mastering the concepts, operations, and best practices outlined in this tutorial, you’ll be well-equipped to write clean, efficient, and expressive code that makes the most of Java’s stream processing capabilities.

Happy coding!

Mastering Java Lambda Expressions: A Comprehensive Guide


Introduction:

Java lambda expressions revolutionized the way we write code by introducing functional programming concepts to the language. Lambda expressions allow us to write more concise and expressive code, enhancing readability and promoting modular design. In this tutorial, we’ll explore lambda expressions in Java, covering their syntax, common use cases, and best practices.

Table of Contents:

  1. What are Lambda Expressions?
  2. Syntax of Lambda Expressions
  3. Functional Interfaces
  4. Working with Lambda Expressions
    • Using Lambda Expressions as Method Arguments
    • Assigning Lambda Expressions to Variables
    • Lambda Expressions with Multiple Parameters
    • Accessing Variables from the Enclosing Scope
  5. Method References vs. Lambda Expressions
  6. Benefits of Lambda Expressions
  7. Common Use Cases
  8. Best Practices for Using Lambda Expressions
  9. Conclusion

Section 1: What are Lambda Expressions?

Lambda expressions are a feature introduced in Java 8 that allows you to write more concise and expressive code by treating functionality as a first-class citizen. In simple terms, lambda expressions enable you to represent anonymous functions as values.

In traditional Java programming, you would typically define an interface with a single abstract method and create an instance of a class that implements that interface to provide the implementation for that method. Lambda expressions provide a more compact alternative by allowing you to define the implementation of the method directly inline, without the need for a separate class.

Lambda expressions are often used in conjunction with functional interfaces, which are interfaces that have exactly one abstract method. The lambda expression provides an implementation for that method, making it a concise way to represent behavior.

The key idea behind lambda expressions is to treat behavior as a value that can be passed around, assigned to variables, and used as method arguments. This functional programming approach promotes modularity and flexibility in your code.

Section 2: Syntax of Lambda Expressions

Lambda expressions consist of three main parts:

  1. Parameters: These are the input parameters that the lambda expression takes. If there are no parameters, you can leave the parentheses empty. If there are multiple parameters, separate them with commas.
  2. Arrow Operator: The arrow operator (->) separates the parameters from the body of the lambda expression. It serves as a visual indicator that the parameters are used to produce the result defined by the expression.
  3. Body: The body of the lambda expression represents the computation or action that the lambda expression performs. It can be a single statement or a block of statements enclosed in curly braces.

Here’s an example of a lambda expression that adds two numbers:

(int a, int b) -> a + b

In this example, the lambda expression takes two integer parameters (a and b) and returns their sum (a + b).

Lambda expressions are commonly used in functional programming constructs and APIs that accept functional interfaces. They enable you to write more expressive and concise code by representing behavior directly inline, without the need for additional classes and method declarations.

Lambda expressions have brought a significant shift in the way Java code is written, enabling developers to embrace functional programming concepts and write cleaner, more modular code.

Section 3: Functional Interfaces

Functional interfaces are a fundamental concept in Java that are closely related to lambda expressions and enable functional programming in the language. In simple terms, a functional interface is an interface that has exactly one abstract method. They provide a way to define the contract for a lambda expression or any other implementation of a single-method interface.

In Java, functional interfaces are annotated with the @FunctionalInterface annotation. While the annotation is not strictly required, it serves as a marker to indicate that the interface is intended to be used as a functional interface. The compiler will enforce the rule of having only one abstract method within an interface marked with @FunctionalInterface.

Functional interfaces can have default methods or static methods, but the key requirement is that they must have exactly one abstract method. This single abstract method represents the primary behavior that the interface expects to define. The other methods can provide additional utility or default implementations.

Java 8 introduced a set of functional interfaces in the java.util.function package to facilitate functional programming and lambda expressions. Some commonly used functional interfaces include:

  1. Supplier<T>: Represents a supplier of results. It has a single abstract method T get() and does not take any arguments but returns a value.
  2. Consumer<T>: Represents an operation that takes a single input argument and returns no result. It has a single abstract method void accept(T t).
  3. Predicate<T>: Represents a predicate (a condition) that takes an argument and returns a boolean value. It has a single abstract method boolean test(T t).
  4. Function<T, R>: Represents a function that takes an argument of type T and returns a result of type R. It has a single abstract method R apply(T t).
  5. BiFunction<T, U, R>: Represents a function that takes two arguments of types T and U and returns a result of type R. It has a single abstract method R apply(T t, U u).

These functional interfaces provide a standardized way to represent common functional programming patterns and facilitate the use of lambda expressions.

By using functional interfaces, you can define behavior that can be passed as arguments to methods, stored in variables, and used as return types. Lambda expressions can be used to implement the single abstract method of a functional interface, allowing for concise and expressive code.

Functional interfaces play a crucial role in enabling functional programming constructs in Java and provide a foundation for leveraging the power of lambda expressions and writing more modular and flexible code.

Section 4: Working with Lambda Expressions

Lambda expressions can be used in various contexts, such as:

  • Method arguments: You can pass lambda expressions as arguments to methods. For example, when working with collections, you can use lambda expressions to define custom sorting or filtering logic.
  • Return values: Lambda expressions can be returned from methods. This is useful when you want to create flexible and reusable code components.
  • Assignments: You can assign lambda expressions to variables and use them as if they were objects.
  • Streams API: Lambda expressions are extensively used with the Streams API to perform operations on collections in a functional and declarative way.

Section 5: Method References vs. Lambda Expressions

  1. Using Lambda Expressions as Method Arguments: Lambda expressions can be passed as arguments to methods, allowing you to define behavior inline without the need for separate classes or explicit implementations. This is commonly used in functional programming constructs and APIs that accept functional interfaces. For example:
List numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach(n -> System.out.println(n));

In the above example, the forEach method of the List interface accepts a Consumer functional interface. Instead of explicitly implementing the Consumer interface with a separate class, we pass a lambda expression (n -> System.out.println(n)) that defines the behavior of consuming each element of the list.

  1. Assigning Lambda Expressions to Variables: Lambda expressions can be assigned to variables of functional interface types. This allows you to reuse the lambda expression and provide a more descriptive name for the behavior it represents. For example:
Predicate<Integer> evenNumberFilter = n -> n % 2 == 0;
List<Integer> evenNumbers = numbers.stream()
    .filter(evenNumberFilter)
    .collect(Collectors.toList());

In this example, we create a variable evenNumberFilter of type Predicate<Integer>, which represents a lambda expression that checks if a number is even. We can then use this variable to filter the numbers list using the filter method of the Stream API.

  1. Lambda Expressions with Multiple Parameters: Lambda expressions can take multiple parameters. If you have multiple parameters, separate them with commas. For example:
BiFunction<Integer, Integer, Integer> addFunction = (a, b) -> a + b;
int sum = addFunction.apply(3, 5);  // sum = 8

In this case, we define a lambda expression (a, b) -> a + b that represents a function that takes two integers (a and b) and returns their sum. We assign this lambda expression to a variable of type BiFunction<Integer, Integer, Integer> and then use it to compute the sum of two numbers.

  1. Accessing Variables from the Enclosing Scope: Lambda expressions can access variables from the enclosing scope. These variables are effectively final or effectively effectively final, meaning they are not allowed to be modified within the lambda expression. This allows lambda expressions to capture and use values from the surrounding context. For example:
int factor = 2;
Function<Integer, Integer> multiplier = n -> n * factor;
int result = multiplier.apply(5);  // result = 10
In this example, the lambda expression (n -> n * factor) captures the factor variable from the enclosing scope. The factor variable is effectively final, and we can use it within the lambda expression to multiply the input value.

Working with lambda expressions allows you to write concise and expressive code by representing behavior directly inline. They provide a more modular and flexible way of defining behavior, making your code easier to read and maintain. By leveraging lambda expressions, you can achieve greater code clarity and focus on the core logic of your application.

Section 6: Benefits of Lambda Expressions

Lambda expressions in Java provide several benefits that make your code more concise, readable, and maintainable. Here are some of the key advantages of using lambda expressions:

  1. Conciseness: Lambda expressions allow you to express instances of single-method interfaces (functional interfaces) more concisely. This reduction in boilerplate code makes your code cleaner and easier to understand.
  2. Readability: Lambda expressions can make your code more readable by eliminating unnecessary details. They allow you to focus on the essential logic of a function or operation.
  3. Expressiveness: Lambda expressions enable a more expressive syntax, making it clear what the code is doing. They often read like a sentence, improving the understanding of the programmer’s intent.
  4. Flexibility: Lambda expressions make it easier to pass behavior as an argument to methods. This flexibility is especially useful when working with collections, sorting, filtering, or defining custom behavior.
  5. Functional Programming: Lambda expressions promote functional programming practices in Java. You can write code in a more functional and declarative style, which can lead to more efficient and robust programs.
  6. Parallelism: Lambda expressions are particularly useful when working with the Java Streams API. They allow you to take advantage of parallel processing easily, as operations can be expressed in a way that doesn’t depend on the order of execution.
  7. Reduced Code Duplication: Lambda expressions can help reduce code duplication by allowing you to encapsulate reusable behavior in a concise form. This promotes the DRY (Don’t Repeat Yourself) principle.
  8. Improved API Design: When designing APIs, lambda expressions can provide a more intuitive and user-friendly way for clients to interact with your code. It allows you to design APIs that accept functional interfaces, making them more versatile.
  9. Easier Maintenance: Code that uses lambda expressions is often easier to maintain because it’s more self-contained and less prone to bugs introduced by accidental changes to shared state.
  10. Compatibility: Lambda expressions are backward-compatible, meaning you can use them in Java 8 and later versions without any issues. This makes it possible to gradually adopt newer language features while maintaining compatibility with older code.
  11. Reduced Anonymity: Lambda expressions provide a name (though not explicit) to otherwise anonymous functions, making it easier to identify and debug issues in stack traces and logs.
  12. Improved Performance: In some cases, lambda expressions can lead to improved performance. The JVM can optimize certain operations performed with lambda expressions more effectively than equivalent code written with anonymous inner classes.

Overall, lambda expressions are a valuable addition to Java, enabling more modern and expressive coding styles while maintaining compatibility with older Java code. They encourage best practices, such as code reusability, readability, and functional programming, ultimately leading to more maintainable and efficient applications.

Section 7: Common Use Cases

Lambda expressions in Java are a versatile tool that can be used in a wide range of scenarios to make your code more concise and expressive. Here are some common use cases where you can benefit from using lambda expressions:

  1. Collections and Streams: Lambda expressions are often used with the Java Collections API and Streams API for tasks like filtering, mapping, and reducing elements in a collection.
  2. Sorting: You can use lambda expressions to specify custom sorting criteria for collections.
  3. Event Handling: Lambda expressions are useful when defining event handlers for GUI components or other event-driven programming scenarios.
  4. Concurrency: Lambda expressions can be employed when working with the java.util.concurrent package to define tasks for execution in threads or thread pools.
  5. Functional Interfaces: Implementing and using functional interfaces is a primary use case for lambdas. You can define custom functional interfaces to model specific behaviors and then use lambda expressions to provide implementations.
  6. Optional: Lambda expressions can be used with Java’s Optional class to define actions that should occur if a value is present or not present.
  7. Functional Programming: Lambda expressions enable functional programming techniques in Java, allowing you to write code that treats functions as first-class citizens. This includes passing functions as arguments, returning functions from other functions, and more.
  8. Custom Iteration: When iterating over custom data structures or performing complex iterations, lambda expressions can simplify the code.
  9. Resource Management: In cases where resources need to be managed explicitly, such as opening and closing files or database connections, lambda expressions can be used to define actions to be taken during resource initialization and cleanup.
  10. Dependency Injection: Lambda expressions can be used in dependency injection frameworks to provide implementations of functional interfaces or to specify custom behaviors for components.

Section 8: Best Practices for Using Lambda Expressions

Using lambda expressions effectively in Java can lead to more readable and maintainable code. To ensure you’re following best practices when working with lambda expressions, consider the following guidelines:

  1. Use Lambda Expressions with Functional Interfaces: Lambda expressions are most powerful when used with functional interfaces. Ensure that the interface you are working with has only one abstract method. If it has more than one, the lambda expression won’t be able to determine which method to implement.
  2. Choose Descriptive Parameter Names: Use meaningful parameter names in your lambda expressions. Descriptive names make the code more readable and help others understand the purpose of the lambda.
    • (x, y) -> x + y // Less readable
    • (value1, value2) -> value1 + value2 // More readable
  3. Keep Lambda Expressions Short and Focused: Lambda expressions should be concise and focused on a single task. If a lambda becomes too complex, it may be a sign that it should be refactored into a separate method or function.
  4. Use Method References When Appropriate: If your lambda expression simply calls an existing method, consider using method references for cleaner and more concise code. Method references are often more readable, especially for common operations like System.out::println.
    • list.forEach(System.out::println);
  5. Explicitly Specify Types When Necessary: While Java can often infer types, explicitly specifying types in your lambda expressions can make the code more readable and less error-prone, especially in complex scenarios.
    • (String s) -> s.length() // Explicit type s -> s.length() // Inferred type
  6. Use Parentheses for Clarity: When your lambda expression has multiple parameters or a complex body, use parentheses to make it clearer.
    • (a, b) -> a + b // Clearer
    • a, b -> a + b // Less clear
  7. Avoid Side Effects: Lambda expressions should ideally be stateless and avoid modifying external variables (unless they are effectively final). Avoid side effects that can make code harder to reason about and test.
  8. Exception Handling: Be cautious with exception handling within lambda expressions. Consider wrapping lambda bodies with try-catch blocks when necessary. If exceptions occur, they may be wrapped in UncheckedIOException or UncheckedExecutionException.
  9. Think About Parallelism: When using lambda expressions with the Streams API, think about the potential for parallelism. Ensure that your lambda expressions don’t have any side effects that could cause issues when used in parallel streams.
  10. Testing: When writing unit tests, use lambda expressions to define behavior that can be easily tested. Lambda expressions make it straightforward to pass mock implementations or behavior to test components.
  11. Documentation: Document the intent and purpose of your lambda expressions, especially if they perform complex operations or are part of a public API. Clear documentation helps other developers understand how to use your code effectively.
  12. Code Reviews: As with any code, it’s essential to conduct code reviews when using lambda expressions, especially in team environments. Reviews can help catch issues related to readability, maintainability, and adherence to best practices.
  13. Code Style: Follow your team’s or organization’s coding style guidelines when using lambda expressions. Consistency in coding style helps maintain code readability and understandability.
  14. Profile for Performance: While lambda expressions are generally efficient, it’s a good practice to profile your code to identify any performance bottlenecks, especially when using them in critical sections of your application.

By following these best practices, you can make the most of lambda expressions in Java and ensure that your code remains clean, readable, and maintainable. Lambda expressions are a powerful tool when used appropriately, and they can lead to more expressive and efficient code.

Section 9: Conclusion

Remember that lambda expressions are most beneficial when used with functional interfaces, which have a single abstract method. These interfaces are designed to work seamlessly with lambda expressions and provide a clear and concise way to define behavior. Additionally, lambda expressions encourage a more functional and declarative style of programming, which can lead to cleaner and more maintainable code.

Happy coding with lambda expressions in Java!

Sort Employee Objects on Age


This program defines an Employee class with properties of name, id, and age, and implements the Comparable interface to enable sorting by age. The main method creates a list of 100 employee objects and sorts them based on age using the Collections.sort method. Finally, the sorted list of employees is printed to the console.

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Employee implements Comparable<Employee> {
    private String name;
    private String id;
    private int age;

    public Employee(String name, String id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public String getId() {
        return id;
    }

    public int getAge() {
        return age;
    }

    @Override
    public int compareTo(Employee other) {
        return Integer.compare(this.age, other.age);
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", id='" + id + '\'' +
                ", age=" + age +
                '}';
    }

    public static void main(String[] args) {
        // Create a list of 100 employee objects
        List<Employee> employees = new ArrayList<>();
        employees.add(new Employee("John", "1001", 25));
        employees.add(new Employee("Jane", "1002", 30));
        employees.add(new Employee("Bob", "1003", 28));
        // ... and so on for the other 97 employees

        // Sort the list of employees based on age (ascending order)
        Collections.sort(employees);
        System.out.println(employees);
    }
}

Different ways of sorting an User Object


There are many ways to sort a java object but it is very hard to figure out which one is more efficient. Here is an example which describes different ways of executing sorting mechanism for User object based on age.

Try to run this application in you local machine to see which method is more efficient and good to use in our regular programming life.

package com.malliktalksjava.java8;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import static java.util.stream.Collectors.toList;

public class SortingExampleUser {

    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(new User("Ram", 28));
        userList.add(new User("Raj", 35));
        userList.add(new User("Rakesh", 31));
        userList.add(new User("Peter", 30));
        userList.add(new User("John", 25));
        userList.add(new User("Sri", 55));

        long starttime = System.currentTimeMillis();
        System.out.println("sortListUsingCollections : " + sortListUsingCollections(userList));
        System.out.println("Time Taken in Millis : " + (System.currentTimeMillis() - starttime));

        long starttime2 = System.currentTimeMillis();
        System.out.println("sortListUsingCollections : " + sortListUsingStreams(userList));
        System.out.println("Time Taken in Millis  2: " + (System.currentTimeMillis() - starttime2));

        long starttime3 = System.currentTimeMillis();
        System.out.println("sortListUsingCollections : " + sortUsingLambda(userList));
        System.out.println("Time Taken in Millis  2: " + (System.currentTimeMillis() - starttime3));


    }


    //using Collections.sort
    private static List<User> sortListUsingCollections(List<User> list){

        Collections.sort(list, Comparator.comparingInt(User::getAge));
        //Collections.reverse(list); // un comment if for descending order

        return list;
    }

    //using streams and comparator
    private static List<User> sortListUsingStreams(List<User> list){

        return list.stream()
                .sorted(Comparator.comparingInt(User::getAge))
                //.sorted(Comparator.comparingInt(User::getAge).reversed()) //-- for reverse order uncomment this line and comment above line
                .collect(toList());
    }

    //using lambda expressions
    private static List<User> sortUsingLambda(List<User> list){

        return list.stream()
                .sorted((User user1, User user2) -> user1.getAge() > user2.getAge() ? 1: 0)
                //.sorted((User user1, User user2) -> user1.getAge() < user2.getAge() ? 1: 0) - uncomment if reverse order needed
                .collect(toList());

    }
}

class User{
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

Algorithms in Java Interviews


In this post, we will see algorithm problems with their solutions which are asked during Java interviews.

How to check if a number is Palindrome?

void checkPalindrome(int n){
  int temp, sum = 0;
  int input=n;

  while(n>0) {
     temp = n%10;
     sum = (sum*10) + temp;
     n = n/10;
  }

  if(input == sum){
   System.out.println("Palindrome");
  } else {
   System.out.println("Not Palindrome");
  }
}

How to check if a number is Prime in Java8?

void checkPrime(int n) {
if(n > 1 && IntStream.range(2, n).noneMatch(i -> i%n==0)) {
System.out.println("Prime");
} else {
System.out.println("Non-Prime");
}
}

How to sort objects in reverse order in Java8?

Student student1 = new Student(372,"Venkat",1);
Student student2 = new Student(2,"Sachin",4);
Student student3 = new Student(2345,"Ganguly",6);
Student student4 = new Student(72,"Karthik",2);
List studlist = new CopyOnWriteArrayList();
studlist.add(student1);
studlist.add(student2);
studlist.add(student3);
studlist.add(student4);

// Iterate in Java8
studlist.forEach(s -> System.out.println(s.name));

// Sort by Ids
studlist.sort((Student s1,Student s2) -> s1.getId() - s2.getId());

// Sort by Rank in reverse Order
studlist.sort((Student s1,Student s2) -> s2.getRank() - s1.getRank());

Find second highest number in an Array?

int arr[] = {45,89, 29,1, 9, 100};
int highest = 0, secondHighest = 0;

for(int i=0; i<arr.length;i++) {   if(arr[i] > highest) {
     highest = arr[i];
  } else if(arr[i] > secondHighest) {
     secondHighest = arr[i];
  }
}

Find Nth highest Salary from a SQL Table?

SELECT MIN(SALARY) FROM EMPLOYEE
       WHERE SALARY IN (SELECT DISTINCT TOP N 
                               FROM EMPLOYEE ORDER BY SALARY desc);

Print Only Numerics from a String?

String sampleStr = "fdsha3430d3kdjafl0737434833";
String numericsOnlyStr = sampleStr.replaceAll("[^0-9]", "");

Print Duplicates in an Array?

for(int i=0;i<arr.length;i++) {
  for(int j=i+1; j< arr.length; j++) {
     if(arr[i] == arr[j]) {
           System.out.println(arr[j]);
     }
  }
}

Fetch Frequency of Elements repeated in an Array?

  Map<Integer, Integer> mp = new HashMap<>(); 
  
        // Iterating through array elements 
        for (int i = 0; i < n; i++) 
        { 
            if (mp.containsKey(arr[i]))  { 
                mp.put(arr[i], mp.get(arr[i]) + 1); 
            } else { 
                mp.put(arr[i], 1); 
            } 
        } 
        
        // Iterating through Map and Printing frequencies 
        for (Map.Entry<Integer, Integer> entry : mp.entrySet()) { 
            System.out.println(entry.getKey() + " " + entry.getValue()); 
        }

Find Triplets in an array whose sum is equal to n?

public class Triplets {
public static List<List> findTriplets(int[] numbers, int sum) {
List<List> tripletsCombo = new ArrayList<List>();
HashSet set = new HashSet();
List triplets = new ArrayList();

if (numbers.length == 0 || sum <= 0) {
   return tripletsCombo;
}

Arrays.sort(numbers);

for (int i = 0; i < numbers.length - 2; i++) {
int j = i + 1;
int k = numbers.length - 1;

while (j < k) {
   if (numbers[i] + numbers[j] + numbers[k] == sum) {
      String str = numbers[i] + "," + numbers[j] + "," +       numbers[k];
      // Check for the unique Triplet
      if (!set.contains(str)) {
               triplets.add(numbers[i]);
               triplets.add(numbers[j]);
               triplets.add(numbers[k]);
               tripletsCombo.add(triplets);
               triplets = new ArrayList();
               set.add(str);
     }
     j++;
     k--;
} else if (numbers[i] + numbers[j] + numbers[k] < sum) {    j++; } else { // numbers[i] + numbers[j] + numbers[k] > sum
   k--;
}
}
}

return tripletsCombo;
}

public static void main(String[] args) {
int[] numbers = { 2, 3, 1, 5, 4 };
int sum = 9;
List<List> triplets = findTriplets(numbers, sum);

if (triplets.isEmpty()) {
   System.out.println("No triplets are found");
} else {
   System.out.println(triplets);
}
}
}

How to check if two strings are Anagrams?

Two strings are called Anagrams if they contain same set of characters but in different order.  Examples:  “Astronomer – Moon starer”, “A gentleman – Elegant man”, “Dormitory – Dirty Room”, “keep – peek”.

void isAnagram(String input1, String input2) {
   //Removing all white spaces from s1 and s2
   String s1_nonSpaces = input1.replaceAll("\\s", "");
   String s2_nonSpaces = input2.replaceAll("\\s", "");

   boolean status = true;
   if(s1_nonSpaces.length() != s2_nonSpaces.length()) {
      status = false;
   } else {
      char[] s1Array = s1_nonSpaces.toLowerCase().toCharArray();
      char[] s2Array = s2_nonSpaces.toLowerCase().toCharArray();
      Arrays.sort(s1Array); 
      Arrays.sort(s2Array); 
      status = Arrays.equals(s1Array, s2Array);
   }
   System.out.print(status?"Anagrams":"Non-Anagrams");
}

Swap numbers without using temp/third variable?

void swapWithoutTemp(int a, int b) {
 a = a+b;
 b = a-b;
 a = a-b;
}

Find number of combinations for Sum of Two Elements from two arrays is equal to N?

We have two arrays of numbers, suppose we take one element from first array and another element from second array. Their sum should be equal to N(given number).

sumOfTwoElementsInTwoArrays() {
  int arr1[] = {4,8,10,12,7};
  int arr2[] = {6,90,34,45};

  int sumValue = 44; 
  HashSet complements = new HashSet();
  int pairCount = 0;

  for(int i=0;i<arr1.length;i++) {
    complements.add(arr1[i] - sumValue);
  }

  for(int j=0;j<arr1.length;j++) {
    if(complements.contains(arr2[j])) {
      pairCount++;
    }
 }

System.out.print("Number of pairs is "+pairCount);
}

First non repeated character in a String?

String str = "BANANA";
char firsNonRepeatedCharacter;
HashMap<Character, Integer> hmp = new HashMap<Character, Integer>();

for(int z=0;z<s.length();z++) {
  if(hmp.containsKey(str.charAt(z))) {
    hmp.put(str.charAt(z), hmp.get(str.charAt(z))+1);
  } else {
     hmp.put(str.charAt(z), 1);
  }
}

Set characterSet = hmp.keySet();
for(Character c:characterSet){
  if(hmp.get(c).toString()equals("1")) {
    firsNonRepeatedCharacter = c;
    break;
  }
}

Find the number of occurrence of an element in an array using Java8?

int b[] = {1,2,34,1};

List bList = Arrays.stream(b).boxed().collect(Collectors.toList());

System.out.println(bList.stream().filter(z -> z.toString().equalsIgnoreCase("1")).count());

100 doors toggle open/close

There are 100 doors in a row, all doors are initially closed. A person walks through all doors multiple times and toggle (if open then close, if close then open) them in following way:

In first walk, the person toggles every door, In second walk, the person toggles every second door, i.e., 2nd, 4th, 6th, 8th, …, In third walk, the person toggles every third door, i.e. 3rd, 6th, 9th, …

Find in nth walk, what will be the status of all doors

doorsOpenClosed(int no_of_walks) {
  int door_id, walk_id;
  int doors[] = new int[101];
  for(int i=0;i<100;i++) {
   doors[i] = 0;
  }

for (walk_id = 1; walk_id <= no_of_walks; walk_id++) {
  for (door_id = walk_id; door_id <= 100; door_id += walk_id) {
    if(door_id%walk_id == 0) {
      doors[door_id]=(doors[door_id] == 0)?1:0;
    }
  }
}

for (int j = 0; j <= 100; j++) {
 if(doors[j] == 1) {
   System.out.println("Open Door number::::"+j);
 }
}

}

Core Java and Java 8 Concepts


In this post, you will see some important Core Java/Java 8 concepts related to Collections, Exception Handling, Multi-threading, Concurrency etc.

Comparable Vs Comparator

Comparable Comparator
Comparable provides a single sorting sequence. In other words, Sorting of  collection is based on a single property of a class such as ID, ItemName or quantity etc. The Comparator provides multiple sorting sequences. In other words, sorting of collection can based of multiple properties such as ID, ItemName, and quantity etc.
Comparable affects the original class, i.e., the actual class is modified. Comparator doesn’t affect the original class, i.e., the actual class is not modified.
Comparable provides compareTo() method to sort elements. Comparator provides compare() method to sort elements.
Comparable is from  java.lang package. A Comparator is from java.util package.
Sorting list of Objects-Comparable type can be done using Collections.sort(List) method. Sorting list of Objects-Comparator type by Collections.sort(List, Comparator) method.

JVM Architecture

Different types of Class Loaders?

  • Bootstrap class Loader
  • Extensions class Loader
  • System class Loader

Boostrap class loader loads the classes from jdk/jre/lib/rt.jar. Extension class loader loads the classes from jdk/lib/ext folder jars. System class loader loads the classes from CLASSPATH.

Difference between ClassNotFoundException and NoClassDefFoundError

  • ClassNotFoundException is an Exception, while NoClassDefFoundError is an Error.
  • ClassNotFoundException occurs when CLASSPATH does not get updated with required JAR files while NoClassDefFoundError occurs when required class definition is not present at runtime.

Example for NoClassDefFoundError :

class Shape {
  public void draw() {
     System.out.println("Drawing Shape!");
  }
}

public class DrawingApp {
  public void draw() {
     System.out.println("Drawing Shape!");
  }
  public static void main(String[] args) {
     Shape shape = new Shape();
     shape.draw();
  }
}

After compilation, Shape.class and DrawApp.class are generated, If Shape.class is deleted and DrawApp is run then NoClassDefFoundError is thrown.

Difference between ConcurrentHashMap and SynchronizedMap

  • ConcurrentHashMap is designed for concurrency and improves performance while Collections.synchronizedMap(map) which is non-synchronized by sort can be synchronized by applying a wrapper using Collections.synchronizedMap(map).
  • ConcurrentHashMap doesn’t support null keys or null values while synchronized HashMap supports one null key.
  • Locking in SynchronizedMap is at object level, so read/write operations performance is slower.
  • Locking in ConcurrentHashMap is at a much finer granularity at a hashmap bucket level.

Differences betwen equals() and hashcode() methods

equals() and hashCode() are methods present in Object class and hashCode method should not be used to check if two object references are same. Reason: hashCode just returns int value for an Object, even two different objects can have same hashCode integer. The value returned by hashCode() is the object’s hash code, which is the object’s memory address in hexadecimal. equals() checks if the two object references are same. If two objects are equal then their hashCode must be the same, but the reverse is not true.

O(1) vs O(n) vs O(log n)

These are measures of time complexity of running a piece of code.

O(1) – if execution time is constant, it requires the same amount of time regardless of the size. Example:  array – accessing any element int i = a[0];

O(n) – if execution time is directly proportional to the size.  Example: Linear search for an element has a time complexity of O(n).

O(log n) – if execution time is proportional to the logarithm of the input size. Example: Performing Binary Search on array of elements

Changes to HashMap in Java8

  • In case of Hash collision entry objects are stored as a node in a LinkedList and equals() method is used to compare keys. That comparison to find the correct key with in a linked-list is a linear operation so in a worst case scenario the complexity becomes O(n).
  • To address this issue, Java 8 hash elements use Balanced Tree instead of LinkedList after a certain threshold is reached. Which means HashMap starts with storing Entry objects in linked list but after the number of items in a hash becomes larger than a certain threshold, the hash will change from using a LinkedList to a Balanced Tree, which will improve the worst case performance from O(n) to O(log n).

Fail Fast Vs Fail Safe Iterators

Fail-Fast Iterators Fail-Safe Iterators
Fail-Fast iterators doesn’t allow  modifications of a collection while iterating over it. Fail-Safe iterators allow modifications of a collection while iterating over it.
Concurrent Modification Exception is thrown if a collection is modified while iterating over it. These iterators don’t throw any exceptions if a collection is modified while iterating over it.
They use original collection to traverse over the elements of the collection. They use copy of the original collection to traverse over the elements of the collection.
These iterators don’t require extra memory. These iterators require extra memory to clone the collection.
Ex : Iterators returned by ArrayList, Vector, HashMap. Ex : Iterator returned by CopyOnWriteArrayList, ConcurrentHashMap.

Difference between map() and flatmap() in Java8

Lets suppose we are applying map and flatmap on stream of streams. Example given below

Stream<List<Character>> stream = Stream.of({'a','b'},{'c','d'})

with map:  For input Stream of two lists {‘a’,’b’} and {‘c’,’d’}, output will be {{‘a’,’b’},{‘c’,’d’}} .Here two lists are placed inside a list, so the output will be list containing lists

With flat map: For input Stream of two lists {‘a’,’b’} and {‘c’,’d’}, output will be {{a,b,c,d}} .Here two lists are flattened and only the values are placed in list, so the output will be list containing only elements

What are Functional interfaces how we can define them?

Functional interfaces are interfaces which have only one single abstract method in it. Example:  Runnable Interface since it has only single abstract method, run().

From Java8, we can use @FunctionalInterface to define a functional interface. Although this annotation is optional, once it is used then declaring more than one abstract method will throw compile time error.

Rules of Method Overloading and Method Overriding

There are specific rules while we implement method overloading and overriding in Java with regards to increasing/decreasing visibility of methods of parent class in child class and throwing Checked Exceptions in child class. Complete rules are posted in this below link

https://malliktalksjava.com/2020/05/29/rules-of-method-overloading-and-overriding/

Exception Handling flow having return statements in try/catch/finally blocks

  • Once try block encounters a return statement, the flow immediately transfers to finally block. Let say,it prints “print statement from finally”.
  • Upon the completion of finally block execution, control goes back to the return statement in the try block and returns “returning from try block”.
  • If finally block has a return statement, then the return statements from try/catch blocks will be overridden.

Exception Handling flow while exceptions thrown in catch/finally blocks

  • If the catch block completes normally, then the finally block is executed. Then there is a choice:
  • If the finally block completes normally, then the try statement completes normally. If the finally block completes abruptly for any reason, then the try statement completes abruptly for the same reason.
  • If the catch block completes abruptly for reason R, then the finally block is executed. Then there is a choice:
    If the finally block completes normally, then the try statement completes abruptly for reason R.
    If the finally block completes abruptly for reason S, then the try statement completes abruptly for reason S (and reason R is discarded).

FixedThreadPool vs CachedThreadPool vs ScheduledThreadPool

  • newCachedThreadPool(): creates an expandable thread pool executor. New threads are created as needed, and previously constructed threads are reused when they are available. Idle threads are kept in the pool for one minute. This executor is suitable for applications that launch many short-lived concurrent tasks.
  • newFixedThreadPool(int n): creates an executor with a fixed number of threads in the pool. This executor ensures that there are no more than n concurrent threads at any time. If additional tasks are submitted when all threads are active, they will wait in the queue until a thread becomes available. If any thread terminates due to failure during execution, it will be replaced by a new one. The threads in the pool will exist until it is explicitly shutdown. Use this executor if you and to limit the maximum number of concurrent threads.
  • newScheduledThreadPool(int corePoolSize): creates an executor that can schedule tasks to execute after a given delay, or to execute periodically. Consider using this executor if you want to schedule tasks to execute concurrently.

What is ThreadLocal?

ThreadLocal class provides thread-local variables. It enables you to create variables that can only be read and write by the same thread. If two threads are executing the same code and that code has a reference to a ThreadLocal variable then the two threads can’t see the local variables of each other.

Diffence Volatile vs AtomicInteger?

volatile keyword is used on variables to solve the visibility problem in multi-threaded environment.  AtomicInteger is used if we perform compound operations(incrementing(i++) decrementing(i–)) on variables.

volatile is used on boolean flags, AtomicInteger is used for counters.

 

Differences between yield, join, & sleep

yield() method pauses the currently executing thread temporarily for giving a chance to the remaining waiting threads of the same priority to execute. If there is no waiting thread or all the waiting threads have a lower priority then the same thread will continue its execution. The yielded thread when it will get the chance for execution is decided by the thread scheduler whose behavior is vendor dependent.

join() If any executing thread t1 calls join() on t2 i.e; t2.join() immediately t1 will enter into waiting state until t2 completes its execution.

sleep() Based on our requirement we can make a thread to be in sleeping state for a specified period of time

Differences between Runnable and Callable

  • Runnable object does not return a result whereas a Callable object returns a result.
  • Runnable object cannot throw a checked exception wheras a Callable object can throw an exception.
  • The Runnable interface has been around since Java 1.0 whereas Callable was only introduced in Java 1.5.
class ThreadA implements Runnable {
@Override
public void run() { }
}

public class ThreadB implements Callable<String> {
@Override
public String call() throws Exception {
return "Thread B ran Successfully";
}
}

What is Semaphore in concurrency?

Semaphore is used to restrict the entry to a service to a fixed number of threads at a given time. This is generally used on slow services to make it available for fixed number of requests.

Semaphore semaphore = new Semphore(no_of_permits);

In run() method of a thread, we can use semaphore.acquire() before accessing the slow service and semaphore.release() after to ensure fixed number (defined as no_of_permits) of threads are eligible to access it.

Difference between CyclicBarrier and CountDownLatch?

Both CyclicBarrier and CountDownLatch are used in Multi threading scenario where one Thread waits for one or more Thread to complete their job before it continues processing but main difference between two is that, you can not reuse same CountDownLatch instance once count reaches to zero and latch is open, on the other hand, CyclicBarrier can be reused by resetting Barrier, Once barrier is broken.

  • Initialization of countdownlatch is CountDownLatch latch = new CountDownLatch(4);
  • Method used to countdown (generally used inside run method of thread at a specific point) is latch.countDown()
  • Method used to await a specific thread till countdown number completes is latch.await()
  • Phaser can be used either to perform functionality of both CyclicBarrier and CountDownLatch

References:
https://docs.oracle.com/en/java/javase/
https://stackoverflow.com/
https://dzone.com/

String.join() Example – Java 8


Java 8 has String.join() method where first parameter is separator and then you can pass either multiple strings or some instance of Iterable having instances of strings as second parameter. Here is the sample program:

package in.mallikatalksjava.java8;
import java.time.ZoneId;

public class StringJoinDemo {
   public static void main(String[] args) {
	String joined = String.join("", "mallik", "talks", "java",".in");
	System.out.println(joined);
		
	String directory = String.join("/", "C:", "java", "programs");
	System.out.println(directory);

	String ids = String.join(", ", ZoneId.getAvailableZoneIds());
	System.out.println(ids);
	}
}

Output:
malliktalksjava.in
C:/java/programs
Asia/Aden, America/Cuiaba, Etc/GMT+9, Etc/GMT+8, Africa/Nairobi, America/Marigot, Asia/Aqtau ....etc.