Объединение оператора assert и сопоставления с образцом instanceof

В Java 19 я пытаюсь использовать сопоставление шаблонов instanceof внутри оператора assert. Я ожидаю, что соответствующий тип будет доступен после оператора, но компилятор Java не распознает новую переменную.

Мы создаем переменную Object obj и не можем знать, содержит ли она Integer или String. Мы передаем переменную двум методам тестирования.

public class AssertPatternMatching {
    public static void main(String[] args) {
        Object obj = args.length == 0 ? Integer.valueOf(42) : "Hello";

        afterAssert(obj);
        insideMessage(obj);
    }

В методе afterAssert() мы утверждаем, что obj является String, и привязываем строку к новой переменной str посредством instanceof сопоставления с образцом. Я ожидаю, что str будет известен и может использоваться в следующей строке. Однако компилятор не знает символ str.

    private static void afterAssert(Object obj) {
        assert obj instanceof String str;
        str.length(); // javac: cannot find symbol variable str
    }

В методе insideMessage() мы используем запутанный оператор assert, чтобы проверить, что obj не является String. Если это так, утверждение не выполняется, и мы можем предоставить сообщение. Поскольку проверка instanceof отменяется, переменная str, соответствующая шаблону, должна быть доступна для сообщения об ошибке. Однако компилятор снова не знает символа str.

    private static void insideMessage(Object obj) {
        assert !(obj instanceof String str) : "Is a string: " + str.length();
        // line above: javac: cannot find symbol variable str
        obj.hashCode();
    }

Это сработает, если мы заменим операторы assert на if:

private static void afterAssertIf(Object obj) {
    if (obj instanceof String str) {
        str.length();
    } else {
        throw new AssertionError();
    }
}

private static void insideMessageIf(Object obj) {
    if (!(obj instanceof String str)) {
        obj.hashCode();
    } else {
        throw new AssertionError("Is a string: "+ str.length());
    }
}

Пример InsideMessage() действительно крайний случай, поэтому я понимаю, что он не поддерживается. Однако я ожидал, что afterAssert() сработает. Это преднамеренный выбор дизайна или ошибка? Если преднамеренно, то в чем причина?

Полный код на https://gist.github.com/enikao/57bb1b10ce3126494ec4baa2bc7db2df

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Операторы assert выполняются только тогда, когда утверждения включены с помощью -ea. Без -ea код не выполняется, поэтому переменная никогда не присваивается/не существует (по умолчанию).

private static void afterAssert(Object obj) {
    assert obj instanceof String;
    ((String) str).length();
}

Поскольку компилятор не может знать, будет ли выполнен оператор assert, он не может полагаться на то, что переменные, объявленные в операторе, будут доступны вне оператора (поскольку оператор мог никогда не выполняться).

-ea — это флаг времени выполнения, верно? Это не должно влиять на компилятор?
werkderk 23.11.2022 21:48

Да, @werkderk, -ea — это флаг времени выполнения. Так что нет, поведение компилятора не может предвидеть предоставленное значение. Но, с другой стороны, скомпилированный код должен работать, когда -ea не предоставляется, и в этом случае instanceof не выполняется. Отсюда следует, что область действия идентификатора, совпадающего с шаблоном, не может выходить за пределы оператора assert.

John Bollinger 23.11.2022 21:53

Спасибо @JohnBollinger, имеет смысл. Так что теоретически мой замысловатый пример insideMessage() должен работать, так как выражение сообщения оценивается только в том случае, если утверждение терпит неудачу. Но никто не стал бы писать такой ассерт (надеюсь!)

werkderk 23.11.2022 21:56

@werkderk прежде всего, утверждения не должны применяться к параметрам. Утверждения предназначены для проверки инвариантов, которые должен поддерживать код, а входящие параметры противоположны этому. Стандартная идиома — это явная проверка, отклоняющая недопустимые аргументы с помощью IllegalArgumentException. Сопоставление с образцом instanceof гладко работает с идиоматическим подходом. Было бы логично, если бы ваш пример в insideMessage работал, но, как обсуждалось в в этом вопросе и ответе, только конструкции, названные JLS явно «должны работать». И assert среди них нет.

Holger 10.03.2023 13:21

Другие вопросы по теме