1. Pacotes
1.1 Propósitos
- Resolver conflitos de nomes entre classes
- Fornecer uma estrutura organizacional eficiente para um grande número de classes
- Controlar o escopo de visibilidade
1.2 Sintaxe de Declaração
// Forma 1: Similar ao Java, usando '.' para delimitação hierárquica
package nome_do_pacote
// Forma 2: Usando '{}' para delimitação hierárquica
/*
1. Um único arquivo fonte pode conter múltiplas declarações de pacotes
2. Classes em subpacotes podem acessar diretamente o conteúdo do pacote pai, sem necessidade de importação
*/
package nivel_pacote1 {
package nivel_pacote2 {
...
}
}
1.3 Regras de Nomenclatura
- Pode conter números, letras e sublinhados, mas não pode começar com uma palavra-chave
- Geralmente segue a estrutura
com.nome\_empresa.nome\_projeto.modulo\_negocio
package com {
import com.corp.Interna // Acesso ao pacote filho requer importação
object Externa {
val textoExt: String = "ext"
def main(args: Array[String]): Unit = {
println(Interna.textoInt)
}
}
package corp {
object Interna {
val textoInt: String = "int"
def main(args: Array[String]): Unit = {
println(Externa.textoExt) // Acesso ao pacote pai sem importação
}
}
}
}
package com {
package startup {
}
}
1.4 Objetos de Pacote
Conceito: Em Scala, é possível definir um objeto de pacote com o mesmo nome do pacote. Membros definidos dentro do objeto de pacote são compartilhados por todas as classes e objetos dentro daquele pacote e podem ser acessados diretamente.
/**
Pacote estilo Java com nome com.analise.demo
Arquivo do objeto de pacote: package.scala
Classes e objetos dentro de com.analise.demo podem acessar variáveis e métodos do objeto de pacote
*/
package object demo {
val valorComum: String = "valor global"
def funcaoComum(): Unit = {
println(s"Acessando ${valorComum}")
}
}
/**
Pacotes aninhados. O objeto de pacote deve estar no mesmo escopo de declaração do pacote.
*/
package com {
package analise {
package demo {
object TestDemo {
def main(args: Array[String]): Unit = {
println(instituicao)
}
}
}
// Objeto de pacote no mesmo escopo da declaração do pacote
package object demo {
val instituicao: String = "UniversidadeFederal"
}
}
}
1.5 Importações
Pacotes importados por padrão:
import java.lang._import scala._import scala.Predef._
Sintaxe de importação:
// 1. Importação global no início do arquivo
import com.analise.Frutas
package com.analise.demo
object Teste {
...
}
// 2. Importação local: apenas no escopo corrente
package com.analise.demo
object Teste {
def main(args: Array[String]): Unit = {
import com.analise.Frutas
...
}
def outroMetodo(): Unit = {
// Frutas não está acessível aqui
}
}
// 3. Importação com curinga
import java.util._
// 4. Renomear classe na importação
import java.{util => ju}
// 5. Importar múltiplas classes do mesmo pacote
import java.util.{HashSet, ArrayList}
// 6. Excluir uma classe da importação
import java.util.{ArrayList => _, _}
// 7. Importação por caminho absoluto
new _root_.java.util.HashMap
| Caso | Descrição |
|---|---|
import com.analise.Frutas |
Importa Frutas (classe ou objeto) do pacote com.analise |
import com.analise._ |
Importa todos os membros do pacote com.analise |
import com.analise.Frutas._ |
Importa todos os membros do objeto Frutas |
import com.analise.{Frutas, Legumes} |
Importa Frutas e Legumes de com.analise |
import com.analise.{Frutas => Macas} |
Importa Frutas e a renomeia como Macas |
import com.analise.{Frutas => Macas, _} |
Importa todos e renomeia Frutas para Macas |
import com.analise.{Frutas => _, _} |
Importa todos, exceto a classe Frutas |
2. Classes e Objetos
2.1 Conceito
- Classe: Uma abstração ou modelo para objetos.
- Objeto: Uma representação de uma entidade concreta.
2.2 Sintaxe
object Programa {
def main(args: Array[String]): Unit = {
val aluno = new Aluno()
println(aluno.idade)
println(aluno.sexo)
aluno.idade = 18
println(aluno.idade)
println(aluno.obterRA)
aluno.definirRA("2023-XYZ")
println(aluno.obterRA)
}
}
/*
Sintaxe de declaração de classe:
[modificador] class nome_da_classe {
propriedades
métodos
}
*/
class Aluno { // Modificador de acesso padrão é público
private val nome: String = "João" // Propriedades são públicas por padrão
var idade: Int = _ // '_' define um valor padrão (0 para Int, null para String, false para Boolean)
var sexo: String = _ // '_' só pode ser usado com 'var'
@BeanProperty
var ra: String = "001" // Gera automaticamente métodos getter/setter compatíveis com Java
}
// Um único arquivo pode conter múltiplas classes
class Professor {
...
}
3. Encapsulamento
3.1 Conceito
Encapsular é combinar dados e as operações que atuam sobre eles em uma unidade, protegendo os dados internos. Outras partes do programa só podem acessar os dados através de métodos autoirzados.
3.2 Comparação com Java e Permissões de Acesso
- Público (padrão em Scala): Não há palavra-chave 'public'. Acesso universal.
- Private: Visível apenas dentro da classe e seu objeto companheiro.
- Protected: Mais restrito que em Java. Visível na classe e subclasses, mas não em classes no mesmo pacote.
- private[nomepacote]: Expande o acesso para classes dentro do pacote especificado.
package com.empresa.projeto
object Pessoa {
def main(args: Array[String]): Unit = {
val pessoa = new Pessoa()
pessoa.falar()
println(pessoa.nome)
println(pessoa.idade)
}
}
class Pessoa {
private var nome: String = "Carlos"
protected var idade: Int = 25
var endereco: String = "Rua Principal"
private[projeto] var genero: String = "M"
def falar(): Unit = {
println(s"$nome $idade $endereco $genero")
}
}
class Funcionario extends Pessoa {
def teste(): Unit = {
// this.nome // Erro
this.idade // Permitido (subclasse)
this.genero // Permitido (acesso via pacote)
}
override def falar(): Unit = {
println(s"$idade $endereco $genero")
}
}
class Animal {
def verificar: Unit = {
// new Pessoa().idade // Erro (protected, mesma linha de herança)
new Pessoa().genero // Permitido (acesso via pacote)
}
}
package com.empresa.outro_projeto
class Gerente extends Pessoa {
def teste(): Unit = {
// this.nome // Erro
this.idade // Permitido (subclasse)
// this.genero // Erro (fora do pacote 'projeto')
}
}
3.3 Construtores
3.3.1 Sintaxe Básica
/*
A classe Scala inclui: construtor primário e construtores auxiliares
*/
class NomeClasse(param1: Tipo1,...) { // Construtor Primário
def this(param1: Tipo1,...) { // Construtor Auxiliar
}
}
Regras:
- Construtores auxiliares são nomeados 'this' e sobrecarregados por assinatura.
- Devem chamar, direta ou indiretamente, o construtor primário.
- Um construtor auxiliar deve ser definido antes de ser chamado.
3.3.2 Parâmetros do Construtor
Os parâmetros do construtor primário podem ter três modificações:
- Sem modificador: Variável local dentro do construtor.
- var: Torna-se uma propriedade mutável da classe.
- val: Torna-se uma propriedade imutável (somente leitura) da classe.
// Parâmetros como variáveis locais (não recomendado)
class Aluno1(_nome: String, _idade: Int) {
def mostrar(): Unit = {
// As variáveis _nome e _idade só existem aqui
}
}
// Parâmetros como propriedades mutáveis (recomendado)
class Aluno2(var nome: String, var idade: Int)
// Parâmetros como propriedades imutáveis
class Aluno3(val nome: String, val idade: Int)
// Construtor auxiliar com propriedade adicional
class Aluno4(var nome: String, var idade: Int) {
var escola: String = _
def this(nome: String, idade: Int, escola: String) {
this(nome, idade)
this.escola = escola
}
}
4. Herança e Polimorfismo
4.1 Herança
// Scala suporta apenas herança simples
class ClasseFilha extends ClassePai {
// Herda atributos e métodos
}
4.2 Polimorfismo (Ligação Dinâmica)
object TestePolimorfismo {
def main(args: Array[String]): Unit = {
def processar(entidade: Entidade): Unit = {
entidade.descricao()
}
val ent = new Entidade
val func = new Funcionario
val gerente = new Gerente
// O método correto é chamado em tempo de execução, baseado no tipo real do objeto
processar(ent) // "sou uma entidade"
processar(func) // "sou um funcionário"
processar(gerente)// "sou um gerente"
}
}
class Entidade {
def descricao(): Unit = {
println("sou uma entidade")
}
}
class Funcionario extends Entidade {
override def descricao(): Unit = {
println("sou um funcionário")
}
}
class Gerente extends Entidade {
override def descricao(): Unit = {
println("sou um gerente")
}
}
5. Classes Abstratas
5.1 Definição
Uma classe abstrata (modificada com abstract) serve como um modelo. Pode conter atributos e métodos tanto abstratos (sem implementação) quanto concretos. Deve ser estendida.
object TesteAbstrato {
def main(args: Array[String]): Unit = {
val aluno: Aluno = new Aluno
println(aluno.nome)
println(aluno.curso)
aluno.estudar()
aluno.dormir()
}
}
abstract class Pessoa {
val nome: String = "Ana" // Atributo concreto
var idade: Int = 20 // Atributo concreto
var curso: String // Atributo abstrato (sem valor inicial)
def estudar(): Unit = { // Método concreto
println("pessoa estuda")
}
def dormir(): Unit // Método abstrato (sem corpo)
}
class Aluno extends Pessoa {
override val nome: String = "Bruno"
idade = 21 // Modifica a propriedade herdada, não a pode sobrescrever com override
var curso: String = "Engenharia" // Implementa o atributo abstrato
override def estudar(): Unit = { // Sobrescreve o método concreto
super.estudar()
println("aluno estuda")
}
def dormir(): Unit = { // Implementa o método abstrato, 'override' é opcional
println("aluno dorme")
}
}
5.2 Classes Anônimas
object TesteAnonimo {
def main(args: Array[String]): Unit = {
val individuo: Pessoa = new Pessoa {
var nome: String = "Pedro"
var curso: String = "Filosofia"
def estudar(): Unit = println("Pedro estuda")
def dormir(): Unit = println("Pedro dorme")
}
println(individuo.nome)
individuo.estudar()
}
}
abstract class Pessoa {
var nome: String
var curso: String
def estudar(): Unit
def dormir(): Unit
}
6. Objetos Companheiros e Singleton
6.1 Conceito
Scala não possui membros estáticos. Em vez disso, usa objetos companheiros: um objeto (declarado com object) com o mesmo nome de uma classe. Os membros do objeto companheiro atuam como membros "estáticos" da classe.
// Classe
class Aluno(val nome: String, val idade: Int) {
def exibir(): Unit = {
println(s"Nome: $nome, Idade: $idade, Escola: ${Aluno.escola}")
}
}
// Objeto Companheiro
object Aluno {
val escola: String = "Escola Estadual" // Membro "estático" compartilhado
}
object ProgramaPrincipal {
def main(args: Array[String]): Unit = {
val a1 = new Aluno("Lucas", 15)
val a2 = new Aluno("Maria", 16)
a1.exibir() // Ambos mostram a mesma escola
a2.exibir()
}
}
6.2 Método Apply
O método apply no objeto companheiro permite criar instâncias da classe associada sem usar new.
class Aluno private(val nome: String, val idade: Int) { // Construtor privado
def exibir(): Unit = {
println(s"Nome: $nome, Idade: $idade")
}
}
object Aluno {
val escola: String = "Escola Municipal"
// Método apply para criação de instâncias
def apply(nome: String, idade: Int): Aluno = new Aluno(nome, idade)
// Sobrecarga do apply
def apply(): Aluno = new Aluno("Padrão", 0)
}
object ProgramaPrincipal {
def main(args: Array[String]): Unit = {
val a1 = Aluno("Ana", 17) // Equivalente a Aluno.apply("Ana", 17)
val a2 = Aluno()
a1.exibir()
a2.exibir()
}
}
6.3 Implementação de Singleton
object TesteSingleton {
def main(args: Array[String]): Unit = {
val obj1 = Config.getInstancia()
val obj2 = Config.getInstancia()
println(obj1 == obj2) // true
}
}
class Config private(param: String)
object Config {
// Implementação "Eager"
// private val instancia: Config = new Config("padrão")
// def getInstancia(): Config = instancia
// Implementação "Lazy"
private var instancia: Config = _
def getInstancia(): Config = {
if (instancia == null) {
instancia = new Config("padrão")
}
instancia
}
}
7. Traits (Características)
7.1 Definição
Traits são a alternativa Scala para interfaces. Podem conter implementações concretas e abstratas de atributos e métodos. Uma classe pode misturar (mixin) múltiplos traits, complementando o mecanismo de herança simples.
7.2 Sintaxe Básica
trait NomeTrait {
val atributoConcreto: String = "valor"
var atributoAbstrato: String // Atributo abstrato
def metodoConcreto(): Unit = {
println("implementação no trait")
}
def metodoAbstrato(): Unit // Método abstrato
}
7.3 Mistura de Traits (Mixins)
// Com herança existente
class ClasseFilha extends ClassePai with Trait1 with Trait2 {}
// Sem herança
class ClasseFilha extends Trait1 with Trait2 with Trait3 {}
object TesteMixin {
def main(args: Array[String]): Unit = {
// Mistura estática na definição da classe
val aluno = new Estudante
aluno.comer()
aluno.aprender()
// Mistura dinâmica na instanciação
val alunoTalentoso = new Estudante with Talento {
override def cantar(): Unit = println("aluno canta bem")
override def dançar(): Unit = println("aluno dança bem")
}
alunoTalentoso.cantar()
}
}
class Pessoa {
def comer(): Unit = println("pessoa come")
}
trait Estudioso {
var conhecimento: Int = 0
def aprender(): Unit
}
trait Talento {
def cantar(): Unit
def dançar(): Unit
}
class Estudante extends Pessoa with Estudioso {
def aprender(): Unit = {
conhecimento += 1
println("estudante aprende")
}
}
7.4 Resolução de Conflitos (Linearização)
Quando múltiplos traits misturados têm membros com o mesmo nome, Scala usa um processo chamado linearização para resolver a ordem de chamada. Um conflito comum ocorre no padrão diamante.
object TesteConflito {
def main(args: Array[String]): Unit = {
val bola = new MinhaBola
println(bola.descrever()) // "minha bola é uma bola-vermelha-de-futebol"
}
}
trait Bola {
def descrever(): String = "bola"
}
trait BolaColorida {
val cor: String = "vermelha"
def descrever(): String = cor + "-" + super.descrever()
}
trait BolaDeFutebol {
val categoria: String = "futebol"
def descrever(): String = categoria + "-" + super.descrever()
}
// A ordem dos traits após 'extends' define a linearização
class MinhaBola extends BolaDeFutebol with BolaColorida {
override def descrever(): String = {
// super.descrever() segue a linearização: BolaColorida -> BolaDeFutebol -> Bola
"minha bola é uma " + super.descrever()
// Para chamar um trait específico: super[BolaDeFutebol].descrever()
}
}
7.5 Tipos Próprios do Trait (Self-type)
Um trait pode exigir que seja misturado em uma classe que estende ou contém um tipo específico, similar à injeção de dependência.
object TesteSelfType {
def main(args: Array[String]): Unit = {
val usuario = new UsuarioRegistrado("maria", "senha123")
usuario.salvar()
}
}
class Usuario(val nome: String, val senha: String)
trait UsuarioDAO {
// Declaração de tipo próprio: exige ser misturado com uma classe que seja um Usuario
_: Usuario =>
def salvar(): Unit = {
// 'this' acessa os membros de Usuario
println(s"Salvando usuário: ${this.nome} no banco")
}
}
// A classe deve herdar de Usuario (ou ser Usuario) e misturar o trait
class UsuarioRegistrado(nome: String, senha: String) extends Usuario(nome, senha) with UsuarioDAO
8. Tópicos Adicionais
8.1 Verificação e Conversão de Tipos
class Animal
class Cachorro extends Animal
object TesteTipos {
def main(args: Array[String]): Unit = {
val animal: Animal = new Animal
val cachorro: Animal = new Cachorro
val rex: Cachorro = new Cachorro
// Verificação de tipo
val teste1: Boolean = animal.isInstanceOf[Animal] // true
val teste2: Boolean = animal.isInstanceOf[Cachorro] // false
val teste3: Boolean = cachorro.isInstanceOf[Cachorro]// true
// Conversão de tipo (com verificação de segurança)
if (cachorro.isInstanceOf[Cachorro]) {
val c: Cachorro = cachorro.asInstanceOf[Cachorro]
}
// Obter a classe
val classeAnimal: Class[Animal] = classOf[Animal]
}
}
8.2 Tipos Personalizados
object TesteType {
def main(args: Array[String]): Unit = {
// 'type' cria um apelido para um tipo existente
type Texto = String
type ListaDeInteiros = List[Int]
val mensagem: Texto = "Olá"
val numeros: ListaDeInteiros = List(1, 2, 3)
}
}