class

Summary

Declares a class.

Syntax

class name
    [<param1 [(constraint1[, constraint2 ... [, constraintN]])][, param2 ... [, paramN]]>]
    :
    inherits1[, inherits2 ... [, inheritsN]]
{
    member1;
    member2;
    member3;
}

Parameters

name
The name of the class. This can be any valid identifier.
paramN
The type parameters of the class. This is used for creating generic classes.
constraintN
The constraints for the type parameters. These can include any reference types.
inheritsN
The superclass or interfaces the class inherits from.
memberN
A member of the class. This can be a field, method, or an inner/nested class.

Description

The class keyword declares a class.

A class is a blueprint for creating objects. For instance, you might have three dogs. The three dogs are all "objects" created from the Dog class blueprint. The Dog class blueprint might define dogs as having four legs and able to bark.

Constructors

The constructor is a function that is executed when the class gets instantiated.

The constructor is defined using the class name with no return type, such as with:

1
2
3
4
5
6
7
class Dog
{
    public Dog()
    {
        // Do something...
    }
}

The constructor can take parameters. The constructor can also be overloaded.

  • If there are no constructors defined for the class, the "default constructor" is used. The default constructor takes exactly zero arguments and just returns a new instance of the class.
  • If a class defines at least one constructor, the default constructor will not be implicitly defined.
  • If a class does not contain a no-args constructor, subclasses must specify which constructor should be called for inheritance.

A private no-args constructor will prevent a class from being instantiated and subclassed. Preventing instantiation is useful for classes composed entirely of static fields and methods (e.g. the System.Math class). A protected no-args constructor will prevent a class from being instantiated, but it can still be subclassed. Public constructors have no restrictions.

In order to invoke the class constructor, the class must be instantiated using the new keyword:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Foo {}
 
Foo foo = new Foo(); // calls default constructor
 
class Bar
{
    private int x;
 
    public Bar() {
        Console.log("No-args constructor");
    }
    public Bar(int x) {
        this.x = x;
        Console.log("Constructor taking one argument");
    }
}
 
Bar bar = new Bar();  // Output: "No-args constructor"
Bar bar = new Bar(1); // Output: "Constructor taking one argument"

In order to invoke a specific constructor overload from another constructor, use the this keyword:

1
2
3
4
5
6
7
8
9
10
11
12
class Foo
{
    public int x;
    public Foo() {
        this(100);
    }
    public Foo(int x) {
        this.x = x;
    }
}
 
(new Foo()).x; // 100

Static Constructors

The static constructor of a class will be executed upon the class declaration - even if no instantiation of the class occurs.

Static constructors cannot have an access modifier. A static constructor is declared using:

1
2
3
4
5
6
7
class Foo
{
    static Foo()
    {
        // Do something...
    }
}

Notice that no access modifier is present for static constructors.

Fields and Methods

Objects are made of data (fields) and behavior (methods).

Variable declarations inside a class will create a class field. A field can hold data for an object's state (instance field) or the class's state (static field). Instance fields can only be accessed by instances of the class (created with the new keyword) while static fields do not require a class to be instantiated at all before they can be used. Fields are private by default.

Methods are functions declared inside a class. Methods are used for defining behavior and can act on the data represented by an object's or class's fields. An instance method can access both instance and static fields. However, static methods can only access static fields (and not instance fields). Static methods can be accessed without an instance, but instance methods require an instance of the class before they can be accessed (created with the new keyword). Methods are public by default.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class MyClass
{
    // Instance field
    public int instanceField = 1;
     
    // Static field
    public static int staticField = 2;
     
    // Instance method
    public void instanceMethod() {
        // ...
    }
     
    // Static method
    public static void staticMethod() {
        // ...
    }
}
 
// Accessing instance fields and methods requires an instance of the class
MyClass obj = new MyClass();
obj.instanceField = 2;
obj.instanceMethod();
 
// Static fields and methods can be accessed directly
MyClass.staticField = 3;
MyClass.staticMethod();

Methods can be overloaded. Overloading allows multiple methods to have the same name but take different parameters and implement different behaviors. For example, a getEmployee method might operate differently depending on whether you supply an Employee ID or name:

