Как обнаружить неоднозначные вызовы методов, которые вызовут ClassCastException в Java 8?

В настоящее время мы переносим приложение с Java 7 на Java 8. После устранения некоторых проблем с компиляцией я наткнулся на проблему, похожую на следующий вопрос: ClassCast Error: Java 7 vs Java 8.

Подведем итог, вот пример кода, который показывает проблему:

public class Test {
    public static void main(String[] args) {
        System.out.println(String.valueOf(getVal("xxx"))); // 7: prints the result, 8: Exception 
    }

    @SuppressWarnings("unchecked")
    public static <T> T getVal(String param) {
        // do some computation based on param...
        return (T) result; // actual return type only depends on param so the caller knows what to expect
    }
}

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

Как указано в вышеприведенном вопросе, пример String.valueOf отлично работал в Java 7, потому что не было вывода типа, поэтому предполагалось Object. Теперь в Java 8 компилятор выбирает наиболее специфический тип (здесь char[]), который вызывает ClastCastException во время выполнения.

Проблема заключается в том, что у нас есть около 350 вызовов этого метода getVal. Есть ли способ обнаружить перегруженные вызовы методов, которые будут отличаться между Java 7 и Java 8? I.E. определить, когда компилятор Java 8 будет выбирать другой метод из компилятора Java 7.

Ответ 1

Лучшей альтернативой может быть:

public class Test {
    public static void main(String[] args) {
        System.out.println(String.valueOf(getVal("xxx"))); // 7: prints the result, 8: Exception
    }

    @SuppressWarnings("unchecked")
    public static <T> T getVal(T param) {
        // do some computation based on param...
        return   param; // actual return type only depends on param so the caller knows what to expect
    }
}

который будет работать как на Java 7, так и на Java 8.

Ответ 2

В итоге решение заключалось в изменении getVal() для возврата Object:

    public static Object getVal(String param) {
        // do some computation based on param...
        return result;
    }

и добавьте второй метод, который также примет желаемый класс в качестве параметра:

    public static <T> T getVal(String param, Class<T> clazz) {
        return clazz.cast(getVal(param));
    }

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

Добавление каста также сработало бы, но это вызвало бы множество предупреждений о безусловных переводах. Безусловный листинг на самом деле все еще существует (с помощью параметра clazz), но это позволяет легко идентифицировать всех вызывающих абонентов, которым требуется приведение, поскольку они используют метод с двумя параметрами.

Дополнительно - и это очень специфично для этого случая - оказалось, что сам param был часто результатом вызова метода на некотором TypedParam<T>, где T был ожидаемым типом возвращаемого значения getVal(), и который также содержал класс T.

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

    public static <T> T getVal(TypedParam<T> param) {
        return getVal(param.stringValue(), param.getValueClass());
    }

и заменил все getVal(param.stringValue()) на просто getVal(param).

Это решение не разрешает общий случай - обнаруживает перегруженные вызовы методов, которые будут отличаться между Java 7 и Java 8, но он разрешает его для методов, которые, как известно, вызывают эту проблему. И с тех пор мы не нашли его в другом месте.