Вопрос

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

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

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; }
}

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

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

Это было полезно?

Решение

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

Единственный способ приблизиться к этому:

  • Разрешить только конечные свойства неизменяемых типов (примитивные типы и классы, которые, как вы знаете, неизменяемы),
  • Требовать, чтобы класс сам был финальным
  • Требовать, чтобы они наследовались от предоставленного вами базового класса (который гарантированно будет неизменяемым).

Затем вы можете проверить с помощью следующего кода, является ли имеющийся у вас объект неизменяемым:

static boolean isImmutable(Object obj) {
    Class<?> objClass = obj.getClass();

    // Class of the object must be a direct child class of the required class
    Class<?> superClass = objClass.getSuperclass();
    if (!Immutable.class.equals(superClass)) {
        return false;
    }

    // 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())
                || !isValidFieldType(objFields[i].getType())) {
            return false;
        }
    }

    // Lets hope we didn't forget something
    return true;
}

static boolean isValidFieldType(Class<?> type) {
    // Check for all allowed property types...
    return type.isPrimitive() || String.class.equals(type);
}

Обновлять: Как предложено в комментариях, его можно расширить для рекурсии суперкласса вместо проверки определенного класса.Также было предложено рекурсивно использовать isImmutable в методе isValidFieldType.Вероятно, это может сработать, и я также провел некоторое тестирование.Но это не тривиально.Вы не можете просто проверить все типы полей вызовом isImmutable, потому что String уже не проходит этот тест (его поле hash не окончательно!).Также вы легко сталкиваетесь с бесконечными рекурсиями, вызывающими StackOverflowErrors ;) Другие проблемы могут быть вызваны дженериками, где вам также придется проверять их типы на неизменяемость.

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

Другие советы

Использовать Неизменный аннотация из Java-параллелизм на практике.Инструмент Найти ошибки затем может помочь в обнаружении классов, которые являются изменяемыми, но не должны быть таковыми.

В моей компании мы определили атрибут под названием @Immutable.Если вы решите прикрепить это к классу, это означает, что вы обещаете, что вы неизменны.

Он работает для документации, а в вашем случае будет работать как фильтр.

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

В принципе нет.

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

Редактировать: Другие люди предложили иметь неизменяемую аннотацию.Это нормально, но вам также нужна документация.В противном случае люди просто подумают: «Если я помещу эту аннотацию в свой класс, я смогу сохранить ее в коллекции» и просто поместят ее на что угодно, как на неизменяемые, так и на изменяемые классы.На самом деле, я бы поостерегся иметь неизменяемую аннотацию на тот случай, если люди подумают, что аннотация делает их класс неизменен.

Это может быть еще одним намеком:

Если у класса нет установщиков, то его нельзя изменить, при условии, что параметры, с которыми он был создан, являются либо «примитивными» типами, либо сами по себе не изменяемы.

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

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

А пока попробуйте взять копию книги Джоша Блока «Эффективная Java», в ней есть статья, связанная с этой темой.Хотя здесь точно не сказано, как обнаружить неизменяемый класс, в нем показано, как создать хороший класс.

Товар называется:«Отдавайте предпочтение неизменности»

связь:http://java.sun.com/docs/books/efficient/

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

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

Предположим, у вас есть этот класс:

class Foo {
  final String x;
  final Integer y;
  ...

  public bar() {
    Singleton.getInstance().foolAround();
  }
}

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

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

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

class A {
  final B b; // might be immutable...
}

class B {
  final A a; // same so here.
}

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

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

Вы можете попросить своих клиентов добавить метаданные (аннотации) и проверить их во время выполнения с помощью отражения, например:

Метаданные:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CLASS)
public @interface Immutable{ }

Код клиента:

@Immutable
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; }
}

Затем, используя отражение класса, проверьте, есть ли у него аннотация (я бы вставил код, но его шаблон, и его можно легко найти в Интернете).

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

Вы можете использовать АОП и @Immutable аннотация из jcabi-аспекты:

@Immutable
public class Foo {
  private String data;
}
// this line will throw a runtime exception since class Foo
// is actually mutable, despite the annotation
Object object = new Foo();

Как уже говорили другие ответчики, ИМХО, не существует надежного способа узнать, действительно ли объект является неизменяемым.

Я бы просто представил интерфейс «Неизменяемый» для проверки при добавлении.Это работает как подсказка о том, что по какой бы причине вы это ни делали, следует вставлять только неизменяемые объекты.

interface Immutable {}

class MyImmutable implements Immutable{...}

public void add(Object o) {
  if (!(o instanceof Immutable) && !checkIsImmutableBasePrimitive(o))
    throw new IllegalArgumentException("o is not immutable!");
  ...
}

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

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;
}

Насколько мне известно, не существует способа идентифицировать неизменяемые объекты, который был бы на 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.

Проверить Джо-и, реализация возможностей Java.

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

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

(примечание:это копия моего комментария здесь: 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 аннотация.Ваш детектор неизменности может проверить наличие этой аннотации в поле, и если она присутствует, он может рассматривать поле как неизменяемое, несмотря на то, что поле может быть неконечным или его тип может быть изменяемым.Детектор может также проверить наличие этой аннотации в классе, таким образом рассматривая класс как неизменяемый, даже не удосужившись проверить его поля.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top