Methods

Sedona

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 based
  • virtual: indicates method that may be polymorphically overridden
  • abstract: indicates a pure virtual method
  • override: required to indicate an override of an inherited method
  • native: indicates a method implemented in native C code
  • action: 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.


Last update: April 28, 2020