Невозможно передать внутренний класс универсального подкласса, используя ограниченный подстановочный знак

Отказ от ответственности: этот вопрос содержит код, использующий библиотеку rxjava, но проблема не связана с ней. Я предоставляю всю информацию, необходимую для ответа тем, кто не знаком с этой библиотекой.

У меня есть следующий метод, который выбирает источник данных на основе кэширования некоторых данных или нет:

public Observable<? extends MetricDataSource> createForOwners() {
    return Observable.defer(new Func0<Observable<? extends MetricDataSource>>() {
        @Override
        public Observable<? extends MetricDataSource> call() {
            if (ownersCache.isCached()) {
                return cacheMetricDataSource;
            } else {
                return androidApiMetricDataSource;
            }
        }
    });
}

Не компилируется со следующей ошибкой:

Error:(29, 26) error: method defer in class Observable<T#2> cannot be applied to given types;
required: Func0<Observable<T#1>>
found: <anonymous Func0<Observable<? extends MetricDataSource>>>
reason: cannot infer type-variable(s) T#1
(argument mismatch; <anonymous Func0<Observable<? extends MetricDataSource>>> cannot be converted to Func0<Observable<T#1>>)
where T#1,T#2 are type-variables:
T#1 extends Object declared in method <T#1>defer(Func0<Observable<T#1>>)
T#2 extends Object declared in class Observable

Подпись Observable.defer:

public final static <T> Observable<T> defer(Func0<Observable<T>> observableFactory)

Func0 в основном содержит метод call(), который возвращает объект универсального типа:

public interface Func0<T> { T call(); }

Источники данных объявляются как:

private final Observable<AndroidApiMetricDataSource> androidApiMetricDataSource;
private final Observable<CacheMetricDataSource> cacheMetricDataSource;

Итак, почему это не удается? defer ожидает Observable<T>, и я даю ему Observable<? extends MetricDataSource>, что должно хорошо сочетаться с этим T.

Изменить: если я заменю метод defer закрытым в текущем классе, а затем заменю Observable<T> на T как в аргументе, так и в возвращаемом типе, он скомпилируется. Однако я должен использовать оригинальный метод Observable.

Таким образом, это не удается:

private static <T> Observable<T> defer(Func0<Observable<T>> observableFactory) {
    return null;
}

И этот он компилирует:

private static <T> T defer(Func0<T> observableFactory) {
    return null;
}

person Alvaro Gutierrez Perez    schedule 13.10.2015    source источник


Ответы (3)


(Представьте Func0 как Supplier, а Observable как Iterable, если это делает проблему более знакомой)

Здесь Func0 и Observable интуитивно ковариантны; в Java их почти всегда следует использовать с подстановочными знаками, иначе рано или поздно вылезут проблемы.

Подпись defter тут точно можно винить

<T> Observable<T> defer(Func0<Observable<T>> observableFactory)

это могло бы быть более общим, поскольку

<T> Observable<? extends T> defer(Func0<? extends Observable<? extends T>> observableFactory)

Но это также приводит к аду с подстановочными знаками ... Я не вижу типов из-за звука крика с подстановочными знаками.

Вместо этого мы могли бы жить опасно и просто опускать подстановочные знаки — когда возникают проблемы, выполнять приведение, чтобы обойти это. В вашем случае нам нужно преобразовать Observable<Subtype> в Observable<Supertype>. Это, очевидно, безопасно, и в этом нет никакой вины.

person ZhongYu    schedule 13.10.2015
comment
мой текст на подстановочных знаках - подстановочный знак, отсутствует подстановочный знак - person ZhongYu; 13.10.2015
comment
Очень полезный ответ, хотя я уже решил его другим способом (см. мой ответ). У меня есть вопрос по вашему ответу. Приведение работает, если я заключаю его в метод, аналогичный вашему vary() примеру по второй ссылке. Он только выдает предупреждение о непроверенном приведении. Но если я попытаюсь выполнить приведение напрямую без метода, он не скомпилируется с ошибкой Incompatible types. Зачем, если он делает то же самое? - person Alvaro Gutierrez Perez; 13.10.2015
comment
да, ваше решение работает (но тогда, возможно, оно тоже столкнется с проблемами и потребует приведения). см. приведение типов бетона - person ZhongYu; 13.10.2015
comment
Итак, это небезопасное приведение, но поскольку оно используется только как потребитель, а не как производитель, проблем во время выполнения не будет. - person Alvaro Gutierrez Perez; 13.10.2015

Можете ли вы попробовать это вместо этого?

public <T extends MetricDataSource> Observable<T> createForOwners() {
    return Observable.defer(new Func0<Observable<T>>() {
        @Override
        public Observable<T> call() {
        if (ownersCache.isCached()) {
            return cacheMetricDataSource;
        } else {
            return androidApiMetricDataSource;
        }
        }
    });
}

Я не уверен, но я предполагаю, что это потому, что если вы не привязываете его к T, он считает, что это ссылки на разные типы, которые могут меняться независимо и, следовательно, не равны. Но не уверен

person Oskar Kjellin    schedule 13.10.2015
comment
Спасибо! Я не пробовал, но вылетает с этой ошибкой: Error:(34, 28) error: incompatible types: Observable<CacheMetricDataSource> cannot be converted to Observable<T> where T is a type-variable: T extends MetricDataSource declared in method <T>createForOwners() - person Alvaro Gutierrez Perez; 13.10.2015
comment
@ ÁlvaroGutiérrez Это работает и на моем компьютере, попробуйте использовать javac для прямой компиляции и посмотрите, что произойдет. - person grape_mao; 13.10.2015
comment
Извините, но это не работает для меня. Я скопировал его точно так, как написал Оскар. Единственное изменение заключается в том, что теперь ошибка возникает только в двух операторах return, а не во всем определении анонимного класса. Я нашел решение и опубликую его сейчас. В любом случае, это был полезный ответ, и я проголосовал за него. Большое вам спасибо за вашу помощь. - person Alvaro Gutierrez Perez; 13.10.2015

Что ж, похоже, если я изменю объявление источников данных с

private final Observable<AndroidApiMetricDataSource> androidApiMetricDataSource;
private final Observable<CacheMetricDataSource> cacheMetricDataSource;

to

private final Observable<MetricDataSource> androidApiMetricDataSource;
private final Observable<MetricDataSource> cacheMetricDataSource;

Затем я могу объявить метод

public Observable<MetricDataSource> createForOwners() {
    return Observable.defer(new Func0<Observable<MetricDataSource>>() {
        @Override
        public Observable<MetricDataSource> call() {
            if (ownersCache.isCached()) {
                return cacheMetricDataSource;
            } else {
                return androidApiMetricDataSource;
            }
        }
    });
}

и теперь он компилируется, как всегда возвращается Observable<MetricDataSource>.

person Alvaro Gutierrez Perez    schedule 13.10.2015