RFR: 8329760: Add indexOf(Predicate<? super E> filter) to java..util.List interface

ExE Boss duke at openjdk.org
Thu Apr 18 22:00:14 UTC 2024


On Fri, 5 Apr 2024 00:00:58 GMT, Evemose <duke at openjdk.org> wrote:

> **Subject**
> Addition of Predicate-based `indexOf` and `lastIndexOf` methods to `java.util.List`
> 
> **Motivation**
> The motivation behind this proposal is to enhance the functionality of the `List` interface by providing a more flexible way to find the index of an element. Currently, the `indexOf` and `lastIndexOf` methods only accept an object as a parameter. This limits the flexibility of these methods as they can only find the index of exact object matches.
> 
> The proposed methods would accept a `Predicate` as a parameter, allowing users to define a condition that the desired element must meet. This would provide a more flexible and powerful way to find the index of an element in a list.
> 
> Here is a brief overview of the changes made in this pull request:
> 
> 1. Added the `indexOf(Predicate<? super E> filter)` method to the `List` interface.
> 2. Added the `lastIndexOf(Predicate<? super E> filter)` method to the `List` interface.
> 3. Implemented these methods in all non-abstract classes that implement the `List` interface.
> 
> The changes have been thoroughly tested to ensure they work as expected and do not introduce any regressions. The test cases cover a variety of scenarios to ensure the robustness of the implementation.
> 
> For example, consider the following test case:
> 
> List<String> list = new ArrayList<>();
> list.add("Object one");
> list.add("NotObject two");
> list.add("NotObject three");
> 
> int index1 = list.indexOf(s -> s.contains("ct t"));
> System.out.println(index1); // Expected output: 1
> int index2 = list.lastIndexOf(s -> s.startsWith("NotObject"));
> System.out.println(index2); // Expected output: 2
> 
> 
> Currently, to achieve the same result, we would have to use a more verbose approach:
> 
> int index1 = IntStream.range(0, list.size())
>                      .filter(i -> list.get(i).contains("ct t"))
>                      .findFirst()
>                      .orElse(-1);
> System.out.println(index1); // Output: 1
> int index2 = IntStream.range(0, list.size())
>                          .filter(i -> list.get(i).startsWith("NotObject"))
>                          .reduce((first, second) -> second)
>                          .orElse(-1);
> System.out.println(index2); // Output: 2
> 
> 
> I believe these additions would greatly enhance the functionality and flexibility of the `List` interface, making it more powerful and user-friendly. I look forward to your feedback and am open to making any necessary changes based on your suggestions.
> 
> Thank you for considering this proposal.
> 
> Best regards

Newly added methods to existing widely‑implemented non‑sealed and non‑preview interfaces in `java.base` must be `default` to avoid source incompatibilities and runtime errors, so move the implementations from `AbstractList` to `List`:

I’m in favour of using `findIndex`, which also avoids the issue with `List<Predicate<…>>::indexOf` being ambiguous and having binary incompatible bridge methods.

src/java.base/share/classes/java/util/AbstractList.java line 29:

> 27: 
> 28: import java.util.function.Consumer;
> 29: import java.util.function.Predicate;

This is now unused:
Suggestion:

src/java.base/share/classes/java/util/AbstractList.java line 221:

> 219:         return -1;
> 220:     }
> 221: 

Suggestion:

src/java.base/share/classes/java/util/AbstractList.java line 269:

> 267:         return -1;
> 268:     }
> 269: 

Suggestion:

src/java.base/share/classes/java/util/List.java line 702:

> 700:      *         (<a href="Collection.html#optional-restrictions">optional</a>)
> 701:      */
> 702:     int lastIndexOf(Predicate<? super E> filter);

Suggestion:

    /**
     * Returns the index of the first occurrence of matching element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the lowest index {@code i} such that
     * {@code filter.test(get(i))},
     * or -1 if there is no such index.
     *
     * @implSpec
     * This implementation first gets a list iterator (with
     * {@code listIterator()}).  Then, it iterates over the list until a
     * matching element is found or the beginning of the list is reached.
     *
     * @param filter a predicate to search for
     * @return the index of the first occurrence of the specified element in
     *         this list, or -1 if this list does not contain the element
     * @throws NullPointerException if passed filter is null
     */
    default int indexOf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        ListIterator<E> it = listIterator();
        while (it.hasNext()) {
            E e = it.next();
            if (filter.test(e)) {
                return it.previousIndex();
            }
        }
        return -1;
    }

    /**
     * Returns the index of the last occurrence of matching element
     * in this list, or -1 if this list does not contain the element.
     * More formally, returns the highest index {@code i} such that
     * {@code Objects.equals(o, get(i))},
     * or -1 if there is no such index.
     *
     * @implSpec
     * This implementation first gets a list iterator that points to the end
     * of the list (with {@code listIterator(size())}).  Then, it iterates
     * backwards over the list until the matching element is found, or the
     * beginning of the list is reached.
     *
     * @param filter a predicate to search for
     * @return the index of the last occurrence of the specified element in
     *         this list, or -1 if this list does not contain the element
     * @throws NullPointerException if passed filter is null
     */
    default int lastIndexOf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        ListIterator<E> it = listIterator(size());
        while (it.hasPrevious()) {
            E e = it.previous();
            if (filter.test(e)) {
                return it.nextIndex();
            }
        }
        return -1;
    }

-------------

Changes requested by ExE-Boss at github.com (no known OpenJDK username).

PR Review: https://git.openjdk.org/jdk/pull/18639#pullrequestreview-1985396823
PR Comment: https://git.openjdk.org/jdk/pull/18639#issuecomment-2046339464
PR Review Comment: https://git.openjdk.org/jdk/pull/18639#discussion_r1555586424
PR Review Comment: https://git.openjdk.org/jdk/pull/18639#discussion_r1555192600
PR Review Comment: https://git.openjdk.org/jdk/pull/18639#discussion_r1555192654
PR Review Comment: https://git.openjdk.org/jdk/pull/18639#discussion_r1555194862


More information about the core-libs-dev mailing list