Рубрики
Без рубрики

Предупреждение Java “непроверенное преобразование”

Автор оригинала: Kai Yuan.

1. Обзор

Иногда, когда мы компилируем наш исходный код Java, компилятор может напечатать предупреждающее сообщение “Непроверенное преобразование” или ” Выражение списка типов нуждается в непроверенном преобразовании .”

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

2. Включение опции “Непроверенное предупреждение”.

Прежде чем мы рассмотрим предупреждение ” unchecked conversion “, давайте убедимся, что опция компилятора Java для печати этого предупреждения включена.

Если мы используем компилятор Eclipse JDT , это предупреждение включено по умолчанию.

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

Обычно мы пишем и создаем нашу Java-программу в среде IDE. Мы можем добавить эту опцию в настройки компилятора IDE.

Например, на скриншоте ниже показано, как это предупреждение включено в JetBrains IntelliJ :

Apache Maven – это широко используемый инструмент для создания Java-приложений. Мы можем настроить maven-compiler-plugin ‘s аргументы компилятора , чтобы включить эту опцию:


...
    
    ...
        
            org.apache.maven.plugins
            maven-compiler-plugin
            ...
            
                ...
                
                    
                
            
        
    

Теперь, когда мы подтвердили, что в нашем компиляторе 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 :

@Test
public void givenRawList_whenAssignToTypedList_shouldHaveCompilerWarning() {
    List 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
  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
...

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

Типичный пример в реальном мире-это когда мы используем Java Persistence API ‘s Query.getResultList() метод. Метод возвращает необработанный тип List object.

Однако, когда мы попытаемся назначить список необработанных типов списку с параметризованным типом, мы увидим это предупреждение во время компиляции:

List results = entityManager.createNativeQuery("... SQL ...", MyEntity.class).getResultList();

Более того, мы знаем, что если компилятор предупреждает нас о чем-то, это означает, что существуют потенциальные риски. Если мы рассмотрим вывод Maven выше, мы увидим, что, хотя мы получаем предупреждение ” непроверенное преобразование “, наш метод тестирования работает без каких-либо проблем.

Естественно, мы можем спросить, почему компилятор предупреждает нас об этом сообщении и какие потенциальные проблемы у нас могут возникнуть?

Теперь давайте разберемся.

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

Наш метод тестирования хорошо работает в предыдущем разделе, даже если мы получаем предупреждение ” непроверенное преобразование “. Это связано с тем, что метод getRawList() добавляет только String s в возвращаемый список.

Теперь давайте немного изменим метод:

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 fromRawList = UncheckedConversion.getRawListWithMixedTypes();
    Assert.assertEquals(4, fromRawList.size());
    Assert.assertFalse(fromRawList.get(3).endsWith("String."));
}

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

Это означает, что ClassCastException было вызвано, когда мы получаем объект Date , вызывая get(3) и пытаясь привести его тип к String.

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

Например, мы назначаем List(). Для каждого String объекта в strList предположим, что мы используем его в довольно сложном или дорогостоящем процессе, таком как внешние вызовы API или транзакционные операции с базой данных.

Когда мы сталкиваемся с ClassCastException на элементе в strList , некоторые элементы были обработаны. Таким образом, ClassCastException приходит слишком поздно и может привести к некоторым дополнительным процессам восстановления или очистки данных.

До сих пор мы понимали потенциальный риск, стоящий за предупреждением “непроверенная конверсия”|/. Затем давайте посмотрим, что мы можем сделать, чтобы избежать риска.

5. Что Нам Делать С Предупреждением?

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

Однако вполне вероятно, что, когда мы сталкиваемся с предупреждением ” непроверенное преобразование “, мы работаем с методом из внешней библиотеки. Давайте посмотрим, что мы можем сделать в этом случае.

5.1. Подавление предупреждения

Мы можем использовать аннотацию SuppressWarnings(“unchecked”) для подавления предупреждения.

Однако мы должны использовать аннотацию @SuppressWarnings(“unchecked”) только в том случае, если мы уверены, что типаж безопасен , потому что он просто подавляет предупреждающее сообщение без какой-либо проверки типа.

Давайте рассмотрим пример:

Query query = entityManager.createQuery("SELECT e.field1, e.field2, e.field3 FROM SomeEntity e");
@SuppressWarnings("unchecked")
List list = query.list();

Как мы уже упоминали ранее, метод Query.getResultList() JPA возвращает необработанный типизированный объект List . Основываясь на вашем запросе, мы уверены, что необработанный список типов может быть приведен к List . Поэтому мы можем добавить @SuppressWarnings над оператором присваивания, чтобы подавить предупреждение ” непроверенное преобразование “.

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

Предупреждающее сообщение ” непроверенное преобразование ” подразумевает, что мы должны проверить преобразование перед назначением.

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

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

Во-первых, предположим, что мы отфильтруем элементы, которые имеют неправильные типы:

public static  List castList(Class clazz, Collection rawCollection) {
    List 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;
}

Давайте проверим метод cast List() выше методом модульного тестирования:

@Test
public void givenRawList_whenAssignToTypedListAfterCallingCastList_shouldOnlyHaveElementsWithExpectedType() {
    List rawList = UncheckedConversion.getRawListWithMixedTypes();
    List 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.")));
}

Когда мы создаем и выполняем метод тестирования, предупреждение ” непроверенное преобразование ” исчезает, и тест проходит.

Конечно, если это необходимо, мы можем изменить наш метод cast List () , чтобы выйти из преобразования типов и немедленно выбросить ClassCastException , как только будет обнаружен неправильный тип:

public static  List castList2(Class clazz, Collection rawCollection) 
  throws ClassCastException {
    List result = new ArrayList<>(rawCollection.size());
    for (Object o : rawCollection) {
        result.add(clazz.cast(o));
    }
    return result;
}

Как обычно, давайте создадим метод модульного тестирования для проверки метода cast List 2() :

@Test(expected = ClassCastException.class)
public void givenRawListWithWrongType_whenAssignToTypedListAfterCallingCastList2_shouldThrowException() {
    List rawList = UncheckedConversion.getRawListWithMixedTypes();
    UncheckedConversion.castList2(String.class, rawList);
}

Описанный выше метод тестирования пройдет, если мы его запустим. Это означает , что как только в rawList появится элемент с неправильным типом, метод cast List 2() остановит преобразование типа и вызовет исключение ClassCastException.

6. Заключение

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

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