Это происходит из-за Java Generi c Типа Erasure . Если вы посмотрите на внутренние сигнатуры типов, вы увидите, что поле NAMES
является просто типом java.util.Map
. Взгляните на descriptor
этого поля.
$ javap -s -p SomeClass
Compiled from "SomeClass.groovy"
public class SomeClass implements groovy.lang.GroovyObject {
private static final java.util.Map<java.lang.String, java.lang.String> NAMES;
descriptor: Ljava/util/Map;
private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo;
descriptor: Lorg/codehaus/groovy/reflection/ClassInfo;
public static transient boolean __$stMC;
descriptor: Z
private transient groovy.lang.MetaClass metaClass;
descriptor: Lgroovy/lang/MetaClass;
private static java.lang.ref.SoftReference $callSiteArray;
descriptor: Ljava/lang/ref/SoftReference;
public SomeClass();
descriptor: ()V
public static void main(java.lang.String...);
descriptor: ([Ljava/lang/String;)V
protected groovy.lang.MetaClass $getStaticMetaClass();
descriptor: ()Lgroovy/lang/MetaClass;
public groovy.lang.MetaClass getMetaClass();
descriptor: ()Lgroovy/lang/MetaClass;
public void setMetaClass(groovy.lang.MetaClass);
descriptor: (Lgroovy/lang/MetaClass;)V
static {};
descriptor: ()V
public static java.util.Map<java.lang.String, java.lang.String> getNAMES();
descriptor: ()Ljava/util/Map;
private static void $createCallSiteArray_1(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
private static org.codehaus.groovy.runtime.callsite.CallSiteArray $createCallSiteArray();
descriptor: ()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;
private static org.codehaus.groovy.runtime.callsite.CallSite[] $getCallSiteArray();
descriptor: ()[Lorg/codehaus/groovy/runtime/callsite/CallSite;
}
Теперь, это не все. Если вы декомпилируете байт-код Groovy в читаемый код Java, вы найдете что-то вроде этого.
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
import groovy.lang.GroovyObject;
import groovy.lang.MetaClass;
import groovy.transform.Generated;
import java.util.Map;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
public class SomeClass implements GroovyObject {
private static final Map<String, String> NAMES;
@Generated
public SomeClass() {
CallSite[] var1 = $getCallSiteArray();
super();
MetaClass var2 = this.$getStaticMetaClass();
this.metaClass = var2;
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
var1[0].callStatic(SomeClass.class, var1[1].call(NAMES));
}
static {
Map var0 = ScriptBytecodeAdapter.createMap(new Object[]{"Male", ScriptBytecodeAdapter.createList(new Object[]{"Bill", "Bryant", "Jack"}), "Female", ScriptBytecodeAdapter.createList(new Object[]{"Lily", "Haley", "Mary"})});
NAMES = var0;
}
@Generated
public static Map<String, String> getNAMES() {
return NAMES;
}
}
На этом уровне NAMES
подпись поля соответствует Map<String, String>
. Но посмотрите на конструктор stati c и как это поле инициализируется. Поле NAMES
типа Map<String, String>
инициализируется необработанным типом Map
. Кроме того, метод ScriptBytecodeAdapter.createMap
возвращает исходную карту, но он также может возвращать параметризованную карту - вы увидите тот же эффект. Если бы эта карта не была необработанной, компилятор жаловался бы и выдавал ошибку из-за несовместимых типов. Но необработанная карта, по сути, позволяет назначить карту, в которой хранятся значения, несовместимые с параметрами.
Вы можете получить тот же эффект в чистом виде Java. Взгляните на следующий пример:
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
final class SomeJavaClass {
private static final Map<String, String> NAMES;
static {
final Map map = new HashMap();
map.put("Male", Arrays.asList("Bill", "Brian", "Jack"));
NAMES = map;
}
public static void main(String[] args) {
System.out.println(NAMES);
System.out.println(((Object) NAMES.get("Male")).getClass());
}
}
Когда мы выполним его метод main
, мы получим следующий вывод:
{Male=[Bill, Brian, Jack]}
class java.util.Arrays$ArrayList
В этом примере мы должны были привести NAMES.get("Male")
до Object
, потому что в противном случае мы получили бы ClassCastException
- ArrayList
нельзя привести к String
. Но когда вы явным образом преобразуете в Object
, вы можете получить ArrayList
из карты, которая по определению должна содержать только строковые значения.
Это известное Java поведение - универсальные c типы стираются, поэтому необработанные типы могут быть совместимы с версиями до Java 1.5. Groovy для его динамических возможностей c часто работает с необработанными классами, такими как Map
или List
, и, таким образом, он может тихо преодолеть некоторые из этих ограничений.
Если вы хотите использовать более ограниченный тип проверки, вы можете использовать аннотацию @groovy.transform.TypeChecked
, чтобы сохранить динамическое поведение Groovy c и добавить более строгие проверки типов. После добавления этой аннотации ваш класс больше не будет компилироваться.