1
2
3
4
5
6
7
8
9
10
11
class MethodOverloadingDemo
{
    public Employee getEmployee(int employeeId)
    {
        // ...
    }
    public Employee getEmployee(string lastName, string firstName)
    {
        // ...
    }
}

Access Modifiers

Access modifiers are used for controlling visibility and access to class members. Class fields are private by default, but all other members are public by default.

Access Modifier Description
publicNo restrictions on access.
protectedThe member can be accessed from within the class it was declared and all subclasses, nested classes, and inner classes.
privateThe member can only be accessed from within the class it was declared (including inner/nested classes).

Inheritance

Inheritance is when another class is based on another class.

When a class inherits from another class, the class that is inheriting from another class is known as the subclass (or derived class) and the class it's inheriting from is known as the superclass (or base class).

The subclass will retain (and can access) all non-private fields and methods of the superclass. In addition, the subclass can be extended with new fields and methods.

JS++ does not allow multiple inheritance. A class cannot inherit from more than one class simultaneously. However, a class can have a superclass, the superclass can then have a superclass, and so on. This is known as the inheritance chain, but the classes in the inheritance chain always inherit from, at most, one class; none of the classes in the inheritance chain ever inherit from more than one class simultaneously.

Although a class can only inherit from one class, it can inherit from any number of interfaces.

The following table provides an overview of possible inheritance relationships:

Inheritance Example
Noneclass MyClass { }
Singleclass DerivedClass : SuperClass { }
None, Implements Interfaceclass MyClass : IMyInterface { }
None, Implements More than One Interfaceclass MyClass : IMyInterface1, IMyInterface2, IMyInterface3 { }
Single, Implements One Interfaceclass DerivedClass : SuperClass, IMyInterface { }
Single, Implements More than One Interfaceclass DerivedClass : SuperClass, IMyInterface1, IMyInterface2, IMyInterface3 { }

Inheritance establishes a subtype relationship. Therefore, within the JS++ type system, a class is considered a subtype of all the classes it inherits from and all the interfaces it implements.

When the base class does not contain a no-args constructor, it is necessary to specify the constructor overload with the super keyword:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Foo
{
    public int x;
    public Foo(int x) {
        this.x = x;
    }
}
 
class Bar : Foo
{
    public Bar(int x) {
        super(x);
    }
}

Properties (Getters and Setters)

JS++ supports mutator methods (getters and setters) in the form of "properties" using the property modifier. The property modifier can only be used to modify a method.

A getter (also known as an accessor) is a method triggered by an access operation, much like a field. Getters are defined by applying the property modifier on a method with no parameters.

A setter is a method triggered by assignment rather than a function call. Setters are defined by applying the property modifier on a method with one parameter.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import System;
 
class PropertiesDemo
{
    // A method with the 'property' modifier and no parameters defines a getter
    public property int getter() {
        return 100;
    }
    // A method with the 'property' modifier and one parameter defines a setter
    public property void setter(int x) {
        Console.log("Setter triggered. 'x' is: " + x);
    }
}
 
PropertiesDemo foo = new PropertiesDemo();
 
// Setters trigger on assignment (rather than a function call)
foo.setter = 10;
// Getters trigger on access (rather than a function call)
Console.log(foo.getter);

Inner Classes and Nested Classes

Classes can also be declared inside another class. Unlike a subclass, a class declared inside another class does not establish an inheritance relationship.

An instance class declared inside a containing class, which requires an instance of the containing class to be accessed, is known as an inner class. A static class, which can be accessed directly (even if the containing class has never been instantiated) and is declared with the static keyword, is known as a nested class.

Only inner classes can be declared inside inner classes. For example, a static nested class cannot be declared inside an inner class. Likewise, an inner class cannot be declared inside a nested class; however, new nested classes can still be declared inside nested classes.

Virtual Methods

Virtual methods are methods that can be resolved at runtime and enable runtime polymorphism.

Virtual methods are declared with the virtual keyword. In order to override an inherited virtual method, use the override keyword. The following is an example of virtual methods using the virtual and override keywords:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import System;
 
