Подтвердить что ты не робот

Как определить неизменяемые объекты в Java

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

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

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

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

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

4b9b3361

Ответ 1

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

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

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

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

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;) Другие проблемы могут быть вызваны дженериками, где вам также нужно проверять их типы на невосприимчивость.

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

Ответ 2

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

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

Элемент называется: "Поддерживать неизменность"

ссылка: http://java.sun.com/docs/books/effective/

Ответ 6

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

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

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

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,...

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

Ответ 7

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

Метаданные:

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

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

Ответ 8

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

Ответ 9

Вы можете использовать AOP и @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();

Ответ 10

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

Я бы просто представил интерфейс "Неизменяемый", чтобы проверять его при добавлении. Это работает как намек на то, что по какой-либо причине вы должны вставлять только неизменные объекты.

interface Immutable {}

class MyImmutable implements Immutable{...}

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

Ответ 11

Попробуйте следующее:

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

Ответ 12

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

Ответ 13

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

Ответ 14

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

Ответ 15

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

(примечание: это копия моего комментария здесь: fooobar.com/info/128302/...)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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