ClojureDocs

导航

命名空间

gen-class

clojure.core

自 1.0 起可用 (源代码)
  • (gen-class & 选项)
When compiling, generates compiled bytecode for a class with the
given package-qualified :name (which, as all names in these
parameters, can be a string or symbol), and writes the .class file
to the *compile-path* directory.  When not compiling, does
nothing. The gen-class construct contains no implementation, as the
implementation will be dynamically sought by the generated class in
functions in an implementing Clojure namespace. Given a generated
class org.mydomain.MyClass with a method named mymethod, gen-class
will generate an implementation that looks for a function named by 
(str prefix mymethod) (default prefix: "-") in a
Clojure namespace specified by :impl-ns
(defaults to the current namespace). All inherited methods,
generated methods, and init and main functions (see :methods, :init,
and :main below) will be found similarly prefixed. By default, the
static initializer for the generated class will attempt to load the
Clojure support code for the class as a resource from the classpath,
e.g. in the example case, ``org/mydomain/MyClass__init.class``. This
behavior can be controlled by :load-impl-ns
 Note that methods with a maximum of 18 parameters are supported.
 In all subsequent sections taking types, the primitive types can be
referred to by their Java names (int, float etc), and classes in the
java.lang package can be used without a package qualifier. All other
classes must be fully qualified.
 Options should be a set of key/value pairs, all except for :name are optional:
 :name aname
 The package-qualified name of the class to be generated
 :extends aclass
 Specifies the superclass, the non-private methods of which will be
overridden by the class. If not provided, defaults to Object.
 :implements [interface ...]
 One or more interfaces, the methods of which will be implemented by the class.
 :init name
 If supplied, names a function that will be called with the arguments
to the constructor. Must return [ [superclass-constructor-args] state] 
If not supplied, the constructor args are passed directly to
the superclass constructor and the state will be nil
 :constructors {[param-types] [super-param-types], ...}
 By default, constructors are created for the generated class which
match the signature(s) of the constructors for the superclass. This
parameter may be used to explicitly specify constructors, each entry
providing a mapping from a constructor signature to a superclass
constructor signature. When you supply this, you must supply an :init
specifier. 
 :post-init name
 If supplied, names a function that will be called with the object as
the first argument, followed by the arguments to the constructor.
It will be called every time an object of this class is created,
immediately after all the inherited constructors have completed.
Its return value is ignored.
 :methods [ [name [param-types] return-type], ...]
 The generated class automatically defines all of the non-private
methods of its superclasses/interfaces. This parameter can be used
to specify the signatures of additional methods of the generated
class. Static methods can be specified with ^{:static true} in the
signature's metadata. Do not repeat superclass/interface signatures
here.
 :main boolean
 If supplied and true, a static public main function will be generated. It will
pass each string of the String[] argument as a separate argument to
a function called (str prefix main).
 :factory name
 If supplied, a (set of) public static factory function(s) will be
created with the given name, and the same signature(s) as the
constructor(s).

:state name
 If supplied, a public final instance field with the given name will be
created. You must supply an :init function in order to provide a
value for the state. Note that, though final, the state can be a ref
or agent, supporting the creation of Java objects with transactional
or asynchronous mutation semantics.
 :exposes {protected-field-name {:get name :set name}, ...}
 Since the implementations of the methods of the generated class
occur in Clojure functions, they have no access to the inherited
protected fields of the superclass. This parameter can be used to
generate public getter/setter methods exposing the protected field(s)
for use in the implementation.
 :exposes-methods {super-method-name exposed-name, ...}
 It is sometimes necessary to call the superclass' implementation of an
overridden method.  Those methods may be exposed and referred in 
the new method implementation by a local name.
 :prefix string
 Default: "-" Methods called e.g. Foo will be looked up in vars called
prefixFoo in the implementing ns.
 :impl-ns name
 Default: the name of the current ns. Implementations of methods will be 
looked up in this namespace.
 :load-impl-ns boolean
 Default: true. Causes the static initializer for the generated class
to reference the load code for the implementing namespace. Should be
true when implementing-ns is the default, false if you intend to
load the code via some other method.
5 Examples
(gen-class
	:name "some.package.RefMap"
	:implements [java.util.Map]
	:state "state"
	:init "init"
	:constructors {[] []}
	:prefix "ref-map-")

