I believe that the most concise and idiomatic way to approach this would be to use Tagir Valeev's answer, but allow an optional third param.
This third param would be a BiFunction that will combine the left and right items in each of the respective lists.
Here are the relevant imports:
import java.util.AbstractMap.SimpleEntry; import java.util.List; import java.util.function.BiFunction; import java.util.stream.IntStream;
Now, here is the implementation of ListUtils.zip with two method signatures:
<L,R> zip(List<L>, List<L>): List<SimpleEntry<L,R>> <L,R,E> zip(List<L>, List<L>, BiFunction<L,R,E>): List<SimpleEntry<L, R>>
/** Utility class for working with lists. */ public class ListUtils { /** * Zips two lists into a list of {@link SimpleEntry} pairs. * * @param left the first list * @param right the second list * @param <L> the type of items in the first list * @param <R> the type of items in the second list * @return a list of {@link SimpleEntry} pairs containing items from both lists */ public static <L, R> List<SimpleEntry<L, R>> zip(List<L> left, List<R> right) { return zip(left, right, SimpleEntry::new); } /** * Zips two lists into a list of elements produced by the given combiner function. * * @param left the first list * @param right the second list * @param combiner the function that combines items from both lists * @param <L> the type of items in the first list * @param <R> the type of items in the second list * @param <E> the type of items in the resulting list * @return a list of items of type {@code Out} produced by combining items from the input lists */ public static <L, R, E> List<E> zip(List<L> left, List<R> right, BiFunction<L, R, E> combiner) { return IntStream.range(0, Math.min(left.size(), right.size())) .mapToObj(i -> combiner.apply(left.get(i), right.get(i))) .toList(); } }
To demonstrate the variant that takes a bi-function, we can create our own Pair class implementation.
/** * A simple record to represent a pair of elements. * * @param <L> the type of the left element * @param <R> the type of the right element */ record Pair<L, R>(L left, R right) { public static <L, R> Pair<L, R> of(L left, R right) { return new Pair<>(left, right); } }
Here is how you would use both variants:
public static void main(String[] args) { List<Integer> list1 = List.of(1, 2, 3); List<String> list2 = List.of("a", "b", "c"); // Default implementation List<SimpleEntry<Integer, String>> zipped = zip(list1, list2); System.out.println(zipped); // Custom implementation with Pair List<Pair<Integer, String>> zipped2 = zip(list1, list2, Pair::of); System.out.println(zipped2); }
Here is the output:
[1=a, 2=b, 3=c] [Pair[left=1, right=a], Pair[left=2, right=b], Pair[left=3, right=c]]