LogoTransferPilot

Java Streams in C# (LINQ)

Overview

Java Streams and C# LINQ both offer a declarative way to process collections. While they share many similarities, there are differences in syntax and functionality. Below, we transfer our knowledge of Java Streams into C# LINQ.

Basic Setup

In Java, you typically import the necessary classes from the java.util.stream package. In C#, LINQ is available through the System.Linq namespace.

import java.util.List;
import java.util.stream.Collectors;
import java.util.Arrays;

List<String> items = Arrays.asList("apple", "banana", "cherry");

Four main concepts: Filtering, mapping, sorting and reducing

Concept #1: Filtering

Filtering allows you to select elements from a collection based on a condition. In both Java Streams and C# LINQ, this is done using the filter and Where methods respectively.

List<String> items = List.of("apple", "banana", "cherry", "date");

List<String> filtered = items.stream()
    .filter(item -> item.startsWith("a"))
    .collect(Collectors.toList());
// Result: ["apple"]

Pretty similar so far, huh?

via GIPHY

Concept #2: Mapping

Mapping transforms each element in a collection into another form. Java Streams use the map method, while C# LINQ uses the Select method.

List<String> items = List.of("apple", "banana", "cherry", "date");

List<String> mapped = items.stream()
    .map(item -> item.toUpperCase())
    .collect(Collectors.toList());
// Result: ["APPLE", "BANANA", "CHERRY", "DATE"]

Concept #3: Sorting

Sorting arranges the elements in a collection in a particular order. Java Streams use the sorted method, while C# LINQ uses the OrderBy method.

List<String> items = List.of("banana", "apple", "cherry", "date");

List<String> sorted = items.stream()
    .sorted()
    .collect(Collectors.toList());
// Result: ["apple", "banana", "cherry", "date"]

Concept #4: Reducing

Reducing combines all elements in a collection into a single result using an accumulator function. Java Streams use the reduce method, while C# LINQ uses the Aggregate method.

List<Integer> items = List.of(1, 2, 3, 4);

int sum = items.stream()
    .reduce(0, Integer::sum);
// Result: 10

Let's talk about types..

In Java, Streams work primarily with objects, and primitive types are handled using specialized streams like IntStream, LongStream, and DoubleStream. In C#, LINQ works with any IEnumerable<T> and provides similar functionality for primitive types using the same interface:

import java.util.stream.IntStream;


int sum = IntStream.of(1, 2, 3, 4, 5)  // IntStream
    .filter(n -> n % 2 == 0)           // IntStream
    .sum();                            // int

IEnumerable<T> and IOrderedEnumerable<T>

In C#, IEnumerable<T> is a generic interface that represents a sequence of elements. It is the base interface for all collections that can be enumerated. It is similar to the Stream<T> in Java, which also represents a sequence of elements and supports aggregate operations. Both are used to process collections in a declarative manner, though Stream<T> is more specifically tied to Java's Stream API for functional-style operations on sequences.

IOrderedEnumerable<T> is an interface that represents a sorted sequence of elements. It extends IEnumerable<T> and is typically returned by sorting methods such as OrderBy and ThenBy. In Java, the equivalent would be a Stream<T> that has been sorted using the sorted method. Both interfaces represent sorted sequences and are used to maintain the order of elements as defined by the sorting criteria.

Complex Queries

Now let's look at a few complex examples that combine filtering, mapping, and reducing:

In this example, we filter the list to include only items that contain the letter "a", sort them alphabetically, map them to their lengths, and then sum these lengths.

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int result = numbers.stream()           // Stream<Integer>
    .filter(n -> n % 2 == 0)            // Stream<Integer>
    .sorted(Collections.reverseOrder()) // Stream<Integer>
    .map(n -> n * 2)                    // Stream<Integer>
    .reduce(0, Integer::sum);           // int
// Result: 60

This example filters for even numbers, sorts them in descending order, doubles each number, and then sums the results.

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

int result = numbers.stream()           // Stream<Integer>
    .filter(n -> n % 2 == 0)            // Stream<Integer>
    .sorted(Collections.reverseOrder()) // Stream<Integer>
    .map(n -> n * 2)                    // Stream<Integer>
    .reduce(0, Integer::sum);           // int
// Result: 60

In this more complex example, we filter people older than 20, sort them by name, map them to their ages, and sum the ages.

class Person {
    String name;
    int age;
    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    int getAge() {
        return age;
    }
    String getName() {
        return name;
    }
}

List<Person> people = List.of(
    new Person("Alice", 30),
    new Person("Bob", 20),
    new Person("Charlie", 25),
    new Person("David", 35)
);

int result = people.stream()                       // Stream<Person>
    .filter(person -> person.getAge() > 20)        // Stream<Person>
    .sorted(Comparator.comparing(Person::getName)) // Stream<Person>
    .map(Person::getAge)                           // Stream<Integer>
    .reduce(0, Integer::sum);                      // int
// Result: 90

Conclusion

Java StreamsC# LINQ
filterWhere
mapSelect
sorted()OrderBy(item => item)
collect(Collectors.toList())ToList()
reduceAggregate

Both Java Streams and C# LINQ provide powerful capabilities for processing collections. While the syntax and some features differ, the core concepts are very similar. If you are interested in leveraging your existing Java knowledge to learn C# efficiently and in-depth, check out our Java to C# Transfer Course.

Happy coding!


Related glossary entry: Functional-style data processing

Share: