Além de funcional, Scala também tem um pezinho na orientação objeto. Com isso, alguns conceitos já conhecidos pra quem vem de outras linguagens podem muito bem ser aplicados em Scala.
Classes Abstratas
São classes que podem conter membros cujo a implementação não está definida. Por esse motivo, uma instância de uma Classe Abstrata não pode ser criada com o operador new.
As classes abstratas são sempre herdadas por uma subclasse, a qual vai implementar seus métodos.
abstract class IntSet {
def contains(x: Int): Boolean
def incl(x: Int): IntSet
def union(other: IntSet): IntSet
}
Vamos implementar a classe abstrata IntSet
como uma árvore binária persistente com 2 tipos possíveis:
- Uma árvore para IntSet vazio
- Uma árvore para IntSet contendo 1 número e 2 sub-árvores
No nosso exemplo, as sub-árvores da direita serão sempre maiores que as da esquerda.
Estruturas de Dados Persistentes são tipos de estruturas que sempre perservam sua versão anterior quando é modificada. São efetivamente imutáveis, pois suas operações não atualizam a estrutura no local, mas sempre produzem uma nova, atualizada.
Estruturas de dados persistentes são um dos pilares da programação funcional.
class Empty extends IntSet {
// Como esse é um IntSet vazio, ele nunca vai conter um elemento
def contains(x: Int): Boolean = false
// Mesmo sendo um IntSet vazio, podem ser adicionados novos "ramos".
def incl(x: Int): IntSet = new NonEmpty(x, new Empty, new Empty)
// Esse método retorna uma representação do objeto em forma de String.
override def toString: String = "."
}
class NonEmpty(elem: Int, left: IntSet, right: IntSet) extends IntSet {
def contains(x: Int): Boolean = {
/*
Esse método vai procurar o valor "x" no IntSet.
Se for menor que o principal do IntSet, vai procurar no elemento à esquerda.
Se for maior, no elemento à direita.
Se for igual ao elemento em questão, retornará true.
*/
if (x < elem) left contains x
else if (x > elem) right contains x
else true
}
def incl(x: Int): IntSet = {
/*
Esse método vai incluir o valor "x" no IntSet.
A lógica é a mesma do método anterior, porém se o valor for
igual ao elemento do IntSet, irá retornar o próprio IntSet,
pois não pode conter elementos repetidos.
*/
if (x < elem) new NonEmpty(elem, left incl x, right)
else if (x > elem) new NonEmpty(elem, left, right incl x)
else this
}
override def toString: String = s"{$left$elem$right}"
}
Classes
As definições de contains
e incl
nas classes Empty e NonEmpty implementam as funções abstratas na classe base IntSet.
Também é possível redefinir uma função não abstrata, como foi feito com a função toString
, utilizando override
antes de declarar a mesma.
Se as classes Empty
e NonEmpty
herdam de IntSet
, isso implica que ambas se conformam à classe IntSet. O que isso quer dizer, de fato, é que tanto Empty quanto NonEmpty podem ser usadas onde IntSet é requirido.
Em Scala, toda classe definida pelo usuário herda de alguma outra classe. Se nenhuma super classe é passada, a classe Object, do pacote java.lang
é adotada.
As super classes, diretas ou indiretas, de uma classe X são chamadas Classes Base dessa classe X. Logo, as classes base de NonEmpty são IntSet e Object.
Objects
Diferente da classe java.lang.Object
, esse Object é um tipo de mescla e abreviação para se definir uma classe de uso único, a qual não pode ser instanciada. Elas também podem ser usadas como Companion Objects, que eu pretendo falar no futuro.
Para que Empty
não precise ser instanciada tantas vezes, a classe pode ser melhor expressada como um object.
object Empty extends IntSet {
def contains(x: Int): Boolean = false
def incl(x: Int): IntSet = new NonEmpty(x, Empty, Empty)
def union(other: IntSet): IntSet = other
override def toString: String = "."
}
Traits
São exatamente iguais às classes abstratas em sua estrutura, tendo como única diferença a possibilidade de ser herdada com with
. Isso quer dizer que você pode herdar apenas 1 classe abstrata (extends C), e quantas traits quiser (with T with T2 with T3).
Traits são parecidas com as Interfaces em java, mas mais poderosas, pois podem conter campos e métodos concretos. Por outro lado, traits não podem conter variáves val
.
Tipos
No topo da hierarquia de tipos estão:
- Any - É a base de todos os tipos existentes.
-
AnyRef - É o tipo base de todos os tipos referência (tipos que não são valores). É um pseudônimo da classe
java.lang.Object
. - AnyVal - É o tipo base de todos os tipos primitivos.
Nothing e Null
Nothing está no fundo da hierarquia de tipos de Scala. Ele é um subtipo de todos os outros tipos, e não existe um valor do tipo Nothing.
Ele é útil para sinalizar terminações anormais e também como o tipo de elemento para uma collection vazia
val lista: List[Nothing]
A forma como Scala lida com exceções é parecida com Java. A expressão throw new Exception
aborta o programa com a exceção Exception
.
O tipo dessa expressão é Nothing.
Null é um subtipo de toda classe do tipo referência, logo, pertence a AnyRef
.
Por ser um subtipo das classes que herdam de java.lang.Object
, Null é incompatível com qualquer subtipo de AnyVal
.
val x: Null = null
val y: Integer = null
val z: Int = null // error: type mismatch
Polimorfismo
Polimorfismo significa que o tipo de uma função pode vir de muitas formas. É uma forma de tornar a linguagem mais expressiva, enquanto continua mantendo toda sua tipagem estática segura.
Usando o polimorfismo, a função ou tipo de dado pode ser escrita genericamente para que possa suportar valores idênticos sem depender de seu tipo.
Existem 2 formas principais do polimorfismo:
- Subtyping - As instâncias de uma subclasse podem ser passadas para uma classe base
- Generics - As instâncias de uma função ou classe são criadas pela parametrização do tipo.
Top comments (0)