Перейти к основному содержимому

Предупреждение Java «Непроверенное приведение»

· 6 мин. чтения

1. Обзор

Иногда, когда мы компилируем наши исходные файлы Java, мы видим предупреждающие сообщения « unchecked cast », распечатываемые компилятором Java.

В этом уроке мы более подробно рассмотрим предупреждающее сообщение. Мы обсудим, что означает это предупреждение, почему нас предупреждают и как решить проблему.

Некоторые компиляторы Java по умолчанию подавляют непроверенные предупреждения.

Давайте удостоверимся, что мы включили опцию компилятора для печати «непроверенных» предупреждений , прежде чем мы рассмотрим это предупреждение « непроверенное приведение ».

2. Что означает предупреждение «unchecked cast» ?

« Непроверенное приведение » — это предупреждение времени компиляции . Проще говоря, мы увидим это предупреждение при приведении необработанного типа к параметризованному типу без проверки типов .

Пример может объяснить это прямо. Допустим, у нас есть простой метод для возврата карты необработанного типа :

public class UncheckedCast {
public static Map getRawMap() {
Map rawMap = new HashMap();
rawMap.put("date 1", LocalDate.of(2021, Month.FEBRUARY, 10));
rawMap.put("date 2", LocalDate.of(1992, Month.AUGUST, 8));
rawMap.put("date 3", LocalDate.of(1976, Month.NOVEMBER, 18));
return rawMap;
}
...
}

Теперь давайте создадим тестовый метод для вызова метода, описанного выше, и приведем результат к Map<String, LocalDate> :

@Test
public void givenRawMap_whenCastToTypedMap_shouldHaveCompilerWarning() {
Map<String, LocalDate> castFromRawMap = (Map<String, LocalDate>) UncheckedCast.getRawMap();
Assert.assertEquals(3, castFromRawMap.size());
Assert.assertEquals(castFromRawMap.get("date 2"), LocalDate.of(1992, Month.AUGUST, 8));
}

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

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

$ mvn clean test
...
[WARNING] .../src/test/java/com/foreach/uncheckedcast/UncheckedCastUnitTest.java:[14,97] unchecked cast
required: java.util.Map<java.lang.String,java.time.LocalDate>
found: java.util.Map
...
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
...
[INFO] Results:
[INFO]
[INFO] Tests run: 16, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
...

Как показывает вывод Maven, мы успешно воспроизвели предупреждение.

С другой стороны, наш тест работает без проблем, даже несмотря на то, что мы видим предупреждение компилятора « unchecked cast ».

Мы знаем, что компилятор не предупредит нас без причины. Когда мы видим это предупреждение, должна быть какая-то потенциальная проблема.

Давайте разберемся.

3. Почему нас предупреждает компилятор Java?

Наш тестовый метод отлично работает в предыдущем разделе, хотя мы видим предупреждение « unchecked cast ». Это связано с тем, что когда мы приводили карту необработанного типа к Map <String, LocalDate> , необработанная карта содержала только записи <String, LocalDate> . То есть приведение типов безопасно.

Чтобы проанализировать потенциальную проблему, давайте немного изменим метод getRawMap() , добавив еще одну запись в необработанный тип Map :

public static Map getRawMapWithMixedTypes() {
Map rawMap = new HashMap();
rawMap.put("date 1", LocalDate.of(2021, Month.FEBRUARY, 10));
rawMap.put("date 2", LocalDate.of(1992, Month.AUGUST, 8));
rawMap.put("date 3", LocalDate.of(1976, Month.NOVEMBER, 18));
rawMap.put("date 4", new Date());
return rawMap;
}

На этот раз мы добавили новую запись на карту с типом <String, Date> в методе выше.

Теперь давайте напишем новый тестовый метод для вызова метода getRawMapWithMixedTypes() :

@Test(expected = ClassCastException.class)
public void givenMixTypedRawMap_whenCastToTypedMap_shouldThrowClassCastException() {
Map<String, LocalDate> castFromRawMap = (Map<String, LocalDate>) UncheckedCast.getRawMapWithMixedTypes();
Assert.assertEquals(4, castFromRawMap.size());
Assert.assertTrue(castFromRawMap.get("date 4").isAfter(castFromRawMap.get("date 3")));
}

Если мы скомпилируем и запустим тест, снова будет напечатано предупреждающее сообщение « unchecked cast ». Кроме того, наш тест будет пройден.

Однако, поскольку наш тест имеет ожидаемый аргумент = ClassCastException.class , это означает, что тестовый метод выдал исключение ClassCastException .

Если мы посмотрим на это поближе, ClassCastException не выбрасывается в строке приведения необработанного типа Map к Map<String, LocalDate>, хотя предупреждающее сообщение указывает на эту строку. Вместо этого исключение возникает, когда мы получаем данные неправильного типа по ключу : castFromRawMap.get("date 4").

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

Иногда мы можем получить исключение слишком поздно.

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

(Map<String, LocalDate>) UncheckedCast.getRawMapWithMixedTypes()

Для каждой записи на карте нам нужно отправить объект LocalDate в удаленный API. Пока мы не столкнемся с ClassCastException , вполне вероятно, что уже было сделано много вызовов API. В зависимости от требований могут потребоваться дополнительные процессы восстановления или очистки данных.

Было бы хорошо, если бы мы могли получить исключение раньше, чтобы мы могли решить, как обращаться с обстоятельствами записей с неправильными типами.

Поскольку мы понимаем потенциальную проблему, стоящую за предупреждением « unchecked cast », давайте посмотрим, что мы можем сделать, чтобы решить эту проблему.

4. Что нам делать с предупреждением?

4.1. Избегайте использования необработанных типов

Обобщения были введены начиная с Java 5. Если наша среда Java поддерживает обобщения, нам следует избегать использования необработанных типов. Это связано с тем, что использование необработанных типов заставит нас потерять все преимущества безопасности и выразительности дженериков.

Более того, мы должны искать устаревший код и реорганизовывать использование необработанных типов в дженерики.

Однако иногда нам приходится работать с некоторыми старыми библиотеками. Методы из этих старых внешних библиотек могут возвращать необработанные коллекции типов.

Вызов этих методов и приведение к параметризованным типам вызовет предупреждение компилятора о непроверенном приведении . Но у нас нет контроля над внешней библиотекой.

Далее, давайте посмотрим, как поступить в этом случае.

4.2. Подавить « непроверенное » предупреждение

Если мы не можем устранить предупреждение « unchecked cast » и уверены, что код, вызвавший предупреждение, безопасен для типов, мы можем подавить предупреждение, используя аннотацию SuppressWarnings(«unchecked») .

Когда мы используем аннотацию @ SuppressWarning («не проверено») , мы всегда должны помещать ее в наименьшую возможную область действия.

В качестве примера рассмотрим метод remove() класса ArrayList :

public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;

@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);

return oldValue;
}

4.3. Выполнение проверки безопасности типов перед использованием коллекции необработанных типов

Как мы узнали, аннотация @SuppressWarning("unchecked") просто подавляет предупреждающее сообщение, фактически не проверяя типобезопасность приведения.

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

5. Вывод

В этой статье мы узнали, что означает предупреждение компилятора « unchecked cast ».

Кроме того, мы рассмотрели причину этого предупреждения и способы решения потенциальной проблемы.

Как всегда, весь код в этой статье доступен на GitHub .