Почему аннотация 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.