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!

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 +
                '}';
    }
}

Example Program: Search Word in Folder files and print output


To Search a word in in list of files available in Folder, you need to find the list of files first and then scan each and every for required word. Below is the sample program to find the a given word Java in D:\\test folder of files.

package in.javatutorials;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import java.util.regex.MatchResult;

/**
 * Search for the files in a folder and prints all file details.
 */
public class WordCrawlerInFolder {

private static String directoryPath = "D://test";
private static String searchWord = "Java";

public WordCrawlerInFolder() {
super();
}

public static void main(String[] args) {
   WordCrawlerInFolder crawler = new WordCrawlerInFolder();
    File directory = new File(directoryPath);

    if (directory == null || !directory.exists()) {
           System.out.println("Directory doesn't exists!!!");
           return;
    }
    crawler.directoryCrawler(directory, searchWord);
}

/**
* Gets all the file and directories and prints accordingly
* @param directory
* Directory path where it should search
*/
public void directoryCrawler(File directory, String searchWord) {

// Get List of files in folder and print
File[] filesAndDirs = directory.listFiles();

// Print the root directory name
//System.out.println("-" + directory.getName());

// Iterate the list of files, if it is identified as not a file call
// directoryCrawler method to list all the files in that directory.
for (File file : filesAndDirs) {

if (file.isFile()) {
searchWord(file, searchWord);
//System.out.println(" |-" + file.getName());
} else {
directoryCrawler(file, searchWord);
}
}
}

/**
* Search for word in a given file.
* @param file
* @param searchWord
*/
private void searchWord(File file, String searchWord){
Scanner scanFile;
try {
scanFile = new Scanner(file);
while (null != scanFile.findWithinHorizon("(?i)\\b"+searchWord+"\\b", 0)) {
MatchResult mr = scanFile.match();
System.out.printf("Word found : %s at index %d to %d.%n", mr.group(),
mr.start(), mr.end());
}
scanFile.close();
} catch (FileNotFoundException e) {
System.err.println("Search File Not Found !!!!! ");
e.printStackTrace();
}
}
}

We have used some escape characters in above class searchWord() method, below is the notation for the same.

  1. (?i) turn on the case-insensitive switch
  2. \b means a word boundary
  3. java is the string searched for
  4. \b a word boundary again.

If search term contain special characters, it would be suggested to use \Q and \E around the string, as it quotes all characters in between. Make sure the input doesn’t contain \E itself.

Other Useful Links:

Javac/Java searching algorithm for other classes

Example program to reverse a Number in Java

How to find count of duplicates in a List

Threads Interview questions in Java

Example Java Program to Search Files in a Folder


Below Java Program lists the file names and directory names available in given folder. To do this implementation , we can get the files list in a folder using File class available in Java API. Iterate the files one by one and write the file name on to console.

If it is identified as a directory instead of a file, then iterate the process as mentioned in directoryCrawler() method in the below class.

package in.javatutorials;

import java.io.File;

/**
* @author malliktalksjava
*
* Search for the files in a folder and prints all file details.
*
*/
public class FolderCrawler {

private static String directoryPath = “D://test”;

/**
* Creating constructor
*/
public FolderCrawler() {
super();
}

/**
* main method
*
* @param ags
*/
public static void main(String[] args) {
FolderCrawler crawler = new FolderCrawler();

File directory = new File(directoryPath);

if (directory == null || !directory.exists()) {
System.out.println(“Directory doesn’t exists!!!”);
return;
}

crawler.directoryCrawler(directory);
}

/**
* Gets all the file and directories and prints accordingly
*
* @param directory
* Directory path where it should search
*/
public void directoryCrawler(File directory) {

// Get List of files in folder and print
File[] filesAndDirs = directory.listFiles();

// Print the root directory name
System.out.println(“-” + directory.getName());

// Iterate the list of files, if it is identified as not a file call
// directoryCrawler method to list all the files in that directory.
for (File file : filesAndDirs) {

if (file.isFile()) {
System.out.println(” |-” + file.getName());
} else {
directoryCrawler(file);

}
}// end of for

}// End of directory Crawler
}

 

Other Useful Links:

Javac/Java searching algorithm for other classes

Example program to reverse a Number in Java

How to find count of duplicates in a List

Threads Interview questions in Java

Bubble Sort Example in JAVA


package in.malliktalksjava;

/**
* @author malliktalksjava
*
*/
public class BubbleSortExample {

private static int[] input = { 4, 2, 9, 6, 23, 11, 44, 0 };

public static void bubbleSort(int arr[]) {
 int count = array.length;
 int var;
 for (int i = count; i >= 0; i--) {
   for (int j = 0; j  array[j+1]) {
          swapNumbers(temp2, var, array);
      }
   }
 printNumbers(array);
 }
}

