Как определить неизменяемые объекты в Java - PullRequest
41 голосов
/ 15 октября 2008

В моем коде я создаю коллекцию объектов, к которым будут обращаться различные потоки, таким образом, чтобы это было безопасно, только если объекты неизменны. Когда делается попытка вставить новый объект в мою коллекцию, я хочу проверить, является ли он неизменным (если нет, я выброшу исключение).

Одна вещь, которую я могу сделать, это проверить несколько известных неизменяемых типов:

private static final Set<Class> knownImmutables = new HashSet<Class>(Arrays.asList(
        String.class, Byte.class, Short.class, Integer.class, Long.class,
        Float.class, Double.class, Boolean.class, BigInteger.class, BigDecimal.class
));

...

public static boolean isImmutable(Object o) {
    return knownImmutables.contains(o.getClass());
}

Это на самом деле дает мне 90% пути, но иногда мои пользователи захотят создавать свои собственные неизменяемые типы:

public class ImmutableRectangle {
    private final int width;
    private final int height;
    public ImmutableRectangle(int width, int height) {
        this.width = width;
        this.height = height;
    }
    public int getWidth() { return width; }
    public int getHeight() { return height; }
}

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

Отредактировано, чтобы добавить: Спасибо за проницательные и полезные ответы. Как указывалось в некоторых ответах, я не стал определять свои цели безопасности. Здесь угроза для невежественных разработчиков - это фрагмент кода, который будет использоваться большим количеством людей, которые почти ничего не знают о многопоточности и не будут читать документацию. Мне НЕ нужно защищаться от вредоносных разработчиков - любой, кто достаточно умен, чтобы мутировать строку или выполнять другие махинации, также будет достаточно умен, чтобы знать, что в этом случае это небезопасно. Статический анализ кодовой базы - это вариант, если он автоматизирован, но на проверки кода нельзя рассчитывать, потому что нет гарантии, что у каждого обзора будут многопоточные рецензенты.

Ответы [ 15 ]

1 голос
/ 30 мая 2014

Насколько мне известно, нет способа идентифицировать неизменные объекты, которые на 100% верны. Тем не менее, я написал библиотеку, чтобы вы ближе. Он выполняет анализ байт-кода класса, чтобы определить, является ли он неизменным или нет, и может выполняться во время выполнения. Это строгая сторона, поэтому она также позволяет вносить в белый список известные неизменяемые классы.

Вы можете проверить это по адресу: www.mutabilitydetector.org

Это позволяет вам написать код в вашем приложении:

/*
* Request an analysis of the runtime class, to discover if this
* instance will be immutable or not.
*/
AnalysisResult result = analysisSession.resultFor(dottedClassName);

if (result.isImmutable.equals(IMMUTABLE)) {
    /*
    * rest safe in the knowledge the class is
    * immutable, share across threads with joyful abandon
    */
} else if (result.isImmutable.equals(NOT_IMMUTABLE)) {
    /*
    * be careful here: make defensive copies,
    * don't publish the reference,
    * read Java Concurrency In Practice right away!
    */
}

Это бесплатный и открытый исходный код под лицензией Apache 2.0.

1 голос
/ 27 июля 2011

Попробуйте это:

public static boolean isImmutable(Object object){
    if (object instanceof Number) { // Numbers are immutable
        if (object instanceof AtomicInteger) {
            // AtomicIntegers are mutable
        } else if (object instanceof AtomicLong) {
            // AtomLongs are mutable
        } else {
            return true;
        }
    } else if (object instanceof String) {  // Strings are immutable
        return true;
    } else if (object instanceof Character) {   // Characters are immutable
        return true;
    } else if (object instanceof Class) { // Classes are immutable
        return true;
    }

    Class<?> objClass = object.getClass();

    // Class must be final
    if (!Modifier.isFinal(objClass.getModifiers())) {
            return false;
    }

    // Check all fields defined in the class for type and if they are final
    Field[] objFields = objClass.getDeclaredFields();
    for (int i = 0; i < objFields.length; i++) {
            if (!Modifier.isFinal(objFields[i].getModifiers())
                            || !isImmutable(objFields[i].getType())) {
                    return false;
            }
    }

    // Lets hope we didn't forget something
    return true;
}
0 голосов
/ 23 января 2015

Я ценю и восхищаюсь объемом работы, которую Грюндлефлек проделал в своем детекторе изменчивости, но я думаю, что это немного излишне. Вы можете написать простой, но практически очень адекватный (то есть прагматичный ) детектор следующим образом:

