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

Тип параметра универсального метода Java внутри скобок и внешних скобок

Есть ли разница между:

static void findMax(LinkedList<? extends Number> list){...}

и

static <T extends Number> void findMax(LinkedList<T> list){...}

Так как оба работают, я хотел бы знать, есть ли какая-то большая разница между ними и что рекомендуется.

4b9b3361

Ответ 1

Основное отличие состоит в том, что во второй версии вы можете получить доступ к типу T, тогда как в первом вы не сможете.

Например, вы можете вернуть что-то, связанное с T (например, возвращая T вместо void):

static <T extends Number> T findMax(LinkedList<T> list){...}

Или вам может понадобиться создать новый список Ts:

static <T extends Number> void findMax(LinkedList<T> list){
    List<T> copyAsArrayList = new ArrayList<> (list);
    //do something with the copy
}

Если вам не нужен доступ к T, обе версии функционально эквивалентны.

Ответ 2

Следует использовать первый, если вам просто нравится, что findMax принимает список, соответствующий определенным критериям (в вашем случае список типов, расширяющий Number).

static Number findMax(LinkedList<? extends Number> list) {
    Number max = list.get(0);
    for (Number number : list) {
        if (number > max) {
            max = number;
        }
    }
    return max;
}

Этот метод возвращает Number. Например, это может быть проблемой, если у вас есть собственный класс, который расширяет Number и имеет некоторые специальные методы, которые вы хотели бы использовать позже в результате этого метода.


Второй следует использовать, когда вы планируете использовать точный тип T в теле метода как параметр метода или как возвращаемый тип метода.

static <T extends Number> T findMax(LinkedList<T> list, T currentMax) {
    T max = currentMax;
    for (T number : list) {
        if (number > max) {
            max = number;
        }
    }
    return max;
}

Вывод:

Функционально они в значительной степени эквивалентны кроме, что список с неизвестным типом (?) не может быть изменен.

Ответ 3

Здесь разность

static void findMax(LinkedList<? extends Number> list){
    list.add(list.get(0));  <-- compile error
}

вы не можете добавить ничего, кроме нулевого в список.

В то же время это компиляция без ошибок или предупреждений

static <T extends Number> void findMax2(LinkedList<T> list){
    list.add(list.get(0));  <-- no error
}

Ответ 4

В терминах мощности сигнатуры оба они точно идентичны. Я имею в виду, что если у вас есть API, который использует вторую подпись, вы можете заменить ее на API, который использует первую подпись, и любой код, который использовал API, будет работать точно так же, как и раньше, и любой код, который не работал до также не будет работать. Таким образом, во внешнем коде нет разницы между ними.

Как бы вы изменили реализацию второго на первый? Другие люди указали примеры с list.add(list.get(0));. Может ли API с первой подписью выполнить это действие? Да. Это очень просто. Просто первым вызовите второй (сделайте второй во внутреннем частном методе). Это называется помощником захвата. Тот факт, что вы можете это сделать, доказывает, что оба могут "делать" одни и те же вещи ( "делать", как с помощью любых внутренних средств, включая вызов других методов) с точки зрения внешнего кода.

static void findMax(LinkedList<? extends Number> list){
    findMaxPrivate(list);
}
static static <T extends Number> void findMaxPrivate(LinkedList<T> list){
    list.add(list.get(0));
}

Ответ 5

Я думаю, что уже есть хорошие ответы о доступе параметра T в теле второго метода.

Я хотел бы добавить, что второе обозначение будет необходимо, если у вас есть много параметров, которые необходимо связать по типу. Пример:

static <T extends Number> int getPosition(LinkedList<T> list, T element){...}

Вы не сможете заставить указанное ограничение не использовать параметр типового типа <T>.