Насколько неэффективно передать Collections.unmodifiable * экземпляр, который уже обернут в Collections.unmodifiable *? - PullRequest
4 голосов
/ 08 ноября 2010

У меня есть кусочки сдельной работы, выполняемые различными пользовательскими (исходный код недоступен) средами, которые возвращают экземпляры Map.К сожалению, эти структуры не согласованы в своих возвращаемых экземплярах Map, которые были обернуты в Collections.unmodifiableMap.Чтобы обеспечить более высокую степень неизменности (для более легкого многопоточного использования) в моем коде, я просто равномерно назвал Collections.unmodifiableMap для всего, что возвращается этими структурами.

Map<String, Record> immutableMap = framework.getRecordsByName();
//does this created a nested set of unmodifiableMap wrapper instances?
this.immutableField = Collections.unmodifiableMap(immutableMap);
.
.
.
Map<String, Record> maybeImmutableMap = framework.getRecordsByName();
//is there some means to get instanceof to work?
if (!(maybeImmutableMap instanceof Collections.UnmodifiableMap))
{
    this.immutableField = Collections.unmodifiableMap(maybeImmutableMap);
}

Я понял, что у меня может бытьпроблема производительности вокруг этой части моего дизайна.И что в некоторых случаях я вызывал Collections.unmodifiableMap, передавая ему экземпляр, который уже был упакован фреймворком в тот же вызов.И то, что моя переупаковка, вероятно, вызвала дополнительный вызов метода во всем экземпляре.

Похоже, что использование instanceof Collections.UnmodifiableMap не работает.И я не могу найти какой-либо способ обнаружить (исключая использование отражения, которое не подходит в этой ситуации - СЛИШКОМ МЕДЛЕННО), нужно ли обернуть экземпляр карты, на который я сейчас ссылаюсь,

Вопросы:

Ответы [ 6 ]

5 голосов
/ 09 ноября 2010

Вам не нужно беспокоиться о производительности, когда вы оборачиваете одну неизменяемую карту в другую. Взгляните на UnmodifiableMap класс:

private static class UnmodifiableMap<K,V> implements Map<K,V>, Serializable {
    ...
UnmodifiableMap(Map<? extends K, ? extends V> m) {
        if (m==null)
            throw new NullPointerException();
        this.m = m;
    }

public int size()                {return m.size();}
    ...
public V put(K key, V value) {
    throw new UnsupportedOperationException();
    }
public V remove(Object key) {
    throw new UnsupportedOperationException();
    }

public Set<K> keySet() {
    if (keySet==null)
    keySet = unmodifiableSet(m.keySet());
    return keySet;
}

public Set<Map.Entry<K,V>> entrySet() {
    if (entrySet==null)
    entrySet = new UnmodifiableEntrySet<K,V>(m.entrySet());
    return entrySet;
}
    ...

Вы можете видеть, что этот класс является лишь тонкой оберткой вокруг реальной карты. Все методы, такие как getSize, isEmpty и другие методы, которые не влияют на состояние карты, делегируются экземпляру обернутой карты. Другие методы, которые влияют на состояние карты (put, remove), просто выбрасывают UnsupportedOperationException Таким образом, почти нулевая перегрузка производительности.

5 голосов
/ 09 ноября 2010

Насколько мне известно:

  • А) Нет.
  • B) Нет.
  • C) Нет.
Коллекции

Guava Immutable* не имеют этой проблемы. Если ImmutableList.copyOf(list) вызывается с list, который сам по себе является ImmutableList, возвращается сам аргумент. Кроме того, вы можете ссылаться на них как (и проверять с помощью instanceof) тип Immutable*, а не на интерфейс, что позволяет легко узнать, есть ли у вас неизменный экземпляр или нет. Поэтому одним из вариантов является копирование результатов из среды в эти неизменяемые коллекции и использование их в вашем собственном коде. (У них также есть преимущество в том, что они действительно неизменяемы ... неизменяемые обертки позволяют изменять сам оригинальный изменяемый экземпляр, который они переносят, если что-то на него ссылается.)

Все это говорит о том, что я не буду слишком беспокоиться о возможных издержках прохождения вызова метода через 1 или 2 неизменяемых слоя-оболочки, если вы не собираетесь каким-либо образом оборачивать их снова и снова. Как отмечали другие, маловероятно, что вы когда-либо заметите проблему с производительностью из-за этого.

