Next: , Previous: , Up: Objects Classes and Modules   [Contents][Index]


19.10 Allocating objects

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 setKeyName: or a addKeyName: method. The latter makes it convenient to add listeners:

(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 addXxx methods and SAM-conversion are currently only recognized in the case of a class known at compile-time, not at run-time.

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:

Procedure: make type args ...

Constructs a new object instance of the specified type, which must be either a java.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)

Next: , Previous: , Up: Objects Classes and Modules   [Contents][Index]