Jointure implementation in Java 21

Sometimes when you want to manage your code efficiency you may need to optimize some Java implementations.

Jointure implementation in Java 21 may be such a case.

Underneath you will find my proposal to implement a jointure in Java 21 environment. This is a generic code and may be optimized in many way for your implementation needs. It use System.Linq but remediation can be done very easily to not depend on this library.

package joins;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class Extensions {

    public static <TL, TR, TK, TJ> List<TJ> innerJoin(List<TL> lefts, List<TR> rights,
                                                      Function<TL, TK> onLeftKey, Function<TR, TK> onRightKey,
                                                      BiFunction<TL, TR, TJ> joinBuilder) {
        Map<TK, List<TR>> rightGroupsByKey = rights.stream()
                .collect(Collectors.groupingBy(onRightKey));

        return lefts.stream()
                .flatMap(left -> {
                    TK key = onLeftKey.apply(left);
                    List<TR> matchedRights = rightGroupsByKey.getOrDefault(key, Collections.emptyList());
                    return matchedRights.stream().map(right -> joinBuilder.apply(left, right));
                })
                .collect(Collectors.toList());
    }

    public static <TL, TR, TK> List<Pair<TL, TR>> leftJoin(List<TL> lefts, List<TR> rights,
                                                           Function<TL, TK> onLeftKey, Function<TR, TK> onRightKey) {
        return leftJoin(lefts, rights, onLeftKey, onRightKey, Pair::new, null);
    }

    public static <TL, TR, TK> List<Pair<TL, TR>> leftJoin(List<TL> lefts, List<TR> rights,
                                                           Function<TL, TK> onLeftKey, Function<TR, TK> onRightKey,
                                                           TR defaultJoined) {
        return leftJoin(lefts, rights, onLeftKey, onRightKey, Pair::new, defaultJoined);
    }

    public static <TL, TR, TK, TJ> List<TJ> leftJoin(List<TL> lefts, List<TR> rights,
                                                     Function<TL, TK> onLeftKey, Function<TR, TK> onRightKey,
                                                     BiFunction<TL, TR, TJ> joinBuilder, TR defaultJoined) {
        Map<TK, List<TR>> rightGroupsByKey = rights.stream()
                .collect(Collectors.groupingBy(onRightKey));
        return lefts.stream()
                .flatMap(left -> {
                    TK key = onLeftKey.apply(left);
                    List<TR> matchedRights = rightGroupsByKey.getOrDefault(key, Collections.singletonList(defaultJoined));
                    return matchedRights.stream().map(right -> joinBuilder.apply(left, right));
                })
                .collect(Collectors.toList());
    }

    public static <TL, TR, TK> List<Pair<TL, TR>> fullJoin(List<TL> lefts, List<TR> rights,
                                                           Function<TL, TK> leftKeyFunc, Function<TR, TK> rightKeyFunc,
                                                           TL defaultLeft, TR defaultRight) {
        List<Pair<TL, TR>> leftJoinResult = leftJoin(lefts, rights, leftKeyFunc, rightKeyFunc, defaultRight);
        Set<TK> joinedRightKeys = leftJoinResult.stream()
                .filter(pair -> pair.getRight() != defaultRight)
                .map(pair -> rightKeyFunc.apply(pair.getRight()))
                .collect(Collectors.toSet());

        List<Pair<TL, TR>> defaultLeftAndMissingRights = rights.stream()
                .filter(right -> !joinedRightKeys.contains(rightKeyFunc.apply(right)))
                .map(right -> new Pair<>(defaultLeft, right))
                .toList();

        List<Pair<TL, TR>> fullJoinResult = new ArrayList<>(leftJoinResult);
        fullJoinResult.addAll(defaultLeftAndMissingRights);

        return fullJoinResult;
    }
}

package joins;

import java.util.Objects;

public class Pair<L, R> {
    private final L left;
    private final R right;

    public Pair(L left, R right) {
        this.left = left;
        this.right = right;
    }

    public L getLeft() {
        return left;
    }

    public R getRight() {
        return right;
    }

    @Override
    public int hashCode() {
        return left.hashCode() ^ right.hashCode();
    }

    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Pair)) return false;
        Pair<?, ?> p = (Pair<?, ?>) o;
        return Objects.equals(p.left, left) && Objects.equals(p.right, right);
    }

    @Override
    public String toString() {
        return "(" + left + ", " + right + ")";
    }
}

Underneath the related unit tests

package joins;


import java.util.Arrays;
import java.util.List;

import org.testng.annotations.Test;

import static org.testng.AssertJUnit.*;

public class JoinOperationsTest {

    @Test
    public void testInnerJoin() {
        List<Person> persons = Arrays.asList(
                new Person(1, "Alice", 1),
                new Person(2, "Bob", 2),
                new Person(3, "Charlie", 3)
        );

        List<Department> departments = Arrays.asList(
                new Department(1, "HR"),
                new Department(2, "Engineering"),
                new Department(3, "Marketing")
        );

        List<Pair<Person, Department>> results = Extensions.innerJoin(
                persons, departments,
                Person::getDepartmentId, Department::getId,
                Pair::new
        );

        assertEquals(3, results.size());
        assertTrue(results.contains(new Pair<>(persons.get(0), departments.get(0))));
    }

    @Test
    public void testLeftJoinWithNoMatchingRight() {
        List<Person> persons = Arrays.asList(
                new Person(1, "Alice", 1),
                new Person(2, "Bob", 4) // No matching department
        );

        List<Department> departments = Arrays.asList(
                new Department(1, "HR")
        );

        List<Pair<Person, Department>> results = Extensions.leftJoin(
                persons, departments,
                Person::getDepartmentId, Department::getId,
                Pair::new, null
        );

        assertEquals(2, results.size());
        assertNotNull(results.get(0).getRight()); // Should have a department
        assertNull(results.get(1).getRight()); // Should not have a department
    }
}
package joins;

public class Department {
    private int id;
    private String name;

    // Constructor, getters and setters
    public Department(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

package joins;

public class Person {
    private int id;
    private String name;
    private int departmentId;

    // Constructor, getters and setters
    public Person(int id, String name, int departmentId) {
        this.id = id;
        this.name = name;
        this.departmentId = departmentId;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public int getDepartmentId() {
        return departmentId;
    }

    public void setDepartmentId(int departmentId) {
        this.departmentId = departmentId;
    }
}

Github implementation in java : https://github.com/cosXsinX/javaJoins