1. Обзор
Иногда, когда мы компилируем наш исходный код Java, компилятор может вывести предупреждающее сообщение «непроверенное преобразование»
или « Выражение типа List нуждается в непроверенном преобразовании
».
В этом уроке мы более подробно рассмотрим предупреждающее сообщение. Мы обсудим, что означает это предупреждение, к какой проблеме оно может привести и как решить потенциальную проблему.
2. Включение опции « Непроверенное
предупреждение»
Прежде чем мы рассмотрим предупреждение о « непроверенном преобразовании
», давайте удостоверимся, что опция компилятора Java для печати этого предупреждения включена.
Если мы используем компилятор Eclipse JDT , это предупреждение включено по умолчанию.
Когда мы используем компилятор Oracle или OpenJDK javac, мы можем включить это предупреждение, добавив параметр компилятора -Xlint:unchecked.
Обычно мы пишем и собираем нашу Java-программу в среде IDE. Мы можем добавить эту опцию в настройки компилятора IDE.
Например, на снимке экрана ниже показано, как это предупреждение включается в JetBrains IntelliJ :
Apache Maven — это широко используемый инструмент для создания приложений Java. Мы можем настроить аргументы компилятора maven-compiler-plugin
,
чтобы включить эту опцию:
<build>
...
<plugins>
...
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
...
<configuration>
...
<compilerArguments>
<Xlint:unchecked/>
</compilerArguments>
</configuration>
</plugin>
</plugins>
</build>
Теперь, когда мы убедились, что в нашем компиляторе Java включена эта опция предупреждения, давайте более подробно рассмотрим это предупреждение.
3. Когда компилятор предупредит нас: «непроверенное преобразование»?
В предыдущем разделе мы узнали, как включить предупреждение, установив параметр компилятора Java. Поэтому нетрудно представить, что «непроверенное преобразование»
— это предупреждение времени компиляции. Обычно мы видим это предупреждение при назначении необработанного типа параметризованному типу без проверки типа.
Это присваивание разрешено компилятором, потому что компилятор должен разрешать это присваивание для сохранения обратной совместимости со старыми версиями Java, которые не поддерживают дженерики .
Пример объяснит это быстро. Допустим, у нас есть простой метод для возврата необработанного типа List
:
public class UncheckedConversion {
public static List getRawList() {
List result = new ArrayList();
result.add("I am the 1st String.");
result.add("I am the 2nd String.");
result.add("I am the 3rd String.");
return result;
}
...
}
Далее создадим тестовый метод, который вызывает метод и присваивает результат переменной с типом List<String>
:
@Test
public void givenRawList_whenAssignToTypedList_shouldHaveCompilerWarning() {
List<String> fromRawList = UncheckedConversion.getRawList();
Assert.assertEquals(3, fromRawList.size());
Assert.assertEquals("I am the 1st String.", fromRawList.get(0));
}
Теперь, если мы скомпилируем наш тест выше, мы увидим предупреждение от компилятора Java.
Давайте создадим и протестируем нашу программу с помощью Maven:
$ mvn clean test
...
[WARNING] .../UncheckedConversionDemoUnitTest.java:[12,66] unchecked conversion
required: java.util.List<java.lang.String>
found: java.util.List
...
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
...
[INFO] Tests run: 13, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
...
Как видно из приведенного выше вывода, мы воспроизвели предупреждение компилятора.
Типичным примером в реальном мире является использование метода Query.getResultList ()
Java Persistence API . Метод возвращает объект списка
необработанного типа .
Однако, когда мы попытаемся присвоить список необработанных типов списку с параметризованным типом, мы увидим это предупреждение во время компиляции:
List<MyEntity> results = entityManager.createNativeQuery("... SQL ...", MyEntity.class).getResultList();
Более того, мы знаем, что если компилятор нас о чем-то предупреждает, значит, есть потенциальные риски. Если мы рассмотрим вышеприведенный вывод Maven, то увидим, что хотя мы получаем предупреждение о « непроверенном преобразовании
», наш тестовый метод работает без каких-либо проблем.
Естественно, мы можем захотеть спросить, почему компилятор предупреждает нас этим сообщением и какие потенциальные проблемы у нас могут быть?
Далее разберемся.
4. Почему нас предупреждает компилятор Java?
Наш тестовый метод хорошо работает в предыдущем разделе, даже если мы получаем предупреждение « непроверенное преобразование ».
Это связано с тем, что метод getRawList()
добавляет только String
в возвращаемый список.
Теперь давайте немного изменим метод:
public static List getRawListWithMixedTypes() {
List result = new ArrayList();
result.add("I am the 1st String.");
result.add("I am the 2nd String.");
result.add("I am the 3rd String.");
result.add(new Date());
return result;
}
В новом методе getRawListWithMixedTypes()
мы добавляем объект Date
в возвращаемый список. Это разрешено, так как мы возвращаем необработанный список типов, который может содержать любые типы.
Далее давайте создадим новый тестовый метод для вызова метода getRawListWithMixedTypes()
и проверки возвращаемого значения:
@Test(expected = ClassCastException.class)
public void givenRawList_whenListHasMixedType_shouldThrowClassCastException() {
List<String> fromRawList = UncheckedConversion.getRawListWithMixedTypes();
Assert.assertEquals(4, fromRawList.size());
Assert.assertFalse(fromRawList.get(3).endsWith("String."));
}
Если мы запустим тестовый метод, описанный выше, мы снова увидим предупреждение « непроверенное преобразование », и тест будет пройден.
Это означает, что было выброшено исключение ClassCastException
, когда мы получили объект Date
, вызвав get(3)
и попытавшись привести его тип к String.
В реальном мире, в зависимости от требований, иногда исключение выдается слишком поздно.
Например, мы присваиваем List<String> strList = getRawListWithMixedTypes().
Для каждого объекта String в
strList
предположим, что мы используем его в довольно сложном или дорогостоящем процессе, таком как внешние вызовы API или транзакционные операции с базой данных.
Когда мы сталкиваемся с ClassCastException
для элемента в strList
, некоторые элементы были обработаны. Таким образом, ClassCastException
возникает слишком поздно и может привести к некоторым дополнительным процессам восстановления или очистки данных.
До сих пор мы понимали потенциальный риск предупреждения о «непроверенной конверсии» .
Далее, давайте посмотрим, что мы можем сделать, чтобы избежать риска.
5. Что нам делать с предупреждением?
Если нам разрешено изменять метод, который возвращает коллекции необработанных типов, нам следует подумать о преобразовании его в универсальный метод. Таким образом, будет обеспечена безопасность типов.
Однако вполне вероятно, что когда мы сталкиваемся с предупреждением о « непроверенном преобразовании
», мы работаем с методом из внешней библиотеки. Давайте посмотрим, что мы можем сделать в этом случае.
5.1. Подавление предупреждения
Мы можем использовать аннотацию SuppressWarnings («не проверено»)
, чтобы подавить предупреждение.
Однако мы должны использовать аннотацию @SuppressWarnings("unchecked")
, только если мы уверены, что приведение типов безопасно, потому что оно просто подавляет предупреждающее сообщение без какой-либо проверки типов.
Давайте посмотрим пример:
Query query = entityManager.createQuery("SELECT e.field1, e.field2, e.field3 FROM SomeEntity e");
@SuppressWarnings("unchecked")
List<Object[]> list = query.list();
Как мы упоминали ранее, метод JPA Query.getResultList()
возвращает необработанный типизированный объект List .
Основываясь на нашем запросе, мы уверены, что необработанный список типов может быть приведен к List<Object[]>
. Поэтому мы можем добавить @SuppressWarnings
над оператором присваивания, чтобы подавить предупреждение о « непроверенном преобразовании ».
5.2. Проверка преобразования типов перед использованием коллекции необработанных типов
Предупреждающее сообщение « непроверенное преобразование
» подразумевает, что мы должны проверить преобразование перед назначением.
Чтобы проверить преобразование типов, мы можем просмотреть коллекцию необработанных типов и привести каждый элемент к нашему параметризованному типу. Таким образом, если есть некоторые элементы с неправильными типами, мы можем получить ClassCastException
до того, как действительно используем элемент.
Мы можем создать общий метод для преобразования типов. В зависимости от конкретного требования мы можем обрабатывать ClassCastException
по-разному.
Во-первых, допустим, мы отфильтруем элементы неправильных типов:
public static <T> List<T> castList(Class<? extends T> clazz, Collection<?> rawCollection) {
List<T> result = new ArrayList<>(rawCollection.size());
for (Object o : rawCollection) {
try {
result.add(clazz.cast(o));
} catch (ClassCastException e) {
// log the exception or other error handling
}
}
return result;
}
Давайте протестируем приведенный выше метод castList()
методом модульного тестирования:
@Test
public void givenRawList_whenAssignToTypedListAfterCallingCastList_shouldOnlyHaveElementsWithExpectedType() {
List rawList = UncheckedConversion.getRawListWithMixedTypes();
List<String> strList = UncheckedConversion.castList(String.class, rawList);
Assert.assertEquals(4, rawList.size());
Assert.assertEquals("One element with the wrong type has been filtered out.", 3, strList.size());
Assert.assertTrue(strList.stream().allMatch(el -> el.endsWith("String.")));
}
Когда мы создаем и выполняем тестовый метод, предупреждение « непроверенное преобразование
» исчезает, и тест проходит.
Конечно, если это необходимо, мы можем изменить наш метод castList() так
, чтобы он не преобразовывал типы и сразу же выбрасывал ClassCastException
при обнаружении неправильного типа:
public static <T> List<T> castList2(Class<? extends T> clazz, Collection<?> rawCollection)
throws ClassCastException {
List<T> result = new ArrayList<>(rawCollection.size());
for (Object o : rawCollection) {
result.add(clazz.cast(o));
}
return result;
}
Как обычно, давайте создадим метод модульного тестирования для проверки метода castList2()
:
@Test(expected = ClassCastException.class)
public void givenRawListWithWrongType_whenAssignToTypedListAfterCallingCastList2_shouldThrowException() {
List rawList = UncheckedConversion.getRawListWithMixedTypes();
UncheckedConversion.castList2(String.class, rawList);
}
Приведенный выше метод тестирования пройдет, если мы запустим его. Это означает, что как только в rawList
появится элемент с неправильным типом , метод castList2()
остановит преобразование типа и выдаст ClassCastException.
6. Заключение
В этой статье мы узнали, что такое предупреждение компилятора о « непроверенном преобразовании ».
Кроме того, мы обсудили причину этого предупреждения и способы избежать потенциального риска.
Как всегда, весь код в этой статье доступен на GitHub .