Необходимо отфильтровать список для определенного подкласса с использованием обобщений - PullRequest
7 голосов
/ 05 января 2011

У меня есть List, который содержит определенный суперкласс (например, Vehicle), и я хотел бы написать метод, который возвращает объекты в этом списке, которые являются экземплярами определенного подкласса (например, Car).

Пока у меня есть это, но оно генерирует типичное "непроверенное" предупреждение компилятора операции:

public <T extends Vehicle> List<T> getVehiclesOfType(Class<T> type) {
    List<T> result = new ArrayList<T>();

    for (Vehicle vehicle : getVehicles()) {
        if (type.isAssignableFrom(vehicle.getClass())) {
            result.add(type.cast(vehicle)); // Compiler warning here
            // Note, (T)vehicle generates an "Unchecked cast" warning (IDE can see this one)
        }
    }

    return result;
}

Warning: Note: Test.java uses unchecked or unsafe operations.

Я в порядке с любым другим методом выполнения этого (я не смог найти ничего в Collections, но возможно, что какой-то метод JDK может сделать это), но в идеале это обеспечило бы следующий интерфейс:

List<Car> cars = getVehiclesOfType(Car.class);

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

Ответы [ 8 ]

5 голосов
/ 06 января 2011

Вы получаете предупреждение, потому что у компилятора (или IDE) нет возможности узнать, что приведение является безопасным, без понимания значения isAssignableFrom().Но isAssignableFrom() это не языковая функция, это просто метод библиотеки.Что касается компилятора, он такой же, как если бы вы сказали

    if (type.getName().contains("Elvis")) {
        result.add(type.cast(vehicle));
    }

Однако вы знаете, что означает isAssignableFrom(), поэтому вы знаете, что это безопасно.Это как раз та ситуация, для которой предназначена @SuppressWarnings.

3 голосов
/ 05 января 2011

Как насчет этого?

if (type.isInstance(vehicle)) {
    result.add((T)(vehicle));
}

Компилятор все еще так жалуется?


Но на вашем месте я бы использовал Гуава ,это сделает ваш метод однострочным:

public <T extends Vehicle> List<T> getVehiclesOfType(Class<T> type) {
    return Lists.newArrayList(Iterables.filter(getVehicles(), type));
}
1 голос
/ 02 февраля 2011

2 вещи:

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

первый: Посмотрите на код класса: он действительно скрывает за вас актерский состав. Обычно приведение к любому произвольному типу (T) должно быть предупреждением, но Class.cast фактически проверяет и игнорирует предупреждение компилятора (нонсенс).

public T cast(Object obj) {
    if (obj != null && !isInstance(obj))
        throw new ClassCastException();
    return (T) obj;
}

второй При этом: общие предупреждения - это первое, что нужно отключить. Имея смесь старого кода и теперь просто не стоит подавлять предупреждения, и я бы не заботился. Я просто жду, чтобы увидеть, как дженерики уменьшают ClassCastException, и это, вероятно, верно только в одном случае: использование add вместо addAll (put / putAll)

1 голос
/ 06 января 2011

Проблема в том, что компилятор недостаточно умен, чтобы знать, что транспортное средство относится к классу "тип".Это проверка во время выполнения, и компилятор не выполняет такой анализ.Есть много таких ситуаций.Например я использую if (true) return;выходить из функции на ранней стадии во время отладки все время.Если я использую только return, компилятор понимает, что есть недоступный код, но с условным компилятором не понять, что в эту ветку невозможно попасть.

Подумайте, замените ли вы свое условное выражение на if (false) {.Код не имеет шансов вызвать исключение, но все еще содержит небезопасное приведение.

По сути, компилятор говорит: «Я не могу подтвердить, что это безопасно, поэтому вы должны убедиться, что знаете, чтоделают."Ваш код не поврежден, вам просто нужно проявить осторожность.

0 голосов
/ 02 февраля 2011

В нескольких ответах на этот вопрос утверждается, что предупреждение связано с тем, что компилятор не может статически проверить , что приведение не может завершиться неудачей. Хотя компилятор действительно не может это проверить, это не повод для предупреждения! Если компилятор будет отмечать предупреждения для всех приведений, которые не могут быть проверены статически, это исключает все значимые приведения, потому что, когда приведение может быть проверено статически, вам обычно не нужно выполнять приведение в первую очередь. Другими словами, весь смысл операции приведения в том, что она может завершиться с ошибкой во время выполнения, что является идеальной ситуацией, которая обрабатывается с помощью ClassCastException.

С другой стороны, предупреждение «unchecked cast» возникает, когда вы делаете приведение, для которого недостаточно информации о типе для проверки приведения во время выполнения . Это может произойти из-за стирания аргументов типа во время выполнения. Предположим, что вы применили что-то статического типа List<?> к List<Vehicle>. Во время выполнения объекты обоих этих типов просто имеют класс ArrayList или LinkedList в качестве единственной информации о типе времени выполнения без аргументов типа. Таким образом, компилятор не может вставить какой-либо код, который будет проверять во время выполнения, что объект действительно является List из Vehicle. Таким образом, компилятор ничего не делает, но выдает предупреждение «unchecked cast».

Это полезное предупреждение, потому что вы можете столкнуться с проблемой, когда начнете использовать результат приведения. Поскольку результат имеет статический тип List<Vehicle>, вы можете написать код, который обрабатывает элементы из списка как Vehicle, без необходимости писать приведение. Но на самом деле во время выполнения все еще происходит приведение, и оно завершится неудачей, когда окажется, что List содержит все, что не является Vehicle. Таким образом, вы можете получить ClassCastException в точке, где вы этого не ожидали.

Безопасный способ обработать такое преобразование из List<?> в List<Vehicle> - это перебирать каждый из элементов, приводить их к Vehicle (то, что вы можете проверить во время выполнения и которое поднимет ClassCastException в четко определенную точку) и добавит их в новый список. Я написал некоторый общий код, чтобы сделать именно это в этом ответе .

0 голосов
/ 31 января 2011

Лично я думаю, что вы используете неправильный подход.Обобщения в Java являются функцией только во время компиляции .Вы знаете , какой тип списка вам нужен во время компиляции, поэтому просто создайте вспомогательный метод, который возвращает правильный тип списка:

public static List<Car> getCars( List<Vehicle> vlist ){ /* code here */ }

Добавьте вспомогательный метод к рассматриваемому классуа затем в своем коде просто сделайте:

List<Car> cars = Cars.getCars( getVehicles() );

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

0 голосов
/ 30 января 2011

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

Таким образом, хотя вы можете приводить значения от Car до Vehicle без предупреждения (начиная с Car extends Vehicle), компилятор не может знать, что переменная с типом Vehicle на самом деле является машиной.

Используя приведение (либо (Car), либо cast(..)), вы сообщаете компилятору, что знаете лучше. Компилятор ненавидит людей и все еще раздает вам предупреждение:)

0 голосов
/ 05 января 2011

Возможно, вы застряли с добавлением @SuppressWarning("unchecked") к методу

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...