Classes
Jactl supports user-defined classes.
Classes are defined using the class
keyword followed by a class name which must start with an uppercase letter.
As with Java, classes can define fields and methods with appropriate types (or def
if dynamic typing is being
used).
Since Jactl is a scripting language, for simplification, there is no concept of public
, protected
, or
private
- all fields and methods are effectively public
.
For example:
class Point { double x; double y }
def p = new Point(x:1, y:2)
p.x // 1
p.y // 2
We can add a method to this class:
class Point {
double x
double y
def distance(other) {
def dx = x - other.x
def dy = y - other.y
(dx*dx + dy*dy).sqrt()
}
}
def p1 = new Point(x:0, y:0)
def p2 = new Point(x:3, y:4)
p1.distance(p2) // 5
We used def
as the return type for our method, but we could have used Decimal
or double
, for example,
if we so desired.
Default Values
Fields can be given default values if needed. These default values can be based on the values of other fields:
class X {
int x
int y = x * x
int z = 0
}
Const Fields
Classes can have constant fields for commonly used numeric and String values.
Const fields use the const
keyword to indicate that they are constant values, then followed by a standard
field declaration with a value assignment.
For example:
class X {
const long MAX_VALUE = 100000
...
}
Only numeric types and String values are support for constant fields.
If the type is omitted, Jactl will infer the type from the value being assigned:
class X {
const MAX_VALUE = 100000L
...
}
Static Methods
Methods of a class can be defined as static
which means that they can be invoked directly without needing
a class instance.
It is therefore an error for a static method to refer to any class fields since there is no class instance
associated with the invocation.
For example, here is a class with a static factory method for creating Circle
instances:
class Circle {
Point centre
double radius
static Circle create(double x, double y, double r) {
die 'Radius must be > 0' if r <= 0
return new Circle(new Point(x,y), r)
}
}
No Static Fields
Jactl does not support static fields. The reason that they are not allowed is twofold:
- Jactl is intended to run in distributed applications where there are multiple instances running. Having a static data member in a class for sharing information across all class instances makes no sense when there are multiple application instances running since there would then be multiple instances of the static data.
- By avoiding the use of static data, it also means that there is no way for multiple scripts on different threads to be trying to access and modify the same data. This means that Jactl does not need to provide any thread sychronisation mechanisms that are notoriously error-prone and avoids having to worry about deadlocks.
Constructors
To construct an instance of a class you used the new
keyword followed by the class name and then parameters
corresponding to one of the class constructors.
Unlike Java, Jactl classes have constructors built for them and does not allow user to create their own. There are two constructors built for each class:
- A constructor taking positional parameters, and
- A constructor taking named parameters.
The constructor taking positional parameters has one parameter per mandatory field in the class in the order in which the fields were defined.
For example:
class Car {
String make
String model
int year
String colour = 'white'
}
def car = new Car('Holden', 'Kingswood', 1975)
The make
, model
, and year
fields are mandatory fields (since they have no default value) and so the
default positional constructor is created with a parameter for each of these mandatory fields.
The named parameter constructor can be used to make it clearer what fields are being set, or when you want to set the values of optional fields. The order of the fields is not important when using named parameters.
For example:
def car = new Car(make:'Ford', model:'Falcon', year:1987, colour:'green')
If you want to create your own constructor in order to perform validation, for example, you can create static
factory method for creating class instances (see example of Circle
class above).
Inheritance
Classes can inherit from other classes using the extends
keyword.
For example:
class ColouredPoint extends Point {
String colour
}
def cp = new ColoredPoint(x:1, y:2, color:'red')
cp.x // 1
cp.colour // red
Child classes can override methods of a parent class to provide their own implementation.
For example, we can have a tree of nodes, some of which are directories, and some of which are
files.
Each type overrides the size()
method as needed:
class Node {
Node parent
String name
long size() { 0 }
}
class Dir extends Node {
List children = []
def addChild(Node child) { children <<= child }
long size() { children.map(size).sum() }
}
class File extends Node {
long fileSize
long size() { fileSize }
}
this
Keyword
Within a regular (non-static) class method, the keyword this
refers to the current instance.
This allows you to explicitly refer to fields where it is otherwise ambiguous, for example:
class Point {
double x,y
double distanceTo(double x, double y) {
def dx = this.x - x
def dy = this.y - y
(dx*dx + dy*dy).sqrt()
}
}
It is also necessary to use this
if a method needs to pass the current instance to another function.
super
Keyword
If you are overriding a method, the super
keyword allows you to invoke the parent's version of the method
from within the overriding method.
For example:
class X {
def doSomething() {
...
}
}
class Y extends X {
def doSomething() {
super.doSomething()
...
}
}
Packages
Classes can be declared in the scripts where they are used (as shown above), but classes can also be put into packages just like in Java so that they can then be used by multiple scripts.
This package hierarchy sits in its own namespace (defined by the application) in order not to conflict with any Java pacakges.
Like in Java, when putting classes in packages, each top level class must be declared in a file that matches the class
name (with a .jactl
suffix).
For example, this could be the definition for our Point
class in a file called Point.jactl
and in package called
app.utils
:
package app.utils
class Point {
double x,y
}
Note that package names must be all lowercase.
Classes and scripts that belong to the same package automatically have access to all other classes within the package.
If a script or class needs to access a class in different package, they need to use the import
statement.
Import Statement
The import
statement is how a script or class imports the definition of another class in order to use that class.
For example:
import app.utils.Point
class Square {
Point p1
Point p2
static
}
It is possible to import references to all classes of a package if there are too many to list individually by using
*
:
import app.utils.*
You can rename a class during import using as
if it clashes with another class name or if you simply want a shorter
name to use within the given script/class:
import app.utils.Point as P
class Square {
P p1, p2
}
Static Import
The import
statement, as well as importing class definitions, can be used to import static methods and constants
from other classes.
As for importing classes, the imported methods/constants can be renamed as part of the import by using as
:
import static utils.Math.PI
import static utils.Math.circleArea as area
def radius = 3
def area = circleArea(radius)
def circumference = 2 * PI * radius
It is also possible to import all static methods and constants of a class:
import static utils.Math.*
Compilation of Class Files
If you are embedding Jactl in your application, it is up to the application to make sure that the classes are compiled in order for scripts to have access to them. See the Integration Guide for more details.
When using classes with command line scripts, the -P
option allows you to specify where the package root(s) of
your Jactl classes are located so that the script can access your classes.
See the Command-Line Scripts Guide for more information.