Trait

Come Java, Scala è un linguaggio a ereditarietà singola (single inheritance language): una classe può avere solo una superclasse. Tramite le interfacce (interface), Java permette di simulare l’ereditarità multipla sui tipi, cioè definire un tipo come sottotipo diretto di più supertipi, ma non permette di definire una classe che eredita codice da più superclassi diertte. Scala fornisce invece il meccanismo dei trait, che consentono di simulare l’ereditarietà multipla sui tipi che sulle classi:

Un trait è dichiarato in modo simile a una classe astratta, ma usando la parola riservata trait invece di abstract class. La differenza sostanziale tra trait e classi astratte è che i trait non hanno costruttori, non ammettono parametri (questa restrizione è proprio ciò che “tiene in piedi” il meccanismo di ricerca dei metodi a runtime, evitando i problemi di ambiguità che tipicamente si hanno con l’ereditarietà multipla sulle classi).

Il seguente esempio mostra la dichiarazione di un traixt Planar, che modella una superficie piana:

trait Planar {
	def height: Int
	def width: Int
	def surface = height * width
}

Eso dichiara due metodi (senza parametri, ovvero campi) astratti, height e width, e un metodo concreto, surface.

Una classe, un object o un trait può ereditare direttamente da al più una classe e da un numero arbitrario di trait. La classe o il primo trait da cui si eredita viene indicato con la parola riservata extends, mentre ciascuno degli eventali altri trait va indicato con la parola riservata with. Ad esempio:

class Square extends Shape with Planar with Movable

(dove Shape potrebbe essere una classe o un trait, mentre Planar e Movable sono dei trait).

Gerarchia delle classi

La gerarchia delle classi di Scala, che comprende classi, object e trait, ha la seguente struttura:

Untitled

Come si può osservare, tale gerarchia è più complessa rispetto a quella di Java. Le parti interessanti sono, in particolare, i top types e i bottom types, cioè rispettivametne i tipi (le classi) in cima alla gerarchia e quelli in fondo (non presenti in Java).

Nel grafo della gerarchia appena mostrato sono indicate anche le conversioni implicite sui tipi base, che rispecchiano quelle sui tipi primitivi di Java. Esse non sono relazioni supertipo/sottotipo: quando un’istanza di un sottotipo viene interpretata come un’istanza di un supertipo l’oggetto rimane invariato, mentre le conversioni implicite tra i tipi base modificano gli oggetti a cui si applicano. Ad esempio, ogni valore del tipo Byte è concettualmente un valore ammesso anche per Short, dunque si potrebbe in un certo senso dire che Byte è un sottotipo di Short, ma per interpretare un valore di Byte come valore di Short è necessario trasformare la sua rappresentazione su un byte in una rappresentazione su due byte.

Top types

La radice della gerarchia è l classe scala.Any, che è il tipo base, il supertipo di tutti i tipi. Essa definisce dei metodi universali, che vengono ereditati da tutti gli oggetti, tra cui:

def equals(that: Any): Boolean
final def ==(that: Any): Boolean
final def !=(that: Any): Boolean

def hashCode: Int
final def ##: Int

def toString: String