Note: information on this page refers to Ceylon 1.1, not to the current release.
Class Declarations
A class is a stateful type declaration that:
- may hold references to other objects,
- may define initialization logic and initialization parameters, and
- except in the case of an
abstract
orformal
class, may be instantiated.
A class may inherit another class, but classes are restricted to a single inheritance model. That is, a class inherits exactly one other class. Since single inheritance is quite often too restrictive, a class may also satisfy an arbitrary number of interfaces.
Usage
A trivial class declaration looks like this:
class Trivial() {
/* declarations of class members */
}
The general form of a class declaration looks like this:
ANNOTATIONS
class Example
<TYPE-PARAMETERS>
(INITIALIZER-PARAMETERS)
of ENUMERATED-SUBCLASSES
extends SUPER-CLASS-INVOCATION
satisfies SUPER-INTERFACES
given TYPE-PARAMETER-CONSTRAINTS {
CLASS-BODY
}
Where:
-
ANNOTATIONS
is a list of class annotations -
TYPE-PARAMETERS
is a,
-separated list of type parameters -
INITIALIZER-PARAMETERS
is a,
-separated list of value parameters -
ENUMERATED-SUBCLASSES
is a|
-separated list of class types -
SUPER-CLASS-INVOCATION
is class invocation expression for the superclass initializer -
SUPER-INTERFACES
is a&
-separated list of interface type expressions -
TYPE-PARAMETER-CONSTRAINTS
is a list of constraints on type parameters declared in the type parameter list -
CLASS-BODY
is the initializer section of the class, followed by the declaration section of the class
Description
Type parameters
A class declaration may have a list of type parameters
enclosed in angle brackets (<
and >
) after the class name:
class Generic<Foo, Bar>() {
/* declarations of class members
type parameters Foo and Bar are treated as a types */
}
A class with type parameters is sometimes called a generic class.
A class declaration with type parameters may also have a given
clause
for each declared type parameter to
constrain the argument types:
class Constrained<Foo, Bar>()
given Foo satisfies Baz1&Baz2
given Bar of Gee1|Gee2 {
/* declarations of class members
type parameters Foo and Bar treated as a types */
}
Initializer parameters
Every class declaration must have a parameter list,
because any class can be invoked to create instances of the class.
(Even an abstract
class, which will be invoked by its subclasses).
The initializer parameters are visible to statements in the class initializer.
Callable type
A class may be viewed as a function that produces new instances of
the class. The callable type of a class expresses, in terms of
the interface Callable
,
the type of this function.
For example the callable type of
class CallableExample(Integer int, Boolean bool) => "";
is CallableExample(Integer, Boolean)
, because the class initializer takes
Integer
and Boolean
parameters and invoking the class results in a
CallableExample
instance being returned to the caller.
(Regular functions also have a callable type.)
Extending classes
The extends
clause is used simultaneously to:
- specify that the class being declared is a subtype of the given class type and,
- invoke that class's initializer.
class S() extends C() {
/* declarations of class members */
}
If a class is declared without using the extends
keywords, it is a
subclass of Basic
.
Satisfying interfaces
The satisfies
clause is used to specify that the class being declared is a
subtype
of the given interface type.
class C() satisfies I1 & I2 {
/* declarations of class members */
}
&
is used as the separator between satisfied interface types
because the class (C
) is being declared as a subtype of an
intersection type (I1&I2
).
If a class is declared without using the satisfies
keyword, it does
not directly inherit any interfaces. However, it may indirectly
inherit interfaces via its superclass.
Enumerated classes
The subclasses of an abstract
class can be constrained to a list of
named class types (including toplevel anonymous classes) using the of
clause.
If the class C
is permitted only two direct subclasses, S1
and S2
,
its declaration would look like this:
abstract class C() of S1 | S2 {
/* declarations of class members */
}
The subclasses have to extend
C
:
class S1() extends C() {
}
class S"() extends C() {
}
Then S1
and S2
are called the cases of C
.
If a class has enumerated subclasses we can use the subclasses as
is
cases in a
switch
statement.
Initializer
The class initializer executes when instances of the class are created
(also known as class instantiation).
The parameters to the initializer are specified in parenthesis after the
name of the class in the class
declaration.
The body of a class must definitely initialize every member of the class.
The following code will be rejected by the compiler because if bool
is false greeting
does not get initialized:
class C(Boolean bool) {
shared String greeting;
if (bool) {
greeting = "hello";
}
}
The typechecker figures out for itself the point in the class at which all class members have been initialized. Everything before this point is in the initializer section of the class, and everything after this point is in the declaration section. In the initializer section you can't use a declaration before it's been declared.
Note that abstract
classes cannot be invoked directly, but
they are still invoked in the extends
clause of their subclasses.
Declaration section
Members
The permitted members of classes are classes,
interfaces, methods, attributes,
and object
s.
Member class refinement
An inner class of a class or interface can be subject to member class refinement, which means its instantiation will be polymorphic.
Here's an example where a Reader
class declares that concrete
subclasses must (because we used formal
) provide an actual Buffer
inner class.
shared abstract class Reader() {
shared formal class Buffer(Character* chars)
satisfies Sequence<Character> {}
// ...
}
shared class FileReader(File file)
extends Reader() {
shared actual class Buffer(Character* chars)
extends super.Buffer(*chars) {
// ...
}
// ...
}
Within Reader
(and elsewhere) we can instantiate the relevant kind of
Buffer
with a normal instantiation, Buffer(chars)
. This allows each
subclass of Reader
to implement an appropriate kind of Buffer
.
Member class refinement is a lot like the 'abstract factory' pattern in other object-oriented languages, but it's a lot less verbose.
Only formal
and default
member classes are subject to member class
refinement. A formal
member class must be refined by concrete subtypes
of the type declaring the member class—just like a formal
method
or attribute. A default
member class may be refined—just like
a default
method or attribute.
In a subtype of the type declaring the member class, the member class
(i.e. in FileReader.Buffer
from the example above) must:
- be declared
actual
, - have the same name as the member class in the declaring type (
Buffer
in the example), - have a parameter list with a compatible signature and,
- extend the member class (you'll need to use
super
in theextends
clause).
Refined member types are similar to, but not the same as, virtual types, which Ceylon does not support.
Different kinds of class
Concrete classes
A class that can be instantiated is
concrete. It follows that abstract
or formal
classes are not concrete.
Abstract classes
An abstract class is a class that may not be instantiated. Abstract classes
may declare formal
members. An abstract class
declaration must be annotated abstract
:
abstract class C() {
/* declarations of class members */
}
Naturally, abstract classes compete with interfaces, since both an abstract class and an interface may contain a mix of concrete and formal members. The crucial difference is:
- an abstract class may contain or inherit state and initialization logic, whereas
- interfaces support a full multiple inheritance model.
Nevertheless, it is often unclear whether a certain situation calls for an interface or an abstract class. Our advice is to incline in favor of using an interface, where reasonable.
shared
classes
A toplevel class declaration, or a class declaration nested inside the body
of a containing class or interface, may be annotated
shared
:
shared class C() {
/* declarations of class members */
}
- A toplevel
shared
class is visible wherever the package that contains it is visible. - A
shared
class nested inside a class or interface is visible wherever the containing class or interface is visible.
formal
classes
A class declaration nested inside the body of a containing class or interface
may be annotated formal
. A formal class must
also be annotated shared
.
Like abstract classes, formal classes may have formal members. Unlike abstract classes, formal classes may be instantiated.
A formal
class must be refined by concrete
subclasses of the containing class or interface.
default
classes
A class declaration nested inside the body of a containing class or interface
may be annotated default
. A default class must
also be annotated shared
.
A default
class may be refined by types which
inherit the containing class or interface.
sealed
classes
A class declaration annotated sealed
cannot be instantiated (either
in an invocation expression or in an extends clause) outside the module
in which it is defined. This provides a way to share a
class's type with other modules while retaining control over subclassing
and instance creation.
Aliases
A class alias is a kind of alias.
Metamodel
Class declarations can be manipulated at runtime via their representation as
ClassDeclaration
instances. An applied class (i.e. with all type parameters specified)
corresponds to either a
Class
or
MemberClass
model instance.
See also
- Interface declarations
- Classes in the Ceylon language spec