Repository: yegor256/cactoos
Pull request #1777 (2025-03-14 12:21:02)

Fix incorrect end predicate in Sliced iterator's second constructor
## Description
This PR fixes an issue with the `Sliced` iterator class where the second constructor was using an incorrect end predicate.

### Problem
The current implementation uses `any -> !iterator.hasNext()` which captures the iterator's state at construction time. This creates a fixed boolean value that doesn't change when the actual `hasNext()` method is called later, leading to incorrect iteration behavior.

### Solution
Changed the predicate to `any -> false` which means "never end because of the predicate". The actual end condition is properly checked in the `hasNext()` method with `this.iterator.hasNext()`.

## Related Issue
Fixes #[issue-number]


Patch:
diff --git a/src/main/java/org/cactoos/iterator/Sliced.java b/src/main/java/org/cactoos/iterator/Sliced.java
index a8af5428..f6e2ab6c 100644
--- a/src/main/java/org/cactoos/iterator/Sliced.java
+++ b/src/main/java/org/cactoos/iterator/Sliced.java
@@ -59,7 +59,7 @@ public final class Sliced<T> implements Iterator<T> {
      * @param iterator Decorated iterator
      */
     public Sliced(final int start, final Iterator<? extends T> iterator) {
-        this(start, any -> !iterator.hasNext(), iterator);
+        this(start, any -> false, iterator);
     }
 
     /**


Test patch:
diff --git a/src/test/java/org/cactoos/iterator/SlicedTest.java b/src/test/java/org/cactoos/iterator/SlicedTest.java
index ce621d55..0e4f432d 100644
--- a/src/test/java/org/cactoos/iterator/SlicedTest.java
+++ b/src/test/java/org/cactoos/iterator/SlicedTest.java
@@ -4,7 +4,11 @@
  */
 package org.cactoos.iterator;
 
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicInteger;
 import org.cactoos.iterable.IterableOf;
 import org.cactoos.scalar.Constant;
 import org.hamcrest.core.IsEqual;
@@ -99,4 +103,65 @@ final class SlicedTest {
             )
         ).affirm();
     }
+
+    /**
+     * Tests that hasNext() calls the underlying iterator only once.
+     */
+    @Test
+    void hasNextCallsOriginOnce() {
+        final List<String> list = new LinkedList<>();
+        list.add("1");
+        final AtomicInteger counter = new AtomicInteger(0);
+        final Iterator<String> hooked = new HookedIterator<>(
+            counter::incrementAndGet,
+            list.iterator()
+        );
+        final Iterator<String> sliced = new Sliced<>(0, hooked);
+        sliced.hasNext();
+        new Assertion<>(
+            "Must call hasNext on underlying iterator only once",
+            counter.get(),
+            new IsEqual<>(1)
+        ).affirm();
+    }
+
+    /**
+     * Iterator that counts calls to hasNext and next.
+     * @param <T> Type of items
+     * @since 1.0
+     */
+    private static final class HookedIterator<T> implements Iterator<T> {
+
+        /**
+         * Hook to run on each method call.
+         */
+        private final Runnable hook;
+
+        /**
+         * Original iterator.
+         */
+        private final Iterator<T> origin;
+
+        /**
+         * Ctor.
+         * @param hook Hook to run
+         * @param origin Original iterator
+         */
+        HookedIterator(final Runnable hook, final Iterator<T> origin) {
+            this.hook = hook;
+            this.origin = origin;
+        }
+
+        @Override
+        public boolean hasNext() {
+            this.hook.run();
+            return this.origin.hasNext();
+        }
+
+        @Override
+        public T next() {
+            this.hook.run();
+            return this.origin.next();
+        }
+    }
 }