This inspection reports stream API call chains which can be simplified. It allows to avoid creating redundant temporary objects when traversing a collection.

The following call chains are replaced by this inspection:

- `collection.stream().forEach()` → `collection.forEach()`
- `collection.stream().collect(toList/toSet/toCollection())` → `new CollectionType<>(collection)`
- `collection.stream().toArray()` → `collection.toArray()`
- `Arrays.asList().stream()` → `Arrays.stream()` or `Stream.of()`
- `IntStream.range(0, array.length).mapToObj(idx -> array[idx])` → `Arrays.stream(array)`
- `IntStream.range(0, list.size()).mapToObj(idx -> list.get(idx))` → `list.stream()`
- `Collections.singleton().stream()` → `Stream.of()`
- `Collections.emptyList().stream()` → `Stream.empty()`
- `stream.filter().findFirst().isPresent()` → `stream.anyMatch()`
- `stream.collect(Collectors.counting())` → `stream.count()`
- `stream.collect(Collectors.maxBy())` → `stream.max()`
- `stream.collect(Collectors.mapping())` → `stream.map().collect()`
- `stream.collect(Collectors.reducing())` → `stream.reduce()`
- `stream.collect(Collectors.summingInt())` → `stream.mapToInt().sum()`
- `stream.mapToObj(x -> x)` → `stream.boxed()`
- `!stream.anyMatch()` → `stream.noneMatch()`
- `!stream.anyMatch(x -> !(...))` → `stream.allMatch()`
- `stream.map().anyMatch(Boolean::booleanValue)` -> `stream.anyMatch()`

Note that the replacements semantic may have minor difference in some cases. For example, `Collections.synchronizedList(...).stream().forEach()` is not synchronized while `Collections.synchronizedList(...).forEach()` is synchronized. Or `collect(Collectors.maxBy())` would return an empty `Optional` if the resulting element is `null` while `Stream.max()` will throw `NullPointerException` in this case.