In the example code that you gave, nothing happens at all because there is no terminal operation. But let's assume that you added the terminal operation .forEachOrdered(System.out::println).
The forEachOrdered() method would call the tryAdvance() method of the map operation, which in turn calls the tryAdvance() method of the filter operation, which in turn calls the tryAdvance() method of the source. The source takes the integer 2 and passes it to the Consumer that the filter operation passed to it. That Consumer checks that 2 is greater than or equal to 3, determines that it's not, and so does nothing more. Control returns to the tryAdvance() method of the source stream, which just returns the value 'true' to indicate to the filter operation that there are more elements remaining, which the filter returns back up to the map operation, which the map operation returns to the forEachOrdered operation.
Because there are more elements remaining, the forEachOrdered operation calls the tryAdvance() method of the map operation a second time. This will call the tryAdvance() method of the filter operation. This will call the tryAdvance() method of the source. The source passes the integer 4 to the Consumer that was passed in by the filter operation. The Consumer checks that 4 is greater than or equal to 3, determines that it is, and so it passes 4 to the Consumer that was passed in by the map operation. The map's Consumer then applies the toString() method on the 4, and passes the String "4" to the Consumer that was passed in by the forEachOrdered operation. The forEachOrdered operation's Consumer then calls System.out.println() on "4".
... the convention that in Java, when specifying a range, the starting index is inclusive and the ending index is exclusive.
The range is cut in half, NOT the data source.
When trySplit() is called on the spliterator (by other code in the Stream API, not by you) and it succeeds, the original spliterator is modified so it will only be responsible for half of what it did before, and it will return a new spliterator that is responsible for iterating over the remaining elements.
Spliterators don't "contain" any elements at all, nor do they enclose a collection. They're just objects that have access to the internals of a collection that does contain the elements.