TL; DR это, очевидно, ошибка, поскольку поведение компилятора зависит от совершенно не связанных вещей, включая аспекты среды вне языка Java.
Я упростил ваш пример и интегрировал реализации Collectors.teeing
и Predicate.not
в пример, чтобы иметь возможность тестировать ваш код с версиями Java от JDK 9 до JDK 12.
Обратите внимание, что, хотя я впервые подумал, что это было какое-то взаимодействие вывода типа var
с обобщенной конструкцией в правой части, другие тесты показали, что проблема существует даже при использовании явных типов для переменные, которые позволили включить JDK 9 в тесты.
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import java.util.stream.Collector;
public class Temp5 {
public static void main(String[] args) {
// List<Character> strs = List.of("abc");
List<List<Character>> lettersAndNumbers = Stream.of('5', 't', 'o', '9', 'p', '1', 'h')
.collect(teeing(
Collectors.filtering(Character::isLetter, Collectors.toList()),
Collectors.filtering(not(Character::isLetter), Collectors.toList()),
List::of
));
}
public static <T, R1, R2, R>
Collector<T, ?, R> teeing(Collector<? super T, ?, R1> downstream1,
Collector<? super T, ?, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger) {
return teeing0(downstream1, downstream2, merger);
}
private static <T, A1, A2, R1, R2, R>
Collector<T, ?, R> teeing0(Collector<? super T, A1, R1> downstream1,
Collector<? super T, A2, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger) {
Objects.requireNonNull(downstream1, "downstream1");
Objects.requireNonNull(downstream2, "downstream2");
Objects.requireNonNull(merger, "merger");
Supplier<A1> c1Supplier = Objects.requireNonNull(downstream1.supplier(), "downstream1 supplier");
Supplier<A2> c2Supplier = Objects.requireNonNull(downstream2.supplier(), "downstream2 supplier");
BiConsumer<A1, ? super T> c1Accumulator =
Objects.requireNonNull(downstream1.accumulator(), "downstream1 accumulator");
BiConsumer<A2, ? super T> c2Accumulator =
Objects.requireNonNull(downstream2.accumulator(), "downstream2 accumulator");
BinaryOperator<A1> c1Combiner = Objects.requireNonNull(downstream1.combiner(), "downstream1 combiner");
BinaryOperator<A2> c2Combiner = Objects.requireNonNull(downstream2.combiner(), "downstream2 combiner");
Function<A1, R1> c1Finisher = Objects.requireNonNull(downstream1.finisher(), "downstream1 finisher");
Function<A2, R2> c2Finisher = Objects.requireNonNull(downstream2.finisher(), "downstream2 finisher");
Collector.Characteristics[] characteristics;
Set<Collector.Characteristics> c1Characteristics = downstream1.characteristics();
Set<Collector.Characteristics> c2Characteristics = downstream2.characteristics();
EnumSet<Collector.Characteristics> c = EnumSet.noneOf(Collector.Characteristics.class);
c.addAll(c1Characteristics);
c.retainAll(c2Characteristics);
c.remove(Collector.Characteristics.IDENTITY_FINISH);
characteristics = c.toArray(new Collector.Characteristics[0]);
class PairBox {
A1 left = c1Supplier.get();
A2 right = c2Supplier.get();
void add(T t) {
c1Accumulator.accept(left, t);
c2Accumulator.accept(right, t);
}
PairBox combine(PairBox other) {
left = c1Combiner.apply(left, other.left);
right = c2Combiner.apply(right, other.right);
return this;
}
R get() {
R1 r1 = c1Finisher.apply(left);
R2 r2 = c2Finisher.apply(right);
return merger.apply(r1, r2);
}
}
return Collector.of(PairBox::new, PairBox::add, PairBox::combine, PairBox::get, characteristics);
}
@SuppressWarnings("unchecked")
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
Результаты катастрофичны. У каждой версии может быть свое мнение о правильности кода, даже если просто изменить минорную версию. Что ж, даже использование OpenJDK вместо дистрибутива Oracle может привести к другим результатам. Кроме того, даже самые маленькие изменения в коде могут повлиять на него. Как вы уже заметили, изменение комментария в фактическое объявление может изменить результат, но даже удаление комментария изменит результат для некоторых версий JDK. Изменение значений , например, использование Stream.of('5', 't', 'o', '9', 'p', '1')
вместо Stream.of('5', 't', 'o', '9', 'p', '1', 'h')
, изменяет результат для некоторых версий.
Мой вывод заключается в том, что что-то в реализации компилятора зависит от чего-то стабильного для конкретной установки, но на самом деле непредсказуемо, как порядок итерации HashMap
. И сама версия JDK, кажется, является частью этого. Это также объясняет, почему результат может измениться при использовании MacOS вместо Linux или Windows. Даже компиляция этого исходного файла вместе с другим, несвязанным исходным файлом может изменить результат.