Да, вы можете написать детектор неизменности.
Прежде всего, вы не собираетесь просто писать метод, который определяет, является ли класс неизменным; вместо этого вам нужно написать класс детектора неизменяемости, потому что он должен поддерживать некоторое состояние. Состояние детектора будет обнаруженной неизменностью всех классов, которые он исследовал до сих пор. Это не только полезно для производительности, но на самом деле необходимо, потому что класс может содержать круговую ссылку, что может привести к тому, что детектор упрощенной неизменности впадет в бесконечную рекурсию.
Неизменяемость класса имеет четыре возможных значения: Unknown
, Mutable
, Immutable
и Calculating
. Возможно, вы захотите иметь карту, которая связывает каждый класс, с которым вы встречались, до значения неизменяемости. Конечно, Unknown
на самом деле не нужно реализовывать, поскольку это будет подразумеваемое состояние любого класса, которого еще нет на карте.
Итак, когда вы начинаете изучение класса, вы связываете его со значением Calculating
на карте, а когда вы закончите, вы заменяете Calculating
либо Immutable
, либо Mutable
.
Для каждого класса вам нужно только проверять членов поля, а не код. Идея проверки байт-кода довольно ошибочна.
Прежде всего, вы должны , а не проверить, является ли класс окончательным; Окончательность класса не влияет на его неизменность. Вместо этого метод, который ожидает неизменный параметр, должен прежде всего вызвать детектор неизменности, чтобы утверждать неизменность класса фактического объекта, который был передан. Этот тест может быть опущен, если тип параметра является конечным классом, поэтому окончательность хороша для производительности, но, строго говоря, не является необходимой. Кроме того, как вы увидите далее, поле, тип которого относится к неконечному классу, приведет к тому, что декларирующий класс будет рассматриваться как изменяемый, но, тем не менее, это проблема декларирующего класса, а не проблема не финального класса. неизменный член класса. Прекрасно иметь высокую иерархию неизменных классов, в которой все неконечные узлы, конечно, должны быть не конечными.
Вы должны не проверить, является ли поле закрытым; для класса совершенно нормально иметь открытое поле, и видимость поля никоим образом не влияет на неизменность декларирующего класса, формы или формы. Вам нужно только проверить, является ли поле окончательным и его тип неизменен.
При изучении класса в первую очередь вы хотите выполнить повторение, чтобы определить неизменность класса super
. Если супер является изменяемым, то потомок по определению тоже изменяемый.
Тогда вам нужно только проверить объявленные поля класса, а не все поля.
Если поле не является окончательным, тогда ваш класс изменчив.
Если поле является окончательным, но тип поля является изменяемым, то ваш класс является изменяемым. (Массивы по определению являются изменяемыми.)
Если поле является окончательным и тип поля Calculating
, игнорируйте его и переходите к следующему полю. Если все поля являются неизменяемыми или Calculating
, то ваш класс неизменен.
Если тип поля является интерфейсом, или абстрактным классом, или неконечным классом, то его следует рассматривать как изменяемый, поскольку у вас нет абсолютно никакого контроля над тем, что может делать фактическая реализация. Это может показаться непреодолимой проблемой, потому что это означает, что перенос изменяемой коллекции в UnmodifiableCollection
все равно не пройдёт тест на неизменяемость, но на самом деле это нормально, и его можно обработать с помощью следующего обходного пути.
Некоторые классы могут содержать неконечные поля и все еще быть эффективно неизменяемыми .Примером этого является класс String
.Другими классами, которые попадают в эту категорию, являются классы, которые содержат неконечные члены исключительно для целей мониторинга производительности (счетчики вызовов и т. Д.), Классы, которые реализуют неизменность эскимо (поиск), и классы, которые содержат членыэто интерфейсы, которые, как известно, не вызывают никаких побочных эффектов.Кроме того, если класс содержит истинные изменяемые поля, но обещает не принимать их во внимание при вычислении hashCode () и equals (), то этот класс, конечно, небезопасен, когда речь идет о многопоточности, но его все равно можно рассматривать какнеизменяемый с целью использования его в качестве ключа на карте.Таким образом, все эти случаи могут быть обработаны одним из двух способов:
Добавление классов (и интерфейсов) вручную в ваш детектор неизменяемости.Если вы знаете, что определенный класс является эффективно неизменяемым, несмотря на то, что тест на неизменяемость для него не пройден, вы можете вручную добавить запись в ваш детектор, которая связывает его с Immutable
.Таким образом, детектор никогда не будет пытаться проверить, является ли он неизменным, он всегда просто скажет «да, это так».
Введение аннотации @ImmutabilityOverride
.Ваш детектор неизменяемости может проверять наличие этой аннотации на поле, и, если он присутствует, он может рассматривать поле как неизменяемое, несмотря на то, что поле может быть не окончательным или его тип может быть изменяемым.Детектор также может проверять наличие этой аннотации в классе, таким образом рассматривая класс как неизменный, даже не удосужившись проверить его поля.
Надеюсь, это поможет будущим поколениям.