2 голосов
/ 09 ноября 2010

Изучив все отзывы, я пришел к выводу, что независимо от того, что я делаю, решением будет какая-то форма клуджа (со слабым запахом).Я думаю, что это связано с тем фактом, что та часть API коллекций, которая создает неизменяемые экземпляры, не позволяла избежать вложения неизменяемых экземпляров, а также не предоставляла «общедоступный» способ для клиента надлежащим образом избежать вложенности.1002 * И из-за соображений, связанных с несколькими загрузчиками классов и сериализацией через RMI, единственное решение, которое мне действительно понравилось (сравнение эталонных классов от Jorn Horstmann), имеет проблемы.Однако, когда я беру его подход и объединяю его с модификацией подхода имени класса (рекомендованного Евгением Кулешовым), я думаю, что я настолько близок, насколько я собираюсь найти решение, которое поможет мне в моем многопоточномсреда распределенной обработки.И это выглядит примерно так:

public class MyCollections {
    private static final String UNMODIFIABLE_MAP_CLASS_NAME =
        Collections.unmodifiableMap(new HashMap()).getClass().getName();

    public static <K, V> Map<K, V> unmodifiableMap(Map<K, V> map) {
        return map.getClass().getName().equals(UNMODIFIABLE_MAP_CLASS_NAME)
                 ? map
                 : Collections.unmodifiableMap(map);
    }
}

Это все еще имеет все преимущества сравнительного сравнения при условии все происходит в одном контексте ClassLoader и строка имени класса была правильно интернирована.И делает это при вежливом сохранении инкапсуляции (избегая моего кода, ссылающегося на имя класса напрямую).Однако, если эти два предположения не выполняются, тогда оценка вернется к стандартному сравнению строк, которое будет работать при условии, что имя класса не меняется между различными версиями библиотеки (что, по-видимому, имеет довольно низкую вероятность).

Есть ли что-то, что я забываю или упускаю в этом подходе?

И еще раз спасибо всем за ваши отзывы.Я действительно ценю это.

2 голосов
/ 09 ноября 2010

Мои мысли о (C) заключаются в том, что компилятор hotspot должен быть достаточно хорош для встраивания небольших методов, которые просто возвращают результат вызова другого метода.Но я сомневаюсь, что он может видеть через несколько уровней косвенности, например, HashMap, обернутый в несколько UnmodifiableMaps.Я также не думаю, что было бы преждевременной оптимизацией, если вы знаете, что ваша библиотека иногда возвращает немодифицируемые карты.Почему бы не создать свою собственную оболочку для Collections.unmodifiableMap, как это (не проверено, и я только что заметил, что простой instanceof не будет работать, поскольку Impl в коллекциях является приватным):

public class MyCollections {
    private static final Class UNMODIFIABLE_MAP_CLASS = Collections.unmodifiableMap(new HashMap()).getClass();

    public static <K, V> Map<K, V> unmodifiableMap(Map<K, V> map) {
        return map.getClass() == UNMODIFIABLE_MAP_CLASS ? map : Collections.unmodifiableMap(map);
    }
}
0 голосов
/ 09 ноября 2010

Как правило, первое правило - не оптимизировать его, если это действительно не нужно. По сути, вам нужно собрать некоторые данные о производительности из вашего приложения, чтобы увидеть, не вызывает ли это завершение сбора данных.

Но если вы действительно хотите проверить, является ли сбор неизменяемым, вы можете проверить «UnmodifiableMap» .equals (Class.getSimpleName ())

0 голосов
/ 08 ноября 2010

A) Нет

B) Нет

C) Возможно, но я ожидаю, что это зависит от реализации JVM.

Рассматриваемый декоратор очень легкийвес, есть ли какая-то конкретная причина, по которой дополнительный вызов метода, который просто вызывает другой метод, может вызвать проблемы с производительностью в вашей среде?Это не должно быть проблемой в любом стандартном приложении, поэтому, если у вас нет действительно специального приложения / среды (интенсивное использование процессора, в режиме реального времени, мобильное устройство ...), это не должно быть проблемой.

...