(defn ref-map-init []
	[[] (ref {})])

(defn ref-map-size [this]
	(let [state (.state this)] (.size @state)))
	
(defn ref-map-isEmpty [this]
	(let [state (.state this)] (.isEmpty @state)))

(defn ref-map-containsKey [this o]
	(let [state (.state this)] (.containsKey @state o)))
	
(defn ref-map-containsValue [this o]
	(let [state (.state this)] (.containsValue @state o)))
	
(defn ref-map-get [this o]
	(let [state (.state this)] (.get @state o)))
	
(defn ref-map-keySet [this]
	(let [state (.state this)] (.keySet @state)))
	
(defn ref-map-values [this]
	(let [state (.state this)] (.values @state)))
	
(defn ref-map-entrySet [this]
	(let [state (.state this)] (.entrySet @state)))
	
(defn ref-map-equals [this o]
	(let [state (.state this)] (.equals @state o)))
	
(defn ref-map-hashCode [this]
	(let [state (.state this)] (.hashCode @state)))
	
(defn ref-map-put [this k v]
	(let [state (.state this)] 
		(dosync (alter state assoc k v)) v))
	
(defn ref-map-putAll [this m]
	(let [state (.state this)]
		(doseq [[k v] (map identity m)] (.put this k v))))
		
(defn ref-map-remove [this o]
	(let [state (.state this) v (get @state o)] 
		(dosync (alter state dissoc o)) v))
	
(defn ref-map-clear [this]
	(let [state (.state this)] 
		(dosync (ref-set state {}))))
	
(defn ref-map-toString [this]
	(let [state (.state this)] (.toString @state)))
;; I found managing state a bit confusing at first.
;; here's a dumb little class with a getter and setter for a "location" field.

(ns com.example )

(gen-class
      :name com.example.Demo
      :state state
      :init init
      :prefix "-"
      :main false
      ;; declare only new methods, not superclass methods
      :methods [[setLocation [String] void]
                [getLocation [] String]])

;; when we are created we can set defaults if we want.
(defn -init []
  "store our fields as a hash"
  [[] (atom {:location "default"})])

;; little functions to safely set the fields.
(defn setfield
  [this key value]
      (swap! (.state this) into {key value}))

(defn getfield
  [this key]
  (@(.state this) key))

;; "this" is just a parameter, not a keyword
(defn -setLocation [this loc]
  (setfield this :location loc))

(defn  -getLocation
  [this]
  (getfield this :location))

;; running it -- you must compile and put output on the classpath
;; create a Demo, check the default value, then set it and check again.
user=> (def ex (com.example.Demo.))
#'user/ex
user=> (.getLocation ex)
"default"
user=> (.setLocation ex "time")
nil
user=> (.getLocation ex)
"time"
;; This example illustrates the syntax intended by the docstring's remark that 
;; "Static methods can be specified with ^{:static true} in the signature's 
;; metadata", and notes a case in which you might not think that you need to use
;; :methods.

;; The docs say, about :methods, "Do not repeat superclass/interface
;; signatures here."  That applies to non-static methods.  If you want to
;; define a static method to hide a static method in the superclass, you
;; are not overriding the superclass method (the behavior is different).
;; You are simply defining a new method with the same name.  In this case,
;; you should add a signature to :methods if you want your function to be
;; visible to Java, with metadata indicating that it is static placed
;; before the signature vector:

(ns 
  ...
  (:gen-class 
    ...
    :methods [[...] ...
              ^{:static true} [name [] java.lang.String]
              [...] ...]
    ...))

(defn -name [] "My name is Foo")

;; Also note that you can replace "^{:static true}" with "^:static".
;; It is possible to attach Java annotations to the class,
;; constructors, and methods 
;; Source: https://github.com/clojure/clojure/blob/8af7e9a92570eb28c58b15481ae9c271d891c028/test/clojure/test_clojure/genclass/examples.clj#L34
(gen-class :name ^{Deprecated {}
                   SuppressWarnings ["Warning1"] ; discarded
                   java.lang.annotation.Target []}
                 clojure.test_clojure.genclass.examples.ExampleAnnotationClass
           :prefix "annot-"
           :methods [[^{Deprecated {}
                        Override {}} ;discarded
                      foo [^{java.lang.annotation.Retention java.lang.annotation.RetentionPolicy/SOURCE
                             java.lang.annotation.Target    [java.lang.annotation.ElementType/TYPE
                                                             java.lang.annotation.ElementType/PARAMETER]}
                           String] void]])
