Как эта Карта строки в строку возможна как Карта строки в список строк в Groovy? - PullRequest
2 голосов
/ 15 марта 2020

Видел какой-то рабочий код в проекте, который похож на приведенный ниже (конечно, это всего лишь пример), но я крайне озадачен тем, как это возможно как Map<String, String>, а не Map<String, List<String>>?

Посмотрел это в Groovy do c, но я не увидел ничего, что объясняло бы эту концепцию. Может ли кто-нибудь объяснить, действительно ли это понятие le git в Groovy?

static final Map<String, String> NAMES = [
   "Male": ["Bill", "Bryant", "Jack"],
   "Female": ["Lily", "Haley", "Mary"]
]

1 Ответ

4 голосов
/ 15 марта 2020

Это происходит из-за 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 и добавить более строгие проверки типов. После добавления этой аннотации ваш класс больше не будет компилироваться.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...