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

В настоящее время мы находимся в процессе переноса приложения с Java 7 на Java 8. После исправления некоторых проблем с компиляцией я наткнулся на проблему, похожую на следующий вопрос: Ошибка ClassCast: Java 7 против 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.


person Didier L    schedule 28.05.2015    source источник
comment
У нас был похожий метод, который также вызывался в сотнях мест. Я заменил его, введя дополнительный параметр Class<T> clazz. Таким образом, вы можете иметь явную обработку ошибок: либо вернуть null, либо выдать какое-то более конкретное исключение. Я только что сделал это вручную, потратив полчаса. Я советую вам сделать то же самое. Я не знаю, как это автоматизировать, поэтому это не считается ответом на ваш вопрос.   -  person Tagir Valeev    schedule 29.05.2015
comment
Есть простой ответ: не подавлять «непроверенные» предупреждения. Они уже говорят вам, что ваш код неверен. Если вы хотите исправить свой код сейчас, когда уже слишком поздно, просто найдите вхождения @SuppressWarnings("unchecked")  -  person Holger    schedule 29.05.2015
comment
@Holger Не могли бы вы рассказать об этом моему коллеге, когда он реализовал это в 2011 году? ;-) Я уже знал, что это была плохая идея (хотя и не такая плохая, как сейчас в Java 8). Кроме того, в самом вызове метода нет @SuppressWarnings, он есть только в объявлении getVal. Таким образом, удаление этой аннотации здесь не покажет каких-либо проблемных вариантов использования.   -  person Didier L    schedule 01.06.2015
comment
Что ж, способ объявления getVal является проблемой. Так что поиск аннотации приведет вас в нужное место. Тогда исправление подписи, конечно же, вызовет ошибки компилятора у вызывающих программ, поэтому их будет легко найти. Если вы не используете IDE, которая рефакторит подпись и вызывающие объекты за один раз, что является жизнеспособной альтернативой. Конечно, как только аннотация привела вас к проблемному методу, поиск вызывающих его функций работает даже без его изменения, если вы используете приличную IDE. Итак, необходимые инструменты имеются…   -  person Holger    schedule 01.06.2015
comment
Я думаю, что это действительно будет путь. На самом деле это не отвечает на вопрос, так как мне придется изменить гораздо больше кода, чем несколько проблемных вызовов, но, по крайней мере, это решит нашу проблему. Кроме того, кажется, что изменение типа возврата на Object автоматически исправит проблемные вызовы, поэтому по иронии судьбы там не будет никаких изменений (и я, возможно, никогда не узнаю, где они были :-).   -  person Didier L    schedule 01.06.2015
comment
Я должен добавить, что даже если я исправил наш метод getVal, я все равно опасаюсь, что где-то есть другие методы, которые могут быть объявлены аналогичным образом (возможно, не таким очевидным образом), которые могут вызвать ту же проблему, и их будет трудно обнаружить…   -  person Didier L    schedule 04.06.2015


Ответы (2)


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

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.

person Shirishkumar Bari    schedule 29.05.2015
comment
Это не альтернатива, поскольку вы навязываете, что возвращаемый тип совпадает с параметром (в моем случае это String, и я не могу легко его изменить). Кроме того, вопрос состоит в том, чтобы определить случаи, когда вызовы перегруженных методов различаются между Java 7 и 8, чтобы избежать их изменения или поиска вручную. - person Didier L; 29.05.2015

В конце концов, решение состояло в том, чтобы изменить 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), но это позволяет легко идентифицировать всех вызывающих абонентов, которым требуется приведение, поскольку они используют метод с 2 параметрами.

Кроме того — и это очень специфично для данного случая — оказалось, что сам 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, — но решает проблему для методов, о которых известно, что они вызывают эту проблему. . И с тех пор мы больше нигде его не находили.

person Didier L    schedule 11.01.2016