;; Defining a custom constructor which calls a superclass constructor:
(ns my.CustomException
  (:gen-class
   :implements [clojure.lang.IExceptionInfo]
   :extends java.lang.RuntimeException
   :constructors {[String Throwable clojure.lang.IPersistentMap] [String Throwable]} ; mapping of my-constructor -> superclass constuctor
   :init init
   :state state ; name for the var that holds your internal state
   :main false
   :prefix "my-ex-"))

(defn my-ex-init [msg t context]
  ;; first element of vector contains parameters for the superclass constructor
  ;; Second element will be your internal state 
  [[msg t] context]) 

(defn my-ex-getData [this]
  (.state this)) ; accessing the internal state
See Also

class-and-interfaces - a vector of class names args - a (possibly empty) vector of arguments to t...

Added by AtKaaZ

When compiling, generates compiled bytecode for an interface with the given package-qualified :nam...

Added by AtKaaZ
5 Notes
    By , created 14.7 years ago

    When implementing interface methods with gen-class (when using :implements) watch out for primitive return types in interface methods.

    When you get weird NullPointerExceptions or ClassPathExceptions then make sure whether the value returned by your functions can be converted by Clojure to the primitive return type defined in the interface for that method.

    Example:

    Given:

    interface Test {
       boolean isTest();
    }
    

    Clojure implementation:

    (gen-class :name "MyTest" :implements [Test])
    
    ; Will throw NPE when executed, 
    ; can't be converted to boolean
    (defn -isTest [this] nil) 
    
    (gen-class :name "MyTest" :implements [Test])
    
    ; Will throw ClassCastExcpetion when executed, 
    ; can't be converted to boolean
    (defn -isTest [this] (Object.)) 
    
    By , created 13.9 years ago, updated 13.7 years ago

    When implementing an interface or extending an abstract class that implements an interface, be careful to implement all methods in the implemented interfaces. Note: The abstract class is not required to implement all methods in the interface. The clojure compiler does not throw compiler errors if you do not implement a method. A runtime error will be thrown when someone tries to use the function. The documentation suggests that you will receive UnsupportedOperationException, however in the case of the abstract class a java.lang.AbstractMethodError is thrown.

    example:

    Log4j appender - Extending AppenderSkeleton will fail with runtime error

    (gen-class :name clj.TestAppender :extends  org.apache.log4j.AppenderSkeleton)
    
    (defn -append [this event]
      (println (.getMessage event)))
    

    Need to implement close and requireLayout. These are not in AppenderSkeleton but are required by the interface Appender.

    (gen-class :name TstAppender :extends org.apache.log4j.AppenderSkeleton)
    
    (defn -append [this event]
      (println (.getMessage event)))
    
    (defn -close [this]) ;nothing to clean up
    
    (defn -requireLayout [this] false)
    
    
    By , created 10.3 years ago, updated 10.3 years ago

    If your namespace has dashes in it, then the class name will be mangled so the dashes become underscores, unless you have a :name directive. Your naive instantiation will fail with ClassNotFoundException.

    (ns my-project.MyClass
      (:gen-class))
    
    (my-project.MyClass.) ;;=> java.lang.ClassNotFoundException
    (my_project.MyClass.) ;;=> #<MyClass ...>
    
    By , created 7.2 years ago

    Note: If you are using :extends you must use the fully qualified class name even if the class is in the :import list for the namespace.

    By , created 6.0 years ago

    Not strictly speaking a gen-class issue, but if you need to override a Java method that's overloaded by specifying different classes for its arguments, this can be done by incorporating the class names into Clojure function names. See:

    https://groups.google.com/forum/#!topic/clojure/TVRsy4Gnf70

    https://puredanger.github.io/tech.puredanger.com/2011/08/12/subclassing-in-clojure

    http://stackoverflow.com/questions/32773861/clojure-gen-class-for-overloaded-and-overridden-methods

    http://dishevelled.net/Tricky-uses-of-Clojure-gen-class-and-AOT-compilation.html