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