Prévia do material em texto
Program ação Orientada a Objetos e Estrutura de Dados Fábio da Silva Souza Rolfi Cintas Gomes Luz Organizador: Adilson da Silva Fábio da Silva Souza Rolfi Cintas Gomes Luz Organizador: Adilson da Silva GRUPO SER EDUCACIONAL PROGRAMAÇÃO ORIENTADA A OBJETOS E ESTRUTURA DE DADOS PROGRAMAÇÃO ORIENTADA A OBJETOS E ESTRUTURA DE DADOS SER_Capa e Contra-Programação Orientada a Objetos e Estrutura de Dados.indd 1,3SER_Capa e Contra-Programação Orientada a Objetos e Estrutura de Dados.indd 1,3 27/01/2023 12:10:3327/01/2023 12:10:33 Programação Orientada a Objetos e Estrutura de Dados © by Ser Educacional Todos os direitos reservados. Nenhuma parte desta publicação poderá ser reproduzida ou transmitida de qualquer modo ou por qualquer outro meio, eletrônico ou mecânico, incluindo fotocópia, gravação ou qualquer outro tipo de sistema de armazenamento e transmissão de informação, sem prévia autorização, por escrito, do Grupo Ser Educacional. Imagens e Ícones: ©Shutterstock, ©Freepik, ©Unsplash. Diretor de EAD: Enzo Moreira. Gerente de design instrucional: Paulo Kazuo Kato. Coordenadora de projetos EAD: Jennifer dos Santos Sousa. Equipe de Designers Instrucionais: Gabriela Falcão; José Carlos Mello; Lara Salviano; Leide Rúbia; Márcia Gouveia; Mariana Fernandes; Mônica Oliveira e Talita Bruto. Equipe de Revisores: Camila Taís da Silva; Isis de Paula Oliveira; José Felipe Soares; Nomager Fabiolo Nunes. Equipe de Designers gráficos: Bruna Helena Ferreira; Danielle Almeida; Jonas Fragoso; Lucas Amaral, Sabrina Guimarães, Sérgio Ramos e Rafael Carvalho. Ilustrador: João Henrique Martins. SOUZA, Fábio da Silva. / LUZ, Rolfi Cintas Gomes. Organizador: SILVA, Adilson. Programação Orientada a Objetos e Estrutura de Dados: Recife: Digital Pages e Grupo Ser Educacional - 2022. 187 p.: pdf ISBN: 978-65-81507-81-7 1. Programação 2. Sistemas 3. Objetos. Grupo Ser Educacional Rua Treze de Maio, 254 - Santo Amaro CEP: 50100-160, Recife - PE PABX: (81) 3413-4611 E-mail: sereducacional@sereducacional.com Iconografia Estes ícones irão aparecer ao longo de sua leitura: ACESSE Links que complementam o contéudo. OBJETIVO Descrição do conteúdo abordado. IMPORTANTE Informações importantes que merecem atenção. OBSERVAÇÃO Nota sobre uma informação. PALAVRAS DO PROFESSOR/AUTOR Nota pessoal e particular do autor. PODCAST Recomendação de podcasts. REFLITA Convite a reflexão sobre um determinado texto. RESUMINDO Um resumo sobre o que foi visto no conteúdo. SAIBA MAIS Informações extras sobre o conteúdo. SINTETIZANDO Uma síntese sobre o conteúdo estudado. VOCÊ SABIA? Informações complementares. ASSISTA Recomendação de vídeos e videoaulas. ATENÇÃO Informações importantes que merecem maior atenção. CURIOSIDADES Informações interessantes e relevantes. CONTEXTUALIZANDO Contextualização sobre o tema abordado. DEFINIÇÃO Definição sobre o tema abordado. DICA Dicas interessantes sobre o tema abordado. EXEMPLIFICANDO Exemplos e explicações para melhor absorção do tema. EXEMPLO Exemplos sobre o tema abordado. FIQUE DE OLHO Informações que merecem relevância. SUMÁRIO UNIDADE 1 Objetivos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 11 Introdução � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 12 Introdução à orientação a objetos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 13 Objetivos e vantagens da poo � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 14 Modelos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �14 Bibliotecas de operações � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �16 Tipos de Dados Primitivos (Representação) � � � � � � � � � � � � � � � � � � � � � � � �16 A classe String � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 20 Variáveis � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 23 Operadores lógicos relacionais e comparadores em java � � � � � � � � � 23 Executando o java � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 30 Inserido Classes � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �33 Operadores lógicos e negação em java � � � � � � � � � � � � � � � � � � � � � � � � � � 34 Estrutura de decisão em java � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 38 Estrutura de repetição em java � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �42 Terminologia da programação orientada a objetos � � � � � � � � � � � � � 44 Instanciando um objeto � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �47 UNIDADE 2 Objetivos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 51 Introdução � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 52 Classes, pacotes, objetos, atributos, métodos, construtores, palavra-chave this e sobrecarga � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 53 Classes � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 53 Pacotes � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 55 Objetos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 57 Atributos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 58 Métodos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 60 Construtores � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 63 Palavra-chave This � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 67 Sobrecarga � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 68 Instanciação e referências de objetos � � � � � � � � � � � � � � � � � � � � � � � � � � �70 Envio de mensagens � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 72 Ciclo de vida de um objeto � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 73 Abstração e encapsulamento � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �76 Abstração � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 76 Encapsulamento � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 78 Estruturas compostas homogêneas � � � � � � � � � � � � � � � � � � � � � � � � � � � � 83 Vetor � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 83 Vetor orientado a objetos � � � � � � � � � � � � � � � � � � � � � � � � � � � 89 Matriz � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 92 Estruturas comportamentais heterogêneas � � � � � � � � � � � � � � � � � � � 94 Instanciação � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 94 Atribuição � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 95 Tipos abstratos de dados � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 97 Tipos de armazenamento � � � � � � � � � � � � � � � � � �� � � � � � � � � 98 Coleções � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 99 UNIDADE 3 Objetivos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 103 Introdução � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 104 Herança � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �105 Utilização de herança � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 105 Palavra-chave super � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 109 Criação e uso de hierarquia de classes � � � � � � � � � � � � � � � � � � � � � � � � � � 111 Classes e subclasses � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 113 Classes abstratas e interfaces � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 113 Classes abstratas � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �114 Interfaces � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 117 Relacionamento entre classes � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 121 Associação � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 121 Composição � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 127 Agregação � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �133 Polimorfismo � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 134 Introdução ao polimorfismo � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 134 Polimorfismo na prática � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �135 Ligação dinâmica (dynamic binding) � � � � � � � � � � � � � � � � � � � � � � � � � � � 138 Tratamento de exeções � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 140 Evitando e capturando exceções � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 140 UNIDADE 4 Objetivos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 143 Introdução � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 144 Algoritmos recursivos � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �145 Técnicas de ordenação � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �146 Método Bubble Sort � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 147 Método Insertion Sort � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 149 Método Selection Sort � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 150 Estruturas Lineares � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 151 Listas � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 151 Lista Estática � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 152 Lista Ligada � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 152 Lista Duplamente Ligada � � � � � � � � � � � � � � � � � � � � � � � � � � � 156 Código Lista Duplamente Ligada � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 158 Lista Collection � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 158 Pilhas � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �160 Código da Pilha de Alocação Estática � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 162 Filas – implementações � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 163 Collection Class Queue – Fila dinâmica � � � � � � � � � � � � � � � � � � � � � � � � � � � � 168 Árvores e suas generalizações � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � �170 Terminologia da estrutura Árvore � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 174 Nó � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 174 Node – Implementação dinâmica � � � � � � � � � � � � � � � � � � 176 Vetor – Implementação sequencial � � � � � � � � � � � � � � � � � 177 Árvores binárias � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 178 Tipos de Árvores binárias � � � � � � � � � � � � � � � � � � � � � � � � � � � 179 Árvores de busca � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 182 Árvore binária e de busca � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 183 Referências � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � � 185 Apresentação Olá, aluno(a). Seja bem-vindo(a) à disciplina! Nessa disciplina será abordada a Programação Orientada a Objetos (POO), utilizando a linguagem de programação JAVA. Tam- bém serão apresentados conceitos de POO e conceitos e estruturas de dados usadas para a manipulação e armazenamento de dados. Dentre essas estruturas apresentadas, vamos encontrar os vetores, as matrizes e as árvores. Para essas estruturas, serão apre- sentadas formas de ordenação de dados. Antes de começarmos, lembre-se que muitas dessas estru- turas possuem formatos diferenciados que contribuem em diversas áreas, como redes, hardware, banco de dados, Sistemas de Infor- mação e negócios, entre outros. No decorrer do conteúdo, todos os conceitos de orientação a objetos serão abordados de forma simplificada para que você possa absorver facilmente os temas abordados no material. Dito isto, desejo a você boa leitura e bons estudos! Autoria Fábio da Silva Souza O professor Fábio da Silva Souza é tecnólogo em Gestão da Tecno- logia da Informação pela Faculdade Tecnológica de Guaratinguetá (Fatec). Formou-se no ano de 2017 e, desde então, atua como pro- gramador e analista de sistemas. Currículo Lattes http://lattes.cnpq.br/0316083306850058 Rolfi Cintas Gomes Luz O professor Rolfi Cintas Gomes Luz é mestre (2012) em Ensino de Ciências pela Universidade Cruzeiro do Sul. É graduado (2008) em Ciência da Computação pela mesma instituição. Atua há duas déca- das na área da computação como analista de sistemas, passando por empresas nacionais e multinacionais, e possui uma década atuando no ensino superior. Currículo Lattes http://lattes.cnpq.br/0778971556248045 Organizador Adilson da Silva O professor Adilson da Silva é formado em Ciência da Computação pela Universidade Católica de Pernambuco (1995), possui especiali- zação em Tecnologia da Informação pela UFPE (Universidade Fede- ral de Pernambuco) e MBA em Gestão de Pessoas por Competências pela faculdade Santa Maria. Também tem especialização em Meto- dologias Ativas da Educação e Mestrado em Engenharia de Software pelo C.E.R.S.A.R., além de possuir certificação Scrum Master. Atua na área de informática há mais de trinta anos, como Analista de Sistemas e como professor de disciplinas como Algoritmo, Banco de dados, Gestão de projetos, Gestão ágil e Segurança da Informação. Currículo Lattes http://lattes.cnpq.br/7221002795193400 UN ID AD E 1 Objetivos ◼ Apresentar as características de Programação Orientada a Objetos. ◼ Introduzir a linguagem Java para Programação Orientada a Objetos. ◼ Entender os conceitos da Programação Orientada a Objetos.◼ Compreender como surgiu o paradigma de Programação Orientada a Objetos. 12 Introdução Caro(a) aluno(a), como vai? A partir de agora veremos a Programação Orientada a Obje- tos. Dessa forma, falaremos sobre os objetivos esperados e quais as vantagens de utilizá-la. Além disso, apresentaremos os principais conceitos de orientação a objetos e a algumas das suas implemen- tações em JAVA. Veremos também como podemos executar os programas em JAVA usando o Netbean. Assim, discutiremos a aplicação de alguns conceitos de lógica de programação em Java, como o uso de opera- dores (Lógicos e aritméticos). Trataremos também das estruturas condicionais e das estruturas de repetição que podem ser usadas. Por fim, apresentaremos algumas terminologias usadas no para- digma de orientação a Objetos. Pensando nisso, até o final desse material, discutiremos: 1. características da Programação Orientada a Objetos; 2. uso da Linguagem JAVA na ferramenta NetBeans; 3. uso de estruturas condicionais e de estruturas de repetição em JAVA; 4. conceitos de Programação Orientada a Objetos. Destaco que a Programação Orientada a Objetos é o paradig- ma de programação mais utilizado atualmente no desenvolvimento de sistemas, trazendo um aumento de produtividade com a utiliza- ção de reuso de código. E, para que você possa se inserir no merca- do, é necessário ter uma boa base nesse paradigma, e é o que essa disciplina lhe dará. Dito isto, vamos iniciar nossos estudos. 13 Introdução à orientação a objetos Caro(a) aluno(a), é notório que computadores estão presentes no cotidiano e servem para automatizar e agilizar a execução de tarefas simples ou complexas. Mas, um computador não é capaz de efetuar qualquer tarefa sem um software, pois é através dele que se proces- sam as informações. Nos primórdios da computação, um programa de computador era escrito de maneira sequenciada, com suas funções uma abaixo da outra e seguindo esta sequência até o final. Dentro da sequência, o pro- grama trabalhava diante de seleções que executavam uma sequência com base em condições (if ou else) verdadeiras (true) ou falsas (false) ou diante de repetições que executassem tal sequência enquanto a condição fosse verdadeira (iterações: for, foreach, do e while). Em síntese, a Programação Orientada a Objetos (POO) con- siste no desenvolvimento de sistemas computacionais utilizan- do linguagens de programação que suportam orientação a objetos por meio de classes e seus atributos e métodos, introduzindo uma abordagem na qual o(a) programador(a) visualiza o programa em execução como uma coleção de objetos cooperantes que se comuni- cam por mensagens. Em outras palavras, podemos dizer que a POO procura atender aos requisitos do(a) cliente através de funções encontradas dentro dos objetos criados. É importante destacarmos que cada um dos objetos é ins- tância de uma classe e todas as classes formam uma hierarquia de classes unidas via relacionamento de herança, como apontado na página 4 do livro Introdução ao Paradigma de Orientação a Objetos, publicado em 1996 e escrito por Carlos Kamienski. Uma das características da Programação Orientada a Objetos, como o próprio nome sugere, é o uso de objetos, em vez de funções ou procedimentos, para a construção do programa. Eles se comuni- cam mediante mensagens e cada objeto é uma instância de classe, possibilitando que o programa seja escrito sem a necessidade de se seguir uma sequência e que um método seja executado em qualquer parte da classe, desde que no padrão aceito pela linguagem. 14 Objetivos e vantagens da poo A Programação Orientada a Objetos tem por objetivo melhorar a produtividade dos(as) programadores(as) no desenvolvimento de sistemas, possibilitando a construção de sistemas robustos com menos linhas de código, de modo que trechos de códigos sejam rea- proveitados. A POO é vantajosa porque: • possibilita a reutilização de código, sendo essa sua maior van- tagem ao permitir que um(a) programador(a) escreva menos linhas, já que um trecho de código pode ser reutilizado inú- meras vezes em qualquer parte do sistema; • possibilita que a aplicação seja escalável, fazendo com que ela cresça de acordo com as necessidades que surgem no decorrer do desenvolvimento ou mesmo diante da necessidade de im- plantar novas funcionalidades; • além disso, a manutenção do sistema é feita de forma simpli- ficada, rápida e eficiente. Modelos Para programar utilizando a orientação a objetos, é necessário criar classes que, em muitos casos, são só modelos de entidades e, para que se possa representar dados a partir das classes, é necessária a criação de objetos e/ou instanciação destas classes. Além disso, a criação de um objeto, ou sua instanciação, é ne- cessária para que se possa ter a representação de dados e a manipu- lação das informações e executar operações contidas na classe. No exemplo: “Livro livro = new Livro();”, a primeira parte antes do igual indica que se está referenciando na memória do sistema opera- cional o objeto denominado “livro”, que tem o tipo Livro, e a segun- da parte é a criação da instância através da palavra-chave “new”. EXEMPLO 15 Já Na página 14 do livro Introdução à Programação Orienta- da a Objetos usando JAVA, editado em 2011, o autor Rafael Santos afirma que: as classes são estruturas das linguagens de programação orientadas a objetos para con- ter, para determinado modelo, os dados que devem ser representados e as operações que devem ser efetuadas com estes dados (SAN- TOS, 2011). Além disso, os dados contidos na classe são atributos do objeto e as classes são abstrações do mundo real no mundo vir- tual. Na programação, tais classes também são chamadas de modelos (models), representações de objetos, pessoas, tarefas e processos, usados no dia a dia, não necessariamente diante de um computador. Para melhor exemplificar isso, o Diagrama 1 representa um modelo de abastecimento num posto de combustível. Diagrama 1 - modelo de abastecimento em posto de combustível Fonte: editorial Digital Pages (2020). 16 O modelo anterior traduz o processo num posto de combustí- vel, onde o frentista ou caixa recolhe dados, como tipo de com- bustível, valor total pago, quantidade de litros, preço por litro e tipo de pagamento. Esses dados possuem comportamentos usados, por exemplo, para fechamento de caixa, determinar qual combustível é o mais vendido, se é necessária a compra de mais combustível, dentre outros trâmites relativos a um posto de combustível. Bibliotecas de operações Caro(a) aluno(a), é preciso que você saiba que nem todo mode- lo possui dados e operações, visto que eles podem incluir ape- nas dados ou operações. Lembre-se que modelos que contêm só dados são pouco usados, pois não faz sentido ter um modelo cujas informações não são manipuláveis. Já modelos que pos- suem só operações são mais encontrados, sendo denominados bibliotecas de operações, criadas para manipulação de dados de outras classes. Tipos de Dados Primitivos (Representação) Em Sistemas de Informação, muito se fala sobre dados, informa- ção e conhecimento. Nesse cenário, a computação, de forma geral, refere-se à informação e ao conhecimento. Previsão ou tendências, por sua vez, são tratadas na Inteligência de Negócios, ou BI (Busi- ness Intelligence). Já os dados podem ser um conjunto de bits, uma palavra, uma letra, um número, ou podem assumir algum valor sem significado aparente. DICA 17 EXEMPLO EXEMPLO EXEMPLO Por exemplo, o abacate, R$ 5,00, 1 kg. Além disso, a informação é a união de diversos dados que possuem sentido ou significado. Por exemplo: “hoje, 1 kg do abacate está custando R$ 5,00 reais”. Já o conhecimento é a relação dos dados e das informações, constituindo uma ação, aplicação ou saber em diversas áreas do co- nhecimento, geralmente empregada quando se cruzam valores. Por exemplo: “há dez anos, no mês de dezembro, 1 kg do abacate custava R$ 5,00 reais”. Por fim, os dados,em Estrutura de Dados, são trabalhados como forma primordial de computar ou solucionar problemas do mundo real, visando atender às regras de negócio, ou seja, às regras do mundo real que devem ser incorporadas no mundo computacio- nal ou atividades empresariais que utilizam esses dados como ca- deia de valor do negócio. É importante destacarmos ainda que a escolha do tipo de dado em Estrutura de Dados leva em consideração alguns aspectos, como: 18 • identificador - nome pelo qual a estrutura será identificada. Geralmente, possui ligação com sua aplicação no mundo real. • tipo de armazenamento - define se serão armazenadas le- tras, números, valores lógicos etc. • velocidade em inserir dados - a velocidade é medida, geral- mente em milissegundos, e pode variar dependendo do tipo de estrutura escolhida. • algoritmo de ordenação e localização de dados - existem muitos algoritmos específicos para cada tipo de estrutura que dependem muito da sua aplicação. Às vezes, possuem estruturas que demoram mais para or- denar, mas que, por outro lado, são mais velozes para localizar os dados. Assim, através do tipo de aplicação de dados, verifica-se qual o melhor formato de dados para tanto. Nesse contexto, surgem os tipos de dados primitivos, que orientam ao programador a estrutura mais adequada para aplicação na regra de negócio. É importante destacarmos que, em Java, há poucos tipos de dados primitivos, também conhecidos como nativos ou básicos. Eles são parte da linguagem, portanto, suas nomenclaturas são pala- vras-chaves e não são instâncias de outras classes. Assim, cada tipo tem limitações, ocupando valores específicos em memória. Para que você possa entender o que estamos tratando, abaixo, na Tabela 1, constam as especificações dos tipos primitivos em JAVA. Veja: Tabela 1.- Tipos Primitivos em JAVA boolean byte true ou false Entre -128 e 127 O valor booleano assume valores true ou false Números inteiros de 8 bits de precisão 8 8 true 108 Tipo primitivo Valores Observações Bits em memória Exemplo 19 Fonte: editorial Digital Pages (2020). Caro(a) aluno(a), é necessário que você saiba que, para o tipo char, cada número de 0 a 65535 é um dos caracteres na ta- bela ASCII (American Standard Code for Information Interchange), sigla em inglês para Código Padrão Americano para o Intercâm- bio de Informação. Para que você entenda o que estamos deta- lhando, o código a seguir dá um melhor entendimento sobre o tipo char. float long char double int short Entre 1.40239846e-46 e 3.40282347e+38 Entre -9.223.372.036.854.775.808 e +9.223.372.036.854.775.807 Entre 0 e 65535 Entre 4.94065645841246544e-324 e 1.7976931348623157e+308 Entre -2.147.483.648 e +2.147.483.647 Entre -32768 e 32767 Pontos flutuantes de precisão sim- ples, usados para representar valo- res decimais Números inteiros de 64 bits de precisão Na teoria, char armazena números inteiros de 16 bits de precisão, mas, na prática, é utili- zado para para qualquer caractere alfanumérico (char = character) Pontos flutuantes de precisão dupla, usados para representar valores decimais Números inteiro de 32 bits de precisão Números inteiros de 16 bits de precisão 32 64 16 64 32 16 34897.75 7234829 s 34897.75 1000 1000 20 EXEMPLO public class ExemploCharASCII { public static void main(String args[]) { char caractereJ = 74; char caractereA = 65; char caractereV = 86; System.out.println(caractereJ + “” + caractereA + “” + caractereV + “” + caractereA ) ; } } Como você deve ter percebido, no código há três atri- butos do tipo char e cada um representa uma letra. Assim, para escrever a palavra JAVA utilizando char, é preciso usar aspas vazias para dizer ao Java que se está concatenando e não somando, pois, como char é inteiro, sem + “” + a linguagem entenderia que os valores estão somados em vez de concate- nados. Assim, executando o código anterior, a saída no con- sole seria “JAVA”. A classe String Em Java, para atribuir qualquer texto no lugar de um array de char (char []), usa-se a classe String. Isso porque, por ser uma classe, String não é um tipo primitivo, apesar de não precisar ser instancia- da. De acordo com Santos (2011), embora não seja preciso declarar um array de char, o conceito de String é que a instância possui zero ou mais caracteres do tipo char, enfileirados em ordem de leitura (da esquerda para direita). Veja: 21 EXEMPLO public class testePalavra { public static void main(String args[]) { //Usando array de char char [] tipoChar = {‘p’,’a’,’l’,’a’,’v’,’r’,’a’}; //usando classe String sem instanciá-la String classeString = “Palavra”; //usando a classe String, instanciando-a String classeString2 = new String (“Palavra”); } } No exemplo a cima, usando char, é preciso criar um array e cada letra precisa ser passada por aspas simples, enquanto na classe String é possível passar diretamente entre aspas duplas ou por parâ- metro entre aspas duplas ao instanciar a classe String. Perceba que, no código anterior, o mais comum é fazer “String nomeVariavel = “palavra”;” em vez de “String nomeVaria- vel = new String(“palavra”);”. É importante destacarmos que o tamanho de uma String depende da quantidade de memória disponível no sistema, ou seja, ela consome poucos ou muitos bits de memória conforme o tamanho do texto. Além do que detalhamos, é preciso que você saiba que a classe String tem métodos essenciais que auxiliam no desenvolvimento de trabalho com textos. Alguns dos métodos, e para que eles servem, são: • length - regressa à quantidade exata de caracteres con- tidos em uma String. Este método, além de caracteres visíveis, também retorna espaços, quebras de linhas, 22 tabulações etc. É importante destacarmos que o método lengh não aceita parâmetros. • charAt - volta ao caractere que está numa posição do texto. Para execução do método, é necessário que se passe um valor inteiro como argumento. • getChars - retrocede a caracteres partindo de uma posi- ção inicial até outra. Exige que se passe por quatro parâ- metros, posição inicial (srcBegin), posição final (srcEnd), destino (dest) e posição do início do destino (dstBegin). Para tanto, é preciso criar um array de char como destino passado como parâmetro. • replace - substitui caractere ou texto e possui como parâme- tro dois argumentos. O primeiro é o caractere ou texto a ser substituído e o segundo, o caractere ou texto substituto. • toUpperCase - deixa todo o texto maiúsculo e não aceita parâmetros. • toLowerCase - deixa todo o texto minúsculo e não aceita parâmetros. • trim: remove espaços contidos no início e/ou no final de um texto, exceto entre duas palavras. Também não aceita parâmetros. A classe String é imutável, não sendo possível alterá-la após cria- da. Por outro lado, uma variável da classe String também admite a junção de textos, denominada como concatenação. A concatenação é feita utilizando o operador de adição (+) que junta dois ou mais textos em um. Ela só é possível porque, na concatenação, é criado um novo objeto temporário da classe String, posteriormente asso- ciado à referência da variável criada. DICA 23 Variáveis No algoritmo e na computação, temos as variáveis, que são definidas como espaço em memória que possui um identificador e pode alo- car dados. Como nos tipos de dados primitivos que compõem a cadeia (inteiro, real e lógico), nas linguagens de programação, temos tipos primitivos mais específicos, em que cada linguagem possui seu tipo primitivo, porém, algumas linguagens possuem um padrão. Para que você não tenha dúvidas, já lhe adianto que, nesta disci- plina, será utilizada a linguagem Java nos exemplos e, nessa linguagem, para definir uma variável, usa-se o conceito de declaração de variável. É importantedestacarmos que Java é uma linguagem de pro- gramação simples, portável, gratuita, robusta e com bibliotecas para aplicações, além de ser possível executá-la em um terminal Unix ou MS-DOS. Por ser multiplataforma, é utilizada para desenvolvimen- to de sistemas web, mobile ou desktop e possui frameworks fáceis de utilizar e também há integração fácil com o banco de dados. Esta linguagem também é fortemente tipada, ou seja, ao de- clarar atributos ou variáveis, declara-se também o tipo de dado (nu- mérico, texto, booleano, entre outros). Grosso modo, diz-se que, para um campo do tipo primitivo int (numérico), não é possível atribuir um caractere não inteiro entre 0 e 9. Isso porque os tipos de dados primitivos são palavras-chaves reservadas pela linguagem de programação e não se pode utilizá-las como nome para uma variável. Operadores lógicos relacionais e comparadores em java Os fluxos de um programa são baseados em operadores lógicos e ló- gicos relacionais. Eles servem para que, durante a execução de um trecho do código e segundo a condição, o programa faça algo ou, em uma iteração, ele execute aquilo até que a condição seja cumprida ou enquanto ela for válida. Dessa maneira, os operadores lógicos rela- cionais em Java retornam o valor booleano true ou false e são listados a seguir, junto com seu uso para verificação. 24 ◼ == (igual): se dois valores são iguais. Se o valor à esquerda e o valor à direita foram iguais, o resultado é true, do contrário é false. ◼ != ( ): se dois valores são diferentes. Se o valor à esquerda for diferente do valor à direita, o resultado é true, do con- trário é false. ◼ > (maior): se um valor é maior que o outro. Se o valor à es- querda for maior que o valor à direita, o resultado é true, do contrário é false. ◼ < (menor): se um valor é menor que o outro. Se o valor à es- querda for menor que o valor à direita, o resultado é true, do contrário é false. ◼ >= (maior ou igual): se um valor é maior ou igual ao outro. Se o valor à esquerda for maior ou igual ao valor à direita, o resultado é true, do contrário é false. ◼ <= (menor ou igual): se um valor é menor ou igual ao outro. Se o valor à esquerda for menor ou igual ao valor à direita, o resultado é true, do contrário é false. É importante destacarmos que os operadores anteriores são uti- lizados para comparar valores nativos numéricos, inclusive o tipo char: public class TesteOperadoresLogicos { public static void main(String args[]) { int numeroComparado = 18; if (15 == numeroComparado) { System.out.println(“os números são iguais”); } else { EXEMPLO 25 System.out.println(“os números são diferentes”); } If (21 != numeroComparado) { System.out.println(“os números são diferentes”); } else { System.out.println(“os números são iguais”); } If (33 > numeroComparado) { System.out.println(“o número é maior que o número comparado”); } else { System.out.println(“o número é menor que o número comparado”); } if (19 < numeroComparado) { System.out.println(“o número é menor que o número comprado”); } else { System.out.println(“o número é menor que o número comprado”); } if ( 18 >= numeroComparado) { System.out.println(“o número é maior ou igual ao número comparado”); } else { System.out.println(“o número é menor que o número comparado”); } 26 EXEMPLO if (18 <= numeroComparado) { System.out.println(“o número é menor ou igual ao número comparado”); } else { System.out.println(“o número é maior que o número comparado”); } } } E, caso seja necessário comparar Strings ou objetos, se usa o método equals, como neste exemplo da classe String: public class ComparaPalavra { public static void main(String args[]) { String palavra1 = “Palavra”; String palavra2 = “Palavra”; If (palavra1.equals(palavra2)) { System.out.println(“As palavras são iguais”); } else { System.out.println(“As palavras são diferentes”); } } } 27 EXEMPLO Perceba que, no exemplo anterior, a palavra1 é igual à pala- vra2, portanto, ao executar o código, é exibido no console o resulta- do “As palavras são iguais”. Ao utilizar o método equals da classe String, todos os caracteres da palavra precisam estar na mesma sequência, inclusive os espaça- mentos. Isso porque o método também diferencia se o caractere é maiúsculo ou minúsculo. Então, “Arroz com Feijão” não é a mesma coisa que “Arroz coM feijão” ou “Arroz comFeijão”. Assim, para sa- ber se uma String é igual a outra, sem distinguir caracteres maiús- culos e minúsculos, é utilizado o equalsIgnoreCase. É importante você saber que todas as classes possuem o mé- todo equals (herdado da classe Object) e, para customizá-lo, é pre- ciso sobrescrevê-lo utilizando a annotation @Override acima do método, veja: public class Livro { private long id; private String nomeAutor; private int anoLancamento; private String titulo; public String getNomeAutor() { return nomeAutor; } DICA 28 public void setNomeAutor(String nomeAutor) { this.nomeAutor = nomeAutor; } public int getAnoLancamento() { return anoLancamento; } public void setAnoLancamento(int anoLancamento) { this.anoLancamento = anoLancamento; } public String getTitulo() { return titulo; } public void setTitulo(String titulo) { this.titulo = titulo; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Livro other = (Livro) obj; 29 if (anoLancamento != other.anoLancamento) return false; if (nomeAutor == null) { if (other.nomeAutor != null) return false; } else if (!nomeAutor.equals(other.nomeAutor)) return false; if (titulo == null) { if (other.titulo != null) return false; } else if (!titulo.equals(other.titulo)) return false; return true; } } No exemplo acima, o método equals foi sobrescrito com a annotation @Override. No código, o objeto passado pelo parâmetro “(Object obj)” é do tipo Object e não do tipo Livro, isso acontece por- que todas as classes herdam direta ou indiretamente a classe Object, sendo ela a base para todas as demais. Em seguida, como no código do exemplo, é feita a conversão explícita do objeto genérico para o tipo Livro (Livro other = (Livro) obj;). Ele também confere cada atributo da classe Livro, com ex- ceção do id, algo que foi feito de forma proposital, pois, se ele fosse verificado também, conforme a circunstância, haveria dois objetos Livros com o mesmo autor, mesmo título e mesmo ano de lançamento, mas com o id diferente, fazendo que o método equals retorne false. 30 Lembre-se que o id é utilizado para aplicações em banco de dados, logo, ao averiguar se um objeto já existe no banco, não se considera o id, pois assim é evitada a duplicidade de dados, já que os demais atributos seriam iguais. Não obstante, isso depende da regra de negócio. Executando o java Após a instalação do JDK e do Netbeans, abra o Netbeans. Clique em Arquivo e, logo em seguida, clique em Novo Projeto, e aparecerá a tela representada na Figura 1: Figura 1 - Ambiente Netbeans: tela inicial de Novo Projeto Fonte: editorial Digital Pages (2019). Logo depois, na tela do Novo Projeto, clique em Java, Aplica- ção Java e Próximo, e aparecerá a tela conforme a Figura 2: 31 Figura 2 - Ambiente Netbeans: criando Nova Aplicação do Projeto Fonte: editorial Digital Pages (2019). Nessa etapa, escreva o Nome do Projeto (todo exemplo e programa que serão desenvolvidos na disciplina será um Projeto Novo). A localização física padrão do projeto é na pasta Documentos\ NetBeansProjects do Usuário local. Deixe selecionadoCriar Classe Principal, assim: Figura 3 - Ambiente Netbeans: tela inicial de Desenvolvimento Fonte: editorial Digital Pages (2019). 32 EXEMPLO Na área de desenvolvimento, temos o botão Play, que é o bo- tão que inicia o comando de execução do projeto e o faz na parte de baixo da área de desenvolvimento. Fique tranquilo(a), pois, na dis- ciplina, será fornecido o código para execução das estruturas. Atente-se ao fato de que todo nome do programa deve- rá (neste primeiro momento) ser o nome do projeto. Logo, veja o exemplo a seguir. public class prjOla { public static void main(String []args) { System.out.println(“Olá”); } } O nome do projeto, ao criar um novo, é prjOla. O Java é case sensitive, isto é, ele diferencia letras maiúsculas e minúsculas. En- tão, prjOLA é diferente de PRJOLA e diferente de prjola. Por isso, no- mes de programas, projetos e comandos devem seguir exatamente como estão escritos no material. No comando anterior, para poder executar, cole o código e di- gite play. Lembre-se que os comandos em Java funcionam via blo- cos, que são as aberturas e fechamentos de chaves. O principal método a ser executado no projeto é o public static void main que, ao ser iniciado, irá apresentar na tela a palavra “Olá” através do comando System.out.println(). A seguir, estão exemplos do uso de tipos primitivos em ope- ração matemática e demonstração em tela. 33 EXEMPLO public class prjSoma { public static void main(String []args) { int numA = 5; int numB = 6; int result = numA + numB; System.out.println(“O resultado de A + B :” + result); } } Inserido Classes Caro(a) aluno(a), para inserir mais uma classe no projeto, clique com o botão direito do mouse em cima do pacote, como na Figura 4. Figura 4 - Ambiente Netbeans: criando uma nova Classe no Projeto (passo 1) Fonte: editorial Digital Pages (2019). 34 Logo em seguida, aparecerá a janela da Figura 5. Neste mo- mento, somente escreva o nome da classe e clique em Finalizar. Figura 5 - Ambiente Netbeans: criando uma nova Classe no Projeto (passo 2) Fonte: editorial Digital Pages (2019). Operadores lógicos e negação em java Os operadores lógicos seguem a mesma ideia que os lógicos relacio- nais e auxiliam na execução de processos diante de circunstâncias para tomada de decisões, em que duas ou mais condições são consi- deradas e retornam um valor booleano true ou false. Há apenas dois operadores lógicos: • && (dois “e comercial”): a pronúncia correta é and e a tradu- ção para o português é “e”. É utilizado para comparar duas ou mais condições e todas precisam ser verdadeiras. • || (duas vezes o símbolo pipe, localizado no mesmo botão que a contra-barra “\”, próximo à letra Z no teclado): a pronúncia correta é or e a tradução para o português é “ou”. É utiliza- do para comparar duas ou mais condições em que pelo menos uma delas precisa ser verdadeira. 35 EXEMPLO public class OperadorLogico { public static void main(String args[]) { int resultado1 = 15 + 12; int resultado2 = 22 + 33; if (resultado1 == 27 && resultado2 == 55) { System.out.println(“As condições foram atendidas”); } else { System.out.println(“As condições não foram atendidas”); } if (resultado1 > 27 || resultado2 == 55) { System.out.println(“Uma ou outra condição foi atendida”); } else { System.out.println(“Nenhuma das condições foi atendida”); } } } No exemplo acima, a primeira condição (primeiro if) é que o resultado1 precisa ser igual a 27 e o resultado2, igual a 55. Como ambas as comparações são verdadeiras, ao executar o có- digo, para a primeira condição é exibido o texto “As condições foram atendidas”. Já na segunda condição, o resultado1 precisa ser maior que 27 e o resultado2, igual a 55. Assim, a primeira condição não é atendi- da, pois resultado1 é igual a 27 e não maior. Já a segunda condição é atendida, pois o resultado2 é igual a 55. Como o operador lógico é 36 EXEMPLO “ou” e só uma condição precisa ser verdadeira, ao executar a segun- da condição, o texto exibido é “Uma ou outra condição foi atendida”. A negação é utilizada para negar (inverter) um valor boolea- no, seja ele verdadeiro (true) ou falso (false). A negação utiliza o sinal ! (exclamação) e sua denominação é not (não). Dessa maneira, toda comparação retorna um valor booleano, assim, a negação impacta diretamente no valor booleano, em que o verdadeiro vira falso e o falso vira verdadeiro. public class ComparaPalavra { public static void main(String args[]) { int resultado1 = 15 + 12; int resultado2 = 22 + 33; if (!(resultado1 == 28) && resultado2 == 55) { System.out.println(“As condições foram atendidas”); } else { System.out.println(“As condições não foram atendidas”); } if (!(resultado1 > 27 || resultado2 == 55)) { System.out.println(“Uma ou outra condição foi atendida”); } else { System.out.println(“Nenhuma das condições foi atendida”); } } } 37 Como você já deve ter percebido, um programa de compu- tador segue a mesma regra da matemática, processando primeiro o conteúdo interno dentro dos parênteses e, depois, o que está fora dos parênteses. Pensando nisso, o raciocínio para o operador de ne- gação parece um pouco complicado em um primeiro momento e, por isso, ele foi separado em tópicos para melhor entendimento. Observando o código anterior, na primeira condição, se tem: !(resultado1 == 28) && resultado2 == 55 • Dentro dos parênteses resultado1 == 28. O resultado dá o valor booleano false, já que o resultado1 é igual a 27, e não 28. • O sinal de negação está negando o resultado dentro dos parênteses. Desse modo, o resultado vira true já que é false (inverso de false); • Depois, se tem a comparação “resultado2 == 55”, o que resul- ta em true. • Então, aparecem dois valores booleanos –true: o “!(resultado 1 == 28)” é verdadeiro e o “resultado2 == 55” também. Como o compa- rador lógico neste caso é “&&” (e), todas as condições precisam ser verdadeiras, o que realmente acontece. Assim sendo, para a primeira condição, é exibido no terminal “As condições foram atendidas”. Já para a segunda condição, se tem o seguinte cenário: !(resultado1 > 27 || resultado2 == 55) • Dentro dos parênteses, há duas comparações de operadores lógicos de comparação e o operador lógico “||” (ou). • O resultado1 é igual a 27, e não maior, logo, o resultado é false. • O resultado2 é igual a 55, logo, o resultado é true. • A condição interna dentro dos parênteses volta true, porque apenas uma das comparações precisa retornar true. • O sinal de negação está negando esse resultado. Como ele é true, o resultado final é false. • Portanto, o resultado exibido no terminal é “Nenhuma das condições foi atendida”. 38 Estrutura de decisão em java Um programa de computador trabalha constantemente em cima de decisões que são executadas a partir dos valores booleanos true e false. Além disso, os comandos if-else permitem que um comando ou bloco seja executado diante de uma comparação lógica. Assim, a instrução if-else possui uma estrutura básica que pode ser observada na Figura 6. Figura 6 - Exemplo de condição IF-ELSE * Fonte: editorial Digital Pages (2020). Como você deve ter observado, na figura 6 se tem a declaração do tipo primitivo boolean, com referência na memória chamada de “verdadeiro”, ou seja, o valor booleano possui valor true. Além disso, na estrutura if-else, a primeira condição, destacada em vermelho e na qual está o if, só é executada caso a condição dentro dos parênteses for verdadeira (true). Na hipótese da primeira condição ser false, é execu- tada a condição dentro de else, destacada em cinza. Assim, em uma es- trutura if-else, se não houver uma condição a ser executada dentro da condição else, ela não é obrigatória e nem é declarada,contendo apenas o if. Em alguns casos, há N condições em uma estrutura if-else, que é, então, denominada de if-else aninhada, como na figura 7. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 39 Figura 7 - Exemplo de decisão IF, ELSE E IF-ELSE * Fonte: editorial Digital Pages (2020). Como você pôde perceber, na figura 7, há uma instrução if-else aninhada, composta por vários blocos de instruções if-else. Se a primeira condição, destacada em vermelho, for true, as demais não são executadas. Mas, caso seja false, é apurada a segunda condi- ção, destacada em verde e, se ela for true, as demais abaixo não são examinadas. E, caso seja false, é verificada a terceira condição, des- tacada em bordô. Isso acontece até a condição em laranja e a última condição só é executada se a condição em laranja não for true. Para comparação de casos mais simples, é possível fazer uso do operador ternário. A sintaxe do operador ternário é: “condiçãoBooleana? se- Verdade : seFalso;”. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 40 Figura 8 - Exemplo de SWITCH-CASE * Fonte: editorial Digital Pages (2020). Na figura 8, em azul, está o condicionador ternário e, em ver- de, a expressão que resulta em um valor booleano true ou false. Se a expressão booleana for true, é executado o trecho entre ? (interro- gação) e : (dois-pontos), destacado em vermelho. Mas, caso a expressão booleana seja false, é executado o tre- cho após os: (dois-pontos), destacado em laranja. O operador ter- nário é similar a uma expressão if-else simples. Similar à um if-else aninhado, há o switch-case, utilizado para uma expressão que possui diversas possibilidades, como na figura 9. Figura 9 - Exemplo de SWITCH-CASE * Fonte: editorial Digital Pages (2020). * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 41 Diferentemente do if-else aninhado, o switch-case não para de ser executado até que a última condição não seja validada, exceto se usada a palavra-chave “break”. Na figura 9, há um switch-case e cada bloco possui a palavra-chave “break” para que, quando a condição for correspondida, o bloco de código switch-case seja interrompido. Destacada em amarelo, a palavra-chave “default” é executa- da quando nenhuma das condições é correspondida. Não obstante, há outras observações importantes quanto ao switch-case: • a palavra-chave “default” não é obrigatória e não precisa ter um break, afinal, não há mais condições a serem comparadas e, por con- sequência, não há necessidade de interromper o bloco switch-case. • caso o bloco do switch-case não possua default, o último case não têm necessidade de ter break. • caso duas ou mais condições executem o mesmo processo, elas são alinhadas abaixo uma da outra sem expressão. Veja na Figura 10 que, na parte destacada em cinza, caso a variável número1 seja 8, 9 ou 10, é exibido no console o texto “O núme- ro é 8, 9 ou 10”; e se não for nenhuma destas opções, é exibido no console “O número não é 8, 9 ou 10”; Figura 10 - Exemplo de SWITCH-CASE ANINHADO * Fonte: editorial Digital Pages (2020). * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 42 • O switch-case também é designado para comparar valo- res numéricos de, no máximo, tipo int, que variam entre -2147483648 e 2147483647. Somente a partir do Java 7, é possível trabalhar com Strings, portanto, tipos boolean, long, float e double não funcionam. Estrutura de repetição em java Além da estrutura de decisão, o sistema conta com estruturas de repetição que só param por uma condição programada, como se nota na Figura 11. Figura 11 - Exemplo de Estrutura de repetição * Fonte: editorial Digital Pages (2020). * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 43 Como você deve ter percebido, na Figura 11 há quatro tipos de estruturas de repetição: • o for, destacado em cinza, realiza uma tarefa enquanto a condição for verdadeira. Na figura 10, a variável i é decla- rada no próprio escopo da condição, mas poderia ser de- clarada fora. A estrutura for é lida da seguinte maneira: “enquanto a condição for verdadeira, execute a tarefa”. Cada trecho é separado por ponto e vírgula. Assim, no pri- meiro ponto e vírgula consta a declaração da variável e a atribuição de um valor a ela. No segundo, há a condição e, no terceiro, a incrementação. • o do-while, destacado em bordô, executa uma tarefa enquanto a condição for verdadeira. A estrutura é lida da seguinte ma- neira: “execute a tarefa, enquanto a condição for verdadeira”. A variável foi declarada fora da iteração, pois, se fosse decla- rada dentro, ela seria criada novamente a cada iteração. Den- tro do bloco do, é concretizada a incrementação e, dentro do while, a condição é verificada. • o while, destacado em vermelho, faz uma tarefa enquanto a condição for verdadeira. A estrutura é lida da seguinte ma- neira: “enquanto a condição for verdadeira, execute a ta- refa”. A variável é declarada fora do escopo do while, pois, se fosse declarada dentro, ela seria criada a cada iteração. Assim, a verificação e o incremento se dão dentro do escopo do while. • o último, embora seja similar ao for, é conhecido como forEa- ch. Ele é utilizado para percorrer arrays e, a cada valor contido no array, ele promove uma tarefa. A estrutura forEach é inter- pretada da seguinte maneira: “a cada dado, execute a tarefa”. O forEach não possui contador porque um array possui chave e valor, em que a chave é o índice e o valor é o dado em si. Logo, ele percorre o índice do array. Embora while e do-while sejam similares, o while só executa se a condição for true, enquanto o do-while executa ao menos uma vez, mesmo que a condição seja false. 44 EXEMPLO public class ExemploRepeticao { public static void main(String args[]) { int j = 0; do { System.out.println( “ O valor de J é “ + j ); } while (j != 0); int k = 0; while (k != 0) { System.out.println( “ O valor de k é “ + k ); } } } Observe que, nesse exemplo, o do-while é executado uma vez, enquanto o while não é executado. Terminologia da programação orientada a objetos No mundo de orientação a objetos, assim como em outras áreas, existem terminologias utilizadas nas linguagens orientadas a ob- jetos, tais como: • objetos - são instâncias de classes que possuem estado e comportamento. O estado é representado pelos campos (atri- butos) daquele objeto, que podem ser modificados ao longo do tempo. Já os comportamentos são os métodos, compostos 45 por um conjunto de instruções, com a característica de alterar o estado do objeto. • classes - são estruturas de um objeto (model) que contêm atributos e métodos. Todo objeto é uma instância pertencente à classe e toda classe precisa de um nome único no pacote em que se encontra. Seguindo a convenção de código, uma classe é declarada usando UpperCamelCase. • atributos - são as propriedades (variáveis) que descrevem o objeto. Em uma linguagem fortemente tipada, precisam ser de um tipo, seja ele primitivo ou de outra classe. Em uma lin- guagem fracamente tipada, o tipo não importa, pois a lin- guagem consegue se arranjar com o tipo de dado presente naquele atributo. • variáveis - são nomes atribuídos ao endereço de memória do computador utilizado para armazenamento de tipos de dados do programa executado. Possuem esse nome porque variam duran- te a execução, não sendo um valor fixo. Seguindo a convenção de código, são declaradas usando LowerCamelCase e devem iniciar por uma letra (de a-z),underline (_) ou cifrão ($). É importante que você nunca inicie o nome de uma variável com números. Uma variável global pode ser acessada por qualquer método na classe e é declarada no cabeçalho da classe. Já uma variável local é visível apenas pelo método que está executando. • constantes - possuem as mesmas características de uma va- riável, mas são imutáveis durante a execução do programa. Seguindo a convenção de códigos, uma constante é declarada usando UpperCase. • escopo - trata-se do local em que uma variável é acessada. Há dois tipos de escopo, o global e o local. Uma variável declarada no cabeçalho de uma classe possui o escopo global, ou seja, pode ser acessada por qualquer método de uma classe. Já uma variável local, declarada dentro de um método, tem o escopo local e só é acessada dentro daquele método, não sendo possí- vel utilizá-la em outro método. 46 • métodos e mensagens - os métodos são processos exe- cutados a fim de realizar alguma operação relacionada ao objeto a que ela pertence. Eles se comunicam por meio de mensagens que podem ou não conter dados. Já as mensa- gens são as informações contidas dentro dos parênteses dos métodos, batizados como parâmetros. A declaração de um método, seguindo a convenção de códigos, é escrita em LowerCamelCase. • herança - admite que um objeto herde as mesmas carac- terísticas de outro objeto, facilitando o desenvolvimento e reaproveitando o código já existente. Uma classe Equi- pamento Eletronico, por exemplo, possui característi- cas (como marca, modelo, altura, largura, profundidade e preço), comportamentos (como liga e desliga) e outras duas classes, TV e Rádio, que apresentam as mesmas ca- racterísticas que o equipamento, fazendo com que herdem esses atributos. • ocultação de informação (privacidade) - termo manifesto como Information Hiding, que provê a ocultação de informa- ções desnecessárias, permitindo a visibilidade de itens es- senciais da classe. Assim, a privacidade é controlada pelos três tipos de modificadores para atributos e métodos: pu- blic, protected e private ou, respectivamente, visível a todas as classes de qualquer pacote; visível a todas as classes do mesmo pacote exceto em caso de herança visível por classes que a herdam em pacotes diferentes; e visível apenas pela própria classe. • encapsulamento de dados - muitos autores afirmam que o encapsulamento é utilizado para a proteção de dados, o que de fato acontece. Porém, a ideia é que esses dados sejam acessí- veis diante dos métodos disponíveis naquela classe, denomi- nados de getters e setters. Contudo, há outras formas de acesso a estes dados. • polimorfismo - em POO, possibilita que uma classe herde outra de maneira que seus comportamentos sejam similares, 47 mas distintos. Um exemplo é a classe animal, na qual quase todos os animais têm o comportamento de emitir som, mas cada espécie emite um som particular. • identação - a identação (ou endentação) é o termo atribuído ao código-fonte de um programa, que auxilia na identificação de hierarquia de blocos de instrução, tornando o código mais “limpo” e de fácil entendimento. A identação não é obrigató- ria na programação, mas sua prática é essencial. Um sistema computacional é gerado com objetivo de solucionar problemas cotidianos e a orientação a objetos é destinada a geren- ciar a complexidade na solução dos problemas com um conjunto de objetos. Assim, baseados em objetos reais, os objetos criados são compostos por características e comportamentos, além de serem únicos na memória do sistema. Instanciando um objeto Para criar um objeto na memória do sistema, é preciso declarar o tipo do objeto seguido de um nome. Isso faz com que o programa reserve um espaço na memória do computador e, mediante o nome do objeto, se obtém acesso aos dados armazenados no objeto. No entanto, isso não é suficiente para que se trabalhe com o objeto, já que ele possui somente uma referência na memória. Para que as informações do objeto sejam acessíveis, é ne- cessário que ele seja instanciado e, para isso, emprega-se a pa- lavra-chave “new” seguida do construtor da classe. Lembre-se que toda classe tem seu construtor, que não precisa ser declara- do, mas, conforme a necessidade, é criado da maneira que me- lhor convém. DICA 48 DICA Figura 12 - Exemplo de Instanciação * Fonte: editorial Digital Pages (2020). Devem ser utilizados ao longo da unidade, sejam gráficos, mapas mentais e/ou conceituais, fluxogramas, organogramas, tabelas e quadros, além de imagens que ilustrem graficamente o texto. Para esse fim se utilize de programas como canva.com, Lucidchart.com, pixabay e freepic. Ao inseri-lo, não esqueça de referenciar a fonte e colocar a legenda. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� Construtor padrão Construtor da classe Car Nova instância do objeto Car Construtor customizado com parâmetros Referência do objeto em memória 49 Caro(a) aluno(a), neste material, discutimos o surgimento da pro- gramação orientada a objetos, sua referência com modelos de en- tidades e explicamos que, para programar, é preciso representar objetos por meio de classes que contêm estados e comportamentos de um objeto. Além disso, explicamos que a POO melhora a produtividade dos(as) programadores(as), permitindo a reutilização de códigos e é de fácil manutenibilidade, além de ser escalável. Sobre a linguagem de programação Java, ressaltamos que ela é 100% orientada a objetos e que funciona em diversos dispositivos e sistemas operacionais diferentes, sendo fortemente tipada, pos- suindo tipos primitivos e uma classe diferenciada chamada String para trabalhar com caracteres alfanuméricos, mas que não precisa ser instanciada, além de haver vários métodos internos essenciais para um(a) programador(a) trabalhar com os caracteres. Também foram abordados operadores lógicos, operadores lógicos relacionais, negação, comparadores de classes, estruturas de deci- são e repetição. E, ao final, foram abordados conceitos da orientação a objetos, incluindo objetos, classes, atributos, variáveis, constan- tes, escopos, métodos e mensagens, herança e encapsulamento. Lembre-se de estudar antes e depois da sua aula, aproveite o mo- mento com o(a) docente para tirar todas as suas dúvidas. Até breve! SINTETIZANDO UN ID AD E 2 Objetivos ◼ Apresentar as características de Programação Orientada a Objetos. ◼ Compreender como surgiu o paradigma de Programação Orientada a Objetos. ◼ Introduzir a linguagem Java para Programação Orientada a Objetos. ◼ Entender os conceitos da Programação Orientada a Objetos. ◼ Apresentar as estruturas de dados compostas homogêneas e heterogêneas. ◼ Apresentar os tipos abstratos de dados. 52 Introdução Olá, aluno(a), como vai? Vamos iniciar mais uma etapa de estudos e, a partir de agora, veremos os principais conceitos de atributos e métodos, entenden- do a ocultação das informações através do encapsulamento. Além disso, serão apresentados os conceitos de instâncias, referências e como as classes enviam mensagens. Destaco que, neste material, será explicado qual o ciclo de vida de um objeto. Também apresentaremos o conceito de estrutura compostas homogêneas e heterogêneas. Dito isto, desejo a você boa leitura e bons estudos! 53 Classes, pacotes, objetos, atributos, métodos, construtores, palavra-chave this e sobrecarga Classes A programação orientada a objetos demanda que, ao desenvolver um programa de computador, constantemente, sejam utilizadas classes, com as quais fazemos a representação de uma entidade, seja ela real ou abstrata. A declaração de uma classe é simples, con- forme a sintaxe expressa na figura 1: Figura 1 - Sintaxe básica de uma classe* Fonte: Editorial Digital Pages, 2020. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponívelno seu Ambiente Virtual de Aprendizagem (AVA)� 54 Como você deve ter percebido, na figura 1 temos o modifica- dor de acesso da classe que determina a visibilidade da classe, e que pode ser public, protected, private ou default. Os modificadores de acesso são palavras-chaves reservadas pela linguagem, e devem ser escritos com caracteres minúsculos. Os modificadores protected e private são raramente usados, e só são possíveis através de classes aninhadas. No contexto da programação em Java, o modificador de acesso de- fault não deve ser declarado, sendo oculto. Além dos modificadores de acesso, temos o nome da clas- se, que deverá ser único no pacote e seguir a convenção de códigos. Além disso, ela deve ser declarada em CammelCase, seguindo padrão UpperCamelCase, seu nome deverá ser um substantivo e ter relação com o que ela realmente propõe, sendo simples e objetivo. Fique atento(a), pois as declarações de atributos e métodos devem ser realizadas dentro do escopo da classe. Importante res- saltar que: • toda classe deve possuir a palavra-chave class declarada entre o modificador de acesso e o nome da classe; • toda classe deve estar contida em um pacote. Isso porque, mesmo que ele não seja definido, por padrão, a classe estará em um pacote default. DEFINIÇÃO VOCÊ SABIA? 55 Pacotes Caro(a) aluno(a), ao programar fazendo uso da orientação a objetos, utilizamos pacotes (packages). Pensando nisso, mesmo que eles não sejam definidos, as classes devem estar dentro de um pacote default do próprio Java. Lembre-se: pacotes são utilizados a fim de estruturarmos melhor o sistema desenvolvido, facilitando a localização de tipos, evitando conflitos de nomes e controlando o acesso. Ao trabalharmos com pacotes diferentes do default, temos que declarar, na estrutura da classe, a palavra-chave packages, se- guida do nome do pacote. Carvalho e Teixeira (2012, p. 36) definem pacotes como sendo um envoltório de classes, um guarda classes e outros pacotes. Dessa maneira, podemos visualizar os pacotes como diretórios, ou pastas, nos quais podemos guardar arquivos (classes) e outros diretórios (pacotes). Veja o exemplo da declaração de uma classe com pacote: package modelos; public class Pessoa { private String nome; } Se declarado, o pacote deve estar na primeira linha de código. DICA DICA 56 Mendes (2009, p. 79) diz que os nomes de pacotes, por reco- mendação, mas não obrigatoriamente, devem ser declarados utili- zando o nome do domínio reverso da empresa. Imagine a seguinte situação: uma empresa com domínio www.mi- nhaempresa.com.br está desenvolvendo o projeto farmacêutica, portanto, durante o desenvolvimento do projeto, ao criar o pacote interfaces, deve ser utilizado o nome br.com.minhaempresa.far- maceutica.interfaces. É importante destacarmos que o uso de pacotes é útil para re- solver problemas com a importação de classes que possuem nomes iguais. E, por convenção, os pacotes devem possuir nomenclaturas que usam letras minúsculas e sem caracteres especiais. Veja um exemplo de estrutura de pacotes de um sistema na figura 2: Figura 2 - Exemplo de estrutura de pacotes Fonte: Editorial Digital Pages, 2020. EXEMPLO 57 Como você deve ter percebido, na figura 2 temos vários pa- cotes do projeto fictício denominado gtc. e, em cada pacote, há um tipo de classe que corresponde ao que ela faz. Além disso, todos os pacotes após o .gtc. estão dentro do primeiro pacote gtc. Objetos Inicialmente, é necessário que você saiba que os objetos são todas as entidades que podem ser modeladas (podendo ser concretas ou não) e que possuem estado e comportamento. Além disso, o estado de um objeto é o conjunto das características que ele pos- sui e que podem ser modificadas. Já o comportamento é definido pelas ações que modificam o estado do objeto. Veja um exemplo na figura 3: Figura 3 - Exemplo de classe com estado, comportamento e outro objeto* Fonte: Editorial Digital Pages, 2020. Para que você entenda, na figura 3 temos duas classes que são representações do objeto carro e fabricante. Destacado em cinza, temos o estado da classe, que são os seus atributos e, em vermelho, temos o comportamento que são os métodos que alteram o estado da classe. Observe que o objeto carro, possui como atributo o objeto fabricante, que também possui estado e comportamento. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 58 Atributos Os atributos são as características que um objeto possui. Dessa manei- ra, podemos entender que um atributo é a representação de um dado ou informação de estado do objeto e cada objeto de uma classe pos- sui seu valor próprio. Existem dois tipos de atributos na programação orientada a objetos: os atributos de objetos e os atributos de classes. Os atributos de objetos são os que descrevem os valores daque- le objeto, em que cada instância possui a sua particularidade. Já os atributos de classe são os valores compartilhados entre todas as instâncias do objeto. Em Java, por se tratar de uma linguagem fortemente tipada, ao declarar um atributo, precisamos dizer de qual tipo é aquele atri- buto. Veja a sintaxe básica para declaração de atributos na figura 4: Figura 4 - Exemplo de declaração de atributos em classe* Fonte: Editorial Digital Pages, 2020. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� Indica que o atributo é da classe Indica que o atributo é da classe Indica que o atributo é da classe Indica que o atributo é da classe Tipo de dado: primitivo ou outro projeto Nome do atributo Indica que o atributo é da classe DEFINIÇÃO 59 Observando a figura 4, podemos fazer algumas observações: • os atributos possuem modificadores de acesso, que podem ser private, protected, public ou default, e que determinam a visibilidade do atributo. Lembre-se que os modificadores são palavras-chave da linguagem e são declarados em le- tras minúsculas. • a palavra-chave static é reservada da linguagem e deve ser declarada em letras minúsculas. É utilizada para informar que aquele atributo é de classe, portanto, todas as instân- cias da classe possuem o mesmo valor e, se em algum mo- mento ele for alterado, todas as instâncias passarão a ter o novo valor. • o tipo do dado é uma característica das linguagens forte- mente tipadas como o Java, em que é necessário declarar qual é o tipo do atributo, que pode ser primitivo ou um outro objeto (classe). • por último, temos o nome do atributo que deve ser único na classe. Seguindo a convenção de códigos, ele deve ser declara- do utilizando LowerCamelCase. Uma observação importante a se fazer sobre os atributos de classe é que, para acessá-los, não é necessário instanciar o obje- to, porém a classe precisa estar visível a outra classe e o atributo precisa ter o modificador public, protected ou default. Caso contrário, eles devem ser acessados por métodos também declarados como estáticos, por exemplo, os métodos getters e setters. Para acessar o atributo estático da classe, precisamos fazer referência ao objeto, seguido do atributo. Carro.quantidadeVendida; ou Carro.getQuantidadeVendida();. EXEMPLO 60 Métodos Os métodos são estruturas contidas dentro de classes que são utili- zadas para realizar operações. Eles, geralmente, alteram o estado de um objeto, porém podem ser utilizados para realizar qualquer fun- ção (não demandando, necessariamente, a alteração de um objeto, e podendo apenas retornar um dado qualquer). Dependendo do que o método faz, se ele é utilizado por vá- rias classes e não realiza operações específicas com atributos de uma classe (como calcular dois valores), ele pode se conter apenas dentro de classes que não possuem atributos, sendo essas classes denominadas bibliotecas de operações. Veja como é a sintaxe da de- claraçãode métodos, na figura 5: Figura 5 - Sintaxe básica de método* Fonte: Editorial Digital Pages, 2020. Dentro da classe mostrada na figura 5, temos: • em azul escuro, os modificadores de acesso aos métodos, que podem ser public, protected, private ou default. • em rosa, o modificador static que possibilita utilizar o método da classe sem a necessidade de instanciá-la. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 61 • em vermelho, duas informações. A primeira está na parte de cima, onde se indica a palavra int. Isso quer dizer que o método deve- rá retornar algum dado que represente o tipo int (qualquer valor dentro da faixa de -2147483648 a 2147483647). Depois, temos a palavra-chave return, que retorna um resultado entre os números inteiros. Um método pode retornar qualquer tipo de dado, seja ele primitivo ou não, além de poder não retornar nada, sendo que o tipo de retorno deve ser void e não possuir a palavra-chave return. • em verde, os nomes dos métodos. Diferente dos atributos, podemos possuir vários métodos com o mesmo nome, porém eles precisam possuir uma assinatura diferente. Lembre-se que a assinatura de um método é composta por nomes e parâ- metros. Assim, a possibilidade de criar dois ou mais métodos com mesmo nome e assinaturas diferentes é chamada de so- brecarga de métodos. Seguindo a convenção de códigos Java, os nomes de métodos devem ser declarados usando LowerCa- melCase e representar um verbo. • em azul, os parâmetros. Eles precisam estar dentro dos parên- teses do método. A quantidade de argumentos passados por pa- râmetros podem ser nenhuma ou várias, e de diferentes tipos de dados ou objetos. Os parâmetros, se declarados, devem possuir tipo e nome, assim como fazemos para declarar atributos na classe. Esses parâmetros devem ser informações que são utiliza- das dentro do método, e não estão acessíveis fora do seu escopo. • em laranja, o escopo do método que está entre as chaves. Nele são realizadas as operações, declaradas variáveis e, se neces- sário, enviado o retorno de que o método necessita. Lembre- -se que toda variável declarada ou objeto instanciado dentro do escopo do método é acessível somente dentro do método e não pode ser reutilizado em outro método. A invocação de um método é feita, geralmente, de forma ex- plícita a partir de outros trechos de códigos. Esses trechos podem estar dentro de outros métodos (um método chamando o outro) que está dentro da própria classe ou a partir de outras classes. Veja o exemplo da figura 6: 62 Figura 6 - Sintaxe para invocação de métodos* Fonte: Editorial Digital Pages, 2020. Como você pode observar na figura 6, temos o método main da classe Start declarado em cinza. Ele é um método padrão da linguagem Java, que é utilizado para dar o pontapé inicial na aplicação. Esse método sempre será public static void e deve possuir como parâmetro um array de argumentos do tipo String, que pode ser ou não passado durante a inicia- lização da aplicação. Para esse caso, os parâmetros não são obrigatórios e nome args pode ser qualquer outro, porém deve ser um array, ou seja, possuir os colchetes declarados após o nome do parâmetro. O método main não possui retorno, já que o retorno esperado é void. Assim, com base na figura 5, a invocação do método realizada na figura 6 é feita de duas maneiras diferentes, conforme explica- remos a seguir. • A primeira maneira está destacada em amarelo, e é feita sem a ne- cessidade de criar uma instância do objeto ExemploMetodo. Isso só é possível graças ao método ter sido declarado com modificador static, o que não funcionaria para o método subtrair. Para fins didá- ticos e uma melhor visualização da declaração de um método, foram separados cada trecho, porém o correto é declarar da seguinte ma- neira: NomeClasse.nomeMetodo(tipoAtributo nomeAtributo);. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 63 DICA • A segunda maneira está destacada em laranja e só é possível após a instanciação da classe ExemploMetodo, destacada em roxo. Isso permite que os métodos estáticos e não estáticos do objeto exemplo fiquem acessíveis. Assim como foi feito para a primeira maneira, a declaração foi separada em trechos para melhor visualização e o correto é instanciaObjeto.nomeMe- todo(tipoAtributo nomeATributo);. Os parâmetros passados ao invocar cada método são núme- ros inteiros, assim como é esperado pelos métodos na classe Exem- ploMetodo. Eles devem ser passados na mesma ordem em que a assinatura do método se encontra. Ou seja, se um método aguarda argumentos do tipo int e boolean em sua respectiva ordem, não se pode passar os argumentos boolean e int (de forma invertida), ou deixar de passar um dos parâmetros. Construtores Construtores são métodos especiais chamados através do uso da palavra-chave new. São usados quando desejamos criar uma ins- tância de classe. Um construtor não precisa ser declarado, mas, dependendo do con- texto, pode ser essencial que ele seja, pois é por meio dele que pode- mos, por exemplo, inicializar uma variável ou executar um método antes de fazer uso do objeto. Por meio da criação de construtores podemos garantir que o código que eles contêm será executado antes de qualquer outro código em outros métodos, já que uma instância de uma classe só pode ser usada depois de ter sido criada com new, o que causará a execução do construtor (SANTOS, 2013). 64 Quando os atributos (primitivos ou de classe) são inicializa- dos, mas não receberam uma programação durante sua estruturação, iniciam-se automaticamente com valores default listados a seguir. • Tipo boolean: inicializados com valor false, por padrão. • Tipo byte, char, int, long e short: inicializados com valor 0 (zero). • Tipo float e double: inicializados com valor 0.0 (zero ponto zero). • Instâncias de classes: inicializadas com valor null. Para que você entenda o que estamos tratando, sugiro que veja o exemplo de uma classe sem um construtor definido: public class BrinquedoAPilha { private int quantidadePilha; } Uma referência com valor null não estará acessível a menos que ela seja inicializada através do uso da palavra-chave new. No exemplo do código anterior, ao instanciar o objeto, o construtor default será executado, porém nada de mais acontecerá. Mas, supondo que a quantidade mínima de pilhas sejam duas, po- demos declarar o construtor para atribuir o valor dois sempre que o objeto for instanciado. Veja o exemplo do que estamos falando: public class BrinquedoAPilha { public Brinquedo () { this.quantidadePilha = 2; } private int quantidadePilha; } EXEMPLO 65 No exemplo do código anterior, foi declarado o construtor padrão que atribui o valor dois ao atributo quantidadePilha sempre que o objeto seja instanciado. Isso porque, se isso não fosse feito, o valor sempre se iniciaria como sendo zero. O construtor, embora seja um método, possui suas particula- ridades o que o diferencia dos demais métodos. São elas: • o construtor não precisa ser declarado. Se não declarado, o compilador usa o construtor default da classe. Assim, o cons- trutor default é assinado pelo nome da classe e sem argumen- tos como parâmetros; • o construtor sempre possui o mesmo nome da classe à qual ele pertence; • o construtor não tem nenhum tipo de retorno e não deve ser declarado como void; • o construtor pode receber todos os modificadores de aces- so, porém não é muito comum encontrar construtores com modificadores de acesso private. Isso ocorre porque acaba não sendo usual uma classe conter um construtor com essa característica, a menos que ele seja chamado a partir de ou- tros construtores com modificador de acesso public, protec- ted ou default; • o construtor só pode ser chamado a partir da declaraçãoda palavra-chave new, que é utilizada somente quando dese- jamos instanciar um novo objeto. Porém, dentro da própria classe, se não desejamos criar uma instância nova do mesmo objeto, mas sim trabalhar com o construtor, devemos fazer o uso da palavra-chave this. Uma classe pode possuir vários construtores com assina- turas diferentes, assim como os métodos comuns. Porém, ao de- clarar um construtor com assinaturas diferentes do default, e para garantir que o código funcionará corretamente, é importante a 66 EXEMPLO criação explícita do construtor default, pois alguns frameworks podem fazer uso deles. public class BrinquedoAPilha { public Brinquedo () { } //Construtor default public Brinquedo (int quantidadePilha) { //Construtor com assinatura diferente this.quantidadePilha = quantidadePilha; } private int quantidadePilha; } O modificador default é similar ao protected, porém a diferença está no contexto de herança. Dessa maneira, o protected permite que classes em diferentes pacotes herdem atributos protected, já o de- fault só permite herança por classes que estejam no mesmo pacote. Perceba que, no exemplo do código anterior, o construtor default foi desenvolvido simplesmente por ter sido criado um outro construtor com assinatura diferente. SAIBA MAIS 67 Palavra-chave This A palavra-chave this (traduzida do inglês como “este”) só pode ser utilizada para referenciar a própria instância do objeto criado, seus atributos e métodos. Além disso, não funciona com atributos e mé- todos com modificador static, já que eles pertencem à classe e não ao objeto. Portanto, quando estamos nos referindo a um atributo ou método correspondente ao objeto, devemos fazer uso do this. Veja o exemplo na figura 7: Figura 7 - Exemplo do uso da palavra-chave this Fonte: Editorial Digital Pages, 2020. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 68 Observe que, na figura 7, temos o Método Main que inicia- liza a aplicação e, ao executá-lo, estamos instanciando um objeto da mesma classe. Ao criar o objeto na linha sete (destacada em cin- za), estamos chamando o construtor declarado na linha 11 (também destacado em cinza) que, posteriormente, chama o construtor da linha 17 (destacado em vermelho) por meio da palavra-chave this, apresentada na linha 13. Perceba que o construtor, que possui o parâmetro int, possui a variável declarada com nome de contador, nome se- melhante ao atributo do objeto. Para distingui-los, usamos a palavra-chave this, em que this.contador pertence ao atributo do objeto, e o que não faz uso da palavra-chave this pertence à variável do método. Importante salientar que o fato de chamar o construtor den- tro da própria classe, por meio da palavra-chave this, não significa que estamos instanciando um novo objeto e nem é possível fazer uso da sintaxe new this();. Sobrecarga A sobrecarga é a denominação da possibilidade de se ter dois ou mais métodos com o mesmo nome. Isso só é possível graças aos métodos possuírem assinaturas que são consideradas pelo nome e argumen- tos passados em seus parâmetros. Alguns escritores defendem que o tipo de retorno faz parte da assinatura, mas, segundo Santos (2013), o tipo de retorno do mé- todo não é considerado parte da assinatura. Essa afirmação é com- provada quando se tenta criar métodos com retornos diferentes, porém com nome e tipos de parâmetros iguais, na mesma ordem. Ela pode ser útil e evita que o método seja escrito com numeradores, por exemplo: metodo1, metodo2 etc. 69 EXEMPLO Um exemplo detalhado pode ser visto na figura 8: Figura 8 - Sobrecarga de métodos Fonte: Editorial Digital Pages, 2020. Conforme pode ser visto na figura 8, possuímos dois métodos com o mesmo nome, porém com assinaturas diferentes. Observe que, na chamada dos métodos, os parâmetros correspondem à quantidade e tipos de atributos contidos dentro dos parâmetros em respectivas ordens. O primeiro método pagarPrestacao tem somente dois pa- râmetros, dos tipos string e double. Já o segundo possui três parâ- metros, um do tipo int, outro do tipo string e outro do tipo double. É importante destacarmos que é indispensável respeitar a ordem dos parâmetros já é suficiente para que o compilador entenda a assina- tura do método e saiba qual deles deverá ser executado. Isso porque, se durante a chamada de um dos métodos essa ordem de atributos fosse invertida, passando tipos de dados diferentes do esperado, o código não funcionaria. 70 Instanciação e referências de objetos A possibilidade de criar um programa de computador com base em objetos, concretos ou não, facilita o cotidiano do(a) programador(a). Isso porque é mais fácil criar programas com estruturas que façam algo com um objeto, do que programas que sigam uma estrutura (programação estrutural). Cada objeto é uma instância de classes e, para serem aces- síveis, devem possuir referências na memória do sistema e, além disso, as referências de um objeto são, basicamente, variáveis de sistema que possuem o tipo da classe. Veja o exemplo da figura 9, sobre referência e instanciação: Figura 9 - Exemplo de referência e instanciação* Fonte: Editorial Digital Pages, 2020. Para instanciar um objeto, precisamos fazer a declaração utilizan- do a palavra-chave new, seguido do construtor da classe. Isso é suficien- te para que criemos uma instância, porém, para que possamos trabalhar com essa instância, é necessário atribuí-la a uma referência do tipo da classe, na memória do sistema. Então poderemos fazer uso do objeto. No exemplo da figura 9, temos duas classes. Na classe Exem- ploReferencia, instanciamos o objeto da classe Carro na referência carro, para que só então tenhamos acesso a seus atributos e métodos. * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 71 Pensando nisso, destacado em cinza na linha quatro, fizemos referência da classe Carro à memória do sistema com o nome carro. Ou seja, nesse ponto estamos dizendo ao compilador que reserve na memória um espaço para um objeto do tipo Carro e que essa refe- rência terá nome de carro. O nome da referência pode ser qualquer um, porém cabe ao(à) programador(a) seguir a convenção de códigos, que diz que de- vemos declarar um nome que realmente corresponda ao que a referência possui em si, a fim de deixar o código mais legível aos humanos. No caso da figura 9, a referência carro está nula, já que ainda não temos um objeto criado para ela. Depois, na linha sete estamos atribuindo, nessa referência (em cinza), um novo objeto da classe Carro (em laranja) e, por isso, fazemos uso da palavra-chave new seguido do construtor default da classe Carro. Somente após termos atribuído o objeto na referência criada em memória é que podemos inserir informações, como é feito nas linhas 10 e 11 por meio dos métodos set, ou obter informações, como é feito na linha 17 por meio dos métodos get. Essa forma de inserir informações, ou recuperar informações, por meio dos métodos get e set, é chamada de encapsulamento de dados, pois seus atributos estão declarados com modificador de acesso private. DICA DEFINIÇÃO 72 Envio de mensagens A programação orientada a objetos promove a comunicação de um objeto com outro. Esta comunicação provém da troca de mensagens que poderá ter ou não um retorno. Segundo Batista e Moraes (2013, p. 59), as mensagens são formadas por três elementos: • emissor - objeto que envia a mensagem; • receptor - objeto que recebe a mensagem; • especificação de serviço - o método deverá ser executado e, se contiver parâmetros, eles devem ser enviados juntos. Veja um exemplo da troca de mensagens na figura 10: Figura 10 - Exemplo de troca de mensagens entre objetos Fonte: Editorial Digital Pages, 2020. No exemplo da figura 10, o emissorPessoa especifica qual serviço deseja que o receptor Lâmpada execute. Portanto, nesse exemplo, a mensagem enviada por Pessoa seria: Lampada.acen- der(); ou Lampada.apagar();. 73 Importante observar que a mensagem enviada deverá sempre ser algo que o outro objeto sabe executar e, se possuir parâmetros, eles devem ser enviados junto com a mensagem. Ciclo de vida de um objeto Todo objeto criado em Java possui um ciclo de vida, que está subdi- vidido em três fases distintas. • Criação de objetos: o fato de referenciar o objeto não significa que estamos criando o objeto. Isso porque todo objeto é criado a partir do momento em que se utiliza a palavra-chave new seguida do construtor da classe. • Acessibilidade do objeto: o objeto só estará acessível a partir do momento em que ele é atribuído a uma referência de uma variável. Ele pode perder a referência e ficar inacessível nos seguintes casos: • caso o objeto seja substituído por outro objeto: o novo obje- to ganha a sua referência e o anterior a perde; • caso a referência a qual o objeto pertence seja atribuída à palavra-chave null. • Descarte de objeto: Java conta com o Garbage Collector (co- letor de lixo), que remove da memória do sistema todo obje- to inacessível. Ele é executado em segundo plano juntamente com a aplicação Java, portanto, não há como prever quando o Garbage Collector entrará em cena e nem mesmo saber quais objetos foram coletados, sendo possível apenas saber quan- tos objetos podem ser coletados. Veja um exemplo do ciclo de vida do objeto na figura 11: DICA 74 Figura 11 - Exemplo de ciclo de vida de objetos* Fonte: Editorial Digital Pages, 2020. Conforme você deve ter observado, no exemplo da figura 11, temos as seguintes informações: • em cinza, temos a criação da referência do objeto na memória; • em laranja, a criação de um novo objeto; • em marrom, a atribuição de um objeto em uma referência já criada. Por outro lado, destrinchando o código da figura 11, temos as seguintes anotações: • na linha quatro, estamos criando a referência em memória denominada carro para algum objeto do tipo Carro, mas, até esse momento, não possuímos nenhum objeto; * Caro(a) aluno(a), para uma melhor visualização da figura, sugerimos que acesse o material disponível no seu Ambiente Virtual de Aprendizagem (AVA)� 75 • já na linha sete, estamos criando um objeto do tipo Carro, mas não o estamos atribuindo a nenhuma referência, portanto ele está inacessível, podendo ser removido pelo Garbage Collector; • na linha dez, estamos criando um novo objeto do tipo Carro e o atribuindo à referência criada na linha quatro, portanto, a partir deste momento a referência carro possui um objeto do tipo Carro. Perceba que, até este momento, dois objetos foram criados, um acessível e outro inacessível; • na linha 13, estamos criando a referência em memória, cha- mada carro2, para o objeto do tipo Carro. Nessa linha, a re- ferência e a criação do objeto são feitas juntas, portanto, até essa linha temos dois objetos acessíveis e um objeto inaces- sível, totalizando três objetos do tipo Carro; • na linha 16, é atribuído um novo objeto à referência carro2, portanto, o objeto que estava vinculado a ela agora já não está mais acessível, estando disponível para o Garbage Collector. Até o momento criamos quatro objetos, dois estão acessí- veis e dois não estão acessíveis; • na linha 19, é atribuída a palavra-chave null à referência carro, tornando o objeto que estava atribuído a ela inacessível e dis- ponível ao Garbage Collector. Dessa maneira, até o momento foram criados quatro objetos, um está acessível pela refe- rência carro2 e três inacessíveis; • da linha 22 a linha 30, estamos fazendo uma iteração que vai de zero a nove (i começa com zero e vai até nove, que é menor que dez), portanto, o código dentro de sua estrutura é realizado dez vezes; • na linha 25, há a criação da referência carro3 e a atribuição de um novo objeto a essa referência, logo são criados dez obje- tos, mas apenas um está acessível (o último); • na linha 28, acontece algo parecido com o caso anterior, po- rém a referência em memória já está criada. Até a sua primeira iteração, a referência está nula e, a partir da segunda iteração, o objeto é substituído e fica disponível apenas o último objeto criado. No total, também são dez objetos criados. 76 Podemos dizer que, nas condições anteriores, até a linha 19, pos- suíamos apenas quatro objetos, em que apenas um estava acessível. Mas, durante a iteração, foram criados 20 objetos do tipo Carro, mas apenas dois deles estão acessíveis, um atribuído à referência carro3 e outro à referência carro. Então, após a conclusão da iteração, fo- ram criados 24 objetos, mas apenas três estavam acessíveis, sendo que 21 deles podem ser coletados, a qualquer momento, pelo Gar- bage Collector. Abstração e encapsulamento Programar orientado a objetos faz com que o(a) programador(a) desenvolva um sistema com base em objetos, abstraindo as infor- mações relevantes para o desenvolvimento. Essas informações, de- pendendo das circunstâncias, necessitam de uma certa proteção, deixando acessível apenas aquilo que realmente interessa ou evi- tando que os atributos sejam acessados de forma direta, seja para alterar ou obter as informações. Assim, vamos discutir as particula- ridades de cada um dos temas. Abstração De modo geral, segundo Moreira Neto (2009, p. 9), a abstração con- siste em compreender um sistema, seja ele concreto ou não, criando um modelo documentado e padronizado que reflita seu comporta- mento, ou para utilizar em uma definição mais específica no desen- volvimento de software. Este conceito se dá ao fato de poder representar um objeto real utilizando uma classe. Ele possui essa denominação devido à pos- sibilidade de abstrair propriedades e funcionalidades em métodos. DICA 77 REFLITA Para entender o que estamos tratando, observe a figura abaixo: Figura 12 - Exemplo para abstração de propriedades e funcionalidades Fonte: Adobe Stock. Acesso em: 08 jun. 2020. Analisando a figura 12, podemos observar as propriedades e funcionalidades, sendo: • propriedades: marca, modelo, ano de fabricação, ano do mo- delo, quantidade de lugares, quantidade de portas, cor etc.; • funcionalidades: acelerar, trocar de marcha, abastecer, trocar pneus, calibrar pneus, substituir estepe, inclinar bancos, ligar e des- ligar a lanterna, o farol, ligar/desligar o limpador de para-brisa etc. Como você deve ter percebido, são inúmeras possibilidades de abs- trações de dados e funcionalidades de um objeto, mas será que de- vemos incluir todas as possibilidades em nosso sistema? Bom, a resposta para esta pergunta é: depende. A abstração acon- tece para definirmos o modelo de um objeto no sistema. Dessa ma- neira, se uma informação ou funcionalidade for irrelevante para o sistema, ela não precisa estar presente, pois não terá nenhuma uti- lidade. Além disso, ter essas funcionalidades inúteis pode consumir memória ou processamento, deixando o sistema mais pesado. Dessa maneira, tudo depende do modelo de negócio aplicado ao sistema. 78 Encapsulamento O termo encapsulamento, para a linguagem de programação orienta- da a objetos, determina a disposição para acesso de métodos ou atri- butos em uma classe pública. Isso possibilita a ocultação de atributos, tornando acessíveis via métodos apenas aqueles que realmente pre- cisam ser acessados. Moreira Neto (2009, p. 73) diz que os atributos de um objeto podem ser escondidos de outros objetos por uma inter- face pública de métodos, de modo a impedir acessos indevidos. O encapsulamento de dados só é possível graças aos modificadores de acesso para membros de classes, conforme pode ser visto no quadro 1: Quadro 1 - Modificadores de acesso para membros de classe Fonte: Adaptado de Moreira Neto, 2009, p. 73. Perceba que, no quadro 1, temos as declarações sobre os mo- dificadores de acesso que determinam a visibilidadede um atributo ou método. Para o cenário private, apenas a classe à qual o atribu- to/método pertence é visível e acessível. Um atributo/método- sem declaração de modificador (<none>) é denominado default e a sua visibilidade é permitida apenas pela própria classe ou classes que estão no mesmo pacote. Atributos/métodos declarados como protected são similares aos que não possuem declaração, porém são acessíveis também por classes que os herdam, mesmo que em diferentes pacotes. Assim, atributo/método com modificadorpublic têm sua visibilidade a par- tir de qualquer classe e independe do pacote em que ele se encontra. private <none> protected public * * * * * * * * * * Modificador Própria classe Classes do mesmo pacote Subclasses (herança) Universo (qualquer classe) 79 Com essa visão sobre a visibilidade dos atributos e métodos, podemos enxergar a importância de encapsular os atributos. Vamos a um exemplo: Maria possui R$ 300,00 em sua conta bancária, logo o atributo saldo é igual a R$ 300,00. Mas, supondo que Maria vá fazer um saque e que, chegando no caixa eletrônico, ela solicite o valor de R$ 400,00. Dessa maneira, sem uma proteção adequada ao atributo saldo, ela poderia realizar o saque e ficar com saldo de R$ -100,00. Então, em um acesso de maneira direta a esse atributo, além da possibilidade de sacar um valor indisponível, também há a possibilidade de que o saldo seja alterado diante de qualquer lugar, por qualquer circuns- tância, seja atribuindo um valor a mais ao que já se tem, ou dimi- nuindo o valor existente. Veja o exemplo em código na figura 13: Figura 13 - Exemplo de classes com atributos sem encapsulamento Fonte: Editorial Digital Pages, 2020. EXEMPLO 80 Observando a figura 13, temos três classes. As classes Pessoa e ContaCorrente estão sem encapsulamento, ou seja, seus atribu- tos estão todos públicos e acessíveis de qualquer lugar. Já na classe ExemploEncapsulamento, temos o método main que é executado para inicializar a aplicação Java e, ao executá-la, criamos, na me- mória, a referência contaCorrente com a instância do objeto do tipo ContaCorrente, que por sua vez instancia o objeto Pessoa em seu atributo pessoa. Para acessar os atributos de pessoa, precisamos apenas utilizar a referência contaCorrente, depois a referência pessoa e depois seu atributo. Com isso, conseguimos acesso direto a esse dado, podendo alterá-lo a qualquer momento. Ainda considerando o exemplo de Maria, vamos ao atri- buto saldo. Veja que foi atribuído o valor de R$ 300,00 e, pos- teriormente, foi feita a retirada de R$ 400,00, deixando o valor do saldo negativo (R$ -100,00). Poderíamos validar se há sal- do suficiente na conta para realizar o saque, mas isso deve- rá ser um serviço da classe ContaCorrente e não da classe ExemploEncapsulamento. DICA 81 Agora, para a segurança da aplicação, vamos a um exem- plo de uma estrutura com os dados encapsulados, apresentado na figura 14: Figura 14 - Exemplo de classes com atributos encapsulados Fonte: Editorial Digital Pages, 2020. Observe que nesse novo caso nas classes Pessoa e ContaCor- rente os atributos possuem encapsulamento, ou seja, seus atributos só estão acessíveis via métodos disponíveis na própria classe. Dessa maneira, ao executar a aplicação, o método main irá instanciar o objeto da classe ContaCorrente na referência de me- mória contaCorrente, igual na figura 13, porém os acessos aos dados só são possíveis por meio dos métodos contidos na própria classe. Lembre-se que, para atribuirmos/acessarmos a variável nome, da classe Pessoa, precisamos utilizar o método getPes- soa da classe ContaCorrente, que irá obter o objeto pessoa e então fazer uso do método setNome da classe Pessoa, passando como parâmetro o texto desejado. Nesse momento, quem é responsá- vel pela atribuição do nome é a classe Pessoa, não mais a classe ExemploEncapsulamento. 82 Observe que temos, na classe ContaCorrente, os métodos realizarSaque, consultarSaldo e depositar. Eles serão responsáveis pelos serviços. Inclusive, o primeiro método deverá validar se há saldo suficiente para saque. Perceba, ainda, que não é mais possível alterar o valor do saldo diretamente, a menos que utilizemos um dos métodos responsáveis pelo serviço. É muito comum declarar métodos dos tipos getters e setters para retornar o valor de um atributo, ou modificá-los (até porque, geralmente as IDEs fornecem essa funcionalidade). Por padrão, para obter informações de atributos, com exceção do tipo boolean, deve- -se prefixar o nome do atributo com a palavra get (obter), seguindo o padrão LowerCamelCase. Assim, quando o tipo for boolean o pre- fixo é is. Veja alguns exemplos: • no atributo name do tipo String, seu método recebe getName(); • no atributo birthday do tipo Date, seu método recebe getBirthday(); • no atributo active do tipo boolean, seu método recebe isActive(); • no atributo alive do tipo boolean, seu método recebe isAlive(). Para alterar atributos, sem exceções, devemos prefixar os atributos com a palavra set (pôr), seguindo o padrão LowerCamel- Case. Veja alguns exemplos: • no atributo name do tipo String, seu método recebe setName(); • no atributo birthday do tipo Date, seu método recebe setBirthday(); • no atributo active do tipo boolean, seu método recebe setActive(); • no atributo alive do tipo boolean, seu método recebe setAlive(). 83 DICA Observe que os nomes dos métodos e os atributos foram escritos em inglês, isso não é uma obrigatoriedade, mas possibilita uma leitura mais fácil do código, justamente pela utilização dos padrões da con- venção de códigos e em virtude das palavras-chaves serem sempre em inglês. Estruturas compostas homogêneas As estruturas compostas homogêneas são aquelas que podem receber diversos valores do mesmo tipo. Os principais repre- sentantes dessa estrutura são os vetores e as matrizes. Vamos conhecê-los. Vetor O vetor é um tipo de estrutura de dados composto por várias instân- cias do mesmo tipo e o identificador somente diferencia seu índice, como endereçamento. Ou seja, nada mais é do que um tipo primitivo com profundidade. Veja um exemplo: String nome[] = new String[3]; nome[0]=“José”; nome[1]=“Paulo”; nome[2]=“João”; EXEMPLO 84 EXEMPLO Observe que iniciamos a estrutura do vetor com os colchetes, após o nome da variável. Após isso, usamos o comando new, que é um comando que instancia o objeto string. Temos o valor 3 entre colchetes que significa 3 “variáveis” do tipo string, com o identifi- cador nome[ ]. Os números entre os colchetes significam sua iden- tificação numérica, que vai até dois. Além disso, temos três variáveis com os ids 0, 1 e 2, e essa identificação numérica, ou id, são as pro- fundidades do vetor. No caso, o id 0 conta como variável, por isso, vai até o id 2. É importante que você saiba o que significa: Instância: é uma convenção da programação orientada a ob- jetos, que cria instâncias ou cópias de objetos ou programas dentro do programa atual. Atribuição: é uma característica das variáveis. Elas recebem valores predeterminados pelo(a) programador(a) ou por intermé- dio de funções que captam valores dos usuários do programa. Abai- xo, seguem alguns exemplos de atribuição em vetor. • Atribuição por valores predeterminados pelo programador: nome[0]=“Rolfi”; • Atribuição por valores que o usuário irá inserir: nome[1]=javax.swing.JOptionPane.showMessageDialog(null, “Digite seu nome”); • Atribuição por variáveis ou por outro vetor: nome[0]=outronome; nome[0]=nome[1]; 85 EXEMPLO Além disso: Operação aritmética: é um conjunto de instruções que pro- movem operações aritméticas em variáveis. Por exemplo: float nota[]=new float[4]; float media; nota[0]=7.5f; nota[1]=10.0f; nota[2]=8.0f; nota[3]=10.0f; media=(nota[0] + nota[1] + nota[2] + nota[3])/4; Navegação: é utilizado para navegar no vetor, pode-se cha- mar pelo id ou fazer um loop navegando por todos os ids. Nos co- mandosapresentados a seguir, o vetor nome está sendo impresso, um a um, na tela. Veja: • Navegando pelo id: System.out.println(nome[0]); • Navegando em todo o vetor: for(int contador=0; contador < nome.length; contador++) { System.out.println(nome[contador]); } 86 O comando abaixo, que é o for-each, é um dos principais co- mandos de navegação de objetos e estruturas de dados: for(int aux: vetor) { System.out.println(aux); } Esse loop navega nas posições do vetor e passa seus valores, um a um, na variável aux. Ordenação: é um conjunto de instruções que visa ordenar uma estrutura de dados, nesse caso, o vetor. Abaixo, segue um dos formatos mais tradicionais de ordenação de vetor. 1 for(int contadorL=0; contadorL< vetor.length ; contadorL++) 2 { 3 for(int contadorC=contadorL+1; contadorC< vetor.length; conta- dorC++) 4 { 5 if(vetor[contadorL]> vetor[contadorC]) 6 { int aux= vetor[contadorL]; vetor[contadorL]=vetor[contadorC]; vetor[contadorC]=aux; 10 } 11 } 12 } DICA 87 Esse algoritmo é o Método Bolha, ou Bubble Sort, um algorit- mo de ordenação tradicional que busca o menor elemento da lista, posicionando ao topo de forma ordenada. Primeiramente, busca-se o menor valor, navegando no vetor através dos índices contadorL e contador. Ao encontrar um valor maior, faz-se a troca, “descendo” o valor maior e “subindo” o valor menor. Por exemplo: valores 9, 0, -1 para os endereços 0, 1 e 2 do vetor. Tabela 1 - Exemplo dos valores iniciais vetor em memória Fonte: Editorial Digital Pages, 2019. Primeiramente, o algoritmo verifica se o vetor na posição 0 é maior que o vetor na posição 1, ou seja, se 9 > 0. Nesse caso, é maior, então, troca- -se o menor pelo maior através das navegações dos índices. Faz-se a pri- meira troca de 9 por 0, usando a variável auxiliar, como nas linhas abaixo: 7 int aux=vetor[contadorL]; 8 vetor[contadorL]=vetor[contadorC]; 9 vetor[contadorC]=aux; O aux recebe o maior valor, que é o valor 9, pois o contadorL nesse primeiro momento é 0, e o vetor na posição 0 recebe o valor de vetor na posição 1, que é o valor de contadorC, recebendo, assim, o valor 0. O vetor na posição 1 recebe o valor de aux. Tabela 2 - Exemplo da primeira troca no vetor Fonte: Editorial Digital Pages, 2019. Continuando o loop, o contadorL na posição 0 verifica se exis- te um outro valor menor dentro do vetor, encontra na posição 2 o valor -1, e faz a troca pelo valor menor. Logo, todos os valores do vetor são percorridos, como na tabela 3. Valor 9 0 -1 Índice vetor[0] vetor[1] vetor[2] Valor 0 9 -1 Índice vetor[0] vetor[1] vetor[2] 88 Tabela 3 - Exemplo da Segunda Troca no Vetor Fonte: Editorial Digital Pages, 2019. Após o primeiro item (o índice 0) possuir o menor valor do vetor, a segunda etapa é passar para verificar se o índice 1 possui o menor valor. Na primeira varredura entre índice 1 com o valor de 9, verifica-se se o próximo índice, o 2, é maior com o valor de 0. Nesse caso, faz-se a troca, obtendo-se o resultado exibido na tabela 4. Tabela 4 - Exemplo da segunda troca no vetor Fonte: Editorial Digital Pages, 2019. Como não existem valores para serem verificados, finaliza-se a ordenação do vetor. Aplicação: um vetor pode ser usado como lista ou estruturas que envolvam listas, como lista de compras, lista de clientes, lista de CEPs etc. Para que você veja, na prática, o que estamos tratando, abaixo temos um exemplo do código do vetor estruturado. No código a seguir, temos um exemplo de vetor em formato estru- turado, ou seja, digitado diretamente da função main. Esse comando cria um vetor na linha 9 com dados predeterminados e na linha 11 ele ordena esses valores de forma crescente. Após a ordenação, tem-se a impressão na linha 20. Valor -1 9 0 Índice vetor[0] vetor[1] vetor[2] Valor -1 0 9 Índice vetor[0] vetor[1] vetor[2] EXEMPLO 89 Figura 15 - Exemplo de vetor em formato estruturado Fonte: Editorial Digital Pages, 2019. Vetor orientado a objetos Os vetores orientados a objetos são vetores que, tradicionalmen- te, podem ser criados em qualquer local da classe. Porém, quando abordamos o paradigma de orientação a objetos dentro do conceito de vetor, temos que criar duas classes, uma será o tads propriamen- te dito, a classe vetorOO, que é chamado como estrutura de dados dentro da classe Prj_VetorOO. Já empregando os conceitos de orientação a objetos dentro da classe VetorOO, observamos o tratamento de dados através do uso de métodos ou funções, diferente do exemplo acima, em que todo o tratamento está dentro da função main. 90 Figura 16 - Exemplo do tratamento de dados através do uso de métodos ou funções Fonte: Editorial Digital Pages, 2019. Como você pode perceber, a classe Prj_VetorOO possui a instan- ciação da classe VetorOO em vet e, dentro da própria vet, temos a função set, valor que insere valores nas posições determinadas da função. Também para inserir valores usamos o inserir que, através de uma sequência estipulada na linha 39, apenas recebe os dados e ve- rifica, de acordo com o número de inserções, se poderá ser inserido. Além disso, o construtor da linha 26 inicia-se recebendo um valor para aprofundar o vetor no valor desejado. As funções getValor e setValor são funções de captura e envio de dados em qualquer estrutura. Assim, o getSize retorna a quan- tidade de itens inseridos, enquanto a getMax retorna a capacidade máxima de itens que podem ser inseridos. O remover retira itens do vetor diminuindo o getSize e o método sort ordena os itens do vetor. Dessa maneira, quando tratamos de orientação a objetos, nesse caso, temos todos os comportamentos dos dados dentro de um vetor, que, no caso, seria a linha 5 private int vetor, que cria o ve- tor como private para que nenhuma classe externa possa acessá-la diretamente, somente a partir dos métodos citados anteriormente. 91 Figura 17 - Exemplo de comportamentos dos dados dentro de um vetor Fonte: Editorial Digital Pages, 2019. 92 Matriz Matriz é uma estrutura homogênea de dados que permite organizar os dados em formato de linhas e colunas, e sua indexação é pelo en- dereço de linhas e colunas, semelhante à estrutura de um vetor, mas com formato bidimensional, podendo assumir mais de duas dimen- sões. Dependendo da linguagem de programação podem assumir até 17 dimensões, por exemplo: String nomes[][]=new String[2][3]; nomes[0][0]=”Rolfi”; nomes[0][1]=”Ana”; nomes[0][2]=”Maria”; nomes[1][0]=”João”; nomes[1][1]=”Pedro”; nomes[1][2]=”Paula”; Em estrutura de memória, ficaria ilustrado da seguinte forma: Tabela 5 - Exemplo em Memória de Matriz Fonte: Editorial Digital Pages, 2019. Observe que as colunas são de, no máximo, três itens. Conta- -se o 0 e temos 0, 1, 2, totalizando três colunas. Nas linhas, temos dois de tamanho. Conta-se o 0 e temos 0 e 1, como no vetor. Dito isto, vamos entender alguns temas relacionados às matrizes. Atribuição: as atribuições em matrizes funcionam da seguin- te forma: nomes[índice da linha][índice da coluna]=valor do tipo da coluna. Linhas Colunas 0 "Rolfi" "João" 1 "Ana" "Pedro" 2 "Maria" "Paula" 93 Por exemplo: nomes[0][0]=”Rolfi”; Operação aritmética com matriz: as operações aritméticas, assim como nas variáveis e vetores, também funcionam da mesma forma na matriz. Deve-se primeiramente fazer referência da posi- ção que se deseja trabalhar e submetê-la às operações matemáticas. Por exemplo: float notas[][]=new float[2][2]; notas[0][0]=10f; notas[0][1]=5.5f; notas[1][0]=6.0f; notas[0][1]=8.0f; float medias =(notas[0][0] + notas[0][1] + notas[1][0] + no- tas[1][1])/notas.length; Navegação: para navegar na matriz, pode-se chamar pelo id da linha e da coluna ou fazer um loop, navegando por todos os ids. Neste comando, a matriz nomes está sendo impressa, um a um, na tela. • Navegando pelo id: System.out.println(nomes[0][0]); • Navegando em toda a matriz, porém, deve ser feito um laço para a linha e coluna: for(int contadorL=0;contadorL < 2; contadorL++) { for(int contadorC=0; contadorC < 3; contadorC++) { System.out.println( nomes[ contadorL ] [ contadorC ] ); } } Ou pelo comando abaixo, que é o for-each (um dos principais comandos de navegação de objetos e estruturas de dados): for(String linha[]: nomes) { for(String coluna: linha) { System.out.println(coluna); } } 94 Esse loop navega nas posições da matriz, passando seus va- lores, um a um, na variável coluna, que recebe, da variável linha, a linha com os valores da matriz nomes. Assim, para cada dimen- são, deve-se ter um loop. Nesse caso, temos duas dimensões, então, usa-se dois loops. Estruturas comportamentais heterogêneas As estruturas heterogêneas consistem em estruturas que podem re- ceber diversos tipos primitivos com um objetivo em comum. Nesse aspecto, temos os conceitos de registro e campo. Por um lado, o campo é um tipo primitivo com identificador que possui uma característica única que, quando linkada a outros campos, produzem um significado ou conjunto de informações. Já o registro é uma informação de um determinado objeto ou entidade que possui diversos campos. Na maioria das literaturas, usa-se o C para explicar a es- trutura heterogênea, pois, por não ser orientado a objetos, conse- gue-se distinguir nitidamente a estrutura heterogênea. Temos o seguinte código: struct Estudante { int ID; char nome[50]; int idade; float media; }; Em que cada estudante adicionado seria um registro em memória. Instanciação Tendo-se o C, uma struct ou estrutura, a instanciação corresponde às estruturas de dados heterogêneos, que possuem diversos atributos 95 (variáveis primitivas) com identificador e tipo primitivo. Logo, sua instanciação ou cópia, é como no exemplo apresentado abaixo. struct Estudante est1; Atribuição Em structs, temos dois tipos de atribuição, são eles: • struct Estudante est1 = {1,”João”,23,10}; ou • struct Estudante est2; est2.ID =2; strcpy(est2.nome , “Pedro”); est2.idade=30; est2.media=7.5; As operações, impressões e acessos são através do ponto, como foi apresentado no exemplo anterior. Em Java, temos diversas formas de trabalhar uma estrutura heterogênea que difere do C. Lembre-se que uma estrutura consiste em uma classe ou objeto. E, além disso, que um objeto é a principal entidade de uma estrutura de programação, orientada a objetos que possuem propriedades e ações,como também podem assumir ca- racterísticas de uma situação real, uma regra de negócio ou entidade em geral. Veja o exemplo: public class Estudante { int ID; String nome; int idade; float media; } 96 public class Exemplo { public static void main(String []args) { Estudante est1=new Estudante(); est1.ID = 1; } } Observe que, no exemplo acima, o código está muito próximo da struct. No caso do Java, também funciona, porém, nos conceitos de orientação a objetos, está fora de padrão e convenção. No exemplo abaixo, segue o código no formato ideal para orientação a objeto, pois usa-se o conceito de encapsulamento, que é o uso das funções sets e gets para gerenciar as variáveis ou atri- butos da estrutura. Para alterar valores, usa-se o set e o nome da variável. Veja: private int idade; public void setIdade(int aux) { this.idade = aux; } Toda variável, ou tipo primitivo, dentro de um objeto que será aproveitado em outras classes deve ser private. Mas, caso não tenha uma palavra reservada como public, o compilador entende como public por default. Observe que, nesse momento, o nome da função assumiu o nome da variável, acrescentando a palavra set como convenção de alteração. Assim, para captar valores, usa-se o get, como na função a seguir: public int getIdade(){ return this.idade; } 97 Abaixo, a classe exemplo seria o formato em que a instan- ciação está correta dentro dos padrões OO. Na linha 6, atribui-se o valor 1 para a variável ID, através do método setID; na linha 8, veri- fica-se o valor atribuído no ID através do método getID. Veja: Figura 18 - Classe Exemplo Fonte: Editorial Digital Pages, 2019. Instanciação: diferentemente do C, o Java é orientado a obje- tos. Por isso, tudo que é desenvolvido em Java e que tenha interação com dados primitivos, geralmente, é um objeto. No código abaixo, tem-se a classe estudante, que é instan- ciada através da palavra new: Estudante est=new Estudante(); Tipos abstratos de dados Os tipos abstratos de dados (TADs) são estruturas que correspon- dem ao funcionamento de estruturas do mundo real, como pilha, fila, lista, árvore, grafos ou qualquer estrutura que organize, de for- ma singular, uma coleção de dados. De acordo com Lafore (2004): o significado de Tipo Abstrato de Dados é mais estendido quando aplicado a estruturas de dados como pilhas e filas. Como em qualquer classe, significa que os dados e as operações podem ser executados neles, mas, neste con- texto, até os fundamentos de como os dados são armazenados tornam-se invisíveis para o usuário (LAFORE, 2004, p. 187). 98 Em algumas situações, os TADs podem ser confundidos com estruturas heterogêneas. Porém, o objetivo deles é possuir em si uma coleção de dados ou objetos, além de se responsabilizar com a sua organização e tratamento, como: inserção, deleção, ordenação, busca e endereçamento. Lembre-se: um TADs em si gerencia sua própria coleção. Em termos de códigos, em Java ou em qualquer outra linguagem de programação, o TADs não envolve comandos complexos e, sim, uma lógica bem definida que faz com que os dados sejam organizados dependendo de sua estrutura. Veja os comandos: Lista: comporta-se como uma lista do mundo real, que rece- be valores e remove em sequência ou por posições. Pilha: comporta-se como uma organização de pilha, como em pilha de livros, em que o primeiro valor que se insere é o último a ser removido. Fila: comporta-se como uma organização de fila, como uma fila de banco, em que o primeiro valor que entra é o primeiro valor que será removido. Tipos de armazenamento Os tipos de armazenamento em TADs podem variar entre di- nâmico e estático. O estático possui capacidade limitada, ou estática, de posições. Já o armazenamento dinâmico possui capacidade de alocação por demanda, ou seja, quando existe a necessidade de inserir algum valor, ele se adapta a uma nova quantidade de valores. DICA 99 Na capacidade estática, tem-se a vantagem de saber a quantidade de valores. Porém, não podendo ser alterada sua capacidade, faz com que seja um formato de armazenamento muito específico, de- pendendo da regra. Coleções Os TADs são muito utilizados em diversas linguagens, porém, em Java, existem algumas bibliotecas prontas para os principais TADs, dentro de orientação a objetos, chamadas de pacotes. O pacote que contém alguns dos principais TADs é o java.util, que possui o collection frameworks, classes e interfaces que pos- suem, prontos, os TADs que não necessariamente precisariam ser reescritos, apenas instanciados para uso. As collections são divididas em conjuntos, listas e mapas. Figura 19- As interfaces da collection Fonte: Adaptada de Oracle, 2019. Acesso em: 20 maio 2022. CURIOSIDADE 100 As listas trabalham em formatos sequenciais de inserção e remoção. Por serem sequenciais, a busca de dados torna-se mais lenta. Além disso, os conjuntos trabalham em formatos algorít- micos para inserção, tornando mais lenta a inserção. Assim, a busca, a navegação e a remoção, porém, tornam-se mais velozes. Por fim, os mapas trabalham organizando os dados juntamente com seus índices, tornando mais complexa a programação. Po- rém, sua busca é veloz. Na figura 20, observa-se as classes que podem ser instancia- das para estrutura de dados das collections, dos grandes eixos list e queue (“sequência”) e set (“conjunto”). Veja. Figura 20 - Classes collections Fonte: Editorial Digital Pages, 2020. 101 Caro(a) aluno(a), chegamos ao final desta etapade estudos. Nesse material estudamos sobre a estrutura de classes. Além disso, apren- demos que atributos e métodos devem estar dentro das classes e que atributos e métodos, com o modificador static, podem ser acessados sem a instanciação de um objeto. Isso porque eles são atributos e métodos da classe e não dos objetos. Além disso, se o atributo static for alterado, isso é refletido em todos os objetos instanciados. Aprendemos também que podemos criar métodos com o mesmo nome, mas que possuam assinaturas distintas. Essas assinaturas são compostas pelo nome e atributos do método e devemos sempre res- peitar a ordem dos atributos, passando os tipos de dados corretamente. Também discutimos o fato de que os construtores são métodos es- peciais que não precisam ser declarados, não possuem tipo de re- torno e devem conter o mesmo nome que a classe. Além disso, você pôde perceber que não é muito viável utilizar métodos construtores com modificadores private, protected ou default, já que o primeiro não é acessível por nenhuma outra classe, e o segundo é apenas por classes do mesmo pacote, ou através de herança. Aprendemos que referência e instanciação são coisas distintas. Enquanto a referência é apenas a declaração que poderá conter um objeto referenciado à memória, a instanciação é a criação do ob- jeto. Para podermos fazer uso do objeto, ele deverá ser atribuído a uma referência. Além disso, vimos a abstração das informações contidas em um objeto e a importância da omissão dessas informações a quaisquer classes, tornando o acesso a elas possíveis apenas através de méto- dos específicos na própria classe que os contém. Até aqui estudamos as estruturas compostas homogêneas, co- nhecidas como vetores e matrizes, além das estruturas compostas heterogêneas, conhecidas como structs ou objetos dependendo da linguagem de programação. SINTETIZANDO 102 Antes de finalizarmos o nosso material, analisamos os conceitos de TADs, que são tipos abstratos de dados, ou seja, uma estrutura que se comporta com características e padrões do mundo real para tratamento de dados, como lista, pilha e fila. Vimos esses conceitos dentro da linguagem Java, em que, na própria linguagem, foi pos- sível visualizar uma API que contém uma collection framework (que possui diversas estruturas prontas para uso), fazendo com que o desenvolvedor foque na regra de negócio. Lembre-se de estudar antes e depois da sua aula, aproveite o mo- mento com o(a) docente para tirar todas as suas dúvidas. Até breve! UN ID AD E 3 Objetivos ◼ Aprender conceitos de herança e sua utilização. ◼ Entender como se dá a criação e o uso da hierarquia de classes. ◼ Aprender a criar e utilizar classes abstratas e interfaces. ◼ Aprender sobre os conceitos dos relacionamentos entre as classes e os tipos existentes. 104 Introdução Olá, tudo bem? A partir de agora veremos o conceito de he- rança e, além disso, vamos entender como são criadas as hierarquias de classes. Você também aprenderá sobre como utilizar as classes abstratas e as interfaces. E, antes de finalizarmos esse material, ve- remos os relacionamentos entre as classes e os tipos existentes. Dito isto, convido você, aluno(a) EAD, a conhecer os temas citados de forma descomplicada e muito intuitiva conforme abor- daremos neste material. Boa leitura e bons estudos! 105 Herança Caro(a) aluno(a), inicialmente é preciso que você entenda que a reu- tilização de classes por meio da herança é uma das características da programação orientada a objetos. O mecanismo de herança permite a reutilização dos atributos e métodos de uma classe em outra, assim, mesmo que não seja declarado explicitamente, todas as classes em Java são dispostas de forma hierárquica. Dessa maneira, classes her- dadas são chamadas de superclasse e possuem atributos e métodos mais generalizados, já classes herdeiras são chamadas de subclasse e possuem atributos e métodos mais específicos. Além disso, podemos encontrar outras nomenclaturas para esses termos, assim, super- classes podem ser referidas como classes pai ou classes mãe e, ana- logamente, subclasses também podem ser chamadas de classes filha. Utilização de herança Para utilizar a herança, a subclasse deve conter a palavra-chave “extends”, depois da declaração de seu nome, seguido do título da superclasse, por exemplo: public class ClasseHerdeira extends ClasseHerdada {}. Dessa maneira, a empregabilidade da herança se dá ao fato de po- der dizer “é um”, na qual um objeto “é um” tipo de outro objeto. Dito isso, analisaremos o diagrama de classes para entendermos melhor, veja. Diagrama 1: Diagrama de Classes sem Uso de Herança Fonte: Editoria Digital Pages (2020). 106 Observando o diagrama, verificamos que as classes Alu- no e Professor são duas entidades distintas, embora ambas possuam alguns atributos em comum, como nome, dataNas- cimento, cpf, endereco e telefone. Dessa forma, de acordo com o diagrama anterior, precisamos criar duas classes. Observe o exemplo a seguir. Figura 1 - Declaração de classes sem herança Fonte: Editoria Digital Pages (2020). Note que, na implementação das classes, ambas compar- tilham atributos em comum. Diante desse cenário, a aplicação de herança para as duas classes é possível, uma vez que todo profes- sor e aluno são pessoas. Nesse caso, precisamos criar uma terceira classe para utilizarmos o conceito de herança; uma classe que seja genérica e contenha atributos e métodos úteis às outras duas classes filhas. Assim, criaremos uma repartição denominada Pessoa e ela conterá os atributos que são comuns entre as subclasses menciona- das, veja abaixo. import java.util.Date; import java.util.List; public class: Aluno { private String nome; private Date datanascimento; private String cpg; private String endereco; private String telefone; private List(string) cursos; private double notas:[]; public( double : obternota(){ int qtdnotas = this.notas.length; double totalnotas = 0; for( int i=o; |< qtdnotas| i++) { totalnotas == this.notas[]i]; } return totalNotas(); } // } import java.util.Date; import java.util.List; public class: Aluno { private String nome; private Date datanascimento; private String cpg; private String endereco; private String telefone; private List(string) cursos; private double notas:[]; public( double : obterSalario(){ return this.salario; } // } 107 Diagrama 2 - Diagrama de Classe com Uso de Herança Fonte: Editorial Digital Pages (2022). No diagrama apresentado, podemos observar a super- classe denominada Pessoa e suas subclasses Aluno e Professor. Observe que Aluno e Professor possuem uma ligação com a su- perclasse Pessoa por meio de uma seta. No diagrama, isso indi- ca que as subclasses Aluno e Professor herdarão os atributos da superclasse Pessoa. Nesse sentido, precisamos criar três classes para simbolizar corretamente o diagrama apresentado. Assim, Aluno e Professor se- rão subclasses que se estenderão à Pessoa, veja. 108 Figura 2 - Declaração de classes com herança Fonte: Editorial Digital Pages (2020). Note que a superclasse Pessoa é declarada normalmente, apesar de poder ser apresentada como classe abstrata. Já as classes filhas (Aluno e Professor) possuem a palavra-chave extends, segui- da do nome da superclasse. Por meio da utilização da palavra-cha- ve, comunicamos que os atributos e os métodos contidos em Pessoa também estão contidos em Aluno e Professor e poderão ser aces- sados normalmente, desde que declarados como public. Já o termo protected permite acesso por meio da herança, ou default, se estive- rem no mesmo pacote. 109 DICA Um detalhe importante é que os atributos das três classes estão declarados com o modificador de acesso private. Isso indica que eles só poderão ser acessados pela própria classe e que, mesmo diante da herança, os atributos dePessoa não estarão acessíveis di- retamente por Aluno e Professor. Nesse sentido, os objetos possui- rão os atributos que herdaram e poderão acessá-los por meio dos métodos getters e setters, ou outros meios que possibilitem a ma- nipulação dos dados. Caso sua intenção, seja de que as subclasses possuam acesso direto aos atributos e métodos da classe mãe, eles devem ser declarados como protected ou public. Os métodos getters e setters não foram declarados para esse exemplo como forma de simplificação, mas saiba que eles precisam ser exter- nados, caso deseje realizar o encapsulamento de dados. Lembre-se que os métodos getters e setters são utilizados para essa finalidade. Outro ponto importante é que a programação Java só permite herança simples entre as classes, ou seja, subclasses só podem her- dar uma única superclasse. Já uma superclasse pode ser herdada por inúmeras outras subclasses. Por fim, quando não é definido uma herança por meio da pala- vra-chave extends, indiretamente a classe herdará a classe Object. Isso porque a classe Object é uma classe genérica que contém o construtor default “public Object()” e onze métodos, cujos mais comumente utili- zados são os getClass(), equals(), hash-Code() e toString(). Palavra-chave super Para acessarmos os atributos e métodos do próprio objeto, fazemos uso da palavra-chave this. Mas, se quisermos acessar os atributos do objeto herdado, podemos fazer uso da palavra-chave super. Pen- sando nisso, a palavra-chave super deve ser utilizada para acessar 110 DICA os atributos e métodos da superclasse, desde que eles não tenham sido declarados como modificadores de acesso private. Assim, o uso da palavra-chave super segue algumas regras de utilização diante da subclasse. Veja: • para chamar o construtor da superclasse, basta declarar a pala- vra-chave super, seguido dos argumentos dentro dos parênte- ses, caso o construtor da superclasse possua. Exemplo: super(); • a chamada do construtor da superclasse só poderá ser feita dentro de construtores da subclasse e deverá ser a primeira linha do construtor; • caso haja hierarquia de classes, por exemplo, A herda B que herda C, e a chamada de construtor deverá ser em cascata, ou seja, A chama construtor da classe B que chama o construtor da classe C; • o construtor padrão da superclasse será chamado automati- camente caso não seja feito uma declaração explícita, ou seja, mesmo que não declarado, o construtor da subclasse chamará o construtor da superclasse; • desde que o modificador de acesso permita, métodos exis- tentes na superclasse podem ser executados fazendo uso da palavra-chave super, seguido do ponto e o nome do método com seus parâmetros caso existam. Exemplo: su- per.somar(int numero1, int numero2);. O mesmo acontece com os atributos. O uso da palavra-chave super não é obrigatório, desde que ambas as classes não possuam métodos ou atributos com o mesmo nome. Mas, caso essa duplicidade ocorra e o método não seja sobrescrito, a não utilização do super desencadeará a execução do método da sub- classe, enquanto o método da superclasse será ignorado. 111 DICA Como essa condição não é usual, é difícil encontrar có- digos que façam uso da palavra-chave super. Dessa forma, quando não há “duplicidade” na subclasse, a palavra-cha- ve this pode ser utilizada para chamar métodos e atributos da superclasse (exceto para o construtor da superclasse, que sempre será super). Isso ocorre devido ao sistema Java, que “procura” métodos e atributos automaticamente, de forma hierárquica, começando pela subclasse até a última super- classe. Assim, caso o atributo ou método não seja encontra- do em nenhuma das classes da hierarquia, haverá um erro de compilação do projeto. Criação e uso de hierarquia de classes Exceto a classe Object, todas as classes em Java são subclasses e, mesmo quando não há uma declaração explícita de heran- ça, a classe herda a classe Object. Por esse motivo, Object é a classe mais genérica em Java, ou seja, é superclasse de todas as demais. Nesse seguimento, a hierarquia permite que uma subclas- se se estenda à apenas uma superclasse, enquanto uma classe mãe pode se estender a inúmeras outras classes filha. Dessa ma- neira, se estruturarmos um sistema de empresa, podemos ates- tar essa estrutura. Lembre-se que a hierarquia de classes possibilita a criação de ca- tegorias com funções mais comuns, assim como permite a criação de classes mais especializadas que herdem essas características e funcionalidades específicas. 112 Diagrama 3 - Diagrama de Classe: Exemplo de Hierarquia Fonte: Editorial Digital Pages (2020). O diagrama apresentado é um exemplo de representação de herança e associação, que demonstra as hierarquias das classes, des- de a mais especializada até a mais genérica. Observe que as classes interligadas pela seta indicam que o objeto herda atributos e méto- dos de forma hierárquica. E, como sabemos, as subclasses mais espe- cializadas possuem atributos e métodos mais específicos do objeto, enquanto as superclasses são genéricas e possuem atributos e mé- todos gerais, que servem às subclasses. Por fim, as classes que estão interligadas pela linha indicam que o objeto está associado ao outro 113 e apontam que um objeto contém outro. Essa associação é um rela- cionamento, mas não indica hierarquia. Um exemplo é que a classe Colaborador possui todos os atributos e métodos contidos em Pessoa Fisica, Pessoa e Object. À vista disso, é preciso apontar que a classe Object contém ape- nas métodos sem atributos. Esses métodos são genéricos e podem ser chamados ou sobrescritos por qualquer classe. Assim, com base no diagrama, a classe Fornecedor pode chamar o método clone da clas- se Object, já que a classe Fornecedor herda PessoaJuridica, que herda Pessoa que, por sua vez, herda Object. Por esse motivo, como mencio- nado, todas as classes possuem os métodos contidos em Object. Classes e subclasses Não há como fugir da hierarquia de classes, já que todas elas her- dam de Object. Contudo, podemos limitar que uma classe não seja superclasse de uma classe filha. Essa limitação é feita pelo uso da palavra-chave final após a palavra-chave class. Isso é feito geralmente por razões de segurança e eficiência. Muitas das classes da biblioteca pa- drão do Java são finais, por exemplo java.lang. System e java.lang.String. Todos os métodos [...] são implicitamente finais.” (BEDER, 2014, p. 66). Portanto, classes declaradas como final não podem ser her- dadas por outras. Classes abstratas e interfaces Planejar um sistema é uma tarefa árdua e deve ser feita de maneira minuciosa por um engenheiro de software, já que determinar quais classes serão utilizadas e identificar suas relações de herança é uma das partes mais trabalhosas do processo. Lembre-se que a determi- nação das classes que serão exclusivamente superclasses e que não serão utilizadas para serem instâncias pode influenciar em como o software será desenvolvido. 114 Por esse motivo, classes determinadas exclusivamente como superclasses podem e devem ser declaradas como abstratas. Essas servem como um guia para a programação, contendo apenas atribu- tos e métodos que devem ser implementados nas subclasses. Assim, além das classes abstratas, Java permite a criação de interfaces que contenham métodos sem corpo. Dessa maneira, diferentemente das hierarquias, uma classe Java pode implementar várias interfaces. Classes abstratas Classes abstratas, além de não poderem ser instanciadas, possibi- litam a criação de assinaturas de métodos, declarados como abs- tract. Elas não possuirão chaves com escopo, devem ser finalizadas com ponto e vírgula e não podem ser declaradas como private. Além disso, elas obrigarão as subclasses a implementarem o método, de- vendo ou não o sobrescrever, fazendo uso da annotation @Override. A declaração de classes e métodos abstratos também deve possuir modificadores abstract após o modificador de acesso daclasse ou método. Para que você entenda o que estamos explicando, a seguir veremos as diferenças em suas declarações. Primeiro, vamos aos exemplos das declarações de classes normais, sem uso do abstract: public class Combustivel { private int litrosDisponiveis; public void abastecerReservatorio(int litros) { this.litrosDisponiveis += litros; } public int getLitrosDisponiveis(){ return this.litrosDisponiveis; } } public class Etanol extends Combustivel { 115 public void printLitrosDisponiveis() { System.out.println(getLitrosDisponiveis() + “ Litros de Etanol”); } } public class Gasolina extends Combustivel { public void printLitrosDisponiveis() { System.out.println(getLitrosDisponiveis() + “ Litros de Gasolina”); } } Note que as classes Etanol e Gasolina se estendem à classe Combustível, e que ela não possui nenhum método abstrato. Assim, se executarmos o código com as três classes declaradas dessa ma- neira, ambas poderão ser instanciadas, todavia, como a classe Com- bustivel não deve ser um objeto e sim uma superclasse, ela poderá ser declarada como abstract, além de conter uma assinatura abstrata para o método printLitrosDisponiveis, presente nas duas subclas- ses. Veja a seguir. public abstract class Combustivel { private int litrosDisponiveis; public void abastecerReservatorio(int litros) { this.litrosDisponiveis += litros; } public int getLitrosDisponiveis(){ return this.litrosDisponiveis; } public abstract void printLitrosDisponiveis(); } 116 Observe, ainda, que a classe Combustivel possui o modifi- cador abstract, impossibilitando que a classe seja instanciada, mas permitindo-a conter métodos abstratos, como o caso do método printLitrosDisponiveis, finalizado com ponto e vírgula e sem esco- po, diferentemente de um método comum. Dessa maneira, teremos: public class Etanol extends Combustivel { @Override public void printLitrosDisponiveis() { System.out.println(- getLitrosDisponiveis() + “ Litros de Etanol”); } } public class Gasolina extends Combustivel { @Override public void printLitrosDisponiveis() { System.out.println(- getLitrosDisponiveis() + “ Litros de Gasolina”); } } Observe também que as classes Etanol e Gasolina possuem, obrigatoriamente, o método printLitrosDisponiveis, que está decla- rado como abstract na classe Combustível. Nesse sentido, uma informação importante sobre as classes abstratas é que seus construtores e atributos não podem ser decla- rados como abstract. Mesmo que a classe abstrata não possa ser ins- tanciada, a subclasse pode chamar o construtor da classe mãe para inicializar atributos, ou executar métodos normalmente, como é feito para casos comuns. Por último, a diferença da declaração de um método abstrato ou não é que o método declarado como abstrato deve ser implemen- tado (escrito) pela subclasse, enquanto os métodos normais devem ser herdados pela subclasse. Ainda assim, pode haver hierarquia de classes abstratas, assim como em classes normais. Todavia, apenas a classe que não for abstrata deverá implementar os métodos. 117 Interfaces Uma classe em Java não pode estender (herdar) duas ou mais clas- ses, o que limita as implementações funcionais de alguns objetos. Contudo, é possível executar em uma classe, seja abstrata ou não, diversas interfaces que contenham assinaturas de métodos. Assim como uma classe abstrata, uma interface não pode ser instanciada, e todos os seus métodos devem ser implicitamente abstract e public. Além disso, se houverem campos, eles serão con- siderados static e final, devendo ser inicializados na sua declaração (SANTOS, 2011). Se uma classe for declarada como abstrata, sem variáveis e com apenas métodos abstratos e/ou constantes, o ideal é transfor- má-la em interface. Lembre-se que interfaces não podem ser ins- tanciadas, assim como as classes abstratas, e possuem seus métodos com modificadores de acesso public e modificadores abstract, de for- ma implícita, dispensando declarações. Embora não seja possível de- clarar atributos nas interfaces, é possível declaração de constantes. Embora Java não permita herança múltipla entre as classes, isso não ocorre com as interfaces, ou seja, é possível uma interfa- ce estender diversas outras. Da mesma forma, uma classe abstrata pode implementar interfaces, porém apenas a próxima subclasse mais especializada e não abstrata da hierarquia implementará os métodos dela. Assim, veremos como é a usabilidade de interfaces: public interface AtivarDesativar { void ativar(); void desativar(); } Primeiro, criamos uma interface que ativará ou desativará alguma coisa em nosso sistema. Essa “coisa” pode ser um objeto que nos faça sentido utilizar ou eliminar. Assim, geralmente, a ati- vação ou desativação de um objeto é utilizada em banco de dados para a exclusão lógica de um registro. 118 DICA public interface VivoMorto { void vivo(); void morto(); } Depois, criamos uma outra interface como exemplo, que ser- ve para “definir” se um ser está vivo ou morto. public interface CalcularIdade { int calcularIdade(); } Por último, uma interface será utilizada para calcular a idade de algo ou de alguém. Note que, em todas as interfaces, sua composição é iniciada pelo modificador de acesso public, seguido da palavra-chave interface e nome. Assim, no escopo da interface temos métodos com assinatura e tipo de retorno, embora pudéssemos ter declarado constantes no escopo da interface. Lembre-se que todo método declarado sempre será public e abstract, e as constantes sempre serão static e final (fa- tor que determina que o atributo é uma constante). Agora, veremos como fazer a implementação de interfaces em classes: import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; public class Pessoa implements AtivarDesativar, VivoMorto, CalcularIdade { 119 private String nome; private boolean ativo; private boolean vivo; private Date nascimento; public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public Date getNascimento() { return nascimento; } public void setNascimento(Date nascimento) { this.nascimento = nascimento; } public boolean isAtivo() { return ativo; } public boolean isVivo() { return vivo; } @Override public int calcularIdade() { GregorianCalendar hoje = new GregorianCalendar(); GregorianCalendar nascimento = new GregorianCalendar(); nascimento.setTime(this.nascimento); 120 int anoAtual = hoje.get(Calendar.YEAR); int anoNascimento = nascimento.get(Calendar.YEAR); return new Integer(anoAtual-anoNascimento); } @Override public void vivo() { this.vivo = true; } @Override public void morto() { this.vivo = false; } @Override public void ativar() { this.ativo = true; } @Override public void desativar() { this.vivo = false; } } Perceba que, na declaração da classe possuímos a palavra- -chave implements, seguida dos nomes das interfaces. E, no de- correr do escopo da classe, possuímos os métodos existentes nas interfaces e, acima de cada método, a annotation @Override, à qual se refere ao método sobrescrito nessa classe. Agora, veja a diferença entre classes abstratas e interfaces. 121 Quadro 1 - Diferenças entre classe abstrata e interface Fonte: Editorial Digital Pages (2020). Relacionamento entre classes Além da ligação entre heranças, há mais três tipos de rela- cionamento entre classes, classificados como: associação, composição e agregação. Esses relacionamentos não indicam hierarquia, uma vez que, nesse caso, podemos dizer que um objeto “tem um” outro objeto, mas não que um objeto “é um” outro objeto. Associação A associação é um relacionamento estrutural entre duas classes que especifica quais são os objetos conectados entre si. Isso possibilita que, por meiodos vínculos do relacionamento, um objeto invoque estados e comportamentos de outros objetos. Veja o exemplo a seguir. InterfacesClasses abstratas Não permite herança múltipla Deve possuir a palavra-chave class Permite a declaração de atributos Métodos podem ser public e abstract Pode conter métodos abstratos Possui construtores Métodos não abstratos podem ser estáticos Permite herança múltipla Deve possuir a palavra-chave interface Não permite a declaração de atributos Métodos são public e abstract Só possui métodos abstratos Não possui construtores Por conter apenas métodos abstra- tos, não é possível declarar métodos estáticos 122 Diagrama 4 - Diagrama de Classe – Associação entre Classes Fonte: Editorial Digital Pages (2020). No diagrama representado, a classe Pessoa poderá possuir uma lista de objetos do tipo Automóvel, por isso está representa- da pelo 0..*, que indica que uma pessoa poderá ter nenhum ou vá- rios automóveis. Em contrapartida, todo automóvel deve pertencer à apenas uma pessoa, por isso do lado da entidade Pessoa temos o valor 1. Nesse caso, o relacionamento entre as classes será visível apenas na classe Pessoa, que conterá a lista de carros. Assim, para utilizarmos esse tipo de relacionamento na programação, devemos trabalhar com arrays, que são listas de objetos do mesmo tipo. Assim, veja na prática a declaração das classes do Diagrama 4. Nesse sentido, iniciaremos pela classe Carro, já que a classe Pessoa terá um array dela: public class Carro { private String placa; private String marca; private String modelo; private String classi; private short anoFabricacao; private short anoModelo; public String getPlaca() { return placa; } 123 public void setPlaca(String placa) { this.placa = placa; } public String getMarca() { return marca; } public void setMarca(String marca) { this.marca = marca; } public String getModelo() { return modelo; } public void setModelo(String modelo) { this.modelo = modelo; } public String getClassi() { return classi; } public void setClassi(String classi) { this.classi = classi; } public short getAnoFabricacao() { return anoFabricacao; } public void setAnoFabricacao(short anoFabricacao) { this.anoFabricacao = anoFabricacao; } 124 public short getAnoModelo() { return anoModelo; } public void setAnoModelo(short anoModelo) { this.anoModelo = anoModelo; } } Agora, veremos a declaração da classe Pessoa: public class Pessoa { public Pessoa() { if(this.carros == null ) { this.carros = new Carro[0]; } } private String nome; private long cpf; private Carro [] carros; public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public long getCpf() { return cpf; } public void setCpf(long cpf) { 125 this.cpf = cpf; } public Carro[] getCarros() { return carros; } public void setCarros(Carro[] carros) { this.carros = carros; } } Perceba que, na classe Pessoa, possuímos um array de obje- tos da classe Carro (Carro [] carros;). Isso indica que a classe Pessoa “tem um” objeto do tipo array de Carro, mas não necessariamente significa que seja obrigatório ter um objeto carro dentro do array do objeto da classe Pessoa. Assim, para evitarmos NullPointerExcep- tion, devemos inicializar o array, conforme feito no construtor da classe Pessoa (note que todo array também é um tipo de objeto, ou seja, é uma lista). NullPointerException é uma exceção gerada pelo sistema quando se tenta acessar um objeto em memória que ainda não foi instan- ciado (ou inicializado). O significado é que o objeto está nulo e não possui um valor definido naquele momento. A classe de exceção é a java.lang.NullPointerException. Geralmente ocorre exceção quan- do um objeto não é inicializado, todavia, isso não se aplica aos ti- pos primitivos. Dessa forma, para entendermos como funciona o relaciona- mento das categorias, nesse caso, vamos à um exemplo de imple- mentação de um código que manipule ambas as classes: DEFINIÇÃO 126 public class Start { public static void main(String[] args) { Carro carro1 = new Carro(); carro1.setAnoFabricacao((short) 2010); carro1.setAnoModelo((short) 2011); carro1.setMarca(“Marca 1”); carro1.setModelo(“Modelo 1”); carro1.setClassi(“77784646846HHjJ46468764”); carro1.setPlaca(“AAA-1234”); Carro carro2 = new Carro(); carro2.setAnoFabricacao((short) 2010); carro2.setAnoModelo((short) 2011); carro2.setMarca(“Marca 2”); carro2.setModelo(“Modelo 2”); carro2.setClassi(“748XJS684633jJ46AA3889”); carro2.setPlaca(“BBB-4321”); Pessoa pessoa = new Pessoa(); pessoa.setNome(“Fulano da Silva Beltrano”); pessoa.setCpf(00011122233); Carro [] carros = {carro1, carro2}; pessoa.setCarros(carros); for(Carro carro : pessoa.getCarros()) { System.out.println(“O veículo “ + carro.getModelo() + “ pertence ao “ + pessoa.getNome()); } System.out.printf( “%s tem %d %s”, pessoa.getNome(), pessoa.getCarros().length, pessoa.getCarros().length > 1 ? “carros” : “carro” ); } } 127 Observe que são instanciados dois objetos do tipo Carro e um objeto do tipo Pessoa. Posteriormente, foi declarado um array de objetos Carro, adicionando os dois objetos Carro ao array, em se- guida fazendo set do array de Carros, relacionando-os à Pessoa. Por último, iteramos todos os carros contidos dentro do ar- ray, escrevendo no terminal pelo comando System.out.println o modelo do carro e o nome do dono. Finalizando, escrevemos no ter- minal o nome da pessoa e quantos carros ela possui, fazendo uso do operador condicional ternário, que é composto pela condição ? ex- pressão1 : expressão2, bastante similar à uma função if else simples. Composição A composição, segundo (BEDER, 2014), é um relacionamento com características “todo-parte”; assim, uma classe só tem sentido em sua existência (parte), se outra classe existir (todo). Veja o exemplo. Diagrama 5 - Diagrama de classes – Associação e Composição entre Classes Fonte: Editorial Digital Pages (2020). 128 No diagrama de classes apresentado, as classes Cliente e Pe- dido estão associadas de forma simples. Nesse sentido, um cliente pode ter nenhum ou vários pedidos, assim como os pedidos só per- tencem a um cliente. Depois, temos a composição entre Pedido, que é o “todo”, e o ItemPedido, considerado a “parte”, isto é, que não pode existir sem a presença de um Pedido. Note, ainda, que a ligação entre as classes Pedido e ItemPedido é feita por meio da linha com o “diamante” escuro e que, obrigatoriamente, o pedido deverá ter pelo menos um itemPedido. Seguindo a mesma lógica, portanto, o itemPedido pertence a apenas um requerimento. Veja, na prática, a declaração das classes do Diagrama 5. Des- taco que iniciaremos pelo ItemPedido, necessário para a construção do Pedido, veja: public class ItemPedido { public ItemPedido () { this.quantidade = 1; } public ItemPedido(String descricao, int quantidade, double valorUnitario)} { this.descricao = descricao; this.setQuantidade(quantidade); this.setValorUnitario(valorUnitario); } private String descricao; private int quantidade; private double valorUnitario; public String getDescricao() { return descricao; } 129 public void setDescricao(String descricao) { this.descricao = descricao; } public int getQuantidade() { return quantidade; } Public void setQuantidade(int quantidade) { If(quantidade > 01) { this.quantidade = quantidade; } else { This.quantidade = 1; } } public double getValorUnitario() { return valorUnitario; } public void setValorUnitario(double valorUnitario) { if(valorUnitario > 0) { this.valorUnitario = valorUnitario; } else { This.valorUitario = 0; } } public void addQuantidade(int quantidade) { this.quantidade += quantidade; } @Override 130 public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((descricao == null) ? 0 : descri- cao. hashCode()); result = prime * result + quantidade;long temp; temp = Double.doubleToLongBits(valorUnitario); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; ItemPedido other = (ItemPedido) obj; if (descricao == null) { if (other.descricao != null) return false; } else if (!descricao.equals(other.descricao)) return false; if (quantidade != other.quantidade) return false; if (Double.doubleToLongBits(valorUnitario) != Double. double- ToLongBits(other.valorUnitario)) return false; 131 return true; } } Observe que nessa classe possuímos dois construtores, um que já inicializa a quantidade igual a 1 e outro que permite a inserção das informações sem precisar fazer os setters. Além disso, no encap- sulamento dos dados, verificamos se a quantidade inserida é maior que zero, já que não faz sentido um item de pedido zerado ou com quantidade negativa. Isso é feito, também, para o valor unitário. Re- pare que sobrescrevemos os métodos equals e hashCode existentes na classe Object. Esses métodos serão essenciais para a funcionali- dade de adição do novo item na classe Pedido. Todavia, não se preo- cupe quanto à sobrescrita destes métodos, pois, possivelmente sua IDE o fará automaticamente. Dito isto, agora, veremos a classe Pedido: import java.util.Date; import java.util.HashSet; import java.util.Set; public class Pedido { public Pedido () { this.itemPedidos = new HashSet<ItemPedido>(); } private Date dataPedido; private String formaPagamento; private Set<ItemPedido> itemPedidos; public Date getDataPedido() { return dataPedido; } public void setDataPedido(Date dataPedido) { this.dataPedido = dataPedido; 132 } public String getFormaPagamento() { return formaPagamento; } public void setFormaPagamento(String formaPagamento) { this.formaPagamento = formaPagamento; } public Set<ItemPedido> getItemPedidos() { return itemPedidos; } public void addItemPedido(ItemPedido item) { if(this.itemPedidos.contains(item)) { this.itemPedidos.stream() .filter(oneItem -> oneItem.equals(item)) .forEach(oneItem -> oneItem.addQuantidade(item. getQuantidade())); } else { this.itemPedidos.add(item); } } public float getTotalPedido() { float total = 0; for(ItemPedido item : itemPedidos) { total += item.getValorUnitario() * item. getQuantidade(); } return total; } } 133 Nessa classe, para o ItemPedido, fizemos uso da interface Set, que é uma collection que não permite duplicidade de dados. Nes- se sentido, por ser uma interface, também fizemos a implementação da classe HashSet. Em seguida, como não é permitida a duplicidade de dados no método addItemPedido, verificamos se o item já existe na lista. Caso exista, fazendo uso de uma expressão lambda, procu- ramos o item em questão e adicionamos à quantidade existente à nova, por meio do método addQuantidade. Perceba que isso só é possível graças aos métodos equals e hash- Code existentes no item do pedido. Lembre-se que o hashCode utiliza um cálculo matemático para determinar um valor àquele atributo e, caso um dos atributos possua valores diferentes, ele não será conside- rado igual e executará a condição else, adicionando o item à lista. Agregação A agregação é similar à associação e sua diferença é apenas conceitual. Nesse sentido, a agregação não é perceptível e, caso existam dúvidas entre agre- gação e associação, opte sempre pela associação (KNOERNSCHILD, 2001). Assim, Beder afirma que: a agregação é um relacionamento com carac- terísticas ‘todo-parte’ [...] menos intenso, po- dendo haver um certo grau de independência entre eles. Ou seja, a existência do objeto ‘par- te’ faz sentido mesmo sem a existência do ob- jeto ‘todo’ (BEDER, 2014, p. 39). Para entendermos o conceito em maior profundidade, obser- ve o diagrama a seguir. Diagrama 6 - Diagrama de Classes – Agregação entre Classes Fonte: Editorial Digital Pages (2020). 134 Observando o diagrama, note que um time pode ter um ou vários atletas, e o atleta pode ou não ter um time (o que significa que o atleta pode existir sem a necessidade da presença de um time). Nes- se contexto, há exclusividade, ou seja, embora o atleta só possa es- tar associado a um único time, ele pode existir sem a existência de um time propriamente dito. Polimorfismo Introdução ao polimorfismo A palavra polimorfismo vem do grego poli morfos, cujo significado pode ser traduzido como “o que possui várias formas”. Em infor- mática, na programação, esse significado não é muito diferente: polimorfismo (“muitas formas”) permite a ma- nipulação de instâncias de classes que herdam de uma mesma classe ancestral de forma unificada: podemos escrever métodos que recebam instân- cias de uma classe C, e os mesmos métodos serão capazes de processar instâncias de qualquer classe que herde da classe C, já que qualquer classe que herde de C é-um-tipo-de C. (SANTOS, 2011, p. 140). Isso significa que, por exemplo, as subclasses Professor e Aluno, por serem tipos de Pessoa, estendem a superclasse Pes- soa e podem executar métodos que sejam declarados na classe Pessoa ou em sua superclasse. Nesse caso, o objeto mais es- pecializado transforma-se em um objeto genérico de qualquer nível acima da sua hierarquia. Em outras palavras, em Java, to- dos os objetos podem ser tratados como se fossem objetos de uma superclasse. DICA 135 Polimorfismo na prática O Diagrama 7 apresenta um exemplo da situação supracitada. Diagrama 7 - Diagrama de Classes: exemplo de polimorfismo Fonte: Editorial Digital Pages (2020). A classe Pessoa tem atributos comuns para qualquer pessoa e a declaração de seu método obterNomeCompleto sugere que este também é comum a qualquer pessoa. Como as classes Professor e Aluno são tipos de pessoa, elas estendem a classe Pessoa e, conse- quentemente, herdam seus atributos e método. Agora, em relação à implementação dessas classes, observe as Figuras 3 e 4. Figura 3 - Implementação da classe Pessoa Fonte: Editorial Digital Pages (2020). 136 Figura 4 -Implementação das classes Aluno e Professor. .Fonte: Editorial Digital Pages (2020). De modo geral, não há novidade alguma até o momento: sim- plesmente, aplicou-se o conceito de herança. Agora, é necessário explorar a manipulação dessas classes, criando uma classe que con- tenha o método main para que seja possível executar o conteúdo e entender o conceito de polimorfismo, veja um exemplo. public class Start { public static void main(String[] args) { Pessoa pessoa = new Pessoa(); Aluno aluno = new Aluno(); Professor professor = new Professor(); pessoa.setNome(“João”); pessoa.setSobrenome(“da Silva”); aluno.setNome(“Ana”); aluno.setSobrenome(“de Oliveira Lima”); aluno.setTurma(“6º B”); professor.setNome(“Guilherme”); professor.setSobrenome(“Nascimento”); professor.setSalario(4389.33); imprimeNomeCompleto(pessoa); imprimeNomeCompleto(aluno); imprimeNomeCompleto(professor); 137 } public static void imprimeNomeCompleto(Pessoa pessoa) { System.out.println(pessoa.obterNomeCompleto()); } } No código apresentado, ocorreu a criação de três objetos: • objeto pessoa do tipo Pessoa; • objeto aluno do tipo Aluno; • objeto professor do tipo Professor. Posteriormente, houve a inserção dos valores dos atributos para os respectivos objetos. Ao final, chamou-se o método impri- meNomeCompleto, que espera receber um objeto do tipo Pessoa. Por três vezes este método foi chamado e um objeto diferente foi passado em cada chamada. Como o parâmetro do método espera um objeto do tipo Pessoa, ao passar os objetos aluno e professor, não há erro de compilação, uma vez que estes são subclasses da superclasse Pessoa: dessa maneira, de acordo com o conceito de herança, ambos não deixam de ser objetos da superclasse. Caso não houvesse polimorfismo, ao passar os objetos do tipo Aluno e Professor por parâmetro ao método imprimeNomeCompleto,haveria um erro de compilação. Com o polimorfismo, porém, os obje- tos aluno e professor, “transformam-se” em um objeto do tipo Pes- soa: ao executar-se a aplicação, o resultado é apresentado conforme o esperado, posto que ambos os objetos têm os mesmos atributos e método herdados da classe Pessoa. Observe o resultado a seguir. Figura 5 - Resultado da execução da classe Start- exemplo do polimorfismo Fonte: Editorial Digital Pages (2020). 138 Santos, em seu livro “Introdução à programação orientada a objetos usando Java”, de 2011, ressalta que “as regras de cast entre instâncias de classes não permitem a conversão de classes mais ge- néricas para classes mais específicas, nem de classes que não este- jam em uma hierarquia de herança” (p. 145). Em outras palavras, se, por exemplo, o parâmetro do método imprimeNomeCompleto fosse do tipo Professor, não seria possível passar os objetos do tipo Aluno e Pessoa por parâmetro, visto que Pessoa é mais genérico que Professor e a conversão do objeto mais genérico para o mais específico não é possível. Por sua vez, o tipo Aluno está no mesmo nível hierárquico que Professor, visto que ambos herdam de Pessoa. Como são dois objetos diferentes por estarem no mesmo nível hierárquico, a classe Aluno não é um tipo de Professor, embora ambos sejam um tipo de Pessoa. Ligação dinâmica (dynamic binding) A ligação dinâmica está muito relacionada aos conceitos do po- limorfismo e herança. Nesse caso, uma subclasse tem métodos herdados da superclasse e os sobrescreve da melhor maneira para realizar suas operações. E, somente durante a execução e utilizando conceitos do polimorfismo, o Java toma a decisão de qual método deve ser usado: se o da subclasse ou de uma das superclasses de ní- veis acima. Para entender o que estamos tratando, veja: a implementação do polimorfismo pode exi- gir facilidades proporcionadas pela ligação dinâmica. De um modo geral, uma ligação (binding) é uma associação, possivelmente, entre um atributo e uma entidade ou entre uma operação e um símbolo. Uma ligação é dita estática se ocorre em tempo de compila- ção [...] e não é mais modificada durante toda a execução do programa. A ligação dinâmica é feita em tempo de execução ou pode ser al- terada no decorrer da execução do programa (KAMIENSKI, 1996, p. 15). 139 Pode-se afirmar, portanto, que a ligação dinâmica aconte- ce apenas durante a execução do programa, quando o próprio Java toma a decisão de qual método de qual classe deve ser usado. Já a li- gação estática é aquela executada diretamente, quando não há apli- cação do polimorfismo. Assim, observe o Diagrama 8 que representa uma hierarquia entre as classes. Diagrama 8 - Diagrama de Classe: Hierarquia de Classes. Fonte: Editorial Digital Pages (2020). Como você deve ter percebido, o diagrama apresenta uma es- trutura longa de hierarquia, na qual as classes Gato, Tigre e Leão são as mais especializadas, e as demais, acima, são sucessivamen- te mais genéricas – até a primeira do topo, que é a classe Object do próprio Java. 140 DICA Tratamento de exeções No desenvolvimento de software, uma exceção não é algo que ocor- re de forma rara. Exceções, na verdade, são eventos que, por algum motivo – neste caso, uma condição excepcional –, geram erros no sistema, os quais podem ou não ser esperados. Em muitos dos casos, exceções ocorrem quando o progra- mador se esquece de inicializar um objeto ou encontra erros du- rante a conversão, ou casting, de objetos. Se essas exceções ocorrerem e o devido tratamento para o caso não for realizado, o sistema provavelmente será interrompido naquele ponto e não executará o processamento restante. Ricarte (2001, p. 29) diz que “para cada exceção que pode ocorrer durante a execução do código, um bloco de ações de trata- mento (um exception handler) deve ser especificado. O compilador Java verifica e reforça que toda exceção ‘não-trivial’ tenha um blo- co de tratamento associado”. Em muitos casos, as exceções podem ser evitadas de forma simples, apenas utilizando condicionadores como if-else ou operador ternário, por exemplo. Em alguns contex- tos, porém, o ideal é capturar e tratar a exceção utilizando o bloco try-catch; e, em determinados casos, até mesmo lançar a exceção para o método acima da pilha de chamadas. Evitando e capturando exceções A forma mais comum para evitar uma exceção é realizar verifica- ções no objeto antes de utilizá-lo para seu propósito. Um exemplo muito comum é checar se o objeto não está nulo, o que evita erros de NullPointerException. Assim, a captura de exceção é feita com os 141 blocos try-catch, que também podem possuir o finally. Além disso, é possível ter na estrutura vários catches que capturam um tipo espe- cífico de exceção. Sua estrutura pode ser da seguinte maneira: try { // Código que implementa alguma função no sistema, que pode gerar alguma exceção } catch (Exception e) { // Trecho no qual é feita a captura da exceção } finally { // Se declarado, o finally será sempre executado, indepen- dentemente se gerou ou não uma exceção } O bloco try-catch-finally precisa estar dentro do escopo de um mé- todo e não há necessidade de implantá-lo em locais nos quais pos- sivelmente não haverá erros. Caro(a) estudante. Chegamos ao final de mais uma etapa de estudos. Nesse módulo, aprendemos sobre a usabilidade da herança e qual é a melhor forma de implementá-la entre as classes. Além disso, dis- cutimos a utilização da palavra-chave super, que permite chamar diretamente métodos e atributos das superclasses (desde que não sejam privates). VOCÊ SABIA? SINTETIZANDO 142 Também vimos como funciona a hierarquia entre as classes e como o Java trabalha para tratar sobre esse assunto, além de observarmos como é a forma correta de implementação de hierarquia. Um outro ponto estudado, referiu-se à utilização das classes abs- tratas e interfaces, além das principais diferenças entre elas. Vimos também que a herança múltipla entre classes em Java não é pos- sível, mas que essa regra não ocorre entre interfaces. Além disso, embora não seja possível que uma subclasse se estenda à mais de uma classe mãe, aprendemos que uma classe pode implementar inúmeras interfaces. Por fim, estudamos que, além da herança, as classes possuem rela- cionamentos classificados em três tipos: agregação, composição e associação. Lembre-se que esses relacionamentos só são possíveis quando podemos dizer que um objeto “tem um”, mas não “é um”. Esperamos ter contribuído com o seu aprendizado. Até a próxima! UN ID AD E 4 Objetivos ◼ Entender o conceito de recursividade. ◼ Entender os algoritmos de ordenação e conhecer o código dos algoritmos mais usados. ◼ Conhecer estruturas de dados: listas, filas, pilhas e árvores. ◼ Conhecer algumas implementações em Java dessas estruturas. 144 Introdução Olá, aluno(a). Como vai? Chegamos a mais uma etapa de estudos e, nesse momento, serão apresentados a você os conceitos de algorit- mos recursivos e algoritmos de ordenação. E, para implementar es- ses conceitos, apresentaremos códigos Java usando vetores. Veremos também o conceito de recursividade e a implemen- tação de algoritmos recursivos. Discutiremos sobre os algoritmos mais conhecidos de ordenação de vetores, mostrando o código de alguns desses algoritmos. Antes de finalizarmos este material, veremos os conceitos de es- trutura de dados conhecidos como Lista, Filas, Pilhas e Árvores, apre- sentando parte dos códigos desenvolvidos para o uso desses conceitos. Dito isto, convido você a conhecer os temas citados de forma des- complicada e muito intuitiva, conforme abordaremos neste material. Boa leitura e bons estudos! 145 Algoritmos recursivos Alguns problemas exigem que sejam trabalhados laços de repeti- ções para executar as mesmas instruções em quantidades estabe- lecidas ou esperadas. Dessa maneira, os algoritmos recursivos são uma espécie de loop que possuem um critério de parada. Porém o mesmo algoritmo“chama a si mesmo com argumentos tratados”. Figura 1 - Exemplo de algoritmo recursivo Fonte: Comitê Editorial Digital Pages (2019). Caro(a) aluno(a), para que você entenda o que abordamos até agora, o programa a seguir imprime do 10 ao 1 na tela. Veja! O método da linha 4, chamado recursão, é iniciado pelo método main, que é o primeiro método a ser executado da classe da linha 16. Dessa maneira, ao ser iniciado, passa-se o parâmetro 10. Esse algo- ritmo faz a contagem regressiva de 10 até 1, chamando a si mesmo class Recursao { public static void repercusao(int contador){ System.out.print(contador); contador = contador--1 if (contador == 0) { return; } //chamando novamente repercusao(contador); } public static void main(string[] args) { repercusao(10); } } EXEMPLO 146 decrementando -1 da variável contador. Mas, caso não tivesse um critério de parada, esta recursão na linha 9 com a condição conta- dor==0, a execução se estenderia até o travamento do computador, pois ele chamaria a si mesmo perpetuamente, como se fosse um loop. Porém, dentro dele, deve ter um critério de parada geralmente com o comando return, como na linha 10. Resumindo, a recursão nada mais é do que uma função que chama ela mesma, recebendo um argumento e repassando o mesmo argu- mento tratado, com um critério de parada dentro da própria função. Figura 2 - Exemplo de recursão Fonte: Comitê Editorial Digital Pages (2019). É importante destacarmos que o fatorial é um exemplo clás- sico de recursão, onde seu critério de parada, geralmente, vem no início da função e a chamada de si mesmo, ou recursão, acontece na mesma linha do return, como na linha 7. O resultado da nova cha- mada é multiplicado ao argumento inicial decrementando 1. Técnicas de ordenação A ordenação é um dos métodos de Estrutura de Dados que visa or- denar dados desordenados. E, para isso, existem muitas formas: class Fatorial { public static int fatorial(int n){ if (n== 1) { return n; } Return (fatorial(n-1)*n); } public static void main(Sstring[] args) { System.out.println(factorial(6)); } } 147 • selection sort; • insertion sort; • quick sort; • merge sort; • heap sort; • radix sort (LSD); • radix sort (MSD); • std::sort (intro sort); • std::stable_sort (adaptive merge sort); • shell sort; • bubble sort; • cocktail shaker sort: • gnome sort; • bitonic sort; • bogo sort. O que diferencia esses métodos é o tempo para ordenação e a forma de troca dos dados. Alguns usam recursão, outros somente loops ou a mescla entre eles. Como você pode perceber, não existe uma forma única de resolver um problema, porém sempre deve ser observado se o algoritmo se encaixa nas regras de negócio. Pensan- do nisso, abaixo você irá conhecer os mais tradicionais: bubble sort, insertion sort, e quick sort. Vamos lá! Método Bubble Sort O método Bubble Sort, ou “método bolha”, tem por objetivo a ordenação dos dados a partir de 2 em 2, trocando sempre o maior valor pelo menor 148 valor e percorrendo o vetor por diversas vezes. O nome deste algoritmo remete à ideia de que os dados se comportam como “bolhas” dentro de um tanque e, embora seja simples, não é tão eficiente quanto outros algo- ritmos de ordenação quando os dados estão totalmente desorganizados. Na classe Bubble Sort, por exemplo, temos a função main na linha 24, que inicia um vetor com valores desordenados e envia esse vetor para o método Bubble Sort na linha 25. Assim, ao chamar o método na linha 25, inicia-se a ordena- ção a partir da linha 2 onde este método possui dois loops: um que percorre todos os índices e o segundo loop que percorre até o pe- núltimo. Perceba que a troca dos dados é feita após a verificação da linha 5 no if que verifica se o valor do índice -1 é maior que o valor do índice do atual. Ou seja, a troca é feita de 2 em 2. Após a ordenação, o programa volta para a função main, execu- tando a linha 26, chamando outra função que é a imprimir, e execu- ta-se o bloco da linha 16, fazendo uma impressão for-each em tela do vetor ordenado, diferente de uma variável que precisa ser uma variável global. Observe que, ao executar o código, não precisamos receber o ve- tor ordenado, ele se ordena pelo endereço de memória, fazendo apenas a referência dele, conseguimos fazer a impressão do vetor ordenado. Figura 3 - Exemplo de Método Bubble Sort Fonte: Comitê Editorial Digital Pages (2019). package bubblesort; public class BubbleSort { public static void main(String args[]) { int[] v = {5, 2, 4, 3, 0, 9, 7, 8, 1, 6}; BubbleSort bs = new BubbleSort(); bs.ordenar(v); for(int num : v) { System.out.print(num + " "); } } public void ordenar(int[] v) { // for utilizado para controlar a quantidade de vezes que o vetor será ordenado. for(int i = 0; i < v.length - 1; i++) { // for utilizado para ordenar o vetor. for(int j = 0; j < v.length - 1 - i; j++) { /* Se o valor da posição atual do vetor for maior que o proximo valor, então troca os valores de lugar no vetor. */ if(v[j] > v[j + 1]) { int aux = v[j]; v[j] = v[j + 1]; v[j + 1] = aux; } } } } } 149 EXEMPLO Método Insertion Sort O método Insertion Sort é um algoritmo de ordenação que per- corre o vetor da esquerda para a direita, trazendo os menores valores para a esquerda e, assim, inicia as trocas a partir do se- gundo elemento. Considere um vetor de 5 posições vetor[]=new [] {7,6,4,2,9}; 1º - 7-6-4-2-9 – desordenado; 2º - 6-7-4-2-9 – a primeira troca do segundo elemento pelo primeiro; 3º - 4-6-7-2-9 – a terceira troca, empurrando os elementos maio- res para a direita e os menores para a esquerda; 4º - 2-4-6-7-9 – a quarta troca, empurrando os elementos maio- res para a direita e os menores para a esquerda, fazendo o 2 ser o primeiro item do vetor; 5º - 2-4-6-7-9, no caso do 9, como é um elemento maior que o 7, ele permanece no mesmo local, retornando o vetor ordenado. Observe no código a seguir que, na linha 23, o método main inicia um vetor desordenado e passa, via parâmetro, para a função insertion sort na linha 2. Assim, o primeiro loop percorre o vetor e o segundo percorre da esquerda para a direita, ordenando os valores. Após a ordenação, chama-se o método imprimir para mostrar o ve- tor ordenado. 150 Figura 4 - Exemplo de Método Insertion Section Fonte: Comitê Editorial Digital Pages (2019). Método Selection Sort O método Selection Sort busca no vetor o menor valor entre todos os elementos e o insere na primeira posição após a verificação. Esse tipo de método é diferente do Bubble Sort, que faz a troca pelo maior ou menor por meio do insert de forma ordenada para todos os elementos. Figura 5 - Exemplo do método Selection Sort Fonte: Comitê Editorial Digital Pages (2019). 1 class BubblesortExemplo { 2 public static void bubblesort(int[] array) { 3 for(int i=0; i < array.length; i++){ 4 for(int j=1; j < (array.length-1); j++){ 5 if(array[j-i] > array[j]){ 6 int aux = array[j-1]; 7 array[j-1] = array[j]; 8 array[j] = aux; 9 } 10 11 } 12 } 13 14 } 15 16 private static void imprimir(int vetor[]) 17 { 18 for(int aux: vetor) 19 { 20 System.out.println(aux); 21 } 22 } 23 public static void main(String[] aux){ 24 int array[] = new int[200,0,-20,-11]; 25 bubblesort(array); 26 imprimir(array); 27 } 28 } public static void selectionSort(int[] vetor) { for (int i = 0; i < vetor.length; i++) { int i_menor = i; for (int j = i + 1; j < vetor.length; j++) if (vetor[j] < vetor[i_menor]) i_menor = j; int aux = v[i]; vetor[i] = v[i_menor]; vvetor[i_menor]= aux; } } 151 Estruturas Lineares As estruturas lineares são estruturas que agrupam dados em se- quência ou em uma ordem pré-determinada. Por exemplo, em se tratando da ordenação em Lista sequencial FIFO (First in First Out – o primeiro que entra é o primeiro que sai) e LIFO (Last in First Out – o último que entra é o primeiro que sai), porém sua retirada pode ser tanto em sequência como de forma desordenada, por prioridade etc. Dessa maneira, as estruturas lineares mais utilizadas são: lista, pilha e fila que implementam o vetor dentro de suas estruturas de acordo com seu comportamento. Perceba que o armazenamento tem sua implementação diferenciada no código. É importante destacarmos que, quando se fala em Listas, no geral, implementa-se a estrutura primitiva do vetor, que é uma estrutura sequencial indexada com o mesmo tipo. E quando se im- plementa uma estrutura em vetor por herança, absorve a maior ca- racterística do vetor à sua limitação quanto ao tamanho. Assim, a limitação, quanto ao tamanho do vetor, faz com que as estruturas possam ser divididas em duas formas de alocação: a es- tática e a dinâmica. A alocação estática faz com que qualquer estrutu- ra tenha um tamanho máximo para inserção de elementos, forçando o programador a usar soluções paliativas para manter a estrutura em vetor. Já a alocação dinâmica é uma estrutura que não implementa o vetor, porém implementa o conceito de No, onde é possível alterar sua estrutura de acordo com a necessidade do(a) programador(a). Dito isto, a partir de agora, vamos detalhar as estruturas li- neares mais utilizadas: a lista, a pilha e a fila. Vamos lá! Listas Inicialmente, é necessário destacarmos que as listas são estruturas que alocam, de forma sequencial, os dados. Temos as listas estáti- cas, que implementam o vetor, e a lista dinâmica, que são as listas ligadas e duplamente ligadas, tratando-se do Java Collections da interface List. 152 EXEMPLO Lista Estática A Lista Estática implementa o vetor, podendo ser de forma estrutu- rada ou objeto. Tanto uma quanto a outra são fixas a seus tamanhos e requerem soluções paliativas após o tamanho máximo ser utiliza- do. A grande vantagem da lista estática ou do uso do vetor na estru- tura é a capacidade de indexação dos elementos, fazendo com que a individualização de cada um seja mais efetiva. Diferente de outras estruturas em que é necessário navegar na estrutura para acessar os valores. Observe o código a seguir: System.out.println( vetor[11]); // Neste código a partir do índice 11, busca-se o valor do vetor na po- sição 11. Lista Ligada É um tipo de dado abstrato que se comporta como o conceito de Lista, mas implementa o Objeto No ou Node. ◼ No ou Node O No ou Node é um objeto que faz autorreferência, tendo um for- mato em que pode ter de 0 a n itens lincados. Em relação ao conceito de No, entende-se que os itens podem ser inseridos e removidos em qualquer local da estrutura, podendo aumentá-los e diminuí-los conforme a necessidade. Sua navegação ocorre por meio da referência do objeto denominado próximo, aces- sado por meio do método getProximo(). 153 O construtor na linha 6 é uma função que recebe como parâmetro o valor a ser alocado e a referência do objeto que será próximo do objeto atual. Na linha 3, armazena-se o próximo item que, para acessá-lo, é preciso usar o método getProximo() e, para setar a referência do próximo No, usa-se o método setProximo(). Além disso, as funções setValor() e getValor() armazenam os valores a serem alocados. Dessa maneira, ao tratar-se da Lista ou qualquer outra estru- tura de dados, deve-se, obrigatoriamente, implementar o código do No para ser referenciado na estrutura. Figura 6 - Implementação do código No Fonte: Comitê Editorial Digital Pages (2019). É importante destacarmos que a Lista Ligada é uma estrutura que implementa o Node e cada elemento dela consiste em um Node, que possui em si mesmo a referência de seu próximo, possibilitando uma capacidade N de armazenar e diminuir itens conforme a neces- sidade. Possui como principal objeto o próximo que armazena o en- dereço do próximo objeto e o valor que armazena os valores alocados. CURIOSIDADE class No { private No proximo; private int valor; No(int v, No n) { valor = v; proximo = n; } public int setValor() { return valor; } public int getValor() { return valor; } public void setProximo(No aux) { proximo = aux; } public No getProximo() { return proximo; } } 154 Figura 7 - Implementação do Node na estrutura de Lista ligada Fonte: Comitê Editorial Digital Pages (2019). Para que você entenda o que estamos abordando, sugiro que observe a seguinte estrutura de Lista Ligada apresentada na figura 8. Figura 8 - Estrutura de Lista Ligada Fonte: Comitê Editorial Digital Pages (2019). O primeiro No é o início do No, trazendo em si a referência do próximo No, que seria o que armazena o -1. Para percorrer os nos, usa-se o método getProximo(), e, no seu retorno, usa-se o ge- tProximo() do outro No. Assim, chega-se ao ultimoNo que, ao usar o getProximo() do ultimoNo, ele retornará null, caracterizado, assim, por ser o último No da estrutura. ◼ Inserção Inserção dos elementos 5 e 7 nas extremidades ocorre após, primei- ramente, os Nos com os valores serem criados e depois inseridos nas extremidades. No caso do 5 que está alocado no início, apenas irá setar como seu próximo, o primeiroNo, fazendo, desse modo, o se- gundo No da estrutura. No exemplo da inserção no último No, temos ele setando o seu objeto próximo para o novo No, tornando o novo No o último, e ele o penúltimo No da lista. valor próximo nullprimeiroNo 0 -1 primeiroNo 10 20 null ultimoNo 155 Figura 9 - Inserção dos elementos 5 e 7 nas extremidades Fonte: Comitê Editorial Digital Pages (2019). ◼ Busca de um elemento A busca em uma estrutura de Lista Ligada se dá pela navegação da função getProximo(), que sempre irá fornecer o endereço do próxi- mo No e, ao chegar no final, ele irá retornar null. Assim, o algoritmo faz um loop entre os getProximo() e confere o valor do No aux com o valor pesquisado, retornando true, independente se localizou ou não o item na lista. Figura 10 - Busca de um elemento em uma estrutura de Lista Ligada Fonte: Comitê Editorial Digital Pages (2019). ◼ Remoção de um elemento Para remover um elemento no No, usa-se o mesmo conceito da busca, ou seja, um loop para achar o No que contém o valor a ser removido. Assim, ao achar o No e criar um objeto temporário cha- mado atual, é necessário fazer uma busca pelo seu No anterior. E, ao localizar, armazená-lo em aux e fazer a referência de aux na função setProximo() no valor do próximo de atual. Perceba que o No que ar- mazena o 0 é lincado ao No que armazena o 10 e, automaticamente, o No que armazena o -1 é retirado da Lista. 05 -1 10 null primeiroNo 20 ultimoNo null 0 -1 10 75 primeiroNo 20 ultimoNo 0 -1 10 20 null primeiroNo ultimoNo aux 156 Figura 11 - Remoção de um elemento no No Fonte: Comitê Editorial Digital Pages (2019). Lista Duplamente Ligada A Lista Duplamente Ligada é uma evolução da Lista Ligada, pois a mesma apenas armazena o endereço do seu próximo e, caso ultra- passe para o próximo na navegação, não consegue fazer o caminho de volta entre os Nodes. O armazenamento do endereço se dá nos objetos próximo e anterior do Node, como nas figuras a seguir. Figura 12 - Armazenamento do endereço Fonte: Comitê Editorial Digital Pages (2019). Figura 13 - Exemplo de referenciação Fonte: Comitê Editorial Digital Pages (2019). -15 100 20 7 null atual primeiroNo ultimoNo -15 100 20 7 null atual aux primeiroNo ultimoNo 0 105 20 7 null aux ultimoNoprimeiroNo próximovaloranterior null null Node nullnull Primeiro nó Útimo nó 0 -1 10 20 157 ◼ Inserção de um elemento A inserção dos valores 5 e 7 nas extremidades se dá, primeiramente, pela criação do Node comos valores e, depois, são apontadas suas referências aos elementos da estrutura. Figura 14 - Inserção de um elemento Fonte: Comitê Editorial Digital Pages (2019). ◼ Remoção de um elemento Na primeira estrutura do algoritmo, é percorrido o elemento que se busca apagar e se armazena o endereço do No no objeto atual. Após localizar o objeto a ser removido, faz-se uma nova varredura, per- correndo o objeto anterior ou atual, e armazena a referência em aux. Dessa maneira, ao localizar o aux, coloca-se o endereço do próximo No do atual em aux, liga-se o aux com o próximo do atual e, auto- maticamente, o atual sai de memória. Figura 15 - Remoção de um elemento Fonte: Comitê Editorial Digital Pages (2019). null null nullnull Primeiro nó Primeiro nó Útimo nó Útimo nó 5 5 -1 0 10 -1 20 20 7 10 7 null null null null null null Primeiro nó Primeiro nó Atual Aux Aux Atual Excluído -1 Primeiro nó Útimo nó Útimo nó Útimo nó 5 5 5 -1 0 0 10 -1 -1 20 20 20 7 10 10 7 7 158 Código Lista Duplamente Ligada A Lista Duplamente Ligada segue alguns padrões da Lista Ligada. Primeiramente, usa-se a classe Node, onde contém as referências anteriores e posteriores do No atual. Na linha 3, o objeto autorreferenciável próximo aloca o en- dereço do próximo No e o objeto autorreferenciável anterior aloca o endereço do No anterior para o primeiro No de uma estrutura. Dessa maneira, o item anterior será null enquanto o último No e o endere- ço do próximo será null. Figura 16 - Código da Lista Duplamente Ligada Fonte: Comitê Editorial Digital Pages (2019). Lista Collection A lista desenvolvida no formato estático implementa a interface Collection, no código a seguir (linha 3), e importa o pacote util.Col- lection que contém as classes que podem ser implementadas da Col- lection como: Vector, ArrayList e LindekList, os quais fazem parte do formato sequencial ou linear de alocação dinâmica. class Node { private Node proximo, anterior; private int valor; Node(int auxvalor, Node auxprox, Node auxant) { valor = auxvalor; proximo = auxprox; anterior = auxant; } public void setValor(int aux) { valor = aux; } public int getValor() { return valor; } public void setProximo(Node aux) { proximo = aux; } public Node getProximo() { return proximo; } public void setAnterior(Node aux) { anterior = aux; } public Node getAnterior() { return anterior; } } 159 Figura 17 - Lista Collection Fonte: Comitê Editorial Digital Pages (2019). 160 DICA Dessa maneira, na linha 11, utiliza-se o LinkedList e na ins- tanciação poderia ser usado qualquer Collection sequencial, como Vector ou ArrayList, seguindo o código, usa-se o <Integer>. Com esse formato, coloca-se o tipo do primitivo ou objeto que irá ser utilizado na estrutura e, neste caso, o objeto col recebe somente valores do tipo inteiro. Quando se usa a interface Collection, as funções são padroni- zadas independente da estrutura utilizada, como: • Add – insere elementos; • Remove – remove elementos; • Constains – busca elementos na estrutura. No código visto nas linhas 9 e 11, faz-se a instanciação. Já nas linhas 13 e 14 são inseridos 100 valores na estrutura dentro do loop e a im- pressão da estrutura é feita nas linhas 16 e 20, com a impressão via for-each na função “imprimir”. Pilhas A TAD (Tipo Abstrato de Dados) Pilha é uma variação da Lista Linear que possui características semelhantes a uma “pilha” do mundo real. Ela armazena os valores inseridos no formato de LIFO - Last in First Out (O último que entra é o primeiro a sair). Como uma “Pilha de livros”, onde o primeiro a ser empi- lhado é o último a ser acessado, segue um exemplo de empilha- mento de dados. 161 Diagrama 1 - Exemplo de Empilhamento de Dados Fonte: Comitê Editorial Digital Pages (2019). A Pilha possui as seguintes operações: • push( X) – aloca ou adiciona o valor na estrutura Pilha; • pop() – desempilha ou remove o valor do topo da Pilha, retor- nando o valor pela função; • peek() – acessa o topo da Pilha sem removê-lo; • isEmpty() – retorna o estado atual da Pilha se possuir itens alocados false, mas, se não possuir, retorna true; • isFull() – retorna true se a Pilha está com todos os espaços alocados, porém, para Pilhas que são de alocação dinâmica, não possui a função; isFull(), – somente as Pilhas que imple- mentam o vetor. 162 Código da Pilha de Alocação Estática Figura 18 - Código da Pilha com Alocação Estática Fonte: Comitê Editorial Digital Pages (2019). Observa-se na linha 5 o Construtor que recebe o tamanho máxi- mo da Pilha para poder colocar os itens e, a partir daí, ele implementa um vetor com esse tamanho máximo e, a partir do momento que ele tem tamanho máximo, ele tem aquela quantidade de itens para poder colocar dentro do nosso vetor que está sendo implementado na Pilha. Já na linha 10 temos a função isEmpty que verifica se o topo = -1. Isso significa que se o topo for -1 ela está vazia, então vai retornar true. Na linha 14 temos os isFull, ele verifica se a Pilha está cheia, ou seja, se o topo é equivalente ao número da quantidade de elementos do vetorPilha.length (o atributo que retorna o tamanho máximo do vetor). O peek nada mais é do que o retorno do valor que está no índice topo da pilha na linha 18. Por outro lado, o método push tem a função de adicionar itens dentro da pilha, incrementando o topo e atribuindo valor na Pilha no índice topo. Já o método pop é a função para remo- ver itens da Pilha, fazendo decremento no topo. 1 public class Pilha { 2 private int vetorPilha[]; 3 private int topo; 4 // Inicia a pilha com Tamanho max 5 public Pilha(int max) { 6 vetorPilha = new int[max]; 7 topo = -1; 8 } 9 // Verificação se existem Valores na Pilha 10 public boolean isEmpty() { 11 return (topo == -1); 12 } 13 // Retorna se a pilha está cheia ou não 14 public boolean isFull() { 15 return (topo == vetorPilha.length -1); 16 } 17 // Retorna o topo da pilha 18 public int pick() { 19 return vetorPilha[topo]; 20 } 21 //Adicionar valor na pilha 22 public void push(int j) { 23 if (isFull()) { 24 topo++; 25 vetorPilha[topo] = j; 26 } 27 } 28 //Excluir valor da pilha 29 public int pop() { 30 return vetorPilha[topo--]; 31 } 32 } DEFINIÇÃO 163 Figura 19 - Código que usa a classe Pilha como estrutura de dados para ser implementada Fonte: Comitê Editorial Digital Pages (2019). Filas – implementações As Filas são estruturas de dados ou TADs que armazenam no for- mato FIFO. First-in-first-out, ou simplesmente FIFO, significa que o elemento que é inserido é o primeiro a ser removido, analogamente a uma fila de banco ou qualquer fila do mundo real. Tabela 1 - A estrutura Fila Fonte: Comitê Editorial Digital Pages (2019). class PrjPilha { public static void main(String[] args){ Pilha pilha = new Pilha(5); pilha.push(10); pilha.push(20); pilha.push(30); pilha.push(40); System.out.println("valor do jogo"+pilha.pick()); System.out.println("Está Vazia:"+pilha.isEmpty()); System.out.println("Está cheia:"+pilha.isFull()); while (!pilha.isEmpty()) { System.out.print(pilha.pop()); } } } 164 Os comportamentos padronizados da Fila são: • Queue(objeto) – insere o elemento objeto ou valor na Fila; • deQueue() – remove os elementos da Fila conforme ordem de entrada; • Peek() – assim como a Pilha, a Fila trabalha o conceito de topo, que nada mais é do que o elemento em evidência na es- trutura (no caso da Fila, é o primeiro elemento que foi inseri- do, enquanto, na Pilha, é o último elemento que foi inserido); • isEmpty() – é a função que retorna true para uma estrutura vazia e false para uma estrutura que já contém pelo menoso primeiro elemento inserido; • isFull() – é uma função que retorna booleano, ou seja, true para uma estrutura completa ou cheia e false caso possa inse- rir mais elementos, considerando uma Fila de alocação está- tica que possui tamanho fixo. O código a seguir demonstra a aplicação do vetor[] no com- portamento de uma Fila que implementa o formato FIFO. Além disso, a classe FilaEstática é uma estrutura que dá permissão para outros programas acessarem como objeto uma fila estática (estru- tura com tamanho fixo) com tamanho passado via construtor. Per- ceba que, na linha 7, tem-se o construtor que inicia a estrutura Fila com o tamanho passado pelo(a) programador(a). As funções isEmpty e isFull retornam resultados booleanos, respec- tivamente, se a estrutura está vazia e se a estrutura está cheia. A função peek retorna a posição do elemento corrente da es- trutura, que é sempre a posição 0 do vetor, pois, tratando-se da aplicação da FIFO, a posição mais importante da estrutura é o pri- meiro elemento, como se vê na figura 20. DICA 165 Figura 20 - Aplicação do Vetor Fonte: Comitê Editorial Digital Pages (2019). A função enQueue, ou enfileirar, recebe por parâmetro o argumento valor que insere no vetor privado o elemento na po- sição qtdElementos, variável que controla a posição de quan- tidades de elementos na fila. Ele se inicia com -1 e, conforme aumenta o número de elementos, incrementa e, quando remove elementos, decrementa, controlando a quantidade de elementos da Fila, o que o torna o principal parâmetro para saber se a Fila está cheia ou não. 1 public class FilaEstatica { 2 private int[] vFila; 3 private int qtdElementos; 4 5 // Construção da fila Estática som tamanho fixo 6 public FilaEstatica(int tamanho) { 7 vFila = new int[tamanho]; 8 qtdElementos = -1; 9 } 10 //Verificação se está vazia 11 public boolean isEmpty() { 12 return (qtdElementos == -1); 13 } 14 //Verificação se está cheia 15 public boolean isFull() { 16 return (qtdElementos == vFila.length - 1) ? true : false; 17 } 18 // Topo da fila 19 public int peak() { 20 return (!isEmpty()) ? vFila[0] : -1; 21 } 22 //Adicionar elementos FIFO 23 public void enQueue(int valor) { 24 if (!isFull()) { 25 qtdElementos++; 26 vFila[qtdElementos] = valor; 27 } 28 } 166 Figura 21 - Função enQueue Fonte: Comitê Editorial Digital Pages (2019). A função deQueue, ou desenfileirar, tem por responsabilidade o comportamento de retirar o elemento do vetor Fila e fazer com que todos os elementos avancem uma posição por meio do loop da linha 35. Já na linha 33, retorna o valor do primeiro elemento e in- crementa a quantidade de elementos da Fila na linha 34. A impressão da Fila se dá na função imprimir, que se refere aos valores da linha 47 no loop for-each. A classe Prj_FilaEstatica aplica a estrutura FilaEstatica e utiliza como estrutura de dados, assim, na linha 5 se faz a instanciação, das linhas 6 a 9 se faz a inserção de elementos na Fila e na linha 11 se faz uma remoção e a impressão do valor da Fila. Na inserção, são adicionados os elementos -1, 25, 5 e 7, res- pectivamente. No caso da organização FIFO, o primeiro elemento se torna o -1 e, ao verificar seu peek ou removê-lo, será trabalhada sempre a primeira posição do vetor que no caso é 0 e está armaze- nando nesse primeiro momento o -1. Para a remoção, chama-se a função deQueue, na linha 11. Pri- meiro, remove-se o elemento dentro da estrutura, juntamente com o incremento da quantidade de elementos da Fila e, após isso, retor- na no System.out.println() o número -1. Assim, automaticamente, a estrutura dos dados muda dentro da Fila, mudando de -1, 25, 5 e 7 para 25, 5 e 7, tornando agora o 25 o valor da primeira posição. 30 //Remover elementoi FIFO 31 public int deQueue() { 32 if (!isEmpty()){ 33 int removido = vFila[0]; 34 qtdElementos--; 35 for (int i = 0; i < qtdElementos; i++) 36 vFila[i] = vFila[i + 1]; 37 return removido; 38 } else { 39 return -1; 40 } 41 } 42 //Impressão dos valores da Fila 43 public void imprimir() 44 { 45 for(int aux: vFila) 46 System.out.println(aux); 47 } 48 } 167 DICA Figura 22 - Função deQueue Fonte: Comitê Editorial Digital Pages (2019). De maneira geral, nessa classe que implementa a alocação estática existem algumas desvantagens: uma delas é a questão de inserção de elementos ultrapassarem o tamanho máximo do estipu- lado na criação. Nesse caso, uma solução paliativa seria armazenar os itens em um objeto auxiliar e reinstanciar o objeto atual com um valor máximo maior em tempo de execução e, assim, inserir nova- mente na estrutura os valores antigos e novos. No entanto, torna-se ineficiente, pois se trata de mais processamento para a aplicação e, dependendo do tamanho da nova estrutura, demora demais. Para aplicações de pequeno porte, que não exijam volumes altos de informação, pode ser trabalhada a Fila estática. Uma grande vanta- gem de trabalhar com o formato estático, tanto Pilha quanto Fila, é a própria aplicação do vetor que torna o código mais simples de ser implementado e reconhecido. 168 Collection Class Queue – Fila dinâmica A Fila dinâmica é uma estrutura de dados que implementa os concei- tos da Fila estática. A diferença se dá, conceitualmente, no tamanho máximo: como na estática, temos o tamanho fixo da estrutura na dinâmica. Nela os elementos podem ser inseridos e removidos sem a estrutura ter um tamanho fixo, tornando a Fila dinâmica em um formato melhor para trabalhar em tempo de execução, isso quando não se sabe a quantidade de elementos que deverão ser inseridos. Além disso, é importante saber que a interface Queue está dentro da API Collection, implementando a alocação dinâmica com Node; possui diversas funções já embutidas nela, por exemplo, add, remove e peek – funções que poderiam ser implementadas na mão, mas, nesse formato, já estão prontas na interface, otimizando a produtividade ao programar. Nos imports desse projeto temos, nas linhas 2 e 3, a primeira referência à Fila dinâmica do projeto, onde se instancia, na linha 8, a interface Queue com o LinkedList e com o tipo Integer, que armazena números inteiros. Das linhas 11 a 13, adicionam-se os valores 1, 2 e 3 por meio da própria função add da LinkedList. Assim, a linha 15 remove um item da Fila, a linha 16 retorna a quantidade de elementos dentro da Fila e a linha 17 retorna o valor booleano para a busca do valor 1 dentro da Fila com a função contains. É importante destacarmos que, no uso da Fila, são demonstradas, no programa, duas formas de navegar nos elementos dela nas linhas 19 a 22 e 24 a 29. Figura 23 - Interface Queue Fonte: Comitê Editorial Digital Pages (2019). 169 Na primeira solução, que está nas linhas 19 a 22, utili- za-se a função toAr- ray(), que retorna um vetor da fila e o ar- mazena no vaux[] e, no loop, faz a varredura pelo vaux. Nessa solução ocorre a transformação de um objeto da API em vetor e pode-se manipulá-lo. Já a segunda solução, a mais ideal, é usar o Padrão de Proje- to Iterator. Os Designers Patterns (Padrões de Projeto) são soluções padronizadas para problemas recorrentes da programação. Em al- gumas linguagens, deve-se implementar do zero e seguir apenas o conceito, mas, no caso do Java e de algumas outras linguagens, há o código pronto, sendo um deles o Iterator. O Iterator é um padrão de projeto que, por meio do conceito de orientação a objetos, encapsula a estrutura de dados como uma co- leção de elementos e permite percorrer todos da estrutura, ou seja, um padrão de solução para navegação em estruturas que não per- mitem indexação, como o vetor, que, ao informar a posição, retorna o elemento vetor[posição]. O for-each é uma das variações de Itera- tor, porém possui algumas funções queprecisam ser invocadas para uso, como na linha 1 se importa o Iterator. Observe que, na linha 24, o Iterator deve ser instanciado com o nome do objeto que será percorrido com o “.iterator()”, onde as classes da API Collection implementam essa solução. Após ser ins- tanciado, cria-se um objeto Iterator da estrutura de dados com o nome filaIterator. No loop da linha 27, a filaIterator.hasNext() retorna true, en- quanto houver elementos para navegação na estrutura, e seu acesso é por meio da função. next(), que retorna o elemento atual do objeto e aponta para o próximo da estrutura, caso houver; e retorna false caso não tenha, encerrando assim o laço de repetição, como se ob- serva na figura 24. DEFINIÇÃO 170 Figura 24 - O padrão Iterator Fonte: Comitê Editorial Digital Pages (2019). Árvores e suas generalizações A Árvore é um tipo de estrutura abstrata de dados que comporta-se de forma semelhante a uma árvore, empregando conceitos de raiz e folhas. Também emprega em si fortemente o conceito de Node, ou Nó, como elemento em nível hierárquico. Lembre-se que, na hierarquia, comu- mente os nós podem assumir representações como pai, filhos e irmãos. O principal Nó da estrutura da Árvore é a raiz, o Nó inicial que dá acesso aos demais elementos da estrutura, ou seja, é o ponto ini- cial em uma navegação e, a partir dele, pode-se navegar nos filhos e conjuntos de pais e filhos, chamados de conjuntos não vazios ou subárvores, como Node 1, Node 2 e Node n. Diagrama 2 – A estrutura Node Fonte: Comitê Editorial Digital Pages (2019). NODE 1 NODE 2 NODE N NODE RAIZ 171 De acordo com critérios de armazenamento, as árvores po- dem formar ordenação de valores e elementos que façam com que alguns Nós sejam armazenados mais à direita ou à esquerda. No diagrama a seguir, tem-se a árvore Exemplo e inicia-se da Raiz de árvore que chama-se Node Raiz. Observe! Diagrama 3 – Node Raiz Fonte: comitê editorial Digital Pages (2019). Como você pode perceber, os filhos de Node Raiz são Node 1 e Node 2; os filhos de Node 1 são Node 3 e 4 e, obviamente, o Node 1 é pai desses Nodes e o filho de Node 2 é o Node 5, tornando o Node 2 pai de Node 5. A leitura dessa Árvore sempre indica o nome do pai e o nome dos filhos e vice-versa. Observe que, após os filhos dos filhos, não há mais Nodes. Nessa situação, ocorre o conceito de folha, nomenclatura para as extremidades da estrutura da Árvore, onde muitos algoritmos utili- zam o Node folha para fazer aplicações dentro da Árvore. Filho de Node 2 Node RaizRAIZ DA ÁRVORE Fo lh as FILHOS DE NODE RAIZ FILHOS DE NODE 1 Node 1 Node 3 Node 4 Node 5 Node 2 DICA 172 As Árvores podem ser classificadas em hierárquica, diagra- ma de inclusão, parênteses aninhados e representação encadeada, como se observa nos diagramas 4, 5, 6, 7 e figura 25. Diagrama 4 - Árvore Hierárquica Fonte: Comitê Editorial Digital Pages (2019). Diagrama 5 – Exemplo de Inclusão Fonte: Comitê Editorial Digital Pages (2019). Figura 25 – Prênteses aninhados Fonte: Comitê Editorial Digital Pages (2019). A B C D E F G H I A B HD I E G C F (A (B (E)) (C (F) (G)) (D (H (I)))) 173 Diagrama 6 - Adaptação do formato Árvore pra representação aninhada Fonte: Comitê Editorial Digital Pages (2019). Diagrama 7 - Adaptação do formato Árvore para representação encadeada Fonte: Comitê Editorial Digital Pages (2019). Pode-se observar que a representação do diagrama 5, passando para o formato da representação encadeada, ficaria como o diagrama 6. 12 10 15 1613 12 10 13 16 15 CURIOSIDADE 174 Terminologia da estrutura Árvore As Árvores possuem várias propriedades e características que mui- tos algoritmos utilizam para poder efetuar operações como bus- ca, inserção, remoção, ordenação e classificação. Dito isto, abaixo abordaremos suas principais terminologias. Nó Estrutura referenciável que armazena valores dentro de uma árvo- re, a qual possui apenas um pai, podendo possuir vários “irmãos”. Ela nunca apresenta dois pais ou ligação com seu irmão. Pensando nisso, abaixo listamos suas principais terminologias. • Arco – também chamado de vértice ou ligação, é o caminho entre dois Nós pais até os filhos. • Raiz – é o primeiro elemento Node ou Nó, que dá acesso a to- dos os elementos da árvore. • Altura ou profundidade – é o nível do Nó folha ou terminal, somando +1. • Chave – é denominado chave o elemento que propõe valor ao elemento Nó. Em diversas situações a própria chave do Nó é seu valor. • Floresta – conjunto de 0 ou mais árvores. • Sub árvore – são conjuntos de Nós pais após a Raiz, poden- do ser consideradas quaisquer Nós contendo sequência de outros Nós. • Pai – é o Nó que possui filhos ou irmãos. Assim, não sendo Raiz, não poderá ser folha, mas, sendo Raiz e não tendo filhos, não poderá ser pai, pois ele mesmo é a folha. • Filho – é o Nó que possui pai, podendo possuir irmãos ou ser um Nó folha. 175 • Rótulo – nome do Nó, geralmente seu dado, valor ou elemento. • Grau de saída – número de filhos linkados a ele. • Grau de uma árvore – é o maior número de filhos de um Nó em toda a árvore. • Folha – são Nós que não possuem filhos, ou seja, possuem grau de saída 0, também chamados de Node terminal. • Nó interno – todos os Nós que possuem filhos ou grau de saí- da maior ou igual a 1, ou seja, que não são um Nó terminal. • Caminho na árvore – é a interligação de Nós consecutivos, que traz a relação de hierarquia, por exemplo: “é pai de” ou “é filho de”. É o caminho do Nó Raiz até determinado Nó dentro da Árvore. • Comprimento do caminho – é o número de arco/vértice ou de ligações entre um Nó e outro. • Nível de um Nó – é a quantidade de arcos da Raiz até o Nó específico; no caso, a Raiz tem 0 arcos, seu nível é 0; sendo que um filho da Raiz possui um arco, então teria nível 1, e assim sucessivamente. Veja a seguir exemplos de valores para cada propriedade da Árvore. Nó: são todos os elementos Nós da estrutura. Raiz: A. Chave: do Nó A o valor é A, do Nó B o valor é B e, assim, sucessiva- mente, pois o exemplo não deu uma identificação específica para cada Nó. Grau de saída: A = 3, B = 1, C = 2, H = 1, E = 0 e I = 0. EXEMPLO 176 Grau de uma Árvore: 3, pois A possui 3 filhos e os demais possuem 2 e 1, que são inferiores. Folhas ou Nó terminal: E, F, G e I. Nó interno: A, B, C, D e H, ou seja, Nós que não são folhas. Comprimento do caminho: A até G é 2 e A até I é 3. Nível de um Nó: A = 0, B = 1, G = 2 e I = 3. Altura ou profundidade: Último nível, que é 3 + 1 = 4. Node – Implementação dinâmica Nos comandos que implementam a Árvore, tem-se a classe Node como classe principal, por ser referenciável e ser moldada ao for- mato de qualquer topologia, estrutura ou formas de organização. Essa classe tem como regra armazenar o endereço do Node da esquerda e da direita, formando, assim, uma ligação entre os filhos para, a partir da ligação com eles, navegar por toda a estrutura. No código a seguir tem-se o atributo-chave, que armazena a informação ou dado do elemento Node como Object, fazendo com que possa receber diversos valores. Porém, de forma geral, utiliza-se o Integer ao invés de Object na linha 2, como nos exemplos anteriores. Figura 26 - Atributo-chave Fonte: Comitê Editorial Digital Pages (2019). 177 A seguir, há uma referência visual da classe Node, em que se observa onde estão sendo feitas as referências à classe da direita, da esquerda e à chave no meio. Figura 27 - Classe Node Fonte: Comitê Editorial Digital Pages (2019). Vetor – Implementação sequencial Nem todas as Árvores trabalham com conceito de Node. Em alguns casos, pode-se implementar conceitos de Árvore de forma sequen- cial, em que as chaves das Árvores são armazenadas em vetor e os seus filhos representam sempre um múltiplo do seu índice dentro do vetor. A fórmula para acesso aos filhos é utilizada por meio de seus índices: para acessar os filhos à direita, 2 * índice, e para osfilhos à esquerda, 2 * índice + 1. Veja um exemplo! Diagrama 8 - Implementação Sequencial Fonte: Comitê Editorial Digital Pages (2019). CHAVEESQUERDA DIREITA 1 3 65 2 4 null vet[0] 1 vet[3] 4 vet[3] 4 vet[1] 2 vet[5] 6 vet[5] 6 vet[4] 5 vet[4] 5 vet[2] 3 vet[2] 3 vet[6] null vet[6] null 178 Árvores binárias Uma Árvore binária, ou binary tree, é uma aplicação de estrutura de Árvore com regras específicas, a saber: • o Nó só pode ter de 0 até 2 filhos; • os dois filhos de uma Árvore binária são chamados de filho à esquerda e filho à direita; • as subárvores de uma árvore binária são subárvores da direita ou da esquerda; • o filho à esquerda de um Nó tem o valor da chave menor que seu pai, já o filho à direita possui o valor da chave maior ou igual a seu pai; • uma Árvore vazia não contém elementos; • todos os elementos da Árvore são Nós e arcos. Diagrama 9 - Árvore Binária Fonte: Comitê Editorial Digital Pages (2019). 5 7 64 3 2 8 Chave ou informação Filhos à esquerda Filhos à direita 179 Tipos de Árvores binárias ◼ Árvore estritamente binária Quando todos os Nós da Árvore possuem dois filhos ou zero sen- do terminais. Diagrama 10 – Exemplo de Árvore Estritamente Binária Fonte: Comitê Editorial Digital Pages (2019). ◼ Árvore binária completa ou cheia São Árvores nas quais o último nível é uma subárvore vazia e, por esse motivo, possui a menor altura e todo o Nó termina no mesmo nível. A altura dessa Árvore pode ser calculada por fórmula. Diagrama 11 - Exemplo de Árvore Binária Completa Fonte: Comitê Editorial Digital Pages (2019). 1 3 5 7 2 4 6 180 ◼ Árvore binária balanceada Esses tipos de Árvores são estruturas que têm como base a altura das subárvores em 1 e, a cada operação na árvore, tem-se seu balan- ceamento, tornando a altura da Árvore sempre em 1. Diagrama 12 - Exemplo de Árvore Binária Balanceada Fonte: Comitê Editorial Digital Pages (2019). ◼ Percurso na Árvore Uma Árvore possui diversos tipos de elementos e organizações e, dependendo da forma que se armazenam os valores, ela pode pos- suir formatos distintos. Porém não há uma indexação para que sai- bamos os elementos ou estruturas que ela possui, ou seja, apenas por meio de uma visita ou percurso na estrutura pode-se saber quais elementos e chaves (dados) ela apresenta. Existem algumas formas de percorrer os elementos de uma Árvore, que são: • pré-ordem; • in-ordem; • pós-ordem. A travessia em pré-ordem, ou pré-fixa, inicia-se pela Raiz e percorre os Nós e subárvores à esquerda para depois fazer o mesmo percurso. Observe! 1 3 65 2 4 181 Figura 28 - A travessia em pré-ordem Fonte: Comitê Editorial Digital Pages (2019). Já a travessia em in-ordem, ou intra-ordem, percorre a su- bárvore à esquerda e, ao chegar na folha, volta para a Raiz e, em seguida, visita a subárvore à direita, assim como o respectivo código em Java para o percurso. Figura 29 – A travessia in-ondem Fonte: Comitê Editorial Digital Pages (2019). B C E D F G I H J 3º 6º 5º 4º 7º 8º 9º 2º 1º B C E D F G I H J 3º 6º 5º 4º 7º 8º 9º 2º 1º 182 Por fim, a travessia em pós-ordem, ou pós-fixa, percorre a subárvore à esquerda, depois a subárvore à direita e a Raiz. Por últi- mo, o respectivo código em Java para o percurso. Figura 29a - A travessia em pós-ordem Fonte: Comitê Editorial Digital Pages (2019). Figura 29b - A travessia em pós-ordem Fonte: Comitê Editorial Digital Pages (2019). Árvores de busca Uma das maiores aplicações e usos da estrutura Árvore é a bus- ca. Há diversos algoritmos que organizam a estrutura de Árvores para maximizar a varredura e melhorar a eficiência do percurso na Árvore ao localizar um valor ou chave. A principal árvore para essa função é a árvore binária de busca, a qual, a partir de seu formato de organização, também possui variações. Veja a seguir cada uma delas. B C E D F G I H J 3º 6º 5º 4º 7º 8º 9º 2º 1º 183 • Árvore 2-3: implementa a árvore binária, porém, na sua inser- ção, em vez de armazenar apenas uma chave, pode armazenar um intervalo de chaves ou duas chaves, fazendo com que o Node possua dois atributos-chave para armazenar valores de um va- lor inferior até um superior. Também nos links dos próximos filhos não se tem apenas o direito e esquerdo, mas também o filho do “meio”, tornando a árvore ternária, em vez de binária. Lembre-se que, a cada inserção, um Nó filho pode possuir mais de um valor na chave e, nesse intervalo, possuir filhos à direita, esquerda e meio, tornando a busca mais veloz por não precisar entrar em novos Nós pais para se aprofundar na Árvore. • Árvore AVL: é uma árvore de busca balanceada, ou seja, bus- ca minimizar as comparações entre seus Nós rotacionando ou balanceando a árvore a cada inserção ou remoção, trazendo sempre o melhor formato entre os Nós da estrutura e tornando as alturas das subárvores à direita e à esquerda bem próximas. • Árvore rubro-negra: possui diversas funções já embutidas nela, como add, remove, contains, entre outras. Essas fun- ções, que poderiam ser implementadas à mão, mas que nesse formato já estão prontas na classe Tree, fazem com que se ga- nhe produtividade ao programar. • Árvore splay: é um tipo de árvore binária que transforma os Nós acessados em Raízes por meio de rotações, fazendo com que os elementos mais acessados tornem-se mais rapida- mente localizáveis na busca. Árvore binária e de busca A Árvore de busca binária (abb) é uma aplicação da estrutura Ár- vore que armazena suas chaves de forma que, a partir da Raiz, todos os elementos menores que sejam alocados na subárvore es- querda e todos os elementos maiores sejam alocados na subárvore da direita, fazendo com que as buscas não dependam de algorit- mos complexos, mas apenas de comparações entre maior ou me- nor até o Nó terminal. 184 Caro(a) aluno(a), caso queira conhecer um pouco mais sobre Árvore binária, sugerimos que acesse: https://joaoarthurbm.github.io/eda/ posts/bst/. Caro(a) aluno(a), chegamos ao final da nossa jornada de estudos. Neste material, aprendemos sobre o uso de algoritmos recursivos e a sua implementação. Além disso, apresentamos o código de al- guns desses algoritmos. Vimos também os algoritmos de ordenação e como são implementados em vetores. Em seguida, entramos na área de Estrutura de Dados, apresentando várias estruturas dife- rentes e os conceitos usados na criação delas. Ao longo do nosso material, você deve ter aprendido que os códigos apresentados e as dicas de páginas que apresentam a implemen- tação de várias dessas estruturas podem ajudar a entender melhor como elas funcionam. Dito isto, sugerimos que tente implementar esses algoritmos usan- do a linguagem Java. Lembre-se que apresentamos a Classe Collec- tion para algumas dessas estruturas. Esperamos ter contribuído com o seu aprendizado. Sucesso! DICA SINTETIZANDO 185 Referências UNIDADE 1 FUGERY, Sergio. Programação Orientada a Objetos: Conceitos e técnicas. Editora Érica. 2014. KAMIENSKI, C. A. . Introdução ao Paradigma de Orientação a Obje- tos. Centro Federal de Educação Tecnológica da Paraíba - Diretoria de Ensino - Coordenação de Informática, 1996. LAFORE, R. Estrutura de dados e algoritmos em Java. Rio de Janei- ro: Ciência Moderna, 2004. PROGRAMAÇÃO Orientada a Objetos (Parte 12): a classe Object e seus métodos. Postado por Carlos Emilio Padilla Severo. (10min. 11s.). son. color. port. Disponível em: <https://www.youtube.com/ watch?v=2ob3rYxpUlE>. Acesso em: 03 jun. 2020. SANTOS, Rafael. Introdução à programação orientada a objetos usando JAVA. 4. reimp. Rio de Janeiro: Elsevier, 2011. UNIDADE 2 BATISTA, R. S.; MORAES, R. A. Introdução à programação orientada a objetos. Teresina: Instituto Federal de Educação, Ciência e Tecno- logia do Piauí - IFPI, 2013. CARVALHO, V. A.; TEIXEIRA, G. F. Programação orientada a objetos: curso técnico em informática. Colatina: Instituto Federal de Educa-ção, Ciência e Tecnologia do Espírito Santo – IFES, 2012. Disponível em: <http://redeetec.mec.gov.br/images/stories/pdf/eixo_infor_ comun/tec_inf/081112_progr_obj.pdf>. Acesso em: 08 jun. 2020. MENDES, D. R. Programação Java com ênfase em orientação a ob- jetos. -. São Paulo: Novatec, 2009. MOREIRA NETO, O. Entendendo e dominando o Java. 3. ed. São Paulo: Digerati, 2009. SANTOS, R. Introdução à programação orientada a objetos usando Java. 2. ed. São Paulo: Elsevier, 2013. 186 LAFORE, R. Estrutura de dados e algoritmos em Java. Rio de Janei- ro: Ciência Moderna, 2004. ORACLE. Tecnologias Java. 2019. Disponível em: <https://www. oracle.com/br/java/technologies/>. Acesso em: 25 mar. 2019. TIOBE. TIOBE index for march 2019. Disponível em: <https://www. tiobe.com/tiobeindex/>. Acesso em: 25 mar. 2019. UNIDADE 3 BEDER, D. M. Introdução à programação orientada a objetos em Java. 1. ed. São Paulo: EDUFSCar. 2014. Disponível em: <http://livresaber. sead.ufscar.br:8080/ jspui/bitstream/123456789/2659/1/SI_Ednal- do_ProgramacaoOrientadaObj_2. pdf> Acesso em 14 abr. 2020. CURSO de Java 42: orientação a objetos: palavra-chave final. Pos- tado por: Loia- ne Groner. (12min. 0s.). son. color. port. Disponível em: <https://youtu.be/858FJ6D- QRVg>. Acesso em: 16 jun. 2020. JAVA8 - aula 2 -expressão lambdas. Postado por eXcript. (5min. 44s.). son. color. port. Disponível em: <https://www.youtube.com/ watch?v=4EyhqB-JIXs&list=PLes- CEcYj003SbwnNhQ9eyF7I- jPlEK0uVg&index=2>. Acesso em: 16 jun. 2020. KAMIENSKI, C. A. . Introdução ao Paradigma de Orientação a Obje- tos. Centro Federal de Educação Tecnológica da Paraíba - Diretoria de Ensino - Coordenação de Informática, 1996. KNOERNSCHILD, K. Java design: objects, UML, and process. 1. ed. Indianópolis: Pearson Education Corporate, 2001. RICARTE, I. L. M. Programação orientada a objetos: uma aborda- gem com Java. Campinas: Unicamp, 2001. Disponível em: <http:// www.dca.fee.unicamp.br/cur- sos/PooJava/Aulas/poojava.pdf>. Acesso em: 10 jun. 2020. SANTOS, R. Introdução à programação orientada a objetos usando Java. Rio de Janeiro: Elsevier, 2011. UNIDADE 4 BEDER, D. M. Introdução à programação orientada a objetos em Java. 1. ed. São Paulo: EDUFSCar. 2014. Disponível em: <http://livresaber. 187 sead.ufscar.br:8080/jspui/bitstream/123456789/2659/1/SI_Ednal- do_ProgramacaoOrientadaObj_2.pdf> Acesso em 14 abr. 2020. CURSO de Java 42: orientação a objetos - palavra-chave final. Pos- tado por: Loiane Groner. (12min. 0s.). son. color. port. Disponível em: <https://www.youtube.com/watch?v=858FJ6DQRVg>. Acesso em: 16 jun. 2020. JAVA8 - aula 2 - expressão lambdas. Postado por eXcript. (5min. 44s.). son. color. port. Disponível em: <https://www.youtube.com/watch?- v=4EyhqB-JIXs&list=PLes-CEcYj003SbwnNhQ9eyF7IjPlEK0uVg&in- dex=2>. Acesso em: 16 jun. 2020. KNOERNSCHILD, K. Java design: objects, UML, and process. 1. ed. In- dianópolis: Pearson Education Corporate, 2001. SANTOS, R. Introdução à programação orientada a objetos usando Java. Rio de Janeiro: Elsevier, 2011.