Почему аннотация Groovy @TypeChecked улавливает, что я помещаю String в переменную int, а не наоборот?

У меня проблемы с пониманием типов Groovy и продвижением типов. И точные обещания Groovy@TypeCheckedаннотация .

-- Или, может быть, у меня проблемы с пониманием философии дизайна Groovy.

Я играл с аннотацией @TypeChecked, и она не вела себя так, как ожидалось. Я сделал два примера скриптов и ожидал, что они оба потерпят неудачу из-за несоответствия типов. Но только ОДИН из сценариев терпит неудачу.

Скрипты очень похожи. Вот я и подумал, что они тоже будут вести себя подобным образом. Основное отличие находится ближе к началу: я либо объявляю x как int, либо как String. А затем я пытаюсь присвоить x другой тип.

Разница скриптов:

      $ diff TypeChecked-fail-int-x.groovy TypeChecked-pass-String-x.groovy -y --width 70
@groovy.transform.TypeChecked           @groovy.transform.TypeChecked
void m(){                               void m(){
    int x                         |         String x

    x = 123                       |         x = "abc"
    println(x)                              println(x)
    println(x.getClass())                   println(x.getClass())

    println()                               println()

    x = "abc"                     |         x = 123
    println(x)                              println(x)
    println(x.getClass())                   println(x.getClass())
}                                       }

m()                                     m()

Когда я объявляю переменную как int, но затем пытаюсь присвоить String, я получаю ожидаемую ошибку:

Скрипт TypeChecked-fail-int-x.groovy: (веб-консоль Groovy здесь.)

      @groovy.transform.TypeChecked
void m(){
    int x

    x = 123
    println(x)
    println(x.getClass())

    println()

    x = "abc"
    println(x)
    println(x.getClass())
}

m()

Выход:

      $ groovy --version
Groovy Version: 3.0.10 JVM: 11.0.17 Vendor: Ubuntu OS: Linux

$ groovy TypeChecked-fail-int-x.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
/home/myuser/TypeChecked-fail-int-x.groovy: 11: [Static type checking] - Cannot assign value of type java.lang.String to variable of type int
 @ line 11, column 9.
       x = "abc"
           ^

1 error

Однако: если я делаю это НАОБОРОТ, тогда все работает нормально. Я ожидал, что средство проверки типов ТАКЖЕ обнаружит это.

Скрипт TypeChecked-pass-String-x.groovy: (веб-консоль Groovy здесь.)

      @groovy.transform.TypeChecked
void m(){
    String x

    x = "abc"
    println(x)
    println(x.getClass())

    println()

    x = 123
    println(x)
    println(x.getClass())
}

m()

Выход:

      $ groovy TypeChecked-pass-String-x.groovy
abc
class java.lang.String

123
class java.lang.String

И не только запускается, но и вдруг123стал строкой"123"!

Я ожидал, что ОБА сценария потерпят неудачу.

Я также попробовал@CompileStaticаннотации , и результаты были такими же.

Вопросы:

  • Это ожидаемое поведение или ошибка? Источники?
  • Почему 123 теперь является строкой? Происходит ли автобоксинг/кастинг/продвижение шрифтов? Могу ли я остановить это?

Обновление 2022-12-01: сбой даже БЕЗ @TypeChecked

Я кое-что выяснил: сбойный скрипт @TypeChecked не сработает, даже если вы удалите @TypeChecked. -- Но теперь происходит сбой с другим сообщением об ошибке и ВО ВРЕМЯ ВЫПОЛНЕНИЯ (а не во время компиляции).

Я не уверен, что все это имеет для меня более или менее смысл сейчас.

      $ cat TypeChecked-fail-int-x.groovy | grep -v TypeChecked > no-typechecked.groovy

$ groovy no-typechecked.groovy
123
class java.lang.Integer

Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'abc' with class 'java.lang.String' to class 'int'
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'abc' with class 'java.lang.String' to class 'int'
        at no-typechecked.m(no-typechecked.groovy:11)
        at no-typechecked.run(no-typechecked.groovy:16)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)

$ groovy --version
Groovy Version: 3.0.10 JVM: 11.0.17 Vendor: Ubuntu OS: Linux

Но на самом деле меня не очень интересовали случаи, когда @TypeChecked перестал работать. Меня больше интересовало, почему НЕ остановило запуск другого дела. И этот новый крупица знаний ничего в этом не меняет.

1 ответ

Что ж... вы столкнулись с двумя концепциями в Groovy, статической проверкой типов (TypeChecked) и потоковой типизацией. Оба они поначалу могут показаться странными.

ТипПроверено

TypeCheckedимеет так называемые правила . Вот фрагмент с указанной страницы:

Объект o типа A может быть присвоен переменной типа T тогда и только тогда, когда:

  • Т равно А
  • или T является одним из String, boolean, Boolean или Class (это наиболее актуально для вопроса)
  • ...

Например, если вы измените начальный тип наBooleanвы также будете удивлены, но это будет соответствовать спецификации Groovy, и вывод будет таким:

      true
class java.lang.Boolean

true
class java.lang.Boolean

Если вам интересно, почему на выходе дваtrueценности, которые вы, возможно, захотите прочитать о The Groovy Truth.

Потоковое типирование

Потоковая типизация — важная концепция Groovy в режиме проверки типов и расширение вывода типов. Идея состоит в том, что компилятор способен определять тип переменных в потоке кода, а не только при инициализации.

Нас также интересует другое утверждение из этой ссылки:

Важно понимать, что не сам факт объявления переменной с помощью def запускает вывод типа. Потоковая типизация работает для любой переменной любого типа. Объявление переменной с явным типом ограничивает только то, что вы можете присвоить переменной.

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

      abc
class java.lang.String

123
class java.lang.Integer

Когда начальный тип всегда будетStringпеременная, и вы можете присвоить объект любого типа переменной String в соответствии с правилами «Проверки типов»«Назначения проверки типов».

Краткое содержание

Итак, отвечая на ваши вопросы:

Это ожидаемое поведение или ошибка? Источники?

Да, это ожидаемо. Пожалуйста, обратитесь к ссылкам и объяснениям выше.

Почему 123 теперь является строкой? Происходит ли автобоксинг/кастинг/продвижение шрифтов? Могу ли я остановить это?

Опять же, пожалуйста, обратитесь к ссылкам и объяснениям выше. Если вы хотите, чтобы переменная изменила свой тип, определите эту переменную черезdefключевое слово. Вы не можете остановить это, потому что (снова указав документ):

Потоковое типирование введено для уменьшения разницы в семантике между классическим и статическим Groovy.

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