The recommended way to create an instance of a type T
is to “call” T
as if it were a function, with the
arguments used to initialize the object.
If T
is a class and T
has a matching constructor,
then the arguments will used for constructor arguments:
(java.util.StringTokenizer "this/is/a/test" "/")
(You can think of the type T
as being
coerced to an instance-constructor function.)
If T
is a container or collection type,
then typically the arguments will be used to specify
the child or component values.
Many standard Scheme procedures fit this convention.
For example in Kawa list
and vector
evaluate to
types, rather than procedures as in standard Scheme,
but because types can be used as constructor functions it just works:
(list 'a (+ 3 4) 'c) ⇒ (a 7 c) (vector 'a 'b 'c) ⇒ #(a b c)
Any class T
that has a default constructor
and an add
method can be initialized this way.
Examples are java.util
collection classes,
and jawa.awt
and javax.swing
containers.
(java.util.ArrayList 11 22 33) ⇒ [11, 22, 333]
The above expression is equivalent to:
(let ((tmp (java.util.ArrayList))) (tmp:add 11) (tmp:add 22) (tmp:add 33) tmp)
Allocating Java arrays (see Creating-new-Java-arrays) uses a similar pattern:
(int[] 2 3 5 7 11)
Sometimes you want to set some named property to an initial value. You can do that using a keyword argument. For example:
(javax.swing.JButton text: "Do it!" tool-tip-text: "do it")
This is equivalent to using setter methods:
(let ((tmp (javax.swing.JButton))) (tmp:setText "Do it!") (tmp:setToolTipText "do it") tmp)
A keyword argument key-name
:
can
can translated to either a
or a set
KeyName
:
method.
The latter makes it convenient to add listeners:
add
KeyName
:
(javax.swing.JButton text: "Do it!" action-listener: (object (java.awt.event.ActionListener) ((actionPerformed e) (do-the-action))))
This is equivalent to:
(let ((tmp (javax.swing.JButton))) (tmp:setText "Do it!") (tmp:addActionListener (object (java.awt.event.ActionListener) ((actionPerformed e) (do-the-action)))) tmp)
Making use of so-called “SAM-conversion” (see SAM-conversion) makes it even more convenient:
(javax.swing.JButton text: "Do it!" action-listener: (lambda (e) (do-the-action)))
The general case allows for a mix of constructor arguments, property keywords, and child values:
class-type
constructor-value
... property-initializer
... child-value
...
constructor-value
::=
expression
property-initializer
::=
keyword
expression
child-value
::=
expression
First an object is constructed with the constructor-value
arguments
(if any) passed to the object constructor;
then named properties (if any) are used to initialize named properties;
and then remaining arguments are used to add child values.
There is an ambiguity if there is no property-initializer
-
we can’t distinguish between a constructor-value
and a child-value
.
In that case, if there is a matching constructor method, then all of the
arguments are constructor arguments;
otherwise, there must a default constructor, and all
of the arguments are child-value
arguments.
There is a trick you can you if you need both
constructor-value
and child-value
arguments:
separate them with an “empty keyword” ||:
.
This matches a method named add
, which means that
the next argument effectively a child-value
- as do
all the remaining arguments. Example:
(let ((vec #(1 2 3))) (java.util.ArrayList vec ||: 4 5 6)) ⇒ [1, 2, 3, 4, 5, 6]
The compiler rewrites these allocations expression to generated efficient bytecode, assuming that the “function” being applied is a type known by the compiler. Most of the above expressions also work if the type is applied at run-time, in which case Kawa has to use slower reflection:
(define iarr int[]) (apply iarr (list 3 4 5)) ⇒ [3 4 5]
However add
methods and SAM-conversion
are currently only recognized in the case of a class known at compile-time,
not at run-time.
Xxx
Here is a working Swing demo illustrating many of these techniques:
(import (class javax.swing JButton Box JFrame)) (define-simple-class HBox (Box) ((*init*) (invoke-special Box (this) '*init* 0))) (define value 0) (define txt (javax.swing.JLabel text: "0")) (define (set-value i) (set! value i) (set! txt:text (number->string i))) (define fr (JFrame title: "Hello!" (Box 1#|VERTICAL|# ||: (javax.swing.Box:createGlue) txt (javax.swing.Box:createGlue) (HBox (JButton ;; uses 1-argument constructor "Decrement" ;; constructor argument tool-tip-text: "decrement" action-listener: (lambda (e) (set-value (- value 1)))) (javax.swing.Box:createGlue) (JButton ;; uses 0-argument constructor text: "Increment" tool-tip-text: "increment" action-listener: (lambda (e) (set-value (+ value 1)))))))) (fr:setSize 200 100) (set! fr:visible #t)
If you prefer, you can use the older make
special function:
Constructs a new object instance of the specified
type
, which must be either ajava.lang.Class
or a<gnu.bytecode.ClassType>
. Equivalent to:type
args
...
Another (semi-deprecated) function is to use the colon notation
with the new
pseudo-function.
The following three are all equivalent:
(java.awt.Point:new x: 4 y: 3) (make java.awt.Point: x: 4 y: 3) (java.awt.Point x: 4 y: 3)