Вывод универсальных типов вложенных статических универсальных функций - PullRequest
12 голосов
/ 07 июня 2011

Может ли компилятор Java вывести тип универсальной статической функции из ее контекста в качестве аргумента другой универсальной статической функции?

Например, у меня есть простой класс Pair:

public class Pair<F, S> {

    private final F mFirst;

    private final S mSecond;

    public Pair(F first, S second) {
        mFirst  = checkNotNull(first);
        mSecond = checkNotNull(second);
    }

    public static <F, S, F1 extends F, S1 extends S> Pair<F, S> of(F1 first, S1 second) {
        return new Pair<F, S>(first, second);
    }

    public F first() {
        return mFirst;
    }

    public S second() {
        return mSecond;
    }

    // ...
}

И у меня есть следующая общая статическая функция:

public static <F, P extends Pair<F, ?>> Function<P, F> deferredFirst() {
    return (Function<P, F>)DEFERRED_FIRST;
}

private static final Function<Pair<Object, ?>, Object> DEFERRED_FIRST = 
        new Function<Pair<Object,?>, Object>() {

    @Override
    public Object apply(Pair<Object, ?> input) {
        return input.first();
    }
};

, которую я хочу использовать следующим образом ( Collections2.transform из Google Guava ):

List<Pair<Integer, Double>> values = ...
Collection<Integer> firsts = Collections2.transform(values, 
        Pair.deferredFirst());

На что жалуется компилятор:

The method transform(Collection<F>, Function<? super F,T>) in the type 
Collections2 is not applicable for the arguments 
(List<Pair<Integer,Double>>, Function<Pair<Object,?>,Object>)

Таким образом, похоже, что компилятору не удалось распространить типы, выведенные для transform (), на deferredFirst (), поскольку он считает, что они являются объектами.

Заставляет компилятор понимать типы любым из следующих способов:

Function<Pair<Integer, ?>, Integer> func = Pair.deferredFirst();
Collection<Integer> firsts = Collections2.transform(values, func);


Collection<Integer> firsts = Collections2.transform(values, 
        Pair.<Integer, Pair<Integer, ?>>deferredFirst());

Можно ли изменить сигнатуру любой функции, чтобы компилятор мог выводить / распространять типы?

Редактировать: Для богемского, вот возможный метод, в котором приведенный выше пример может быть использован в:

public static int sumSomeInts(List<Pair<Integer, Double>> values) {
    Collection<Integer> ints = Collections2.transform(values, 
            Pair.deferredFirst());
    int sum = 0;
    for(int i : ints)
        sum += i;
    return sum;
}

Ответы [ 3 ]

4 голосов
/ 07 июня 2011

Вывод типа неприятен и сложен. Они должны где-то остановиться. Рассмотрим

static <T> T foo();

String s = foo();

print( foo() )

В контексте назначения намерение программиста ясно, T должно быть String

В следующей строке не так уж много.

Метод print не очень хороший пример, он сильно перегружен. Предположим, что print не перегружен, его тип параметра фиксирован, поэтому T может быть явно выведен. Разве компилятор не должен быть достаточно умным, чтобы понять это?

Это звучит разумно, пока кто-нибудь не решится прочитать связанный текст спецификации, 15.12 Выражения вызова метода Удачи, что-нибудь изменилось в этом беспорядке!

Это так сложно, даже авторы компилятора не понимают этого. Есть тонны ошибок в javac и других компиляторах, которые произошли из этого раздела спецификации.

1 голос
/ 09 июня 2011

То, что я недавно придумал, это:

@SuppressWarnings("rawtypes")
private static final Function ExtractFirst = new Function() {
    @Override
    public Object apply(final Object from) {
        Preconditions.checkNotNull(from);
        return ((Pair)from).first;
    }
};

@SuppressWarnings("unchecked")
public static <A> Function<Pair<A,?>,A> extractFirst() {
    return ExtractFirst;
}

Не позволяйте "SuppressWarnings" оттолкнуть вас, он отлично работает.

Пример:

List<Pair<String,String>> pairs = ImmutableList.of(Pair.of("a", "b"),
    Pair.of("c", "d"),Pair.of("e", "f"));
Iterable<String> firsts = Iterables.transform(pairs,
    Pair.<String>extractFirst());

К сожалению, да, вы должны предоставить универсальный аргумент для extractFirst (). Я думаю, что это лучшее, что вы получите.

1 голос
/ 07 июня 2011

Попробуйте это дженерики кунг-фу:

public static int sumSomeInts(List<Pair<Integer, Double>> values) {
    Collection<Integer> ints = Collections2.transform(values, 
        Pair.<Integer, Double>deferredFirst());
    int sum = 0;
    for(int i : ints)
        sum += i;
    return sum;
}

Вы можете набрать вызов метода и передать общие значения до следующего вызова.

Я не уверен в точных общих параметрах, которые следует использовать, потому что вы не включили достаточно кода. Если вы вставите весь метод, где проблема, я отредактирую этот ответ, чтобы он компилировался. РЕДАКТИРОВАНИЕ: с новой информацией из вопроса

Пожалуйста, дайте мне знать, если он компилируется. Если это не то решение, оно будет близко. Ключ заключается в вводе статического метода с использованием синтаксиса Class.<Type>staticMethod().

...