class Animal
{
    virtual void eat() {
        Console.log("Animal has eaten.");
    }
}
class Bird : Animal
{
    override void eat() {
        Console.log("Bird has eaten.");
    }
}
class Turtle : Animal
{
    override void eat() {
        Console.log("Turtle has eaten.");
    }
}
 
Animal animal = new Bird();
animal.eat(); // Bird has eaten.
animal = new Turtle();
animal.eat(); // Turtle has eaten.

As indicated in the example above, the variable animal is declared with type Animal. The compiler will only allow methods from the class Animal to be called. However, without virtual methods, the method calls are resolved at compile-time. If we remove the virtual and override keywords from the example above, we get a very different output:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import System;
 
class Animal
{
    void eat() {
        Console.log("Animal has eaten.");
    }
}
class Bird : Animal
{
    void eat() {
        Console.log("Bird has eaten.");
    }
}
class Turtle : Animal
{
    void eat() {
        Console.log("Turtle has eaten.");
    }
}
 
Animal animal = new Bird();
animal.eat(); // Animal has eaten.
animal = new Turtle();
animal.eat(); // Animal has eaten.

As observed from the non-virtual example above, if we do not declare the methods as virtual methods, the method calls are resolved at compile-time. Since the compiler can only resolve that the variable animal has type Animal at compile time, it calls the eat() method from the class Animal only. By declaring the methods as virtual, the compiler will generate code to resolve the methods at runtime instead.

Abstract Classes

An abstract class enables the creation of a class whose implementation is incomplete and must be completed by derived classes.

Abstract classes are created with the abstract modifier. Both the class and unimplemented method must include the abstract modifier.

In an abstract class, some methods may be implemented while others might not. As an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import System;
 
abstract class Animal
{
    abstract void eat(); // abstract method
 
    public void walk() {
        Console.log("Walking...");
    }
}
class Turtle : Animal
{
    public override void eat() { // derived classes must implement the abstract method
        Console.log("Turtle is eating.");
    }
}
 
Animal turtle = new Turtle();
turtle.walk(); // Walking...
turtle.eat();  // Turtle is eating.

In order to implement a method from an abstract base class, the override keyword must be used.

Abstract classes cannot be directly instantiated. Only non-abstract derived classes of the abstract base class can be instantiated.

Final Fields, Methods, and Classes

Fields, methods, and classes can be declared final using the final modifier.

The final modifier prevents a field from being further assigned to. Unlike variable declarations, when the final modifier is applied to a class field, the class field does not need to be initialized immediately. Instead, the field can be initialized in the constructor. However, once initialized, the initialized value or object reference is the "final" value or object reference for the field. It cannot be changed further.

When the final modifier is applied to a class declaration, the class cannot be subclassed.

When the final modifier is applied to a virtual method, it cannot be further overridden by subclasses.

Generic Classes

JS++ supports generic programming (parametric polymorphism). More information on generic programming can be found here.

Hoisting

Methods and inner/nested classes are "hoisted". In other words, they can be accessed before their declarations. Fields can only be accessed before their declarations from inside methods. For more information, see Scoping.

Conversions to/from External

Classes are - by default - internal types. In order to interact with JavaScript, JS++ provides classes with the ability to convert to and from the external type.

In order to define a conversion to external (outgoing data), use the toExternal pattern as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Point
{
    int x;
    int y;
 
    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
 
    // Define an outgoing conversion to JavaScript
    public function toExternal() {
        return {
            x: this.x,
            y: this.y
        };
    }
}

In order to define a conversion from external (incoming data), define a fromExternal method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import System.Exceptions;
 
class Point
{
    int x;
    int y;
 
    Point() {}
 
    // Define an incoming conversion from JavaScript
    public void fromExternal(external e) {
        if (typeof e != "object" || typeof e.x != "number" || typeof e.y != "number") {
            throw new Exception("Invalid incoming data for 'Point' type.");
        }
 
        this.x = e.x;
        this.y = e.y;
    }
}

See Also

Share

HTML | BBCode | Direct Link