Как определить неизменяемые объекты в 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 ]

29 голосов
/ 15 октября 2008

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

29 голосов
/ 15 октября 2008

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

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

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

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

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

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

9 голосов
/ 15 октября 2008

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

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

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

8 голосов
/ 15 октября 2008

В основном нет.

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

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

4 голосов
/ 15 октября 2008

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

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

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

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

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

4 голосов
/ 15 октября 2008

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

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

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

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

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

Изделие называется: "неизменность благосклонности"

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

3 голосов
/ 18 февраля 2013

Вы можете использовать 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();
3 голосов
/ 15 октября 2008

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

3 голосов
/ 15 октября 2008

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

Metadata:

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

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

2 голосов
/ 15 октября 2008

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

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

interface Immutable {}

class MyImmutable implements Immutable{...}

public void add(Object o) {
  if (!(o instanceof Immutable) && !checkIsImmutableBasePrimitive(o))
    throw new IllegalArgumentException("o is not immutable!");
  ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...