private static void swapNumbers(int var1, int var2, int[] array) {
   int temp = array[var1];
   array[var1] = array[var2];
   array[var2] = temp;
}

private static void printNumbers(int[] input) {
  for (int i = 0; i < input.length; i++) {
    System.out.println(input[i] + ", ");
  }
}
}

Other Useful Links:

Selection Sort Example Program in JAVA

Insertion Sort Example program in JAVA

Quick Sort Example Program in Java

Merge Sort Example in Java

Number series example program using JAVA


package in.malliktalksjava;

/**
* @author malliktalksjava.in
*
* This program prints the numbers in below pattern
* 1
* 123
* 12345
* 1234567
* 12345
* 123
* 1
*/
class PrintingNumberPatterns{

public static void main(String[] args){
printNumberSeries();
}

/**
* Print the numbers in assending and decending order by iterating it.
*/
private static void printNumberSeries() {
for (int i = 1; i <= 7; i += 2) {
for (int j = 1; j <= i; j++) {
System.out.print(j);
}
System.out.println();
}

for (int i = 5; i >= 1; i -= 2) {
for (int j = 1; j < i + 1; j++) {
System.out.print(j);
}
System.out.println();
}
}
}

Example program to reverse a Number in Java


 

package in.javatutorials;

public class ReverseNumber {

public static void main(String[] args) {
System.out.println(“The reversed number is ” + reverse(1234));
}

public static int reverse(int input) {
int result = 0;
int rem;
while (input > 0) {
rem = input % 10;
input = input / 10;
result = result * 10 + rem;
}
return result;
}
}

 

Avoid nested loops using Collection Framework in Java


High performance is essential for any software implemented in any programming language. And, loops plays major role in this regard. This post explains how to avoid the loops using Java’s Collection framework.

Below are the two Java programs to understand how the performance could be increased using the Collection framework.

Using nested loops

package in.javatutorials;

/**
* Finds out the Duplicates is String Array using Nested Loops.
*/
public class UsingNesteadLoops {
  private static String[] strArray = { "Cat", "Dog", "Tiger",     "Lion", "Lion" };

  public static void main(String[] args) {
   isThereDuplicateUsingLoops(strArray);
  }

  /**
   * Iterates the String array and finds out the duplicates 
   */
   public static void isThereDuplicateUsingLoops(String[]     strArray) {

   boolean duplicateFound = false;
   int loopCounter = 0;

   for (int i = 0; i < strArray.length; i++) {
   String str = strArray[i];
   int countDuplicate = 0;

   for (int j = 0; j < strArray.length; j++) {
      String str2 = strArray[j];
      if (str.equalsIgnoreCase(str2)) {
         countDuplicate++;
      }
      if (countDuplicate > 1) {
         duplicateFound = true;
         System.out.println("Duplicates Found for " + str);
      }
      loopCounter++;
   }// end of inner nested for loop

   if (duplicateFound) {
    break;
   }
}// end of outer for loop

System.out.println("Looped " + loopCounter + " times to find the result");
}

}

If we run the above program, it will be looped 20 times to find out the duplicates in the string array which has the length of 5. Number of loops increases exponentially depending on size of array, hence the performance takes a hit. These are not acceptable to use in applications which require high performance.

Without using nested loops

package in.javatutorials;

import java.util.HashSet;
import java.util.Set;

/**
* Finds out the Duplicates is String Array using Collection.
*/
public class AvoidNesteadLoopsUsingCollections {

private static String[] strArray = { "Cat", "Dog", "Tiger", "Lion", "Lion" };

public static void main(String[] args) {
 isThereDuplicateUsingSet(strArray);
}

/**
* Iterates the String array and finds out the duplicates
*/
public static void isThereDuplicateUsingSet(String[] strArray) {
  boolean duplicateFound = false;
  int loopCounter = 0;
  Set setValues = new HashSet();

  for (int i = 0; i < strArray.length; i++) {
    String str = strArray[i];

    if(setValues.contains(str)){
        duplicateFound = true;
        System.out.println("Duplicates Found for " + str);
    }
    setValues.add(str);
    loopCounter++;

    if (duplicateFound) {
       break;
    }
   }// end of for loop

   System.out.println("Looped " + loopCounter + " times to find the result");
 }

}
  • Above approach takes only 5 loops to identify the duplicates in the same array.
  • It is more readable , easier to maintain and performs better.
  • If you have an array with 1000 items, then nested loops will loop through 999000 times and utilizing a collection will loop through only 1000 times.

Other Useful links: