Подтвердить что ты не робот

Несоответствие типов генерируемых потоков Java

При манипулировании потоками Java 8 я столкнулся с ошибкой, когда компилятор, похоже, "забыл" тип моих общих параметров.

Следующий фрагмент создает поток имен классов и пытается сопоставить поток с потоком Class<? extends CharSequence>.

public static Stream<Class<? extends CharSequence>> getClasses() {

    return Arrays.asList("java.lang.String", "java.lang.StringBuilder", "Kaboom!")
        .stream()
        .map(x -> {
            try {
                Class<?> result = Class.forName(x);

                return result == null ? null : result.asSubclass(CharSequence.class);
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            return null;
        })
        //.filter(x -> x != null)
        ;

}

Когда я раскомментирую фильтр для удаления нулевых записей из потока, я получаю ошибку компиляции

Несоответствие типов: невозможно преобразовать из класса < capture # 15-of? расширяет CharSequence > к классу <Object>

Может ли кто-нибудь объяснить мне почему добавление фильтра вызывает эту ошибку?

PS: Код здесь несколько произволен, и достаточно легко сделать ошибку: Присвойте отображаемый поток временной переменной перед применением фильтра. Меня интересует почему приведенный фрагмент кода генерирует ошибку времени компиляции.

Изменить: Поскольку @Holger указал, что этот вопрос не является точным дубликатом Java 8 Streams: почему Collectors.toMap ведет себя по-разному для дженериков с помощью подстановочных знаков?, потому что проблематичный фрагмент, который в настоящее время компилируется без проблем, в то время как фрагмент здесь отсутствует.

4b9b3361

Ответ 1

Это из-за вывода типа:

Тип "догадывается" от него: мы знаем, что карта (что-либо) должна вернуть "Stream<Class<? extends CharSequence>>", потому что это возвращаемый тип функции. Если вы привязываете это возвращение к другой операции, например, к фильтру или карте, мы теряем этот тип вывода (он не может пройти "через" цепочки)

Тип вывода имеет свои пределы, и вы его находите.

Решение прост: вы сказали, что если вы используете переменную, вы можете указать цель, а затем помочь вывести тип.

Этот компилятор:

public static Stream<Class<? extends CharSequence>> getClasses() {
Stream<Class<? extends CharSequence>> map1 = Arrays.asList ("java.lang.String", "java.lang.StringBuilder", "Kaboom!").stream ().map (x -> {
  try {
    Class<?> result = Class.forName (x);
    return result == null ? null : result.asSubclass(CharSequence.class);
  } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace ();
  }

  return null;
});
return map1.filter(x -> x != null);

Обратите внимание, что я изменил код, чтобы возвращать всегда значение null, чтобы показать, что выведенный тип не исходит из возвращаемого типа лямбда.

И мы видим, что тип map1 выводится объявлением переменной, его целью. Если мы вернем его, это будет эквивалентно, целью будет тип возврата, но если мы его цепью:

Это не скомпилируется:

public static Stream<Class<? extends CharSequence>> getClasses () {

return Arrays.asList ("java.lang.String", "java.lang.StringBuilder", "Kaboom!").stream ().map (x -> {
  try {
    Class<?> result = Class.forName (x);
    return result == null ? null : result.asSubclass(CharSequence.class);
  } catch (Exception e) {

    e.printStackTrace ();
  }

  return null;
}).filter(x -> x != null);

В первом объявлении карты нет цели, поэтому указанный тип определяется по умолчанию: Stream<Object>

Edit

Другим способом заставить его работать было бы сделать операцию вывода типа с возвращаемым значением Lambda (вместо целевого), вам нужно указать тип возвращаемого значения с помощью cast, например. Это скомпилирует:

public static Stream<Class<? extends CharSequence>> getClasses2 () {

return Arrays.asList ("java.lang.String", "java.lang.StringBuilder", "Kaboom!").stream ().map (x -> {
  try {
    Class<?> result = Class.forName (x);
     return (Class<? extends CharSequence>)( result == null ? null : result.asSubclass(CharSequence.class));
  } catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace ();
  }

  return (Class<? extends CharSequence>)null;
}).filter(x -> x != null);

}

Обратите внимание, что это связано с цепочки операций, вы можете заменить .filter(x → x!= null) на карту (x- > x), у вас будет такая же проблема.

Изменить: изменить примеры, чтобы точно отвечать на вопрос.

Ответ 2

В дополнение к @pdem answer, это также работает для вас:

public class Test {

    public static void main(String[] args) {
        getAsSubclasses(CharSequence.class, "java.lang.String", "java.lang.StringBuilder", "Kaboom!")
                .forEach(System.out::println);
    }

    public static <C> Stream<Class<? extends C>> getAsSubclasses(Class<C> type, String... classNames) {
        return Arrays.stream(classNames)
                .map(new ToSubclass<>(type))
                .filter(c -> c != null);
    }

    static final class ToSubclass<C> implements Function<String, Class<? extends C>> {

        final Class<C> type;

        ToSubclass(Class<C> type) {
            this.type = type;
        }

        @Override
        public Class<? extends C> apply(String s) {
            try {
                return Class.forName(s).asSubclass(type);
            } catch (Exception e) {
                return null;
            }
        }

    }

}

Ответ 3

Потому что тип возврата вашей лямбда-функции не может быть определен (или компилятор просто не пытается это сделать) правильно. Использование явного анонимного объекта Function с правильными параметрами типа полностью устраняет проблемы с типом вывода:

public static Stream<Class<? extends CharSequence>> getClasses() {

    return Arrays.asList("java.lang.String",
                         "java.lang.StringBuilder",
                         "Kaboom!")
    .stream().map(
        new Function<String, Class<? extends CharSequence>>() {
            public Class<? extends CharSequence> apply(String name) {
                try {
                    return Class.forName(name).asSubclass(CharSequence.class);
                } catch (Exception e) {
                }
                return null;
            }
        }
    ).filter(Objects::nonNull);

}

Чтобы узнать, какой фактический тип возврата лямбда-функции разрешен компилятором, попробуйте попросить Eclipse присвоить выражение ...stream().map(<your initial lambda>) локальной переменной (нажмите Ctrl+2, затем L с курсором, стоящим перед выражением). Это Stream<Class<? extends Object>> возвращаемый тип, разрешенный компилятором, не ожидаемый Stream<Class<? extends CharSequence>>.