SARL enables to declare objects with an object-oriented programming approach for convenience to develop complex programs, i.e. the SARL developer must not learn another language such as Java for programming the objects that are used within the SARL program. Because of this convenience, the support of object-oriented programming may be less complete than in main stream object programming languages.
This document describes the basic support of object-oriented programming provided by SARL. Before reading this document, we recommend that you read the General Syntax Reference.
The support of the object-oriented programming (OOP) statements in SARL is less complete than a real object-oriented language such as Java. The basics of OOP are enabled in the SARL scripts. If you need more complex or more complete support of the OOP, you should use a dedicated language, such as Java, Xtend, or Scala.
Note The SARL Eclipse product includes the tools for programming with the Java and Xtend languages.
A comparison between SARL and other programming languages could be found here.
Objects are structures that contain both data and procedures. Classes are definitions for the data format and available procedures for a given type or class of objects. They may also contain data and procedures (known as class methods) themselves.
For defining a class, you could use the class
keyword. The following example defines the class MyClass
.
The members of the class, i.e. the fields, methods and constructors must be written between the braces that are following the class declaration.
class MyClass { }
Languages that support classes almost always support inheritance. This allows classes to be arranged in a hierarchy that represents “is-a-type-of” relationships.
For example, class Employee
might inherit from class Person
.
All the data and methods available to the parent class also appear in the child class with the same names.
For example, class Person
might define variables firstName
and lastName
with
method getFullName
. These will also be available in class Employee, which might add the variables
position
and salary
.
The definition of the inheritance relationship between two classes is done with the extends
keyword.
class Person {
var firstName : String
var lastName : String
def getFullName : String {
this.firstName + " " + this.lastName
}
}
class Employee extends Person {
var position : String
var salary : float
}
A generic class declaration looks like a non-generic class declaration, except that the class name is followed by a type parameter section.
The type parameter section of a generic class can have one or more type parameters separated by commas. These classes are known as parameterized classes or parameterized types because they accept one or more parameters.
There may be times when you’ll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for. To declare a bounded type parameter, list the type parameter’s name, followed by:
extends
keyword, followed by its upper bound; orsuper
keyword, followed by its lower bound.Example:
class AType<T> {
var t : T
def add(t : T) {
this.t = t
}
def get : T {
return this.t
}
}
class Vector<T extends Number> {
var x : T
var y : T
def norm : Vector<? extends Number> {
var v = new Vector
var n = Math::sqrt(this.x.floatValue**2 + this.y.floatValue**2)
v.x = this.x.floatValue / n
v.y = this.y.floatValue / n
return v
}
}
An SARL class can define any number of constructors. Unlike Java, you do not have to repeat the name of the
class over and over again, but use the keyword new
to declare a constructor.
Constructors can also delegate to other constructors using this(args...)
in their first line.
If the super class does not define a no-argument constructor, you have to explicitly call
one using super(args...)
as the first expression in the body of the constructor.
class MyClass extends AnotherClass {
new (s : String) {
super(s)
}
new() {
this("default")
}
}
If no constructor is defined in the type and a super-type is declared, implicit constructors will be assumed. Implicit constructors has the same prototypes as the constructors of the super type. Details on implicit constructors are given in the reference documentation related to the synthetic functions.
A static constructor is used to initialize any static data, or to perform a particular action that needs to be performed once only. It is called automatically before the first instance is created or any static members are referenced. In the Java programming language, the static constructor is known as static initialization block.
In the following example, the static field staticField
is defined with the final
modifier.
It means the field must be intialized into the field declaration or into a static constructor.
The static constructor is defined with the new
prefixed by the static
.
The code block of the static constructor contains an assignment that is initializing the staticField
field.
class MyClass {
static val staticField : int
static new {
staticField = 2
}
}
Static constructors have the following properties:
RuntimeException
or Error
).A field could be declared into a class following the variable declaration syntax.
A field may be declared with the static
modifier. In this case, it becomes a static field, or class variable.
A static field is a variable defined in a class of which a single copy exists, regardless of how many instances of the class exist.
A static field is not an instance variable. It is a special type of class attribute (or class property, field, or data member).
A method could be declared into a class following the function declaration syntax. The overriding of an inhertited method is explained in section “Method Overriding”.
A method may be declared with the static
modifier. In this case, it becomes a static method, or class method.
A static method is a function that is not associated to a specific instance of the class.
Modifiers are used to modify declarations of types and type members. This section introduces the modifiers for the class. The modifiers are usually written before the keyword for defining the class.
The complete description of the modifiers’ semantic is available in this section.
A top class may be declared with one or more modifiers, which affect its runtime behavior:
public
: the class is accessible from any other type (default);package
: the class is accessible from only the types in the same package.abstract
: the class is abstract and cannot be instanced.final
: avoid to be derived.strictfp
: avoid the methods of the class to use intermediate floating number formats.Examples:
public class TopClass1 {
}
package class TopClass2 {
}
abstract class TopClass3 {
}
final class TopClass4 {
}
strictfp class TopClass5 {
}
A nested class may be declared with one or more modifiers, which affect its runtime behavior:
public
: there are no restrictions on accessing the class (default);protected
: the class is accessible within the same package, and derived classes;package
: the class is accessible only within the same package as its class;private
: the class is accessible only within its class.abstract
: the class is abstract and cannot be instanced.final
: avoid to be derived.static
: the inner class do not have access to the non-static members of the enclosing type.strictfp
: avoid the methods of the class to use intermediate floating number formats.Terminology Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are called static nested classes. Non-static nested classes are called inner classes.
Note The modifiers may differ from the previously described, depending on the enclosing type, e.g. agent.
Caution Until now, all the nested classes must be declared as static. This restriction may be removed in later versions.
class EnclosingClass {
public static class NestedClass1 {
}
protected static class NestedClass2 {
}
package static class NestedClass3 {
}
private static class NestedClass4 {
}
abstract static class NestedClass5 {
}
final static class NestedClass6 {
}
strictfp static class NestedClass7 {
}
}
The modifiers for the fields in a class are:
public
: there are no restrictions on accessing the field;protected
: the field is accessible within the same package, and derived classes;package
: the field is accessible only within the same package as its class;private
: the field is accessible only within its class (default).static
: the field is a class field, not an instance field.transient
: the field is never serialized.volatile
: the field is modified by different threads. It is never cached thread-locally, and synchronized.Examples:
public var a : Object
protected var b : Object
package var c : Object
private var d : Object
static var e : Object
transient var h : Object
volatile var g : Object
The modifiers for the methods in a class are:
public
: there are no restrictions on accessing the method (default);protected
: the method is accessible within the same package, and derived classes;package
: the method is accessible only within the same package as its class;private
: the method is accessible only within its class.abstract
: the method has no implementation in the class.dispatch
: the method provides an implementation for the dispatch method mechanism.final
: the method cannot be overridden in derived classes.native
: the implementation of the method is inside a native library (DLL, SO, DYNLIB).static
: the method is a class method, not an instance method.strictfp
: avoid the method to use intermediate floating number formats.synchronized
: the method is synchronized on the class instance.Examples:
public def fct1 { }
protected def fct2 { }
package def fct3 { }
private def fct4 { }
abstract def fct5
final def fct6 { }
native def fct7
static def fct8 { }
strictfp def fct9 { }
synchronized def fct10 { }
// Dispatch functions
dispatch def fct11(p : Integer) { }
dispatch def fct11(p : Float) { }
The modifiers for the constructors of a class are:
public
: there are no restrictions on accessing the constructor (default);protected
: the constructor is accessible within the same package, and derived classes;package
: the constructor is accessible only within the same package as its class;private
: the constructor is accessible only within its class.Examples:
public new(p : int) { }
protected new(p : float) { }
package new(p : char) { }
private new(p : boolean) { }
Method overriding is a language feature that allows a subclass or child class to provide a specific implementation of a method that is already provided by one of its super classes or parent classes.
The implementation in the subclass overrides (replaces) the implementation in the superclass by providing a method that has same name, same parameters or signature, and same return type as the method in the parent class.
The version of a method that is executed will be determined by the object that is used to invoke it. If an object of a parent class is used to invoke the method, then the version in the parent class will be executed, but if an object of the subclass is used to invoke the method, then the version in the child class will be executed.
The following code defines the class PersonEx
as a subclass of Person
,
and in which the title (mister, madam, miss) is added.
Then the full name of the person becomes the sequence of the title, first name and last name.
Since the first name and last name are already sequenced in the function
getFullName
of the superclass, we should override this function for changing
its behavior. The override
keyword is specified for clearly marking this
implementation of getFullName
as an override of the parent’s implementation.
Note The return type of the getFullName
method (called with the name fullName
, according to the property access syntax) is not specified in the overriding prototype since it could be inferred by the SARL compiler.
For preventing a function to be overridden, you should add the final
modifier in the signature of
the method (as in Java).
class PersonEx extends Person {
var title : String
override getFullName {
return title + " " + super.fullName
}
}
Local classes (or nested classes, i.e. classes defined inside an other type) have
the static
modifier. It means that a nested class cannot have access to the
fields and methods of the instance of the enclosing type. Only accesses to static members are allowed.
class EnclosingClass {
static var field1 : int
static def fct1 {
}
static class NestedClass {
def fct2 : int {
// Explicit type specification for the field access
EnclosingClass::field1
}
def fct3 {
// Implicit type specification for the function's call
fct1
}
}
}
An interface is a description of the actions that an object can do. For example when you flip a light switch, the light goes on, you don’t care how, just that it does. In object-oriented programming, an interface is a description of all functions that an object must have in order to be an “X”.
The purpose of interfaces is to allow the program to enforce these properties, and to know that an object of type T (whatever the interface is) must have functions called X,Y,Z, etc.
In the following example, the Light
interface is defined with the two methods turnOn
and turnOff
.
interface Light {
def turnOn
def turnOff
}
It is possible to specialize the definition of an interface. In the following example, the VariableIntensityLight
interface that is refining the previous Light
interface and add specific functions.
interface VariableIntensityLight extends Light {
def setLightIntensity(intensity : float)
def getLightIntensity : float
}
A generic interface declaration looks like a non-generic interface declaration, except that the interface name is followed by a type parameter section.
The type parameter section of a generic interface can have one or more type parameters separated by commas. These interfaces are known as parameterized interfaces or parameterized types because they accept one or more parameters.
There may be times when you’ll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for. To declare a bounded type parameter, list the type parameter’s name, followed by:
extends
keyword, followed by its upper bound; orsuper
keyword, followed by its lower bound.Examples:
interface AnInterface<T> {
def add(t : T)
def get : T
}
interface Vector<T extends Number> {
def norm : Vector<? extends Number>
}
A class is able to implement an interface. The implements
keyword is used for defining
the implementation relationship between a class and an interface.
The class must provide an implementation of all the functions defined in the interface.
The only one exception is when the class is abstract. In this case, the derived classes must implement the
functions of the interface.
class TheLight implements Light {
var isSwitchedOn = false
def turnOn {
this.isSwitchedOn = true
}
def turnOff {
this.isSwitchedOn = false
}
}
As in Java 8, interface types could provide default function implementation if the implementation class is not providing one. This feature is known as the default method mechanism.
In the following example, two default implementations of the turnOn
and turnOff
functions are provided.
The implementing class needs to provide a function code only for the switchLight
function:
interface Light {
def turnOn {
switchLight(true)
}
def turnOff {
switchLight(false)
}
def switchLight(state : boolean)
}
class TheLight implements Light {
var isSwitchedOn = false
def switchLight(state : boolean) {
this.isSwitchedOn = state
}
}
Modifiers are used to modify declarations of types and type members. This section introduces the modifiers for the interface. The modifiers are usually written before the keyword for defining the interface.
The complete description of the modifiers’ semantic is available in this section.
A top interface may be declared with one or more modifiers, which affect its runtime behavior:
public
: the class is accessible from any other type (default);package
: the class is accessible from only the types in the same package.abstract
: the interface is abstract (not needed since all the interfaces are abstract).strictfp
: avoid the methods of the implementing classes to use intermediate floating number formats.Examples:
public interface TopInterface1 {
}
package interface TopInterface2 {
}
abstract interface TopInterface3 {
}
strictfp interface TopInterface4 {
}
A nested interface may be declared with one or more modifiers, which affect its runtime behavior:
public
: there are no restrictions on accessing the interface (default);protected
: the interface is accessible within the same package, and derived classes;package
: the interface is accessible only within the same package as its class;private
: the interface is accessible only within its class.abstract
: the interface is abstract (not needed since all the interfaces are abstract).static
: the inner interface do not have access to the non-static members of the enclosing type.strictfp
: avoid the methods of the interface to use intermediate floating number formats.Terminology Nested interfaces are divided into two categories: static and non-static. Nested interfaces that are declared static are called static nested interfaces. Non-static nested interfaces are called inner interfaces.
Note The modifiers may differ from the previously described, depending on the enclosing type, e.g. agent.
class EnclosingClass {
public interface NestedInterface1 {
}
protected interface NestedInterface2 {
}
package interface NestedInterface3 {
}
private interface NestedInterface4 {
}
abstract interface NestedInterface5 {
}
static interface NestedInterface6 {
}
strictfp interface NestedInterface7 {
}
}
The modifiers for the fields in an interface are:
public
: there are no restrictions on accessing the field (default);static
: the field is a class field, not an instance field (default).Caution Only fields defined with val
can be put in an interface.
public val a : Object;
static val e : Object;
The modifiers for the methods in an interface are:
public
: there are no restrictions on accessing the method (default);abstract
: the method is abstract (not needed since all the interface methods are abstract).Examples:
public def fct1
abstract def fct5
An enumeration specifies a list of constant values assigned to a type.
The SARL enumeration is not object-oriented unlike the enumeration in the Java programming language. It means that you cannot define methods nor attributes in the enumeration.
For defining an enumeration, you could use the enum
keyword.
The following example defines the enumeration MyEnum
with two constants:
enum MyEnum {
CONSTANT_1,
CONSTANT_2
}
Modifiers are used to modify declarations of types and type members. This section introduces the modifiers for the enumeration. The modifiers are usually written before the keyword for defining the enumeration.
The complete description of the modifiers’ semantic is available in this section.
A top enumeration may be declared with one or more modifiers, which affect its runtime behavior:
public
: the class is accessible from any other type (default);package
: the class is accessible from only the types in the same package.Examples:
public enum TopEnumeration1 {
CST1, CST2
}
package enum TopEnumeration2 {
CST3, CST4
}
A nested interface may be declared with one or more modifiers, which affect its runtime behavior:
public
: there are no restrictions on accessing the enumeration (default);protected
: the enumeration is accessible within the same package, and derived classes;package
: the enumeration is accessible only within the same package as its class;private
: the enumeration is accessible only within its class.static
: the inner enumeration do not have access to the non-static members of the enclosing type.Terminology Nested enumerations are divided into two categories: static and non-static. Nested enumerations that are declared static are called static nested enumerations. Non-static nested enumerations are called inner enumerations.
Note The modifiers may differ from the previously described, depending on the enclosing type, e.g. agent.
class EnclosingClass {
public enum NestedClass1 {
CST1, CST2
}
protected enum NestedClass2 {
CST3, CST4
}
package enum NestedClass3 {
CST5, CST6
}
private enum NestedClass4 {
CST7, CST8
}
static enum NestedClass5 {
CST9, CST10
}
}
An annotation is a form of syntactic metadata that can be added to SARL source code. Annotations can be reflective in that they can be embedded in binary files generated by the SARL compiler, and may be retained by the Virtual Machine to be made retrievable at run-time.
For defining an annotation, you could use the annotation
keyword.
The following example defines the annotation MyAnnotation
.
This annotation defines three parameters:
value
, an array of strings of characters, without default value;isTricky
, a boolean value, with the default false
;lotteryNumbers
, an array of integer numbers, with a default value.Examples:
annotation MyAnnotation {
val value : String[]
val isTricky : boolean = false
val lotteryNumbers : int[] = #[ 42, 137 ]
}
Modifiers are used to modify declarations of types and type members. This section introduces the modifiers for the annotation types. The modifiers are usually written before the keyword for defining the annotation type.
The complete description of the modifiers’ semantic is available in this section.
A top annotation type may be declared with one or more modifiers, which affect its runtime behavior:
public
: the annotation type is accessible from any other type (default);package
: the annotation type is accessible from only the types in the same package.abstract
: the annotation type is abstract and cannot be instanced.Examples:
public annotation TopAnnotationType1 {
}
package annotation TopAnnotationType2 {
}
abstract annotation TopAnnotationType3 {
}
A nested annotation type may be declared with one or more modifiers, which affect its runtime behavior:
public
: there are no restrictions on accessing the annotation type (default);protected
: the annotation type is accessible within the same package, and derived classes;package
: the annotation type is accessible only within the same package as its class;private
: the annotation type is accessible only within its class.abstract
: the annotation type is abstract and cannot be instanced.static
: the inner annotation type do not have access to the non-static members of the enclosing type.Terminology Nested annotation types are divided into two categories: static and non-static. Nested annotation types that are declared static are called static nested annotation types. Non-static nested annotation types are calledinner annotation types.
Note The modifiers may differ from the previously described, depending on the enclosing type, e.g. agent.
class EnclosingClass {
public annotation NestedAnnotationType1 {
}
protected annotation NestedAnnotationType2 {
}
package annotation NestedAnnotationType3 {
}
private annotation NestedAnnotationType4 {
}
abstract annotation NestedAnnotationType5 {
}
static annotation NestedAnnotationType6 {
}
}
The modifiers for the values in an annotation type are:
public
: there are no restrictions on accessing the value;static
: the value is a class value, not an instance value.Examples:
public val val1 : int
static val val2 : int
Anonymous classes enable you to make your code more concise. They enable you to declare and instantiate a class at the same time. They are like local classes except that they do not have a name. Use them if you need to use a local class only once.
While local classes are class declarations, anonymous classes are expressions, which means that you define the class in another expression.
The following example, HelloWorldAnonymousClasses
, uses anonymous classes in the
initialization statements of the local variables frenchGreeting
and spanishGreeting
:
interface HelloWorld {
def greetSomeone(someone : String)
}
class HelloWorldAnonymousClasses {
def sayHello {
var frenchGreeting = new HelloWorld {
var name = "tout le monde"
def greetSomeone(someone : String) {
name = someone
println("Salut " + name)
}
}
var spanishGreeting = new HelloWorld {
var name = "mundo"
def greetSomeone(someone : String) {
name = someone
println("Hola, " + name)
}
}
frenchGreeting.greetSomeone("Marc")
spanishGreeting.greetSomeone("Marco")
}
}
As mentioned previously, an anonymous class is an expression. The syntax of an anonymous class expression is like the invocation of a constructor, except that there is a class definition contained in a block of code.
Consider the instantiation of the frenchGreeting object ion the code below.
The anonymous class expression consists of the following:
new
operatorHelloWorld
.Because an anonymous class definition is an expression, it must be part of a statement.
In this example, the anonymous class expression is part of the statement that instantiates
the frenchGreeting
object.
var frenchGreeting = new HelloWorld {
var name = "tout le monde"
def greetSomeone(someone : String) {
name = someone
println("Salut " + name)
}
}
Anonymous classes can capture variables; they have the same access to local variables of the enclosing scope:
Anonymous classes have restrictions with respect to their members:
Note You can declare the following in anonymous classes: fields, extra methods (even if they do not implement any methods of the supertype), instance initializers, local classes. However, you cannot declare constructors in an anonymous class.
class HelloWorldAnonymousClasses {
var name : String
def sayHello {
var frenchGreeting = new HelloWorld {
def greetSomeone(someone : String) {
name = someone
println("Salut " + name)
}
}
}
}
In this section, the semantic of the different modifiers is explained.
An abstract type is a type that is declared abstract
(it may or may not include abstract methods).
Abstract types cannot be instantiated, but they can be derived.
An abstract method is a method that is declared without an implementation (without braces, and followed by a semicolon), like this:
abstract def moveTo(deltaX : double, deltaY : double)
If a type includes abstract methods, then the type itself must be declared abstract, as in:
abstract class GraphicObject {
// declare fields
// declare nonabstract methods
abstract def draw
}
When an abstract type is derived, the subtype usually provides implementations for all of the abstract methods in its parent type. However, if it does not, then the subtype must also be declared abstract.
Access level modifiers determine whether other types can use a particular field or invoke a particular method. There are two levels of access control:
A type may be declared with the modifier public
, in which case that type is visible to
all types everywhere. If a type has the modifier package
, it is visible only within its own package
(packages are named groups of related types).
At the member level, you can also use the public
modifier or package
modifier just as
with top-level types, and with the same meaning.
For members, there are two additional access modifiers: private
and protected
.
The private
modifier specifies that the member can only be accessed in its own type.
The protected
modifier specifies that the member can only be accessed within its own package
as with package
) and, in addition, by a derived type of its type in another package.
The following table shows the access to members permitted by each modifier.
Modifier | From the type | From the package | From subtypes | Other |
---|---|---|---|---|
public |
yes | yes | yes | yes |
protected |
yes | yes | yes | no |
package |
yes | yes | no | no |
private |
yes | no | no | no |
The first column indicates whether the type itself has access to the member defined by the access level. As you can see, a type always has access to its own members. The second column indicates whether types in the same package as the type (regardless of their parentage) have access to the member. The third column indicates whether subtypes of the type declared outside this package have access to the member. The fourth column indicates whether all types have access to the member.
Examples:
class C1 {
public def pubfct {}
protected def protfct {}
package def packfct {}
private def privfct {}
def test0 {
pubfct;
protfct;
packfct;
privfct;
}
}
class C2 extends C1 {
def test1 {
pubfct;
protfct;
packfct;
}
}
class C3 {
def test3(obj : C1) {
obj.pubfct;
obj.protfct;
obj.packfct;
}
}
Generally, method resolution and binding is done statically at compile time. Method calls are bound based on the static types of arguments.
Sometimes this is not what you want. Especially in the context of extension methods you would like to have polymorphic behavior.
The dispatch
modifier permits defining a dispatch method.
For a set of visible dispatch methods in the current type hierarchy with the same name and
the same number of arguments, the compiler infers a synthetic dispatcher method.
This dispatcher uses the common super type of all declared arguments.
dispatch def getType(x : Number) {
"it's a number"
}
dispatch def getType(x : Integer) {
"it's an int"
}
dispatch def getType(x : String) {
"it's a string"
}
def clientCode {
getType(4.5).println
getType(4).println
getType("a string").println
}
This modifier enables to mark a field, a formal parameter, or a local variable as an extension provider.
Extension methods allow adding new methods to existing types without modifying them. This is really helpful as they can greatly improve the readability. They use a simple syntactic trick: the first parameter of a method can either be passed in after opening the parentheses or before the method call. For example, given a method:
def removeVowels(s : String) {
s.replaceAll("[aeiouAEIOU]", "")
}
We can call this method either like in Java:
removeVowels("Hello")
or as an extension method of String:
"Hello".removeVowels
By adding the extension
keyword to a field, a local variable or a parameter declaration, its
instance methods become extension methods.
In the following example, three functions are defined for illustrating the three types of extension providers.
class Examples {
//
// Example of an extension provider on a class field.
//
extension var list : ArrayList<String> = newArrayList
def extensionFieldExample(value : String) : boolean {
value.contains // calls this.list.contains(value)
}
//
// Example of an extension provider on a method parameter.
//
def extensionParameterExample(value : String, extension o : ArrayList<String>) : boolean {
value.contains // calls o.contains(value)
}
//
// Example of an extension provider on a local variable.
//
def extensionLocalVariableExample(value : String) : boolean {
extension var o : ArrayList<String> = newArrayList
value.contains // calls o.contains(value)
}
}
The final
keyword is used in several different contexts to define an entity which may only be
assigned once.
Once a final variable has been assigned, it always contains the same value. If a final variable holds a reference to an object, then the state of the object may be changed by operations on the object, but the variable will always refer to the same object.
Caution The SARL compiler complains if you write the final
modifer in conjonction with the var
or val
modifiers. Indeed, the val
modifier defines a final variable; and the var
modifier defines a no-final variable.
This applies also to arrays, because arrays are objects; if a final variable holds a reference to an array, then the components of the array may be changed by operations on the array, but the variable will always refer to the same array.
A final method cannot be overridden or hidden by subclasses. This is used to prevent unexpected behavior from a subtype altering a method that may be crucial to the function or consistency of the type.
A final type cannot be derived. Doing this can confer security and efficiency benefits, so many
of the Java standard library classes are final, such as java.lang.System
and
java.lang.String
. All methods in a final type are implicitly final.
class A {
//
// Final field
//
val field = 4
//
// Final method
//
final def cannotBeOverriden {
}
}
final class B {
// This class cannot be derived
}
The native
keyword is applied to a method to indicate that the method is implemented in
native code, i.e. outside SARL and Java, using the Java Native Interface.
Note This modifier is provided for enabling agents to access to low-level resources that are not supported by the Java API. You should not use this modifier if you can use a higher-level API in place of.
class A {
native def fct
}
The static
keyword is used for creating fields and methods that belong to the type, rather than to an
instance of the type.
Sometimes, you want to have variables that are common to all instances. This is accomplished with
the static
modifier. Fields that have the static modifier in their declaration are called static
fields (or class variables in object-oriented programming). They are associated with the type,
rather than with any instance. Every instance of the type shares a static field, which is in one
fixed location in memory. Any instance can change the value of a static variable, but static variables
can also be manipulated without creating an instance of the type.
Note Constants are usually defined as final static fields.
Static methods, which have the static
modifier in their declarations, should be invoked with the
type name, without the need for creating an instance of the type.
class A {
//
// Static field
//
static var field : int
//
// Constant
//
static val constant = 4
//
// Static method
//
static def aMethod {
}
}
The strictfp
modifier is a keyword that restricts floating-point calculations to ensure portability.
The strictfp
command was originally introduced into Java with the Java virtual machine (JVM) version 1.2
and is available for use on all currently updated Java VMs.
The IEEE standard IEEE 754 specifies a standard method for both floating-point calculations and storage of floating-point values in either single (32-bit, used in float) or double (64-bit, used in double) precision, and, for intermediate calculations, also extended precision formats.
Since JVM 1.2, intermediate computations are not limited to the standard 32 bit and 64 bit precisions. On platforms that can handle other representations e.g. 80-bit double extended on x86 or x86-64 platforms, those representations can be used, helping to prevent round-off errors and overflows, thereby increasing precision.
For some applications, a programmer might need every platform to have precisely the same floating-point behavior, even on platforms that could handle greater precision. However, if this level of precision is not necessary the VM does not use intermediates by default.
From the VM perspective, turning on this higher precision means the following:
Precision | Intermediate |
---|---|
32 bits | 64 bits |
64 bits | 80 bits (if available) |
The strictfp
modifier accomplishes this by truncating all intermediate values to IEEE single precision and
double precision, as occurred in earlier versions of the JVM.
class A {
strictfp def fct { }
}
The SARL programming language provides two basic synchronization idioms: synchronized methods and synchronized statements. The synchronized statements are described in the general reference. This section is about synchronized methods.
To make a method synchronized, simply add the synchronized
modifier to its declaration (see
example below).
Synchronized methods enable a simple strategy for preventing thread interference and memory consistency errors: if an object is visible to more than one thread, all reads or writes to that object’s variables are done through synchronized methods. (An important exception: final fields, which cannot be modified after the object is constructed, can be safely read through non-synchronized methods, once the object is constructed) This strategy is effective, but can present problems with liveness.
class SynchronizedCounter {
var c = 0
synchronized def increment {
c++
}
synchronized def decrement {
c--
}
synchronized def value {
return c
}
}
Java and SARL provide a mechanism, called object serialization where an object can be represented as a sequence of bytes that includes the object’s data as well as information about the object’s type and the types of data stored in the object.
The transient
modifier is a keyword used as a field modifier.
When a field is declared transient, it would not be serialized even if the type to which it belongs
is serialized.
class A {
transient var field : int = 4
}
While the volatile
modifier itself comes from the C programming language, it has a completely
different meaning in Java, and then in SARL. This may not help in growing an understanding of it,
googling for volatile could lead to different results. Let’s take a quick side step and see what
volatile means in C first.
In the C language the compiler ordinarily assumes that variables cannot change value by themselves. While this makes sense as default behavior, sometimes a variable may represent a location that can be changed (like a hardware register). Using a volatile variable instructs the compiler not to apply these optimizations.
Back to Java and SARL. The meaning of volatile in C would be useless in Java. The JVM uses native libraries to interact with the OS and hardware. Further more, it is simply impossible to point Java variables to specific addresses, so variables actually won’t change value by themselves.
However, the value of variables on the JVM can be changed by different threads. By default the compiler assumes that variables won’t change in other threads. Hence it can apply optimizations such as reordering memory operations and caching the variable in a CPU register. Using a volatile variable instructs the compiler not to apply these optimizations. This guarantees that a reading thread always reads the variable from memory (or from a shared cache), never from a local cache.
class A {
volatile var field : int = 4
}
This documentation is based on documentations from the Xtext and Xtend projects, and from the Java tutorials. Thank you to the contributors to these documents.
Copyright © 2014-2023 SARL.io, the Original Authors and Main Authors.
Documentation text and medias are licensed under the Creative Common CC-BY-SA-4.0; you may not use this file except in compliance with CC-BY-SA-4.0. You may obtain a copy of CC-BY-4.0.
Examples of SARL code are licensed under the Apache License, Version 2.0; you may not use this file except in compliance with the Apache License. You may obtain a copy of the Apache License.
You are free to reproduce the content of this page on copyleft websites such as Wikipedia.
Generated with the translator docs.generator 0.14.0-SNAPSHOT.