Kawa provides various mechanisms for defining new classes.
The define-class
and define-simple-class
forms
will usually be the preferred mechanisms. They have basically
the same syntax, but have a couple of differences.
define-class
allows multiple inheritance as well as true nested
(first-class) class objects. However, the implementation
is more complex: code using it is slightly slower, and the mapping to
Java classes is a little less obvious. (Each Scheme class is implemented
as a pair of an interface and an implementation class.)
A class defined by define-simple-class
is slightly more
efficient, and it is easier to access it from Java code.
The syntax of define-class
are mostly compatible with that
in the Guile and Stk dialects of Scheme.
Syntax: define-class
class-name
(
supers
...
)
(annotation
|
)option-pair
*
field-or-method-decl
...
Syntax: define-simple-class
class-name
(
supers
...
)
(annotation
|
)option-pair
*
field-or-method-decl
...
Defines a new class named
class-name
. Ifdefine-simple-class
is used, creates a normal Java class namedclass-name
in the current package. (Ifclass-name
has the form<xyz>
the Java implementation type is namedxyz
.) Fordefine-class
the implementation is unspecified. In most cases, the compiler creates a class pair, consisting of a Java interface and a Java implementation class.
class-name
::=
identifier
option-pair
::=
option-keyword
option-value
field-or-method-decl
::=
field-decl
| method-decl
The class inherits from the classes and interfaces listed in supers
.
This is a list of names of classes that are in scope (perhaps imported
using require
), or names for existing classes or interfaces
optionally surrounded by <>
, such as <gnu.lists.Sequence>
.
If define-simple-class
is used, at most one of these may be
the name of a normal Java class or classes defined using
define-simple-class
; the rest must be interfaces or classes
defined using define-class
.
If define-class
is used, all of the classes listed
in supers
should be interfaces or classes defined using
define-class
.
interface:
make-interface
Specifies whether Kawa generates a Java class, interface, or both.
If make-interface
is #t
, then a Java interface is generated.
In that case all the supertypes must be interfaces, and
all the declared methods must be abstract.
If make-interface
is #f
, then a Java class is generated.
If interface:
is unspecified, the default is #f
for define-simple-class
. For define-class
the default
is to generate an interface, and in addition (if needed) a helper
class that implements the interface. (In that case any non-abstract methods
are compiled to static methods. The methods that implement the interface
are just wrapper methods that call the real static methods. This
allows Kawa to implement true multiple inheritance.)
access:
kind
Specifies the Java access permission on the class.
Can be one of 'public
(which is the default in Kawa),
'package
(which the default "unnamed" permission in Java code),
'protected
, 'private
,
'volatile
, or 'transient
.
Can also be used to specify final
, abstract
, or enum
, as in Java.
(You don’t need to explicitly specify the class is abstract
if any method-body
is #!abstract
,
or you specify interface: #t
.)
The kind
can also be a list, as for example:
access: '(protected volatile)
class-name:
"
cname
"
Specifies the Java name of the created class.
The name
specified after define-class
or define-simple-class
is the Scheme name,
i.e. the name of a Scheme variable that is bound to the class.
The Java name is by default derived from the Scheme name,
but you can override the default with a class-name:
specifier.
If the cname
has no periods, then it is a name in
the package of the main (module) class.
If the cname
starts with a period,
then you get a class nested within the module class.
In this case the actual class name is moduleClass
$
rname
,
where rname
is cname
without the initial period.
To force a class in the top-level (unnamed) package (something
not recommended) write a period at the end of the cname
.
field-decl
::=
(
field-name
(annotation
| opt-type-specifier
| field-option
)*)
field-name
::=
identifier
field-option
::=
keyword
expression
As a matter of style the following order is suggested, though this not enforced:
Each field-decl
declares a instance "slot" (field)
with the given field-name
.
By default it is publicly visible, but you can specify
a different visiblity with the access:
specifier.
The following field-option
keyword
s are implemented:
type:
type
Specifies that type
is the type of (the values of) the field.
Equivalent to ‘::
’.
type
allocation:
kind
If kind
is 'class
or 'static
a single slot is shared
between all instances of the class (and its sub-classes).
Not yet implemented for define-class
,
only for define-simple-class
.
In Java terms this is a static
field.
If kind
is 'instance
then
each instance has a separate value "slot", and they
are not shared. In Java terms, this is a non-static
field.
This is the default.
access:
kind
Specifies the Java access permission on the field.
Can be one of 'private
, 'protected
,
'public
(which is the default in Kawa),
or 'package
(which the default "unnamed" permission
in Java code).
Can also be used to specify volatile
, transient
,
enum
, or final
, as in Java,
or a quoted list with these symbols.
init:
expr
An expression used to initialize the slot. The expression is evaluated in a scope that includes the field and method names of the current class.
init-form:
expr
An expression used to initialize the slot.
The lexical environment of the expr
is that of the define-class
;
it does not include the field and method names of the current class.
or define-simple-class
.
init-value:
value
A value expression used to initialize the slot.
For now this is synonymous with init-form:
, but that may change
(depending on what other implementation do), so to be safe only use
init-value:
with a literal.
init-keyword:
name
:
A keyword that that can be used to initialize instance in make
calls.
For now, this is ignored, and name
should be the same as the
field’s field-name
.
The field-name
can be left out. That indicates a "dummy slot",
which is useful for initialization not tied to a specific field.
In Java terms this is an instance or static initializer, i.e., a
block of code executed when a new instance is created or the class is loaded.
In this example, x
is the only actual field. It is first
initialized to 10, but if (some-condition)
is true
then its value is doubled.
(define-simple-class <my-class> () (allocation: 'class init: (perform-actions-when-the-class-is-initizalized)) (x init: 10) (init: (if (some-condition) (set! x (* x 2)))))
method-decl
::=
((
method-name
formal-arguments
)
method-option
* [deprecated-return-specifier
] method-body
)
method-name
::=
identifier
method-option
::=
annotation
| opt-return-type
| option-pair
method-body
::=
body
| #!abstract
| #!native
deprecated-return-specifier
::=
identifier
Each method-decl
declares a method,
which is by default public and non-static, and whose name is method-name
.
(If method-name
is not a valid
Java method name, it is mapped to something reasonable.
For example foo-bar?
is mapped to isFooBar
.)
The types of the method arguments can be specified in the
formal-arguments
. The return type can be specified by
a opt-return-type
, deprecated-return-specifier
,
or is otherwise the type of the body
.
Currently, the formal-arguments
cannot contain optional, rest,
or keyword parameters. (The plan is to allow optional parameters,
implemented using multiple overloaded methods.)
A method-decl
in a define-simple-class
can have the following option-keyword
s:
access:
kind
Specifies the Java access permission on the method.
Can be one of 'private
, 'protected
,
'public
, or 'package
.
Can also be 'synchronized
, 'final
, 'strictfp
,
or a quoted list.
allocation:
kind
If kind
is 'class
or 'static
creates a static method.
throws:
( exception-class-name
... )
Specifies a list of checked exception that the method may throw.
Equivalent to a throws
specification in Java code.
For example:
(define-simple-class T (prefix) ((lookup name) throws: (java.io.FileNotFoundException) (make java.io.FileReader (string-append prefix name))))
The scope of the body
of a method includes the field-decl
s
and method-decl
s of the class, including those inherited from
superclasses and implemented interfaces.
If the method-body
is the special form #!abstract
,
then the method is abstract. This means the method must
be overridden in a subclass, and you’re not allowed to
create an instance of the enclosing class.
(define-simple-class Searchable () interface: #t ((search value) :: boolean #!abstract))
If the method-body
is the special form #!native
,
then the method is native, implemented using JNI.
The special method-name
‘*init*
’ can be used to name
a non-default constructor (only if make-interface
discussed above
is #f
).
It can be used to initialize a freshly-allocated instance
using passed-in parameters.
You can call a superclass or a sibling constructor using
the invoke-special
special function.
(This is general but admittedly a bit verbose; a more compact
form may be added in the future.)
See the example below.
In the following example we define a simple class 2d-vector
and a class 3d-vector
that extends it. (This is for illustration
only - defining 3-dimensional points as an extension
of 2-dimensional points does not really make sense.)
(define-simple-class 2d-vector () (x ::double init-keyword: x:) ;; Alternative type-specification syntax. (y type: double init-keyword: y:) (zero-2d :: 2d-vector allocation: 'static init-value: (2d-vector 0)) ;; An object initializer (constructor) method. ((*init* (x0 ::double) (y0 ::double)) (set! x x0) (set! y y0)) ((*init* (xy0 ::double)) ;; Call above 2-argument constructor. (invoke-special 2d-vector (this) '*init* xy0 xy0)) ;; Need a default constructor as well. ((*init*) #!void) ((add (other ::2d-vector)) ::2d-vector ;; Kawa compiles this using primitive Java types! (2d-vector x: (+ x other:x) y: (+ y other:y))) ((scale (factor ::double)) ::2d-vector (2d-vector x: (* factor x) y: (* factor y)))) (define-simple-class 3d-vector (2d-vector) (z type: double init-value: 0.0 init-keyword: z:) ;; A constructor which calls the superclass constructor. ((*init* (x0 ::double) (y0 ::double) (z0 ::double)) (invoke-special 2d-vector (this) '*init* x0 y0) (set! z z0)) ;; Need a default constructor. ((*init*) #!void) ((scale (factor ::double)) ::2d-vector ;; Note we cannot override the return type to 3d-vector ;; because Kawa doesn't yet support covariant return types. (3d-vector x: (* factor x) y: (* factor (this):y) ;; Alternative syntax. z: (* factor z))))
Note we define both explicit non-default constructor methods, and we associate fields with keywords, so they can be named when allocating an object. Using keywords requires a default constructor, and since having non-default constructors suppresses the implicit default constructor we have to explicitly define it. Using both styles of constructors is rather redundant, though.