(примечание: это копия моего комментария здесь: https://stackoverflow.com/a/28111150/773113)

Прежде всего, вы не собираетесь просто писать метод, который определяет, является ли класс неизменным; вместо этого вам нужно написать класс детектора неизменяемости, потому что он должен поддерживать некоторое состояние. Состояние детектора будет обнаруженной неизменностью всех классов, которые он исследовал до сих пор. Это не только полезно для производительности, но на самом деле необходимо, потому что класс может содержать круговую ссылку, что может привести к тому, что детектор упрощенной неизменности впадет в бесконечную рекурсию.

Неизменяемость класса имеет четыре возможных значения: Unknown, Mutable, Immutable и Calculating. Возможно, вы захотите иметь карту, которая связывает каждый класс, с которым вы встречались, до значения неизменяемости. Конечно, Unknown на самом деле не нужно реализовывать, поскольку это будет подразумеваемое состояние любого класса, которого еще нет на карте.

Итак, когда вы начинаете изучать класс, вы связываете его со значением Calculating на карте, а когда вы закончите, вы заменяете Calculating либо Immutable или Mutable.

Для каждого класса вам нужно только проверять членов поля, а не код. Идея проверки байт-кода довольно ошибочна.

Прежде всего, вы должны , а не проверить, является ли класс окончательным; Окончательность класса не влияет на его неизменность. Вместо этого метод, который ожидает неизменный параметр, должен прежде всего вызвать детектор неизменности, чтобы утверждать неизменность класса фактического объекта, который был передан. Этот тест может быть опущен, если тип параметра является конечным классом, поэтому окончательность хороша для производительности, но, строго говоря, не обязательна. Кроме того, как вы увидите далее, поле, тип которого относится к неконечному классу, приведет к тому, что декларирующий класс будет рассматриваться как изменяемый, но, тем не менее, это проблема декларирующего класса, а не проблема не финального класса. неизменный член класса. Прекрасно иметь высокую иерархию неизменяемых классов, в которой все неконечные узлы, конечно, должны быть не конечными.

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

При изучении класса в первую очередь вы хотите выполнить повторение, чтобы определить неизменность класса super. Если супер является изменяемым, то и потомок по определению тоже изменяемый.

Затем вам нужно только проверить объявленные поля класса, а не все поля.

Если поле не является окончательным, то ваш класс изменчив.

Если поле является окончательным, но тип поля является изменяемым, то ваш класс является изменяемым. (Массивы по определению являются изменяемыми.)

Если поле является окончательным и тип поля Calculating, игнорируйте его и переходите к следующему полю. Если все поля являются неизменяемыми или Calculating, то ваш класс неизменен.

Если тип поля является интерфейсом, или абстрактным классом, или неконечным классом, то его следует рассматривать как изменяемый, поскольку у вас нет абсолютно никакого контроля над тем, что может делать фактическая реализация. Это может показаться непреодолимой проблемой, потому что это означает, что перенос изменяемой коллекции в UnmodifiableCollection все равно не пройдёт тест на неизменяемость, но на самом деле это нормально, и его можно обработать следующим обходным путем.

Некоторые классы могут содержать неконечные поля и все еще быть эффективно неизменяемыми . Примером этого является класс String. Другими классами, которые попадают в эту категорию, являются классы, которые содержат неконечные члены исключительно для целей мониторинга производительности (счетчики вызовов и т. Д.), Классы, которые реализуют неизменность эскимо (посмотрите), и классы, которые содержат члены это интерфейсы, которые, как известно, не вызывают никаких побочных эффектов. Кроме того, если класс содержит истинные изменяемые поля, но обещает не принимать их во внимание при вычислении hashCode () и equals (), то этот класс, конечно, небезопасен, когда речь идет о многопоточности, но его все равно можно рассматривать как неизменяемый с целью использования его в качестве ключа на карте. Таким образом, все эти случаи могут быть обработаны одним из двух способов:

  1. Добавление классов (и интерфейсов) вручную в ваш детектор неизменяемости. Если вы знаете, что определенный класс является эффективно неизменяемым, несмотря на то, что тест на неизменяемость для него не пройден, вы можете вручную добавить запись в свой детектор, которая связывает его с Immutable. Таким образом, детектор никогда не будет пытаться проверить, является ли он неизменным, он всегда просто скажет «да, это так».

  2. Введение аннотации @ImmutabilityOverride. Ваш детектор неизменяемости может проверять наличие этой аннотации на поле, и, если он присутствует, он может рассматривать поле как неизменяемое, несмотря на то, что поле может быть не окончательным или его тип может быть изменяемым. Детектор также может проверять наличие этой аннотации в классе, таким образом рассматривая класс как неизменный, даже не удосуживаясь проверить его поля.

Надеюсь, это поможет будущим поколениям.

0 голосов
/ 04 марта 2009

То, что работает для большого процента встроенных классов, является тестом например Comparable. Для классов, которые не являются неизменяемыми, например Date, в большинстве случаев они часто рассматриваются как неизменяемые.

0 голосов
/ 04 марта 2009

Посмотрите joe-e , реализацию возможностей для Java.

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