Не все эти языковые функции используют одну и ту же технику.
прокси
Макрос proxy
генерирует имя класса, основываясь исключительно на классе и списке наследуемых интерфейсов. Реализация каждого метода в этом классе делегирует Clojure fn, хранящийся в экземпляре объекта. Это позволяет Clojure использовать один и тот же прокси-класс каждый раз, когда наследуется один и тот же список интерфейсов, независимо от того, является ли тело макроса одинаковым или нет. Фактическая перезагрузка класса не происходит.
материализовать
Для reify
тела методов компилируются непосредственно в класс, поэтому использование proxy
не будет работать. Вместо этого новый класс генерируется при компиляции формы, поэтому, если вы измените тело формы и перезагрузите его, вы получите совершенно новый класс (с новым сгенерированным именем). Итак, опять же, фактическая перезагрузка класса не происходит.
генераторной класс
С помощью gen-class
вы указываете имя для сгенерированного класса, поэтому ни один из методов, используемых для proxy
или reify
, работать не будет. Макрос gen-class
содержит только своего рода спецификацию для класса, но не содержит тела метода. Сгенерированный класс, похожий на proxy
, относится к функциям Clojure для тел методов. Но поскольку имя связано со спецификацией, в отличие от proxy
, оно не будет работать, чтобы изменить тело gen-class
и перезагрузить его, поэтому gen-class
доступно только при предварительной компиляции (компиляция AOT) и перезапуск без перезагрузки JVM невозможен.
deftype и defrecord
Здесь происходит реальная перезагрузка динамического класса. Я не очень хорошо знаком с внутренними компонентами JVM, но немного поработал с отладчиком, и REPL проясняет одну мысль: каждый раз, когда необходимо разрешить имя класса, например, при компиляции кода, который использует класс, или когда Вызывается метод класса forName
класса, используется метод Clojure DynamicClassLoader/findClass
. Как вы заметили, это ищет имя класса в кеше DynamicClassLoader, и его можно установить для указания на новый класс, снова запустив deftype
.
Обратите внимание на предостережения, которые вы упомянули в уроке о том, что перезагруженный класс - это другой класс, несмотря на то, что он имеет одно и то же имя, все еще применяется к классам Clojure:
(deftype T [a b]) ; define an original class named T
(def x (T. 1 2)) ; create an instance of the original class
(deftype T [a b]) ; load a new class by the same name
(cast T x) ; cast the old instance to the new class -- fails
; ClassCastException java.lang.Class.cast (Class.java:2990)
Каждая форма верхнего уровня в программе Clojure получает новый DynamicClassLoader, который используется для любых новых классов, определенных в этой форме. Это будет включать в себя не только классы, определенные с помощью deftype
и defrecord
, но также reify
и fn
. Это означает, что загрузчик классов для x
выше отличается от нового T
. Обратите внимание, что числа после @
отличаются - каждый получает свой загрузчик классов:
(.getClassLoader (class x))
;=> #<DynamicClassLoader clojure.lang.DynamicClassLoader@337b4703>
(.getClassLoader (class (T. 3 4)))
;=> #<DynamicClassLoader clojure.lang.DynamicClassLoader@451c0d60>
Но пока мы не определим новый класс T
, новые экземпляры будут иметь один и тот же класс с тем же загрузчиком классов. Обратите внимание, что число после @
здесь совпадает со вторым номером выше:
(.getClassLoader (class (T. 4 5)))
;=> #<DynamicClassLoader clojure.lang.DynamicClassLoader@451c0d60>