Когда я добавляю объекты к неизменяемой карте, я это заметил.
если я добавлю один объект, например:
Map<String, Object> model = ImmutableMap.of(
"post", post
);
Я получаю ошибку времени компиляции, говоря, что сообщение не является объектом.
Если я это сделаю:
Map<String, Object> model = ImmutableMap.of(
"post", post,
"asdf", new Object()
);
Он прекрасно компилируется. Есть ли способ для меня передать объект post объекту, чтобы он работал с одним объектом?
Или что еще более важно, почему это происходит?
Я считаю, что проблема связана с типом вывода.
В вашем втором примере вы используете метод of(..)
объявленный как
public static <K, V> ImmutableMap<K, V> of(K k1, V v1, K k2, V v2) {
В этом случае компилятор попытается вывести аргумент типа для привязки к переменной типа V
из использования метода. В вашем случае это
Map<String, Object> model = ImmutableMap.of(
"post", post,
"asdf", new Object()
);
Где post
и new Object()
- это выражения, которые будут проверяться, чтобы вывести аргумент типа. Компилятор будет использовать самый низкий общий предок среди ссылочных типов. Если типы не связаны, это, очевидно, будет Object
. поэтому V
привязан к типу Object
а тип возвращаемого метода соответствует типу переменной, которой присваивается возвращаемое значение.
В вашем первом примере,
Map<String, Object> model = ImmutableMap.of(
"post", post
);
если post
объявлен как что-либо иное, кроме Object
, оно не будет выполнено, потому что Map<String, Anything>
не является Map<String, Object>
.
Этот метод объявляется как
public static <K, V> ImmutableMap<K, V> of(K k1, V v1) {
Компилятор снова связывает параметр типа, основанный на использовании. Вы использовали post
где ожидается V
V
привязан к любому объявленному типу post
.
Вы можете быть более явным и предоставлять фактические аргументы типа
Map<String, Object> model2 = ImmutableMap.<String, Object>of(
"post", post
);
Правила вывода типа чрезвычайно длинные и несколько раз трудно понять. Вы можете попробовать и прочитать их, если хотите.
Объявление ImmutableMap.of()
выглядит так:
public static <K, V> ImmutableMap<K, V> of(K k1, V v1, ...)
Поэтому он будет возвращать по умолчанию предполагаемый тип ключа и значения, а generics являются инвариантными (поэтому <String, PostType>
не является подтипом <String, Object>
).
Чтобы заставить его использовать Map<String, Object>
, вам нужно только использовать (уродливые) явные типы:
Map<String, Object> model = ImmutableMap.<String, Object>of("post", post)
На самом деле, эта ситуация является неудачным следствием некоторых особенностей генерических возможностей Java, а именно того факта, что Java использует дисперсию использования сайта (в отличие от дисперсии объявления-сайта, например, в Scala), а также от того, что интерфейсы Java-коллекции очень широкие.
Guava ImmutableMap
, как следует из его имени, неизменен - невозможно добавить к нему элементы после его создания. Из-за этого это естественно ковариантная структура, то есть абсолютно безопасно рассматривать, например, ImmutableMap<String, Number>
как подтип ImmutableMap<String, Object>
. Если бы Java объявила о различиях на уровне объявления и ImmutableMap
, то, предположительно, ваш код успешно скомпилировался.
(Однако будет одна проблема. Интерфейс Java Map<K, V>
слишком "толстый": он требует мутирующих методов, таких как put()
или remove()
. Это означает, что интерфейс Map
по своей сути является инвариантным. как в этой ситуации будет работать дисперсия объявления-сайта. Вероятно, это не сработает вообще.)
Во всяком случае, Java имеет только разницу между сайтом и сайтом в виде подстановочных знаков. Вы можете переписать свой код следующим образом:
Map<String, ?> model = ImmutableMap.of("post", post).
Как уже говорили другие, ImmutableMap.of("post", post)
inferred type будет выглядеть как Map<String, Post>
и потому, что Post
является подтипом Object
, тогда Map<String, Post>
является подтипом Map<String,?>
, Поэтому этот код будет скомпилирован. Кроме того, это также мешает вам вызывать put()
и некоторые другие методы во время компиляции.
Проблема заключается в том, что компилятор выводит общий тип и использует наиболее специфический (объявленный тип post
). Добавить явное приведение к Object
(или типа свидетель <String, Object>
между точкой и of
.