Methods
Overview¶
Methods are functions used to implement executable behavior. The following keywords may be applied to a method declaration:
static
: class based method versus instance basedvirtual
: indicates method that may be polymorphically overriddenabstract
: indicates a pure virtual methodoverride
: required to indicate an override of an inherited methodnative
: indicates a method implemented in native C codeaction
: promotes a method to a component action
In addition to the keywords above, a method may be annotated with a
protection scope keyword. If no protection
scope is specified, then public
is assumed.
Return Values¶
If the method returns a value to the calling function, the return value
type is included in the function definition. If the method does not
return anything, the keyword void
is used instead of a return type.
class Example
{
void echo() { Sys.out.print("echo").nl() } // returns nothing
int add(int x, int y) { return x + y } // returns an int value
}
In Sedona, a method may return any primitive type, or a reference to any
built-in or user-defined class type; the only exception is an action
method, which always returns void
.
Static Methods¶
Static methods are prefixed with the static
keyword. Static methods
are essentially global functions scoped within a class name. They are
declared just like Java methods:
class Example
{
static void echo() { Sys.out.print("echo").nl() }
static int add(int x, int y) { return x + y }
}
Static methods are called with an implict or explicit type literal:
Example.echo() // explicit
int five = add(2, 3) // implicit (only inside Example or subclasses)
Instance Methods¶
Instance methods are declared whenever the static
keyword is omitted.
Instance methods contain an implicit this
parameter, which is the
instance the method is called on:
class Example
{
int add() { return x + y }
int sub() { return this.x - this.y }
int x
int y
}
Note in the example, that every instance method has an implicit
parameter accessed via the this
keyword. Instance methods are called
with an implict or explicit instance:
add() // implicit against this
this.sub() // explicit against this
x.sub() // explicit against x
x?.sub() // null safe call
See Safe Navigation for how to use the "?." operator.
Virtual Methods¶
Virtual methods are designed to be overridden by a subclass to enable
polymorphism. Methods must be marked using the virtual
keyword before
they can be overridden by subclasses. Subclasses must declare they are
overriding a method using the override
keyword:
class Animal extends Virtual
{
virtual void talk() { Sys.out.print("generic\n") }
}
class Cat extends Animal
{
override void talk() { Sys.out.print("meow\n") }
}
animal.talk() // prints generic
cat.talk() // prints meow
By default an overridden method cannot itself be overridden by a
subsequent subclass. In order for Cat.talk()
to be overridden by the
subclass Kitten
, it must include again the keyword virtual
:
class Cat extends Animal
{
override virtual void talk() { Sys.out.print("meow\n") } // override AND virtual
}
class Kitten extends Cat
{
override void talk() { Sys.out.print("mew!\n") } // this now compiles
}
kitten.talk() // prints mew!
Classes that declare virtual methods must derive from sys::Virtual
. Be
aware that virtual classes have the overhead of an extra pointer for
their vtable (typically 4 extra bytes).
Abstract Methods¶
Abstract methods are virtual methods without an implementation. They are
declared using the abstract
keyword. Abstract methods are implied to
be virtual - it is an error to use both the abstract
and virtual
keyword. Abstract methods must not provide a method body. The containing
class must also be declared abstract
.
Super¶
By default any virtual method call with an implicit or explicit target
of this
invokes the most specific version of that method. You can use
the super
keyword to invoke the super class version of a method:
class Kitten extends Cat
{
override void talk() { super.talk() }
}
kitten.talk() // now prints meow
Constructors¶
A class may have one constructor, which is compiled into a method called
_iInit
. Whenever a class declares instance fields with a default
value, the compiler auto-generates a constructor
for you. A class may declare an explicit constructor using a syntax
similiar to Java:
final class BufInStream extends InStream
{
BufInStream(Buf abuf) { this.buf = abuf }
Buf buf
}
To keep Sedona lightweight and simple, the following rules apply to constructor methods:
- A class may only declare one explicit constructor (parameter overloading is not supported).
- A class with an explicit constructor must be marked
final
. - Subclasses of
sys::Component
cannot declare a constructor with parameters. - Declared constructors are used with unsized classes to specify an array length.
The constructor method for a class is called whenever an object of that
type is instantiated. For static inline fields, this happens as soon as
the scode is loaded when the Sedona VM boots up. For instance inline
fields, this happens when the Sedona VM loads the Sedona application and
calls the constructors for the application's components as well as each
component's inline object fields. If the running app is modified
remotely, however, any new components will be instantiated immediately
by App.add()
, which will call the component's constructor (and all
its non-static inline object fields) at that time.
For example:
class Foo
{
// static constructor calls
static inline Buf(100) buf
static inline BufInStream(buf) in
static inline BufOutStream(buf) out
static inline Foo inst
// instance constructor calls
inline Buf(20) ibuf
}
Inline static fields are initialized in declaration order on VM startup,
which calls the appropriate constructors. These constructor calls often
result in instance constructor calls, which in turn recursively chain
for nested inline fields. For the example above, the compiler will
create the following code; Foo._sInit
is then automatically called
when the VM boots:
static void Foo._sInit()
{
Foo.buf._iInit(100)
Foo.in._iInit(Foo.buf)
Foo.out._iInit(Foo.buf)
Foo.inst._iInit()
}
void Foo._iInit()
{
this.ibuf._iInit(20)
}
Native Methods¶
The native
keyword is used on methods that are implemented in C code.
Like abstract methods, native methods must not define a body. See
Native Methods for more details.
Action Methods¶
Methods may be annotated with the action
keyword to promote the method
into a Component action. Actions must be instance methods on a subclass
of sys::Component
. See Component Actions
for more details.