Ответ 1
Не все эти языковые функции используют ту же технику.
прокси
Макрос 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 [email protected]>
(.getClassLoader (class (T. 3 4)))
;=> #<DynamicClassLoader [email protected]>
Но пока мы не определяем новый класс T
, новые экземпляры будут иметь один и тот же класс с одним и тем же загрузчиком классов. Обратите внимание, что число после @
здесь совпадает с номером, указанным выше:
(.getClassLoader (class (T. 4 5)))
;=> #<DynamicClassLoader [email protected]>