Prévia do material em texto
03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 1/19 PROGRAMAÇÃO VOLTADA A OBJETOS AULA 1 Prof. Leonardo Gomes 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 2/19 CONVERSA INICIAL Nesta etapa, faremos uma introdução ao tópico de programação orientada a objetos e à linguagem de programação Java. Falaremos sobre os diferentes paradigmas de programação com foco na orientação a objetos. Daremos também os primeiros passos no Java, uma importante linguagem de programação que servirá de base para este estudo como um todo. Ao final desta etapa, esperamos atingir os seguintes objetivos que serão avaliados ao longo do estudo da forma indicada: Quadro 1 – Objetivos Objetivos Avaliação 1 – Contextualização sobre o paradigma de programação orientado a objetos e à Linguagem de programação Java Questionário e questões dissertativas 2 – Capacidade de criar projetos Java simples dentro da IDE Eclipse Questionário e questões dissertativas 3 – Desenvolver programas básicos utilizando Java Questionário e questões dissertativas Fonte: Gomes, 2020. TEMA 1 – PARADIGMAS DE PROGRAMAÇÃO E HISTÓRIA DA PROGRAMAÇÃO ORIENTADA A OBJETOS Neste tópico, discutiremos os diferentes paradigmas de programação e um pouco de seu histórico com foco na Orientação a Objetos. Antes mesmo do surgimento dos primeiros computadores na década de 1940, já existiam modelos e regras formais para descrever algoritmos 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 3/19 que serviram de base para as primeiras linguagens de programação propriamente ditas. Chamamos de paradigma de programação um dos meios de classificar linguagens de programação de acordo com sua estruturação, abstração e funcionalidades. Nestes primeiros anos da programação, destacamos o paradigma procedural, o paradigma funcional, o paradigma lógico e o paradigma orientado a objetos. Geralmente, iniciamos o aprendizado na programação por meio do paradigma procedural, e a linguagem C utiliza exclusivamente esse paradigma. Nele, baseamos nosso código em comandos que mudam o estado da memória de forma detalhada e sequencial, ou seja, procedural. Reservamos espaços de memória por meio de variáveis para armazenar nossos dados e criamos funções que definem comportamentos desejados para esses dados. Esse paradigma se aproxima da forma que o processador interpreta os comandos e trabalha com os dados efetivamente, dando maior liberdade para o programador desenvolver algoritmos eficientes. Junto ao paradigma procedural, surgiu também o paradigma funcional, no qual o código é pensado e descrito por meio da resolução de funções matemáticas. Esse paradigma facilita o desenvolvimento de certos algoritmos que são mais facilmente representados de forma puramente matemática. Tal paradigma ainda é muito utilizado em certas linguagens que combinam paradigmas como a linguagem Scala. Outro importante paradigma que surgiu na década de 1950 foi o paradigma lógico, no qual uma base de declarações lógicas matemáticas é gerada pelo programador, com a qual o computador se baseia para calcular respostas fundamentadas na base inicialmente criada. Uma linguagem proeminente que adota esse paradigma de forma exclusiva é o Prolog, que possui diversas aplicações na inteligência artificial. Em meados dos anos 1960, um pesquisador chamado Alan Kay, influenciado por sua formação em biologia e matemática, além de outras tecnologias da época, como o Sketchpad e a Simula, pensou em uma nova arquitetura de programação que chamou de paradigma orientado a objetos. Em sua concepção original, a programação orientada a objeto deveria se basear em células independentes trocando mensagens entre si e retirando o foco dos dados. As linguagens de programação Simula e Smalltalk foram as primeiras a adotar as práticas propostas por Alan Kay. As linguagens modernas de programação se inspiram e combinam esses paradigmas oferecendo diversas opções de estratégias para o desenvolvimento de algoritmos. 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 4/19 As soluções aplicadas em um paradigma podem ser de forma direta ou indireta representadas em outro, tornando cada paradigma ótimo para um tipo de situação diferente. Um programador versado em diversos paradigmas terá um ferramental maior na hora de gerar soluções para problemas complexos. Neste estudo, vamos abordar o paradigma orientado a objetos, que é muito popular em soluções comerciais hoje por ser especialmente adequado para projetos de grande escala que necessitam de constante manutenção e ampliação. Figura 1 – Representação dos paradigmas de programação Fonte: Gomes, 2020. Imperativo são os paradigmas voltados para representar os comandos que resolvem o problema, focado em como resolver. Em contrapartida, temos os paradigmas declarativos que não focam em mudanças de estado sequencial de um programa, mas sim no que se deve resolver. Dessas duas classes derivam os paradigmas que discutimos até aqui. 1.1 PARADIGMA DE PROGRAMAÇÃO ORIENTADA A OBJETOS O paradigma orientado a objeto foi pela primeira vez aplicado de forma adaptada na linguagem de programação Simula 67, nos anos de 1960, posteriormente também sendo utilizado de forma exclusiva na linguagem Smalltalk da Xerox. Sua grande popularidade influenciou todas as principais linguagens de programação de hoje, C++, C#, PHP, Python e Java, que é a linguagem base do nosso estudo. 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 5/19 O pesquisador Alan Kay, que atuou na empresa Xerox, foi quem liderou o projeto que encabeçou as primeiras linguagens de programação baseadas em orientação a objetos, sendo inclusive responsável por cunhar o termo. Alan Kay propôs uma abstração em que a programação de computadores poderia ser encarada como um organismo vivo, no qual cada célula é entendida como um elemento independente, relacionando-se com outras células de forma a manter o funcionamento de todo um organismo. Alan Kay expandiu essa ideia para outras relações além das intercelulares, pois a forma como entendemos o mundo se dá por meio das relações de objetos e pessoas, cada um com suas características individuais, realizando ações uns sobre os outros e permitindo a construção de sistemas complexos. Essa forma de pensar é natural e intuitiva para nós, pois emula como vemos o mundo. Em programação estruturada, o foco está nas ações: em um programa de computador de vendas, por exemplo, o conjunto de instruções que efetua a compra de itens por um cliente geralmente seria agrupado em uma função chamada comprar(), porém, em um sistema orientado a objetos, pensamos primeiro no objeto “quem está realizando essa compra?”, e teríamos um objeto cliente com todos os dados dos clientes, bem como um comando como cliente.comprar(). Assim, associamos sempre os objetos (cliente neste exemplo) à ação (comprar), que deixa mais clara e intuitiva a leitura dos códigos, pois há maior contexto quando pensamos em um “cliente comprando” que simplesmente na ação de comprar isolada, trazendo o código mais perto de como entendemos o mundo. Associando essa prática a outros conceitos, como herança, polimorfismo, encapsulamento, entre outros, promovemos ganhos de produtividade, especialmente na manutenção de códigos pela sua maior facilidade no entendimento. No próximo tópico, será apresentada a linguagem de programação Java, que é completamente voltada ao paradigma de orientação a objetos. TEMA 2 – HISTÓRIA DO JAVA Neste tópico vamos discutir o histórico, a origem e a importância da linguagem Java. Conhecer este contexto nos ajuda a compreender a função dos principais atores tecnológicos da computação. 2.1 PRIMEIROS ANOS 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 6/19 A Linguagem Java surgiu no início dos anos 1990 em uma importante empresa de tecnologia chamada SunMicrosystems. A equipe responsável pelo desenvolvimento desta linguagem atuava no chamado Green Project e foi liderada pelo cientista da computação James Gosling. O Green Project tinha por objetivo gerar tecnologias voltadas para conectividade de equipamentos domésticos. O primeiro produto foi um dispositivo chamado StarSeven, que semelhante a um tablet, poderia receber comandos de toque e controlar os demais dispositivos. A comunicação entre os dispositivos deveria se dar por meio de uma linguagem de programação que fosse independente de plataforma. A equipe de Gosling a princípio tentou adaptar o C++ para esta tarefa, mas, por fim, optou por desenvolver uma linguagem própria, que chamaram na época de Oak (do inglês, carvalho). A linguagem Oak possuía o diferencial de ser uma interpretada por uma máquina virtual fornecida pela Sun, e todo o dispositivo que rodasse a máquina virtual da Sun seria capaz de executar códigos Oak sem a necessidade de compilação específica para o dispositivo em questão. Embora o projeto de conectividade doméstica não tenha emplacado como planejado, a tecnologia de uma linguagem independente de plataforma casou muito bem a internet e os navegadores que se popularizaram muito na época. Por questões legais e de registro de marca, a linguagem Oak mudou seu nome para Java em 1995. Com isso, popularizou-se muito o uso de pequenos programas chamados applets, os quais eram baixados de um servidor web e poderiam ser executados na máquina dos clientes independentemente da plataforma, desde que eles possuíssem uma máquina virtual Java previamente instalada. 2.2 O JAVA MODERNO Nas décadas de 1990 e 2000, a popularização da internet levou a uma grande popularização da linguagem Java, que recebeu suporte de grandes companhias de informática, tais como a IBM. De certa forma, o objetivo inicial do Green Project foi atingido com o Java sendo utilizado para conectar todo o tipo de dispositivo móvel, tais como celulares, tablets, computadores e até mesmo uma das primeiras sondas espaciais robóticas a atingir o solo marciano em 2004. Figura 2 – Opportunity, sonda espacial que realizou missão exploratória do solo marciano 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 7/19 Créditos: rzulev/Shutterstock. A linguagem Java adotou licença de software livre GPL v3 em 2006, o que significa que os programas feitos pela Sun para permitir o funcionamento do Java e de suas bibliotecas possuem código aberto para consulta, cópia e modificação, desde que o desenvolvedor que faça modificações também disponibilize seu código livremente. A Sun Microsystems foi adquirida pela Oracle em 2010, que é quem oferece suporte ao Java até hoje. Embora applets não sejam mais usualmente adotadas, a popularidade do Java se mantém, de forma que essa linguagem é adotada nos aplicativos do sistema operacional Android, em diversos tipos de servidores, em leitores de livros digitais como Kindle e TV digital DTVI e até no tradicional programa de Imposto de Renda brasileiro, dentre outros muitos exemplos. No momento da edição deste documento, a linguagem Java se encontra na versão 13, lançada em setembro de 2019. Trata-se de uma linguagem Orientada a Objetos com sintaxe baseada na linguagem C. No próximo tópico, discutiremos questões técnicas da arquitetura e organização das soluções Java. TEMA 3 – ORGANIZAÇÃO DO JAVA 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 8/19 Agora, vamos debater em detalhes a tecnologia Java em si. Mais que uma linguagem e bibliotecas, o Java necessita de um ambiente próprio para o seu funcionamento. Ele também acompanha um conjunto completo de programas que também iremos apresentar. Tradicionalmente, as linguagens de programação passam por um processo denominado compilação, o qual transforma o código de alto nível escrito pelo programador no que chamamos de código de máquina ou binário. Esse código nativo é lido pelo processador que executa as instruções. Os programas .exe do Windows são um exemplo de binário. Em contrapartida, existem também linguagens que são interpretadas que não passam por esse processo de compilação. O código escrito pelo programador em tempo de execução é traduzido para código de máquina. Códigos interpretados são essencialmente menos eficientes que códigos compilados pela quantidade extra de instruções que requer a interpretação dele. Porém, possuem a vantagem de serem facilmente portados para diferentes plataformas justamente por não necessitarem de recompilação para cada plataforma. Dessa forma, um mesmo código interpretado sem alterações pode ser executado em diferentes computadores com sistema operacional Linux, Windows ou Mac. Quanto ao Java, dependendo do ambiente de execução, é possível trabalhar com ele tanto interpretado quanto compilado. Porém, ele tipicamente funciona em um processo em dois passos. Primeiro, o código de alto nível é compilado para um formato chamado bytecode. Esse código bytecode posteriormente é interpretado por um programa chamado máquina virtual Java, em inglês Java Virtual Machine. Ao longo dos nossos estudos, chamaremos pela sigla JVM. As JVM para as principais plataformas são mantidas pela Oracle, mas podem ser desenvolvidas de forma independente para os mais diversos dispositivos por qualquer equipe, visto que possuem licença livre. Portanto, um mesmo bytecode pode ser executado em qualquer sistema que possua uma JVM. Pelo fato de o Java utilizar JVM para interpretar seus bytecodes, existe uma perda em desempenho quando comparado a um código compilado nativo. Porém, as JVM evoluíram muito ao longo dos anos, e uma das principais tecnologias neste sentido é o chamado Hotspot. Estudos estatísticos mostram que, na grande maioria dos programas, 80% do processamento se concentra em somente 20% do código. O Hotspot é uma tecnologia que identifica esses trechos de código com muito processamento e executa uma compilação dos mesmos durante a execução do código. 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 9/19 Essa tecnologia de compilação em tempo de execução é chamada de Just in time compilation, mais conhecida pela sigla JIT. A combinação das duas tecnologias, dentre outras melhorias, tornou o Java muito eficiente e diminuiu a distância em relação às linguagens compiladas. No contexto da computação, nós chamamos de Benchmark os testes que buscam comparar desempenho. Na figura 3, vemos um benchmark do Java comparado ao C++ em três algoritmos diferentes. Em um dos testes, o Java chega a apresentar um tempo de execução melhor que o C++. No link presente na legenda da figura, mais testes podem ser vistos. Figura 3 – Benchmark comparando Java ao C++ em três algoritmos distintos. Versão dos compiladores: Java, openjdk13 17-9-2019; C++, g++ 9.2.1-9ubuntu2 Fonte: <https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/java-gpp.html>. Quando se deseja apenas executar bytecodes do Java, é necessário instalar em sua máquina o Ambiente de Execução Java (em inglês, Java Runtime Environment), mais conhecido pela sigla JRE. Ele é composto principalmente pela JVM e bibliotecas padrão do Java. O JRE pode ser encontrada para as mais diversas plataformas na página oficial da Oracle. Agora, quando desejamos programar em Java, precisamos instalar o Kit de desenvolvimento Java (em inglês, Java Development Kit), mais 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 10/19 conhecido pela sigla JDK. Ele é composto por um conjunto de utilitários, como o compilador de bytecode, além de uma JRE. A JDK também é encontra na página oficial da Oracle. Neste tópico, vimos muitas siglas e termos. Para facilitar o entendimento e disponibilizar uma rápida referência, vamos agrupar todos no quadro a seguir: Quadro 2 – Termos e siglas relacionados ao Java Nome Tradução Definição Java Virtual Machine JVM Máquina Virtual Java Programa responsável por interpretar e executar o códigoBytecode Java Bytecode Código em byte O equivalente ao executável Java, o Bytecode é gerado após o processo de compilação dos códigos fontes Java Java Development Kit JDK Kit de desenvolvimento Java Conjunto de bibliotecas, compiladores e demais ferramentas para o desenvolvimento de programas Java Java Runtime Environment JRE Ambiente de execução Java Conjunto de biblioteca padrão Java e JVM para execução de códigos Bytecode Just in time compilation JIT Compilação dinâmica Técnica que permite a JVM compilar partes críticas do código em linguagem de máquina em tempo de execução, oferecendo significativo ganho de memória Garbage Collection Coletor de Lixo Técnica que isenta o programador da responsabilidade de desalocar memória, a JVM regularmente se encarrega de liberar memória alocada não utilizada Fonte: Gomes, 2020. No próximo tópico, daremos sequência ao nosso estudo e faremos nosso primeiro código Java. TEMA 4 – VERSÕES DO JAVA E PRIMEIRO CÓDIGO Neste tópico, vamos finalmente colocar a mão na massa. Faremos nosso primeiro projeto Java com base no programa Eclipse. 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 11/19 4.1 VERSÕES DO JAVA E A IDE ECLIPSE No caso do Java, assim como na grande maioria das linguagens de programação, é possível codificar utilizando qualquer editor de texto e, posteriormente, por meio de um compilador dedicado, gerar o seu binário (Bytecode, no caso do Java). Porém, é muito mais produtivo, especialmente em projetos de grande escala, utilizar um programa próprio direcionado ao desenvolvimento de códigos que combine editor de texto, compilador, depurador, bibliotecas, entre outras funcionalidades. Esse tipo de programa é conhecido como um ambiente de desenvolvimento integrado, do inglês Integrated Development Environment, ou apenas IDE. Existem diversas IDEs de excelente qualidade para o Java, das quais destacamos o Netbeans, da própria Oracle; o IntelliJ IDEA, da empresa JetBrains; e o mais popular de todos, o Eclipse, inicialmente da IBM, mas, hoje, com licença software livre. O Eclipse é o que utilizaremos em nossos estudos, mas todas funcionam de forma análoga. O Eclipse, originalmente desenvolvido como IDE para Java, foi adaptado por plugins desenvolvidos pela comunidade para as mais diversas linguagens de programação, podendo ser encontrado no site: <https://www.eclipse.org/>. O Java conta com três versões principais, Java Micro Edition (ME), Standard Edition (SE) e Enterprise Edition (EE). A seguir, mencionamos suas características: O Java ME visa à construção de softwares para dispositivos embarcados, sistemas de propósito específico com poucos recursos computacionais. Ele é compatível com uma biblioteca básica de classes e se torna especialmente importante no contexto de soluções desenvolvidas pensando na Internet das Coisas; Java SE é a edição padrão do Java com o principal conjunto de bibliotecas, perfeita para desenvolver programas desktop e de console. Por console, entenda programas com interface puramente em modo texto, que são geralmente executados por prompt de comando do sistema operacional Windows ou terminal do Linux; Java EE é a edição mais completa que já vem equipada com bibliotecas prontas para soluções empresariais, especialmente voltadas para internet e banco de dados. Trata-se de uma série de especificações que foi desenvolvida integral ou parcialmente na forma de servidor de aplicações por diversos fornecedores. É uma importante tecnologia que ajuda a formar a espinha dorsal da internet atual. https://www.eclipse.org/ 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 12/19 A IDE do Eclipse conta com diversas opções para instalação. Para nosso estudo, busque a versão SE ou EE. 4.2 PRIMEIRO CÓDIGO As principais IDEs incluindo Eclipse possuem o conceito de projeto. O projeto engloba todas as classes e bibliotecas necessárias para a geração de um programa. Na figura 4, vemos a estrutura de um projeto Java na IDE Eclipse. Os principais elementos que compõem o projeto são: Bibliotecas: são bytecodes com funcionalidades específicas implementadas. Permitem ao programador reaproveitar códigos geralmente desenvolvidos por equipes diversas e que já são muito bem testados e eficientes. O Eclipse já inclui uma biblioteca básica em todos os projetos; Pacotes: conceito semelhante ao de pasta/diretório para organizar a estrutura dos códigos Java. Supondo que temos um projeto grande com muitos códigos, podemos agrupar os arquivos ligados a bancos de dados de um pacote que esteja ligado à interface visual de outro, por exemplo. Usualmente, o pacote principal de um projeto é nomeado com o inverso do domínio da sua instituição. Por exemplo, empresa.com se torna com.empresa. Essa é uma prática comum, porém, não é necessária. Esse pacote principal fica inserido dentro de uma pasta nomeada src (que vem da palavra source, isto é, código, em inglês); Classe: os códigos são descritos em arquivos com extensão .java, geralmente um arquivo por classe. Figura 4 – Estrutura de projeto básico Eclipse com uma única classe Uma vez aberto o programa Eclipse, para criar um projeto novo basta ir à opção File/New Project. Em seguida, dê um nome apropriado ao projeto e utilize as opções padrões. O projeto 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 13/19 criado estará vazio. Para criar uma primeira classe Java que receberá o método principal, basta clicar com o botão direito sobre a pasta com o nome do projeto e ir à opção New/Class. Dê um nome para a classe e um nome para o pacote. Por se tratar da primeira classe com o método principal (equivalente ao main do C/C++), é interessante marcar no checkbox a opção public static void main(String[] args). Um arquivo com aproximadamente o seguinte código deve surgir: Vamos analisar linha por linha o código acima: packagecom.empresa; Indica o nome do pacote no qual a classe está; publicclass PrimeiraClasse Esta é a linha na qual se informa o nome da classe. O comando public indica que a classe pode ser acessada de forma pública por outras classes. Os conceitos de classes públicas e privadas e suas implicações serão discutidos em detalhes em conteúdo posterior; public static void main(String[] args) { Esta linha é a declaração do método, na qual static indica que o método pertence à classe, e não ao objeto – o conceito de métodos estáticos será discutido com detalhes em conteúdo posterior –; main é o nome do método principal, equivalente à função principal em linguagens como C/C++ e indica que esse método será o primeiro a ser executado pelo programa; e String [] args é a declaração de um array (estrutura semelhante a uma lista) de objetos da Classe String como parâmetro de entrada do método. Caso o programa seja executado em modo console, eventuais parâmetros de execução na chamada do programa serão lidos e direcionados para a variável args. As chaves { } representam blocos de código, marcam onde começa e termina a classe e onde começa e termina o método. Como primeiro comando, escreva dentro do bloco de código do método main o seguinte comando: 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 14/19 Esse é o comando que imprime a mensagem que estiver entre aspas na tela em modo console. Atenção ao ponto-e-vírgula ao final de cada comando Java. Para executar e testar o seu primeiro programa, vá na opção Run/run ou utilize o atalho CTRL + F11. Se tudo ocorrer bem, você verá a sua mensagem impressa na tela. Parabéns pelo seu primeiro programa Java! Dica: o Eclipse conta com um atalho para o comando acima, basta escrever sout e fazer o comando CTRL + Espaço. Existem diversos atalhos dentro do Eclipse, assim, procure os principais na internet ou no próprio Eclipse, dentro de Help/Show Active Keybindings. Dominar alguns atalhos contribuirá para um aumento significativo na produtividade em longo prazo. Alguns dos comandos Java parecemlongos e intimidadores quando comparados com os de outras linguagens, porém, com a experiência, você notará que eles seguem uma padronização que se torna intuitiva e clara com o tempo. O Java conta com uma biblioteca padrão muito extensa que permite um grande reaproveitamento de códigos. No próximo tópico, faremos um apanhado geral sobre os principais comandos Java. TEMA 5 – VISÃO GERAL SOBRE O CÓDIGO JAVA Neste tópico, vamos avançar a discussão sobre a linguagem Java, mas sem entrar em detalhes sobre a orientação a objetos. Além disso, também vamos passar brevemente pelos principais comandos e estruturas de dados disponíveis nessa linguagem. 5.1 PRINCIPAIS COMANDOS A sintaxe da linguagem Java é baseada em C/C++, portanto, quem as conhece automaticamente já domina grande parte dos principais comandos Java. No entanto, mesmo quem vem de outras linguagens como Python terá facilidade, visto que a lógica continua a mesma e muda apenas a forma de descrever certos comandos. 5.1.1 ENTRADA E SAÍDA 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 15/19 Vimos em conteúdo anterior, um primeiro comando de impressão no console, porém, a seguir vemos algumas de suas variantes: Quanto à leitura de dados, é necessário realizar alguns passos. A seguir, temos um código Java que utilizaremos para exemplificar. Primeiro precisamos importar a biblioteca java.util.Scanner, que possui as definições da classe Scanner necessárias para a leitura. Veja o código a seguir na linha com a Obs 1, o comando import é utilizado para esta tarefa, e as importações devem vir sempre logo abaixo da declaração do nome do pacote. Em seguida, declaramos uma variável (objeto) do tipo (classe) Scanner. Essa classe é responsável por ler os dados de alguma fonte passada por parâmetro, no caso, o parâmetro deve ser System.in, que aponta a entrada padrão do sistema, o teclado. Veja a Obs 2 no código, o nome do objeto pode ser qualquer um, no caso, optou-se pelo nome teclado. Na sequência, devemos utilizar o objeto que declaramos para fazer a leitura. Para ler um valor inteiro, teclado.nextInt(); para um valor real, teclado.nextFloat() ou teclado.nextDouble(); e para ler uma string, teclado.next(). Veja a Obs 3 no código para um exemplo. 5.1.2 COMANDOS DE DESVIO 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 16/19 Assim como nas principais linguagens de programação, o principal comando de desvio é o if(). Entre parênteses, colocamos a expressão associada, se a expressão for verdadeira, o fluxo do código é desviado. As variantes com else if e a variante com else também estão presentes na linguagem. No exemplo a seguir, o código que imprime verifica o valor de uma variável idade e imprime a mensagem Criança, Adolescente ou Adulto, dependendo do valor. Atenção: o ponto-e-vírgula não aparece depois do comando if. O comando switch case também está presente na linguagem Java, permitindo o desvio de fluxo. Primeiro, a expressão dentro do switch é avaliada, e o código é desviado para o case pertinente ou default, caso nenhum esteja adequado. A seguir, um exemplo genérico do seu uso: 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 17/19 5.1.3 COMANDOS DE REPETIÇÃO Os principais comandos de repetição também estão presentes em Java: while, do-while e for. Entre parênteses vai a condição que deve ser avaliada para analisar se o loop deve repetir ou não. Atenção: o ponto-e-vírgula não aparece depois do comando while. O comando for é dividido em três partes separadas por ponto-e-vírgula. Na primeira, trata-se do que deve ser executado antes do loop, sendo geralmente a inicialização de alguma variável de controle. Na sequência, a condição de continuidade do loop, semelhante à condição do while e, por fim, o que deve ser executado ao final de cada iteração do loop, geralmente um incremento. 5.2 TIPOS DE DADOS No Java, os dados são tipados, ou seja, antes de criar uma variável, é necessário declará-la e indicar o tipo de dado. No Java, chamamos de primitivas as variáveis dos tipos básicos presentes nas principais linguagens de programação, como a seguir. Em um nível de abstração maior, os dados podem ser armazenados também em tipos não primitivos, como String, Array e Classes. No quadro a seguir, temos as primitivas: 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 18/19 Quadro 3 – Tipos básicos no Java Tipo Tamanho Descrição byte 1 byte Números inteiros (-128 até 127) short 2 bytes Números inteiros (-32.768 até 32.767) int 4 bytes Números inteiros (-2.147.483,648 até 2.147.483.647) long 8 bytes Números inteiros (-9.223.372.036.854.775.808 até 9.223.372.036.854.775.807) float 4 bytes Armazena números inteiros e fracionários até 6 e 7 dígitos decimais double 8 bytes Armazena números inteiros e fracionários até 15 dígitos decimais boolean 1 bit Armazena apenas 0 ou 1 (false ou true) Char 2 bytes Armazena um único caractere, letra Stores a single character/letter or ASCII values Fonte: Gomes, 2020. 5.2.1 STRING As strings ou sequência de caracteres no Java são representadas com uma classe chamada justamente de String. No Java, constantes da classe String devem ser escritas entre aspas duplas. Elas possuem diversos métodos internos. No código a seguir, temos um exemplo com comentários: 5.2.2 ARRAYS Os arrays também são essencialmente classes que trabalham de forma análoga ao C/C++ e muito semelhante às listas do Python. Podem ser declarados entre chaves e acessados com colchetes indexados por meio do valor zero. Segue o exemplo: 03/11/2022 11:20 UNINTER https://univirtus.uninter.com/ava/web/roa/ 19/19 Os arrays também contam com diversos métodos, tais como o método length(), que como na classe String, retorna a quantidade de itens do array. Considerando o exemplo anterior, o comando nomes.length() retornaria o número 4. FINALIZANDO Nesta etapa, iniciamos nosso aprendizado introduzindo o contexto histórico dos paradigmas de programação e da linguagem Java. Aprendemos a arquitetura da linguagem, quais suas versões e fizemos nosso primeiro programa. Por fim, realizamos uma visão geral sobre a linguagem Java e os principais comandos. Ainda não entramos em contato direto com Programação Orientada a Objetos em si, mas preparamos o terreno oferecendo o ferramental para nos aprofundarmos utilizando o Java como base para o estudo. Posteriormente, exploraremos mais a criação e a utilização das classes, o principal conceito dentro da programação orientada a objetos. 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 1/24 PROGRAMAÇÃO ORIENTADA A OBJETOS AULA 2 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 2/24 Prof. Leonardo Gomes CONVERSA INICIAL Nesta aula, vamos abordar o principal conceito dentro do paradigma de programação orientado a objetos: as classes. As classes dentro de orientação a objetos são compostas principalmente por atributos e métodos, e é por meio delas que geramos os objetos que dão nome ao paradigma. Além da definição dos conceitos, teremos exemplos do uso das classes e objetos com maiores detalhes de como aplicar esses conceitos. Ao final desta aula, esperamos atingir os seguintes objetivos, que serão avaliados ao longo da disciplina da forma indicada: Quadro 1 – Objetivos Objetivos Avaliação 1. Aplicar os conceitos de classes e objetos em linguagem Java. Questionário e questões dissertativas 2. Desenvolver algoritmos que combinam, agrupam e interagem com diferentes objetos. Questionário e questões dissertativas 3. Aplicar o conceito de construtor em linguagem Java. Questionário e questões dissertativas TEMA 1 – CLASSES E ATRIBUTOS Neste tema, vamos debater os dois conceitos mais importantes da Programação Orientada a Objetos (POO), as classes e objetos. 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 3/24 Quando programamospensando em POO, o objetivo é modelar o mundo real dentro do contexto que nos interessa. Esse modelo deve ser simples e considerar apenas os elementos que forem relevantes para o problema abordado. Os objetos são os elementos em si que compõem o nosso sistema, enquanto as classes são a descrição desses objetos, e como o nome sugere, classificam um conjunto de objetos que pertençam a um mesmo conjunto. Por exemplo, suponha que desejamos criar um jogo eletrônico que implemente uma corrida de carros. É pertinente representar todos os diferentes carros que participam dessa corrida. Cada carro individualmente possui suas características próprias, uma cor, um valor de velocidade máxima, um nível de combustível, uma localização na pista de corrida, entre outros. Cada carro individualmente, portanto, é um objeto. Ainda no exemplo da corrida, observe que todos os carros são representados pelo mesmo conjunto de características, embora possuam valores diferentes para cada variável. E dentro da POO o código que descreve os atributos que todos os carros possuem é chamado de classe. Em resumo, enquanto o termo carro em si de maneira global se refere à classe, se falamos do carro vermelho que está liderando a corrida, estamos falando de um carro específico, portanto, de um objeto. De forma geral cada objeto possui três aspectos principais: Atributos: São as variáveis que descrevem o objeto. Métodos: São como funções que dizem o que cada objeto faz. Estado: Seria o valor de cada atributo que representa aquele objeto específico. O conceito de registro (ou struct) é presente em diversas linguagens de programação e, semelhante ao registro, as classes possuem atributos que caracterizam seus objetos. Na Figura 1, novamente utilizamos o exemplo de uma classe Carro para ilustrar esse conceito. Todos os carros possuem os mesmos atributos, porém, em estados diferentes. Figura 1 – Representação da classe Carro com os atributos cor, modelo e velocidade com 3 objetos distintos cada um com um estado 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 4/24 Crédito: Colorlife/Shutterstock. Já os métodos representam o que cada objeto da classe é capaz de fazer, são funções associadas a cada objeto. A classe carro, por exemplo, poderia ter o método acelerar(int), que receberia um valor inteiro e modificaria a velocidade do objeto pelo valor passado por parâmetro. A chamada do método acelerar com o parâmetro 50 associado ao objeto Sally: Sally.acelerar(50) faria o estado do atributo Velocidade do objeto Sally ser modificado de 210 para 260, por exemplo. 1.1 CLASSE EM JAVA O Java é uma linguagem orientada a objetos, o que significa que todo o código que escrevemos está dentro de alguma classe e as interações entre os dados se dão por meio de métodos e objetos. A convenção entre os programadores Java é a de criar um arquivo separado para cada Classe Java. O arquivo que contém o método main também é uma classe própria geralmente. Anteriormente, fizemos o nosso primeiro programa (código abaixo) e nele observe a linha: A palavra public diz respeito à visibilidade quais outras classes poderão visualizar o código. Estudaremos mais detalhes sobre visibilidade posteriormente, por hora basta entender que é 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 5/24 necessário o comando public. Na sequência, temos o comando class PrimeiraClasse, que indica que entre chaves está o código de uma classe chamada PrimeiraClasse. Como exemplo, vamos fazer um programa Java com duas classes. A classe Principal e uma classe Aluno que representará quais dados cadastrais possuem os alunos de uma instituição. Você já aprendeu, anteriormente, como fazer um programa básico com uma única classe contendo o método main. Para a primeira classe, dê o nome de Principal e marque a opção no checkbox a opção public static void main(String[] args). Para a segunda classe, certifique-se de que a nova classe fique no mesmo pacote que a classe Principal e dê um nome de Aluno. As demais opções devem ser as padrões do sistema, especialmente a opção public static void main(String[] args), só pode existir um único método main no projeto. A estrutura do seu projeto deve ficar como na Figura abaixo. Figura 2 – Projeto Eclipse contendo duas classes Na classe Aluno, vamos declarar como atributo dados que desejamos representar. Para uma classe com nome, número de matrícula e CPF, ficaria assim: 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 6/24 De volta à classe Principal, dentro do método main, podemos criar objetos do tipo Aluno, modificar e comparar seus atributos. No código a seguir, declaramos dois alunos (Mario e Luigi) e queremos imprimir o nome do aluno mais antigo, ou seja, aquele que possui a menor matrícula. Confira o código: 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 7/24 No POO, um objeto pode ser chamado de instância de uma classe e criar uma nova instância, o que é chamado de instanciação. Na linha 2 e 7, temos os objetos sendo instanciados pelo comando new, seguido do nome da classe com abre fecha parênteses, esses parênteses estão presentes, pois se trata de um construtor, que funciona semelhante a um método que especifica detalhes de como o objeto deve ser inicializado. Veremos mais sobre construtores no futuro. O comando new reserva um espaço de memória para aquele objeto. Veja que, na linha 11, uma variável chamada antigo do tipo Aluno está sendo criada, porém, sem ser instanciada com o comando new. Nesse caso, a variável é apenas um ponteiro e serve para referenciar outros objetos. No caso, ela irá referenciar qual dos dois alunos (Mario ou Luigi) é o que possui a menor matrícula. Na linha 18 antigo.nome irá imprimir o nome Super Mario. Se mario.nome fosse alterado para outro valor a alteração passaria a valer para antigo.nome e vice-versa, mario e antigo são referências 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 8/24 (ponteiros) para o objeto criado na linha 2. Nas linhas 3, 4, 5, 8, 9 e 10 os atributos de cada objeto estão sendo acessados e modificados. O código orientado a objeto no caso geral é mais legível que na programação estruturada. Em uma versão estruturada cada aluno deveria ser representando com 3 variáveis diferentes para cada aluno, assumindo um sistema em que os alunos sejam representados por 50 atributos diferentes rapidamente o código ficaria muito difícil de administrar sem uso de classe ou registro/struct. Como desafio, considere o código anterior e modifique-o para termos um terceiro aluno, Yoshi. Descubra qual é o mais antigo dentre os três e imprima todos os dados dele. TEMA 2 – MÉTODOS Neste tema, vamos discutir os métodos que representam as ações que podem desempenhar cada objeto de uma determinada classe. Mais do que agrupar um conjunto de variáveis, as classes também possuem o que chamamos de métodos, que são equivalentes às funções em programação estruturada, um bloco de código que só é executado quando chamado. Os métodos podem receber dados de entrada (parâmetros) e opcionalmente um valor de retorno. A diferença básica do método em relação à função, é que o método está sempre associado a um objeto e consegue acessar os dados internos do objeto associado. Suponha a classe Aluno que criamos anteriormente. Dentro da classe, poderíamos criar um método conforme no código abaixo: 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 9/24 Na linha 5, criamos o método info() que irá imprimir na tela todos os dados referentes ao aluno, os atributos nome, matricula e CPF são acessados de dentro do contexto do método. No entanto, vale o questionamento: qual aluno está tendo os atributos acessados pelo método? Será aquele que está associado ao método? Vamos exemplificar com o código a seguir, supondo o seguinte código na main: 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 10/24 Naslinhas 1 até 9, declaramos os objetos. Na sequência, quando acessamos mario.info(); o método info() será executado considerando os dados dos atributos do objeto mario, enquanto luigi.info(); será executado considerando os atributos do objeto luigi. 2.1 EXERCÍCIOS Desenvolva os exercícios listados aqui e depois compare sua resposta com os códigos apresentados na seção Apêndice ao final deste documento. 1. Crie uma classe Nota, com três atributos reais chamados: nota1, nota2, nota3. E crie também dois métodos, cada um para calcular e retornar diferentes tipos de média. Média Aritmética; e Média ponderada (pesos 2, 3 e 4 respectivamente). 2. Crie uma classe Conta, para administrar contas correntes de um banco com os seguintes atributos: String correntista; float saldo; float limiteSaque; E os métodos: void sacar( float valor) void depositar( float valor) void info() 3. Complemente o exercício anterior implementando um método abaixo: transferir(Conta destino, float valor); O método deve transferir o valor passado por parâmetro do objeto conta que executa o método para a conta destino passada por parâmetro. TEMA 3 – PADRÕES E MODIFICADOR STATIC 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 11/24 Neste tema, vamos discutir dois assuntos importantes dentro da programação Java: os padrões de nomenclatura da linguagem e os usos do modificador static. 3.1 PADRÕES DE NOMENCLATURA Embora a linguagem Java em si não imponha um padrão de nomenclatura, aceitando códigos escritos com qualquer estilo, a comunidade de programadores Java adota certos padrões que são amplamente utilizados. Desde as principais e mais elaboradas bibliotecas internas do Java até os projetos mais simples utilizam os mesmos padrões que apresentaremos aqui facilitando a leitura dos códigos e padronizando códigos desenvolvidos por equipes de diversos programadores. O padrão principal da linguagem é o Camel Case, que consiste em descrever uma palavra composta ou frase sem dar espaços ou utilizar underline (ou sublinha), mas utilizando letras maiúsculas para indicar a letra inicial da próxima palavra. Suponha uma variável que representa o nome completo de um usuário poderia ser declarada no código como String nomeCompleto (letra maiúscula quando começa outra palavra) ao invés de nome_completo ou nomecompleto dentre outras opções. O nome Camel Case vem da semelhança das letras maiúsculas se sobressaindo no nome com as corcovas nas costas de um camelo. A Figura 2 ilustra a brincadeira com o nome. Figura 2 – Camel Case, padrão de escrita na programação Java Crédito: Eric Isselee/Shutterstock. 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 12/24 Dentro do Java, os padrões são os seguintes: Pacotes: são descritos inteiramente em letras minúsculas. Ex: com.empresa Classes: inicia com letra maiúscula e segue o Camel Case. Ex: Aluno Métodos, atributos e variáveis: iniciam com letra minúscula e seguem o Camel Case. Ex: nomeCompleto Constantes: inteiramente com letras maiúsculas separadas por underline. Ex: VALOR_PI A palavra reservada static possui dois usos na linguagem JAVA. Um quando é associada a um método e outro quando é associada a um atributo. Nos dois casos significa que o atributo ou método poderá ser acessado de forma independente de instâncias. Métodos e atributos sempre são relativos a um objeto, porém métodos e atributos estáticos são independentes. Um atributo estático pode ser entendido como uma variável global da classe, todas as instâncias podem trabalhar sobre a mesma variável. Veja o exemplo a seguir. As variáveis numeroComum e numeroEstatico se comportam de maneira diferente quando o método incremento for invocado por diferentes objetos. Se dois objetos invocarem o método incremento duas vezes cada um. numeroComum apresentará os valores 0 e 1 duas vezes, enquanto numeroEstatico apresentará os valores 0, 1, 2 e 3. No caso dos métodos estáticos, eles funcionam de forma semelhante a uma função comum do paradigma estruturado. Um bloco de código associado a um nome. Observe que o método main que 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 13/24 inicia a execução dos códigos Java também possui o modificador static. No exemplo anterior, temos uma classe com um método estático, que pode ser invocado por qualquer outra classe sem a necessidade de instanciar, mas colocando nome da classe antes do método com no comando: resultado = Aritmetica.somar(3,2); Esse comando irá chamar o método somar da classe Aritmética passando os valores 3 e 2 como parâmetro e retornando 5 para armazenar em uma variável de nome resultado. Como desafio experimente implementar os métodos somar, subtrair, multiplicar e dividir de forma estática dentro de uma classe denominada Aritmética. Acesse depois estes métodos a partir de outra classe. TEMA 4 – INTERAÇÃO ENTRE OBJETOS Neste tema, vamos reforçar o conceito de objetos e demonstrar as possibilidades de interação entre eles. A ideia da programação orientada a objetos consiste em mapear o problema considerando os objetos e a relação entre eles. Uma forma visual e muito prática de representar graficamente classes e suas relações é por meio da Unified Modeling Language (UML), em português, Linguagem de Modelagem Unificada. A UML é muito utilizada na documentação de projetos, especialmente de grande escala por sua facilidade de compreensão. É especialmente útil durante a etapa de projeto, pois conseguimos representar os elementos que compõe o domínio do nosso problema sem a necessidade de implementação. Na UML existem diversos tipos de diagramas que representam diferentes aspectos do projeto de software, entre eles o diagrama de classes que, como o nome sugere, representa justamente as 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 14/24 classes. Cada classe é graficamente apresentada com um retângulo contendo geralmente três áreas. Na parte superior, o nome da classe; no meio, os atributos; e na parte inferior, os métodos. Na Figura 3, a classe Aluno é representada. Figura 3 – Classe Aluno, com atributos métodos descritos Outra vantagem do UML é a possibilidade de demonstrar a relação entre as classes. Podemos, por exemplo, ter atributos de uma classe sejam objetos de outra classe ou que seus métodos recebam objetos como parâmetros. Suponha, por exemplo, que desejamos criar uma classe que represente uma turma de alunos. Podemos representar a classe turma da seguinte forma. 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 15/24 Nas linhas 5 até 7, temos os atributos. Cada turma é composta por uma quantidade variável de alunos portanto o primeiro atributo é um ArrayList, uma classe que implementa arrays com grande facilidade, pois possui diversos métodos para adicionar, remover, contar e buscar elementos de forma dinâmica. Para utilizar essa classe, é necessário importar o arquivo onde está definida, o comando de importação está na linha 2. Dentro do operador <> podemos definir o tipo do ArrayList, que no caso é um ArrayList da classe Aluno, ou seja, todo o elemento desse array representará um aluno diferente que está matriculado naquela turma, lembrando novamente que o comando new ArrayList() gera uma instância nova da classe ArrayList, sem esse comando listaAlunos seria uma referência, um ponteiro. Na linha 6, temos um elemento da classe Professor que representa o orientador daquela turma, também um objeto de uma classe que é um atributo nesta. Suponha que a classe Professor composta por alguns poucos atributos e um método info() análogo ao aluno. Nas linhas 9 até 11 da classe Turma, temos o método que adiciona um novo aluno no ArrayList que compõem todos os alunos. O método add da classe ArrayList adiciona ao final do array, se necessário, o método de forma transparente aloca memória extra para realizar a adição do elemento. Abaixo, na Figura 4 representamos a relação entre essas classesno diagrama de classes UML. No caso Turma, possui uma relação que chamamos de composição com relação às duas outras classes. O paradigma orientado a objetos tenta representar o domínio dos problemas de forma mais natural e semelhante ao problema real. Portanto, assim como na vida real, as turmas são compostas por um grupo de alunos da mesma forma a relação que entre elas é chamada de composição. Na Figura a indicação 0..1 significa que cada turma possui de zero até um professor e 0..* significa que a turma possui de zero até vários alunos. Mais detalhes sobre o digrama de classes UML serão apresentados na nossa disciplina em um momento oportuno. 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 16/24 Figura 4 – Classes Aluno, Professor e Turma representadas TEMA 5 – CONSTRUTORES No momento que instanciamos um objeto no Java internamente um bloco de código é executado, esse bloco de código é denominado construtor. Neste Tema vamos discorrer sobre como criar e utilizar os construtores. No bloco de código de um construtor qualquer código pode ser escrito, mas usualmente utilizamos para carregar alguma informação, especialmente informações que sejam cruciais para o funcionamento do objeto no momento da sua criação. Os construtores são criados de forma semelhante aos métodos, porém, devem possuir o mesmo nome da classe e não possuem valor de retorno. É possível também possuir diversos construtores para uma mesma classe variando apenas os parâmetros de entrada. Abaixo segue um código que exemplifica isso: 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 17/24 Mais uma vez exemplificamos utilizando a classe Aluno. Dessa vez criamos três construtores diferentes, linhas 6, 11 e 14. No primeiro construtor, temos três parâmetros sendo passados, ou seja, é permitido instanciar o objeto passando o nome, a matrícula e o CPF. Conforme vemos nas linhas 7, 8 e 9, os parâmetros passados para os atributos da classe Aluno. A palavra reservada this serve para distinguir os atributos dos parâmetros, visto que ambos possuem os mesmos nomes. Aluno mario = new Aluno(“Super Mario”, 1001, "111.111.111-1" ); Ao instanciar aluno desta maneira, ele já terá valores em todos os seus atributos sem necessidade de indicar um a um individualmente. Aluno luigi = new Aluno(“Super Luigi”); 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 18/24 Por termos criado três construtores, temos três opções de instanciação. Esta instanciação por ter apenas um parâmetro String invoca o construtor da linha 11 que também possui apenas um parâmetro String e atribui um valor inicial para o nome baseado no parâmetro do construtor, os demais atributos são ignorados. Note que os atributos são o que distingue diferentes construtores. Aluno yoshi = new Aluno(); Se instanciamos um objeto sem parâmetros, o construtor sem parâmetros é chamado, aquele presente na linha 14 irá imprimir uma mensagem. Quando criamos uma classe, um construtor vazio implícito que não executa nenhum código é criado. No entanto, a partir do momento que criamos um construtor qualquer, esse construtor vazio implícito deixa de existir. Para testar isso, observe que se apagarmos apenas o construtor da linha 14, essa instanciação vazia não vai funcionar, porém se apagarmos todos os construtores ele volta a funcionar novamente. 5.1 EXERCÍCIO Crie uma classe Horario com os atributos inteiros, hora, minuto e segundo. Crie três construtores, um que recebe três parâmetros e inicia os três atributos com os valores passados, outro que recebe apenas a hora e outro vazio. Caso algum dos valores inicializados não esteja no intervalo adequado (hora entre 0 e 23, minutos e segundos entre 0 e 59), inicialize o valor em questão em zero e emita uma mensagem de erro. FINALIZANDO Nesta aula, iniciamos propriamente dito o conteúdo relativo à orientação a objetos. Vimos o conceito de classe e objeto que é o mais importante dentro desse Paradigma. Também trabalhamos com métodos, o modificador static e vimos o conceito de construtores. Com o conteúdo desta aula, o(a) aluno(a) dá um passo importante na compreensão da orientação a objetos. Nessa fundamentação, o aluno tem a base para os conceitos mais aprofundados que seguem posteriormente. Em outra oportunidade, exploraremos os conceitos de visibilidade e encapsulamento, importantes para diminuir inconsistências nos códigos orientados a objeto. 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 19/24 APÊNDICE 1. Crie uma classe Nota, com três atributos reais chamados: nota1, nota2, nota3. E crie também dois métodos, cada um para calcular e retornar diferentes tipos de média. Média Aritmética; e Média ponderada (pesos 2 3 4). E na classe com o método main para testar: 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 20/24 2. Crie uma classe Conta, para administrar contas correntes de um banco com os atributos abaixo. 3. Complemente o exercício anterior implementando um método abaixo. (Código responde os exercícios 2 e 3) transfererir(Conta destino, float valor); E os métodos: 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 21/24 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 22/24 Na main para testar o código: O método deve transferir o valor passado por parâmetro do objeto conta que executa o método para a conta destino passada por parâmetro. 4. Crie uma classe Horario com os atributos inteiros, hora, minuto e segundo. Crie três construtores, um que recebe três parâmetros e inicia os três atributos com os valores passados. Outro que recebe apenas a hora e outro vazio. Caso algum dos valores inicializados não esteja no intervalo adequado (hora entre 0 e 23, minutos e segundos entre 0 e 59), inicialize o valor em questão em zero e emita uma mensagem de erro. 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 23/24 REFERÊNCIAS BARNES, D. J.; KÖLLING, M. Programação orientada a objetos com Java. 4. ed. São Paulo: Pearson Prentice Hall, 2009. DEITEL, P.; DEITEL, H. Java Como programar. 10. ed. São Paulo: Pearson, 2017. LARMAN, C. Utilizando UML e Padrões: Uma Introdução à Análise e ao Projeto Orientados a Objetos e ao Desenvolvimento Iterativo. 3. ed. Porto Alegre: Bookman, 2007. MEDEIROS, E. S. de. Desenvolvendo software com UML 2.0: definitivo. São Paulo: Pearson Makron Books, 2004. PAGE-JONES, M. Fundamentos do desenho orientado a objetos com UML. São Paulo: Makron Book, 2001. PFLEEGER, S. L. Engenharia de software: teoria e prática. 2. ed. São Paulo: Prentice Hall, 2004. 03/11/2022 11:22 UNINTER https://univirtus.uninter.com/ava/web/roa/ 24/24 SINTES, T. Aprenda programação orientada a objetos em 21 dias. 5. reimp. São Paulo: Pearson Education do Brasil, 2014. SOMMERVILLE, I. Engenharia de software. 9. ed. São Paulo: Pearson, 2011. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 1/27 PROGRAMAÇÃO ORIENTADA A OBJETOS AULA 3 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 2/27 Prof. Leonardo Gomes CONVERSA INICIAL Nesta aula, vamos abordar a questão da visibilidade nas classes. Nas linguagens de programação estruturadas, existem diferentes técnicas para limitar quem tem acesso a determinadas funções e variáveis. Essa estratégia permite, entre outras coisas, esconder atributos e métodos que são utilizadas apenas internamente. Na orientação a objetos, isso é feito por meio de modificadores próprios que veremos em detalhes na sequência. Também vamos discutir algumas bibliotecas importantes dentro do Java que podem facilitar o desenvolvimento de aplicações. Objetivos da aula: ao final desta aula, esperamos atingir os seguintes objetivos que serão avaliados ao longo da disciplina da forma indicada. Quadro 1 – Objetivos Objetivos Avaliação 1 – Aplicar os conceitos de encapsulamentodentro da orientação a objetos. Questionário e questões dissertativas 2 – Desenvolver algoritmos que fazem uso correto dos modificadores public e private. Questionário e questões dissertativas 3 – Conhecer outras classes internas e importantes ao Java. Questionário e questões dissertativas TEMA 1 – VISIBILIDADE 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 3/27 Neste tema, vamos debater o conceito de visibilidade por meio dos modificadores public, protected e private que definem quais outras classes podem acessar seus atributos e métodos. Na orientação a objetos, à medida que os projetos crescem em tamanho, desenvolvemos um grande número de classes e se torna cada vez mais difícil lembrar de todos os métodos e atributos pertinentes para cada situação. Neste sentido, como já discutimos em aulas anteriores, é importante aplicarmos boas práticas na programação como escolher bons nomes para todos os elementos da classe e seguir padronizações no estilo de codificação. Outra prática que nos ajuda a simplificar o uso das classes é esconder métodos e atributos que sejam de uso interno da classe, de forma que, ao fazermos uso da classe posteriormente, não seja exibido detalhes de implementação, e sim apenas os métodos e atributos que sejam pertinentes. Antes da definição de cada método, atributo e classe, podemos colocar um dos três modificadores: Public: o elemento é público e pode ser acessado por qualquer outra classe sem restrições. Private: o elemento é privado e só pode ser acessado internamente na classe. Protected: o elemento é protegido e será acessado somente de dentro da própria classe, outras classes no mesmo pacote e também por classes filhas. A definição de classe filha será abordada em detalhes na aula de herança. Default (sem nenhum modificador): o elemento, neste caso, é acessível por classes dentro do mesmo pacote. Tabela 1 – Tabela demonstrativa dos modificadores de visibilidade 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 4/27 1.1 EXEMPLO COM CÓDIGO No exemplo a seguir, temos um código que demonstra o uso dessas palavras reservadas. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 5/27 Em relação ao código acima, digamos que o atributo matrícula não seja uma informação de interesse para quem utiliza a classe Aluno, mas que internamente na classe é uma informação importante para talvez vincular o aluno em um banco de dados, por exemplo. Para essas situações, atributos como matricula podem ser definidos como privados, garantindo uma abstração que invisibiliza informações que sejam pertinentes apenas para o contexto interno da classe. Ainda no exemplo, os atributos cpf e nome, por serem pertinentes para quem for utilizar a classe, ficam públicos. Observe que o atributo notas foi definido como protected, por algum motivo poderia não ser interessante permitir seu público e direto ao array que guarda os dados, mas ao mesmo tempo suponha que existam algumas classes específicas que gostaria de oferecer acesso, nesta situação, nem public e nem private atendem a necessidade, protected concede visibilidade apenas 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 6/27 para classes relacionadas a classe Aluno, mais especificamente suas classes filhas, o conceito de classe filha e Herança será visto em detalhes nas próximas aulas. 1.2 VISIBILIDADE NO DIAGRAMA DE CLASSES UML Nas aulas anteriores, demonstramos como representar classes utilizando diagrama de classes UML. Nesse diagrama, podemos também representar a visibilidade dos métodos e atributos utilizando os seguintes símbolos: Quadro 2 – Símbolos e significados Símbolo Significado + Público - Privado # Protegido A seguir, o exemplo da classe Aluno. Figura 1 – Classe Aluno Desafio: seguindo o exemplo da classe Aluno, desenvolva o diagrama de classe UML para descrever um ITEM qualquer que seja comercializado por uma loja on-line, não existe uma resposta certa, cada contexto requer atributos e métodos diferentes, mas pense como imagina sendo a melhor forma. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 7/27 Quais informações você acredita serem importantes armazenar para exibir ao comprador e registrar a venda na loja? Quais atributos imagina que seriam privados por serem utilizados apenas internamente? E quais seriam públicos por interagir com outros objetos? TEMA 2 – ENCAPSULAMENTO Entende-se que o Paradigma Orientado a Objetos possui três pilares, herança, polimorfismo e o encapsulamento. Alguns autores defendem a abstração como um quarto pilar, no entanto, ela pode também ser entendida como parte do encapsulamento. Neste tema, vamos debater em mais detalhes os benefícios e práticas do encapsulamento. Quando pensamos em uma cápsula no mundo real, fora do contexto da programação, imaginamos o invólucro de algum objeto sensível e/ou perigoso, que serve tanto para proteger o conteúdo interno do mundo exterior quanto o mundo exterior desse conteúdo interno. Se pensarmos no monitor para computadores, ele é um exemplo de encapsulamento. Seus componentes ficam resguardados pela estrutura de plástico que o envolve, evitando que quem manipula o equipamento receba descargas elétricas ou danifique o aparelho. Na programação orientada a objetos, tentamos abstrair no código a interação entre os objetos reais, portanto, a lógica do encapsulamento é a mesma, como boa prática de programação orientada a objetos, devemos utilizar as propriedades public, private e protected para invisibilizar os componentes internos das classes que não são pertinentes e deixar visível o estritamente necessário. Dessa forma, o programador que fará uso da classe encapsulada tem uma camada extra de segurança no momento que fizer uso da classe. No contexto da computação, chamamos de interface os elementos responsáveis por conectar de forma física ou lógica diferentes objetos ou partes distintas de um sistema. No caso do exemplo dado do monitor, a conexão hdmi compõe sua interface, já no caso de uma classe sua interface se dá por meio dos seus métodos que recebem e devolvem informações. Em uma classe que segue boas práticas de encapsulamento, seria possível trocar a classe por outra que possua a mesma interface (métodos com os mesmos nomes e parâmetros) e, por mais que as suas implementações internas mudem, o sistema continuará funcional sem maiores mudanças. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 8/27 No mundo físico, mais uma vez o exemplo do monitor, se trocarmos o equipamento por outro, o sistema continua funcionando sem maiores complicações, desde que ambos possuam a mesma interface (cabo HDMI) basta desconectar um e conectar o outro, mesmo que sejam monitores com especificações diferentes (tecnologia LCD ou LED, tamanhos diferentes etc.). Já no contexto da computação acontece o mesmo se temos duas classes com uma mesma interface, mas que internamente resolve códigos de maneira distintas, por exemplo, utilizando diferentes estruturas de dados ou aplicando diferentes algoritmos para ordenarem seus dados, o restante do código ficaria inalterado. O que possibilita inclusive diferentes equipes que não mantém contato algo produzirem soluções que conversem. Tal qual o exemplo do monitor que mesmo quando produzidos por uma empresa diferente do restante do equipamento continua funcionando. Em resumo, a vantagem do encapsulamento são: 1. A abstração oferecida em que o funcionamento interno dos objetos da classe não fica visível ao programador que utiliza a classe. 2. A possibilidade de acrescentar funcionalidades à classe desde que respeitando a interface original manterá o sistema funcional sem alterações. 3. Simplificação da utilização dos objetos em um alto nível acelera o desenvolvimento dos códigos. 4. O sistema fica robusto a mudanças internas, mesmo uma substituição completa do código que poderia até mesmo ser desenvolvidopor outra equipe que não manteve contato com a primeira, bastando respeitar a interface. 2.2 EXEMPLO JAVA DO PARADIGMA ORIENTADO A OBJETO Uma das práticas do encapsulamento consiste em criar métodos chamados de getter e setter para conceder acesso aos atributos da classe. A ideia está em deixar todos os atributos (variáveis internas da classe), como privadas e aquelas que convém ao usuário acessar são disponibilizadas por meio desses métodos. No inglês, get significa pegar, e os métodos getters são métodos que pegam os valores daqueles atributos e retornam para quem chamou o método enquanto set significa definir, servindo justamente para definir os atributos com o valor passado por parâmetro. Essa lógica é aplicada às mais diversas linguagens de programação com pequenas variações, mas a ideia de restringir ao 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 9/27 acesso, encapsulando a classe, é a mesma. No caso do Java, vejamos um exemplo a seguir com uma classe que representa horários. Observe o padrão de nomenclatura das funções setHora e getHora para o atributo hora, esse padrão é amplamente adotado na comunidade de programadores sempre com a mesma funcionalidade, definir e retornar o valor para determinado atributo. No caso, é possível inclusive realizar validação no momento de definir o dado para garantir a integridade a informação. Neste exemplo, não seria possível definir uma hora fora do intervalo 0-23. Suponha que essa classe seja adota em aplicações críticas como as bancárias então garantir a integridade se torna ainda mais importante. Desafio: desenvolva uma classe Data, com atributos, dia, mês e ano encapsulados com get/set. Para simplificar, assuma que todo mês pode seja composto por 30 dias. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 10/27 TEMA 3 – COLLECTIONS Neste Tema, vamos debater uma importante API em Java chamada Collections, essa API consiste em um conjunto de classes que implementam diferentes estruturas de dados, essas estruturas são encapsuladas respeitando uma mesmo acordo, interface, o que traz diversos facilidades. Por estrutura de dados entenda a estratégia que você utiliza para organizar seus dados na memória. Você certamente deve estar familiarizado com a estrutura de dados array (ou lista) e matriz. Porém, existem diversas outras estruturas, como pilha, fila, hash, árvores, lista encadeada, entre outros. O estudo de estrutura de dados é todo um campo dentro da ciência da computação com diversas ramificações e que tem uma importância muito grande, adotar a estrutura de dados correta em cada situação pode ser a diferença entre um algoritmo que leva dias para executar ou poucos minutos. No entanto, implementar as estruturas de dados adequadamente toma tempo e, por possuir soluções bem conhecidas, a API Collections já nos traz as classes prontas com essas estruturas; dessa forma, não precisamos “reinventar a roda”. 3.1 PRINCIPAIS CLASSES DA COLLECTIONS Dentro das Collections, existem diversas estruturas de dados, de forma geral, elas são divididas em quatro grupos, dispostos em azul na Figura 1. O grupo list (listas), que funciona como uma sequência ordenada de valores, o grupo set (conjuntos), que forma um agrupamento bde itens sem ordem definida, o grupo map (mapas), que mapeia dados chamados chaves para outros dados chamados de valores e, por fim, o grupo queue (filas), que implementam um tipo de array em que a posição de cada elemento define a prioridade dele em relação aos demais, uma abstração chamada de fila de prioridades. Na Figura 2, vemos os quatro grupos e suas classes e, ao longo do texto, descrevemos algumas das que consideramos mais importantes. Figura 2 – Tabela demonstrativa dos modificadores de visibilidade 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 11/27 ArrayList é uma das classes mais comuns dentro da API Collections, como já apresentada anteriormente ele representada um array dinâmico. Os elementos dentro dela possuem uma ordem definida e trazem métodos de manipulação, como remoção, inserção, busca, entre outros. LinkedList é outra classe que também implementa um array dinâmico com as mesmas funcionalidades do ArrayList tradicional, no entanto, sua implementação interna utiliza a estratégia de lista ligada e realiza as operações de remoção e inserção de forma muito mais rápida e a busca por elementos de forma muito mais lenta em comparação ao ArrayList tradicional. De maneira geral, é mais comum ao longo da execução de um programa que mais buscas sejam executadas do remoções/inserções, portanto, o ArrayList de forma geral é o mais indicado, entretanto, para aqueles casos em que isso não ocorre, contamos com essa alternativa. HashSet na computação, o termo Hash diz respeito a uma função que transforma um valor em outro, e a palavra Set tem diversos significados diferentes sendo que, nessa situação específica, significa conjunto. Então uma HashSet é um conjunto de elementos organizados por meio de uma função Hash. Ela realiza operações de adição, remoção e busca de forma muito rápida, contudo, não garante uma ordem dos elementos. LinkedHashSet é semelhante a HashSet, porém, ele armazena a ordem em que os elementos foram adicionados. HashMap é a estrutura de dados também baseada em Hash, com a diferença que é possível mapear (daí o nome Map) uma ID de um tipo diverso. Por exemplo, podemos ordenar os filmes de 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 12/27 um catálogo não por um valor inteiro, mas por uma string contendo o nome do filme seguido do ano de exibição. Não possui ordem garantida. TreeMap semelhante a HashMap, entretanto, também armazena a ordem dos itens, essa ordem pode ser livremente manipulada combinando as características de em um array. LinkedHashMap é semelhante a HashMap, todavia, internamente também armazena a ordem em que os elementos foram adicionados. Queue é uma estrutura de dados geralmente adotada para representar filas de prioridade, queue no inglês significa fila. Ela pode implementar uma fila de prioridade comum, semelhante a uma fila convencional no mundo real em que o primeiro elemento a entrar é o primeiro a ser atendido. Stack é outra classe semelhante a fila de prioridades, mas implementa uma fila reversa à ordem de inserção, o último elemento adicionado é o primeiro a ser tratado, conceito denominado pilha, se empilharmos diversos objetos um sobre o outro, o último objeto empilhado será o primeiro que vamos acessar. 3.2 EXEMPLO DE USO DE ARRAYLIST/LINKEDLIST A seguir, demonstramos alguns métodos aplicados nas classes ArrayList e LinkedList. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 13/27 3.3 EXEMPLO DE USO DE HASHMAP A seguir, o código demonstra o uso da classe HashMap. As classes baseadas na estrutura de mapa possuem o conceito de chave e valor. A chave é o que indexa ela e o valor o conteúdo dentro daquele índice. No código a seguir, o HashMap capitais indexa capitais (valor) por meio do nome do país (chave). É diferente de termos um array composto de uma struct contendo o par de strings, pois, nesse caso, o índice continuaria sendo um inteiro, marcando a posição, já na Hash, o que marca a posição é o próprio nome da capital. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 14/27 3.4 EXEMPLO DE USO DE HASHSET A seguir, o código demonstra o uso da classe HashSet. As classes baseadas na estrutura de Conjunto armazenam dados sem se preocupar em manter uma ordem, o conceito de ordem em conjunto simplesmente não existe, se adicionarmos um item primeiro, ele não estará na frente do que adicionarmos por segundo. Sem ter a necessidade de se preocupar com ordem, certos métodos se tornam mais rápidos, por exemplo, descobrir se determinado elemento existe ou não é muito mais rápido em uma estrutura de dados de Conjunto em comparação à Lista. 03/11/2022 11:31 UNINTERhttps://univirtus.uninter.com/ava/web/roa/ 15/27 3.5 EXEMPLO DE USO DE PRIORITYQUEUE A seguir, o código demonstra o uso da classe PriorityQueue. As classes baseadas no grupo das filas de prioridade armazenam dados se preocupando de forma muito restrita com a ordem, existem filas em que desejamos visualizar os elementos conforme a ordem que foram adicionados (fila tradicional) ou na ordem reversa ao que foram adicionados (pilha). É indicado adotar esse tipo de estrutura quando temos dois tipos de entidades distintas, um tipo produzindo algum tipo de dado e outro consumindo os dados. O exemplo clássico é a gestão de documentos de uma impressora. Podemos ter vários serviços gerando dados para serem impressos e existe a impressora que consome (imprime) os dados na ordem que foram adicionados na fila. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 16/27 3.6 MÉTODOS ESTÁTICOS COLLECTIONS Além das classes, existem vários métodos estáticos dentro do framework Collections que implementam soluções para problemas que são relativamente comuns entre os algoritmos. Por exemplo, o método sort implementa a ordenação dos elementos que compõem um objeto da classe Collections, ou seja, podemos com uma única chamada colocar os itens de um ArrayList em ordem crescente. A seguir, uma lista com exemplos de alguns dos métodos mais importantes: Sort (List<> lista): coloca em ordem crescente os itens da lista passada por parâmetro. Shuffle (List<> lista, Random rnd): embaralha de forma aleatória os elementos da lista passada por parâmetro, a aleatoriedade do embaralhamento é dada pelo objeto da classe Random passado por parâmetro também. Max (Collection<> coll, Comparator<> comp): retorna o maior elemento, aceita tanto lista, quanto hash. Como segundo parâmetro, você pode indicar como deseja realizar a comparação com um objeto da classe Comparator, caso passe null como segundo parâmetro, a ordem natural será adotada. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 17/27 Min (Collection<> coll, Comparator<> comp): análogo ao max, porém, retorna o menor elemento. Reverse (List<> lista): coloca todos os itens em ordem reversa. O código a seguir exemplifica o uso desses métodos: 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 18/27 TEMA 4 – ITERATOR 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 19/27 Neste tema, vamos discutir formas de navegar pelos dados de uma das classes presentes no framework Collections, vamos dar destaque especial a uma estratégia chamada Iterator. Quando desejamos visitar os dados em uma estrutura de dados, as estratégias mudam dependendo da estrutura, listas contam com índices inteiros, mapas são indexados pelas chaves que foram definidas, conjuntos não possuem forma de indexação alguma. No entanto, os iterators são uma ferramenta poderosa nesse sentido, pois com eles é possível navegar pelos dados independentes da classe Collections utilizada. Vamos ver alguns exemplos de código. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 20/27 Nesse código, temos três formas diferentes de calcular a soma dos elementos de um ArrayList, vamos discutir cada uma dessas formas: 1 - Das linhas de código 7 até 10, temos a forma tradicional, utilizando um for indexando a lista pelos seus índices indo de 0 até o tamanho total da lista.size(). 2 - Na sequência, temos o que chamamos de um for each, nas linhas 12 até 15, um comando de repetição for que em vez de ser dividido em três partes é dividido em duas e que dispensa o uso de índices, ele executa o loop para cada elemento daquele Collection que for passada depois dos dois pontos, no caso lista. A cada execução, a variável que vai antes dos dois pontos, no caso item, receberá um valor distinto. Esse tipo de for é interessante por ser simples e também se aplicar a diferentes classes dentro das Collections, no entanto, ela é bastante engessada não permitindo operações mais elaboradas, como remover um item específico que foi visitado, entre outras operações. 3 - Na sequência, linha 17 em diante, temos a demonstração do uso do Iterator. Ele é compatível com diversos estruturadas de dados diferentes, nas linhas 18, 19 e 20, vemos o iterator sendo utilizado respectivamente com mapa, conjunto e lista. O código é o mesmo para todos. o iterator é uma espécie de ponteiro para o elemento da Collection. Na linha 21, o método hasNext() verifica se existe um próximo elemento e retorna verdadeiro ou falso. Na linha 22, o método next() atualiza o iterador para o próximo item da coleção. Como os ponteiros iteradores podem apontar para qualquer tipo primitivo ou objeto, é necessário indicar ao compilador como aquele item deve ser interpretado, no caso (int) é o que chamamos de cast-type na programação e serve para indicar ao compilador que o comando que vem na sequência deve ser lido como um inteiro. Importante observar que o cast-type não é uma conversão, ele não modifica o dado, apenas muda a forma como aquele dado é visualizado pelo compilador, o uso inadequado desse comando pode gerar exceções na execução do programa. O iterator é bastante simples e compatível com diversas estruturas de dados, o que o torna particularmente útil para criar um código flexível e independente da estrutura de dados adotada. Além do exemplo visto no código anterior, seria possível também navegar de forma reversa, de trás para frente como no código a seguir. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 21/27 Observe que aqui é utilizada a classe ListIterator em vez de apenas Iterator, como visto no exemplo anterior. Neste caso, é necessário especificar que o iterator age sobre listas, pois nelas existe a ideia de uma ordem sequencial dos dados, em outras estruturas, como Map e Set, essa ordem não existe por padrão. O método listIterator (linha 1) aceita como parâmetro uma posição, 0 para primeira posição, 1 para segunda posição e assim por diante. No caso, o tamanho da lista é o parâmetro, portanto, o retorno será um iterator que aponta para a última posição. O método hasPrevious() do inglês se traduz como “existe um anterior?” e como o nome sugere ele retorna true ou false como resposta. Por sua vez, o método previous(), do inglês “anterior”, retorna o iterator em si da posição anterior. Em outras palavras, enquanto existir um elemento anterior, o programa segue imprimindo na tela o anterior do anterior do anterior e assim por diante. Também é possível remover elementos apontados pelo Iterator, segue novo código abaixo. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 22/27 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 23/27 No código acima, temos uma lista de strings com nomes de frutas e todos os elementos são impressos na tela, antes e depois da string “Laranja” ser removida da lista. Na linha 17, vemos o loop utilizando while e o método “hasNext()” já discutido anteriormente, na linha 19, fazemos uma comparação para decidir se a string que o iterator aponta é realmente “Laranja”, se for o caso, ela é removida com o método remove(), então o loop é encerrado utilizando a instrução break. TEMA 5 – CLASSE LOCALDATE Em diversos projetos, nos deparamos com o desafio de como lidar com a representação de datas e horários, essa é uma questão especialmente comum em projetos quando envolve banco de dados e acesso web. O Java conta com algumas soluções implementadas internamente. Neste Tema, vamos discutir as principais soluções. Originalmente, nas primeiras versões do Java, foi implementada uma classe para datas chamada java.util.Date, essa classe originalmente possuía diversas limitações e, por isso, em uma atualização do Java, surgiu a classe java.util.Calendar, que também não atendia às necessidades da comunidade de desenvolvimento Java, por isso, se popularizou muito uma biblioteca chamada Joda Time. A Oracle, ao identificara preferência da comunidade pela biblioteca externa na versão 8, integrou esta ao Java com o nome java.util.LocalDate (e suas derivações). Portanto, a recomendação é a utilização da LocalDate para versão 8 do Java em diante e, se por alguma restrição específica de projeto for necessário utilizar uma versão antiga do Java, recomenda-se o uso da Joda Time. A seguir, o código que representa o uso do LocalData 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 24/27 A classe LocalDate representa datas de forma bem prática. Cada data sendo um objeto LocalDate, para, por exemplo, recuperarmos a data do momento da execução de uma linha de código. LocalDate.now();(linha 4), que significa “agora” em inglês. Se imprimirmos na tela objeto do tipo LocalDate, teremos a impressão no formato americano que coloca o mês na frente do dia. Para representar no formato brasileiro, é necessário criar um objeto do tipo DateTimeFormatter, que estabelece formatações para datas e horários. Na linha 7 do código, vemos a formatação estabelecida sendo dd/MM/yyyy, que representa a data na ordem que estamos acostumados e separadas por uma barra. dd = dia do mês em dois dígitos; MM = mês em dois dígitos; yyyy = ano em quatro dígitos; HH = horas, até 23, em dois dígitos; mm = minutos em dois dígitos; ss = segundos em dois dígitos; hh = horas, até 12, em dois dígitos; d = dia do mês em um ou dois dígitos; M = mês do ano em um ou dois dígitos; 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 25/27 yy = ano em dois dígitos; H = horas, até 23, em um ou dois dígitos; m = minutos em um ou dois dígitos; s = segundos em um ou dois dígitos; h = horas, até 12, em um ou dois dígitos. Quando desejamos trabalhar com horário, o procedimento é semelhante. Confira o código a seguir. Desafio: combine os tópicos abordados nesta aula e construa um HashMap que irá mapear nomes (string) com seus respectivos aniversários representados por um LocalDate. Registre ao menos três aniversários no mapa e depois, utilizando um loop, imprima todos os aniversários. FINALIZANDO Nesta aula, abordamos diversos assuntos, com especial destaque à visibilidade e ao encapsulamento. Esses conceitos são de extrema importância para a abstração dentro da orientação 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 26/27 a objetos, permitindo esconder detalhes de implementação e tornar classes complexas em termos de implementação simples de serem utilizadas. Também discutimos Collections, iterators e a classe LocalDate, tópicos importantes para quem deseja aprofundar o desenvolvimento em Java. Outras linguagens de programação orientada a objetos possuem classes semelhantes para representar estruturas de dados iteradores e data, portanto, apesar de particular à linguagem Java, o conceito acaba se tornando amplo na prática. Nas próximas aulas, continuaremos com o conteúdo de herança, outro importante pilar da orientação a objetos. REFERÊNCIAS BARNES, D. J.; KÖLLING, M. Programação orientada a objetos com Java. 4. ed. São Paulo: Pearson Prentice Hall, 2009. DEITEL, P.; DEITEL, H. Java: como programar. 10. ed. São Paulo: Pearson, 2017 LARMAN, C. Utilizando UML e padrões: uma introdução à análise e ao projeto orientados a objetos e ao desenvolvimento iterativo. 3. ed. Porto Alegre: Bookman, 2007. MEDEIROS, E. S. de. Desenvolvendo software com UML 2.0: definitivo. São Paulo: Pearson Makron Books, 2004. PAGE-JONES, M. Fundamentos do desenho orientado a objetos com UML. São Paulo: Makron Book, 2001 PFLEEGER, S. L. Engenharia de software: teoria e prática. 2. ed. São Paulo: Prentice Hall, 2004. SINTES, T. Aprenda programação orientada a objetos em 21 dias. 5. reimpressão. São Paulo: Pearson Education do Brasil, 2014. SOMMERVILLE, I. Engenharia de software. 9. ed. São Paulo: Pearson, 2011. 03/11/2022 11:31 UNINTER https://univirtus.uninter.com/ava/web/roa/ 27/27 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 1/19 PROGRAMAÇÃO ORIENTADA A OBJETOS AULA 4 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 2/19 Prof. Leonardo Gomes CONVERSA INICIAL Nesta aula, vamos abordar um princípio muito importante na orientação a objetos, a herança, princípio que permite que classes compartilhem atributos e métodos evitando retrabalho. Abordaremos também conceitos relacionados, como sobrecarga, e veremos maiores particularidades da linguagem Java no que diz respeito a essa relação. Ao final desta aula, esperamos atingir os seguintes objetivos que serão avaliados ao longo da disciplina da forma indicada. Quadro 1 – Objetivos da aula Objetivos Avaliação 1. Aplicar os conceitos de herança dentro da orientação a objetos Questionário e questões dissertativas 2. Desenvolver algoritmos que fazem uso de múltiplas classes relacionadas por herança. Questionário e questões dissertativas 3. Lidar com situações específicas como herança de construtores e sobrecarga de métodos. Questionário e questões dissertativas TEMA 1 – HERANÇA Neste tema vamos debater o princípio da herança, que é um dos quatro pilares do paradigma orientado a objetos. Não é raro nos depararmos com uma situação na qual desejamos representar classes que possuem semelhanças entre si e em que uma das classes pode ser entendida como baseada em outra. O conceito de herança atua nesse sentido, facilitando essa representação. Na vida real, 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 3/19 recebemos uma herança genética de nossos pais. Em parte, essa herança genética dita nossas características do que somos e do que conseguimos fazer, no entanto somos diferentes de nossos pais e possuímos características únicas. A herança na orientação a objetos se baseia nessa mesma ideia. É possível que uma classe (filha) herde comportamentos (métodos) e características (atributos) de outra classe (mãe), e nessa relação a classe filha pode ter suas características e comportamentos únicos também. De forma geral, a classe herdeira pode ampliar as funcionalidades da classe herdada e também modificar algumas das tais funcionalidades. Com isso, podemos utilizar o conceito de herança para organizar uma classe de forma hierárquica como no exemplo a seguir. Figura 1 – Representação hierárquica de classes No alto da hierarquia, classes mais genéricas e abaixo, mais específicas. Com as classes específicas herdando características, comportamento e o próprio tipo da classe superior. Um celular é um tipo de telefone que, por sua vez, é um tipo de dispositivo eletrônico. Essa relação hierárquica entre classes herdadas e herdeiras recebe diversos nomes na literatura, por exemplo: Quadro 2 – Termos atribuídos à classe herdada e à classe herdeira Classe herdada Classe herdeira Classe mãe Classe filha Superclasse Subclasse Classe base Classe específica Classe original Classe derivada 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 4/19 Contextualmente esses termos podem ser utilizados dando uma contextualização diferente para cada abstração que seu código utiliza, mas representam a mesma relação hierárquica em termos práticos. No próximo tema vamos trabalhar com isso de forma mais prática e ver o conceito aplicado na linguagem Java. Vale reforçar que o conceito é o mesmo para qualquer linguagem moderna, pois mudam apenas alguns detalhes, por exemplo, certas linguagens permitem herança múltipla (uma classe com múltiplas superclasses) e outras não, mas há meios de se aplicar essa ideia de outras formas. TEMA 2 – HERANÇA NA LINGUAGEM JAVA Neste tema vamos analisar alguns exemplos de como a linguagem Java especificamente trata herança. Vamos iniciar analisando o código que consta na Figura 2. Suponha que desejamos implementar o registro de livros para um site de vendas. Para isso criamos a seguinte classe Livro. Figura 2 – Exemplo de linguagemJava (1) 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 5/19 A classe Livro possui atributos (linhas 4-8), um método de cálculo de lucro, baseado no custo de produção e valor da venda (linha 11) e também possui um método de cálculo de imposto, assumindo 20% sobre o lucro (linha 15) e por fim um método que imprime o título do livro (linha 19). Porém suponha que nossa loja virtual passa a trabalhar com dois tipos de livros, os físicos e digitais. Ambos são livros, mas o livro digital possui certas características distintas que nossa classe Livro não possui. Na venda de livro digital existe link para download por exemplo que não faz sentido no livro físico. Então a implementação da classe LivroDigital fica como apresentada na Figura 3. Figura 3 – Exemplo de linguagem Java (2) 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 6/19 A classe LivroDigital possui atributos (linhas 3-9) possui o mesmo método lucro (linha 12) e tem um método que calcula quantos Mb possui em média cada página do livro (linha 17). Outro método imprime o título do livro (linha 22) e, por fim, um método que faz o cálculo do imposto (linha 27). Veja que, nesse exemplo, muito do código foi replicado, e se desejarmos efetuar uma alteração na representação dos nossos livros (por exemplo, em vez de simplesmente armazenar o nome do autor, criar um array para comportar múltiplos autores para um mesmo livro), essa alteração teria que ser feita duas vezes, gerando mais trabalho e duas vezes mais chance de erros por parte do programador. Na Figura 4, vemos como a classe LivroDigital seria representada utilizando herança da classe Livro na qual se baseia. Figura 4 – Exemplo linguagem Java (3) 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 7/19 Observe, na linha 2, a palavra reservada extends (significa estender no inglês). Essa palavra indica a herança na linguagem. A classe LivroDigital é filha, herdeira, e a classe Livro é a classe mãe, a classe herdada. Ou ainda, no linguajar proposto pelo Java, a classe LivroDigital é uma extensão da classe Livro. Todas as declarações de atributos e métodos que se repetem nas duas classes podem ser omitidas. Apenas o que é original da classe LivroDigital precisa ser declarado. Nessa situação, se desejarmos realizar a modificação de autor para um array de autores, bastaria uma única mudança que afetaria as duas classes, sem retrabalho. Observe que existem duas situações ocorrendo na criação dos métodos: 1. O método tamanhoPorPagina() é um método exclusivo, uma adição. Basta declará-lo e utilizá- lo sem maiores problemas; 2. O método imposto(), por outro lado, já existia originalmente na classe Livro. O que acontece aqui é o que chamamos de uma sobrescrita do método imposto(). Para um objeto do tipo LivroDigital, o método sobrescrito será invocado em vez do método original. Na Figura 5, vemos um código de exemplo utilizando as duas classes. Figura 5 – Exemplo linguagem Java (4) 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 8/19 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 9/19 O código começa com a criação de um objeto Livro e o preenchimento dos seus atributos (linhas 6-11). Depois, os atributos são impressos na tela (linha 14). Em seguida, os métodos lucro() e imposto() são chamados (linha 16). Na sequência, a mesma coisa é feita para o Livro Digital: primeiro, ele é declarado e tem os atributos preenchidos (linhas 18-25); depois, os métodos lucro(), imposto() (linha 30) e tamanhoPorPagina() (linha 32) são chamados também. Observe que, no caso do método imposto(), foi sobreescrito pela classe filha LivroDigital. A versão do método da classe filha que será executada pois livro2 é uma instância de LivroDigital. TEMA 3 – CONSTRUTORES E HERANÇA Neste tema vamos discutir como trabalhar com os construtores e como estes se comportam com o uso de herança. Os construtores funcionam de forma parecida com os métodos, pois são códigos executados no momento da instanciação. A aplicação mais comum dos construtores é a definição de valores para os 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 10/19 atributos no momento em que o objeto é instanciado. Relembrando o que já sabemos sobre o tema, para criar construtores, devemos fazê-lo semelhante a um método só que sem um parâmetro de retorno e com nome igual ao da classe. Diferente dos métodos e atributos, os construtores não são herdados pelas classes filhas, mas podem ser invocados por elas. No código presente na Figura 6, vemos esse conceito do construtor sendo aplicado. Figura 6 – Exemplo linguagem Java (5) 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 11/19 No código acima, colocamos três classes juntas para facilitar a visualização, porém, caso deseje implementar esse código para testes, lembre-se de colocar cada classe em um arquivo próprio ou fazer como foi feito no código acima, deixando apenas a classe com o método principal (Teste) como pública. Observando o código, vemos que a classe Base (linha 1) implementa dois construtores, Base() (linha 3) que imprime uma mensagem na tela e Base (int) (linha 6) que inicializa o atributo x. Em seguida, temos a classe Derivada (linha 11), que é filha da classe Base, e que também possui dois construtores, Derivada() (linha 13) e apenas imprime uma mensagem, e Derivada(int,int) (linha 16), que inicializa os atributos x e y. Esta faz isso chamando o construtor da classe base, utilizando o comando super (linha 17). Veremos em detalhe o funcionamento dessa palavra reservada e seus usos na sequência. Para ilustrar com outro exemplo, observe na Figura 7 o código para os construtores das classes Livro e LivroDigital que utilizamos anteriormente. Figura 7 – Exemplo linguagem Java (6) 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 12/19 Na classe Livro, o construtor recebe todos os parâmetros e os inicializa normalmente. Já na classe LivroDigital o primeiro comando do construtor (linha 9) invoca o construtor da classe mãe e, na sequência, inicializa os atributos únicos da classe LivroDigital. TEMA 4 – PALAVRAS RESERVADAS SUPER E THIS Vamos agora debater dois comandos muito úteis para corrigir ambiguidades entre outras aplicações: o comando super e o this. O comando super faz uma referência explícita à superclasse, à classe herdada, semelhante à palavra this que faz referência explícita à classe corrente. Observe, no código da Figura 6, que na classe Base declaramos o atributo x (linha 2) depois, no construtor (linha 6), recebemos um parâmetro x. Com o mesmo nome dentro do construtor quando escrevemos x, o compilador irá entender que se trata de uma referência para o parâmetro, mas se desejamos que a referência seja feita ao atributo, utilizamos this. O mesmo pode ser feito na classe filha, por exemplo: se de dentro da classe Derivada desejamos referenciar x de forma explícita, podemos fazer escrevendo super.x, mas observe que isso só é realmente necessário em caso de conflito de nomes entre variáveis, parâmetros e atributos. Se 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 13/19 esse conflito não existe, é possível referenciar diretamente apenas escrevendo x, por exemplo, sem utilizar this ou super. Outro uso importante e bastante comum da palavra reservada super é para invocar o construtor da classe mãe, no código anterior (linha 17) (Figura 6) o construtor Derivada (int,int) chamada o construtor Base(int) passando x como parâmetro. Essa chamada de construtor só é possível exclusivamente nessa situação. Um construtor de classe filha invocando na primeira linha o construtor da classe mãe. Qualquer outro momento que esse código super(int) fosse invocado geraria um erro. Ainda falando sobre o código anterior ao final temos uma classe Teste com o método principal, nele vemos sendo criado dois objetos da classe Derivada,obj1 com construtor vazio (linha 28) e obj2 com o construtor de dois inteiros (linha 29) (Figura 6). Ao executarmos temos o seguinte resultado. > Construtor classe base Construtor classe derivada x = 10, y = 50 Observe que o construtor da classe Base foi invocado. Isso acontece, pois quando não colocamos explicitamente a chamada de um construtor da classe mãe o construtor vazio da classe mãe é chamado de forma implícita na primeira linha do construtor da classe filha. Portanto, quando criamos o obj1, o Construtor Derivada() na primeira linha invoca Base() por isso vemos primeiro a mensagem do construtor base seguido do construtor derivada. 4.1 PALAVRA RESERVADA INSTANCEOF Além de herdar atributos, a tipagem também é herdada. Ou seja, se temos uma classe Base e outra Derivada herdeira de Base, então objetos da classe Derivada também são do tipo Base. Em outras palavras, as classes filhas são consideradas do tipo da classe mãe também. Existe um comando chamando instanceof (instância de, em tradução do inglês), que é utilizado justamente para identificar se uma determinada instância pertence a determinada classe, ele retorna true ou false (verdadeiro ou falso) caso seja ou não uma instância. No código da Figura 8, por exemplo, será impresso true três vezes como resposta. 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 14/19 Figura 8 – Exemplo linguagem Java (7) No código acima criamos quatro classes: Animal, que é a classe mãe; Reptil e Mamifero, filhas; e Cachorro, por sua vez, filha de Mamifero (linhas 1 até 4). Instanciamos objetos dessas classes e comparamos se a instância de Mamifero também é do tipo Animal, e se a instância de Cachorro também é do tipo Mamifero e Animal. Para as três verificações, a resposta é verdadeiro e, portanto, o código responde com true. TEMA 5 – HERANÇA E UML Vamos debater novamente o diagrama de classes UML. Aagora que definimos o conceito de herança, podemos debater as principais relações que são representadas por meio dessa modelagem. Como podemos observar, as classes possuem relacionamentos entre si. Por vezes, elas compartilham informações, se comunicam e colaboram uma com as outras. Dentre os principais tipos de relacionamento, temos: Associação; Agregação; Composição; 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 15/19 Herança; Dependência. 5.1 ASSOCIAÇÃO Associação é um cenário bastante comum, em que existe um vínculo entre as classes. No exemplo da Figura 9 temos uma associação entre as classes Gerente e Funcionário, em que um gerente pode gerenciar diversos funcionários, e um funcionário só pode ser gerenciado por um único gerente. Pode ser pertinente nesse tipo de relação criar uma classe intermediária caso essa relação precise armazenar informações adicionais, por exemplo, seu sistema necessita saber desde que dia tal funcionário passou a ser responsabilidade de determinado gerente. Figura 9 – Exemplo de associação É possível representar também quantos elementos existem na relação. Chamamos isso de multiplicidade da relação. Quadro 3 – Multiplicidade da relação Código Descrição 0..1 0 ou 1 objeto, não é obrigatório e no máximo um único objeto da classe na relação. 1..1 Apenas 1 objeto, nunca mais ou menos. 0..* Muitos objetos. De zero até um número qualquer de objetos na relação 1..* Pelo menos um. Podem existir mais objetos na relação mais ao menos 1. 2..4 Na presença de valores específicos a relação está limitada aos valores apresentados. 5.2 AGREGAÇÃO 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 16/19 Agregação é um tipo especial de associação em que a temos uma classe que representa o todo e outra classe que representa a parte. Por exemplo, quando fazemos compras online, é comum os produtos escolhidos irem para um carrinho virtual e, no final da compra, fecharmos o pedido dos itens do carrinho. Ao implementar esse sistema, poderíamos ter a classe Carrinho (todo) e a classe Produto (parte). Na agregação, faz sentido existirem as partes mesmo sem o todo. Se a classe Carrinho não existisse, a classe Produto ainda faria sentido para ser utilizada em outros contextos, para, por exemplo, modificar o preço dos itens. Quadro 4 – Exemplo de agregação 5.3 COMPOSIÇÃO Composição pode ser entendida como uma variação da agregação, pois também representa uma relação de todo-parte, no entanto a relação aqui é mais próxima entre o todo e a parte, sendo que a parte não faz sentido sem o todo, pois o todo cria e destrói as partes. Por exemplo, uma classe que represente uma empresa (todo) pode ter uma classe chamada Localidade (parte). Na classe Empresa, as Localidades são criadas, modificadas e destruídas conforme convém e existem apenas para compor a classe Empresa. Assim, não faz sentido no sistema em questão olhar uma localidade separadamente da empresa. Figura 10 – Exemplo de composição 5.4 HERANÇA A herança é outra relação representada pelo UML e serve para indicar qual é a classe geral e a classe especializada. É importante observar que os métodos da classe superclasse são acessados 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 17/19 pelas subclasses também. A relação é representada por um triângulo vazio. A imagem do Quadro 5 exemplifica essa relação com a classe Livro e Livro Digital, que temos utilizado como exemplo ao longo da aula. Quadro 5 – Exemplo de relação Herança 5.5 DEPENDÊNCIA Dependência é outra relação importante que pode ser representada pelo UML. Essa relação indica simplesmente a dependência para compilar de uma classe pela outra. Por exemplo, uma classe A utiliza internamente objetos de outra classe B, e para um código assim compilar é necessário incluir a definição dessa outra classe B no código da primeira classe A. Assim, temos uma relação em que a classe A depende da classe B. Na Figura 11, temos um exemplo prático de uma classe Cliente que utiliza métodos da classe Fornecedor para realizar alguma tarefa, portanto depende da classe Fornecedor. Figura 11 – Exemplo de dependência Os diagramas UML são bastante versáteis e servem para auxiliar a comunicação em uma equipe de desenvolvimento, trazendo uma descrição visual do que é desejado de uma implementação. Não é necessário utilizar todos os recursos do UML simultaneamente, uma vez que ela é uma ferramenta que deve ser útil aos seus propósitos e só se faz necessário descrever com ela o que se deseja demonstrar. Existem diversas ferramentas que geram diagramas levando em conta as implementações e também o contrário: cria classes com atributos e o escopo dos métodos, 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 18/19 baseando-se em um modelo UML. Na Figura 12, temos mais um exemplo de uma modelagem UML envolvendo mais classes e combinando diversas relações distintas. Figura 12 – Diagrama UML FINALIZANDO Nesta aula, introduzimos o conceito da herança, um dos pilares da programação a objetos, o que permite que diferentes classes compartilhem métodos e atributos evitando retrabalho de programarmos a mesma coisa para diversas classes que possuem escopo parecido. O conceito consiste de uma abstração em que temos uma superclasse com as principais definições e uma ou mais subclasses que se baseiam nela trazendo apenas as modificações particulares. Iniciamos o texto discutindo o conceito de herança de forma geral, as diferentes terminologias. Na sequência, apresentamos como funciona sua aplicação na linguagem Java com alguns exemplos. Logo após, discutimos a utilização de construtores junto ao conceito de herança e debatemos também as palavras reservadas super, this e instanceof. Por fim, discutimos de forma mais completa os diagramas de classe UML e as várias relações que ele representa, incluindo herança. 03/11/2022 11:28 UNINTER https://univirtus.uninter.com/ava/web/roa/ 19/19 REFERÊNCIAS BARNES, D. J.; KÖLLING, M. Programação orientada a objetos com Java. 4. ed.São Paulo: Pearson Prentice Hall, 2009. DEITEL, P.; DEITEL, H. Java: como programar. 10. ed. São Paulo: Pearson, 2017. LARMAN, C. Utilizando UML e padrões: uma Introdução à análise e a projeto orientados a objetos e ao desenvolvimento iterativo. 3. ed. Porto Alegre: Bookman, 2007. MEDEIROS, E. S. de. Desenvolvendo software com UML 2.0: definitivo. São Paulo: Pearson Makron Books, 2004. PAGE-JONES, M. Fundamentos do desenho orientado a objetos com UML. São Paulo: Makron Book, 2001 PFLEEGER, S. L. Engenharia de software: teoria e prática. 2. ed. São Paulo: Prentice Hall, 2007. SINTES, T. Aprenda programação orientada a objetos em 21 dias. 5. reimp. São Paulo: Pearson Education do Brasil, 2014. SOMMERVILLE, I. Engenharia de software. 9. ed. São Paulo: Pearson, 2011. 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 1/17 PROGRAMAÇÃO ORIENTADA A OBJETOS AULA 5 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 2/17 Prof. Leonardo Gomes CONVERSA INICIAL Nesta aula, o principal conceito que iremos abordar é o polimorfismo, que em linhas gerais é a capacidade de entendermos uma variável como pertencendo a diferentes tipos, baseando-se na forma com que é instanciada. Essa capacidade traz diversas facilidades e aumenta muito a flexibilidade dos códigos orientados a objeto. O polimorfismo também é considerado um dos pilares da programação orientada a objetos. Ao final desta aula, esperamos atingir os seguintes objetivos, que serão avaliados ao longo da disciplina da forma indicada. Quadro 1 – Objetivos Objetivos Avaliação Aplicar os conceitos de polimorfismo dentro da orientação a objetos. Questionário e questões dissertativas. Desenvolver algoritmos que fazem uso de uma lógica que envolva polimorfismo. Questionário e questões dissertativas. Conhecer e diferenciar o conceito de classe abstrata e interface. Questionário e questões dissertativas. TEMA 1 – POLIMORFISMO Neste tema, vamos debater o conceito de polimorfismo dentro da orientação a objetos e seus benefícios para a representação de códigos. O nome polimorfismo vem do grego (polýs = muitas; morphé = formas) e geralmente descreve a capacidade de objetos de uma superclasse assumirem a forma (métodos e atributos internos) de diferentes subclasses, mas existem outras situações que também são denominadas de polimorfismo que veremos na sequência. 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 3/17 Segundo a classificação mais amplamente adotada na literatura sobre polimorfismo, podemos agrupá-lo em duas categorias, universal e ad hoc: Universal: é o tipo de polimorfismo em que temos um mesmo algoritmo, um mesmo comportamento, que pode ser executado para diversas classes ou dados primitivos. Ad Hoc: é outra forma de polimorfismo, que atua em um conjunto específico de classes para as quais uma mesma chamada de método permite comportamentos diferentes para cada tipo. Dentro de cada uma das duas categorias, existem outros dois tipos de polimorfismos, totalizando quatro tipos diferentes: subtipagem, paramétrico, coerção e overloading. Veja, a seguir, a organização das diferentes categorias de polimorfismo e uma explicação mais detalhada de cada uma. Figura 1 – Representação das diferentes categorias de polimorfismo 1.1 SUBTIPAGEM É a forma mais usualmente associada ao nome polimorfismo. Ela ocorre quando temos uma superclasse que possui determinado método, e suas subclasses reimplementam esse método com outro comportamento. Lembramos que uma subclasse, além de herdar métodos e atributos, herda ainda a tipagem, portanto, é possível instanciar uma subclasse e referenciá-la como membro da sua superclasse. No próximo tema, veremos exemplos práticos em código Java. 1.2 PARAMÉTRICO É o tipo de polimorfismo em que uma função e os dados dentro dela podem ser escritos de forma genérica para diferentes tipos de dado. Uma função matemática que você deseja que funcione da mesma forma para valores de entrada do tipo int, flat ou double seria um exemplo de uso. Nas 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 4/17 linguagens Java e C#, o conceito é chamado de Generics, enquanto no C++ damos o nome de Template. Veremos detalhes de implementação ainda nesta aula. 1.3 COERÇÃO Esse tipo de polimorfismo mais frequentemente visto em códigos com tipos primitivos, embora seja possível também com objetos, ocorre quando fazemos conversão seja ela implícita, feitas de forma automática pelo compilador, ou explícita, com código descrevendo a transformação entre tipos diferentes de dados. As coerções facilitam a escrita dos códigos, e em certas situações podem inclusive aumentar a legibilidade. No entanto, pode ser também fonte de erros de difícil detecção. A seguir, um exemplo de alguns códigos em que a coerção existe. flat altura = 2; int peso = 77.5; idade = (int) 30.5; No primeiro caso, altura está recebendo um valor inteiro. Considerando que a variável é flat, existe uma coerção implícita, o dado poderia ser descrito como 2.0f caso o programador prefira deixar explícita que se trata de um flat. Um erro comum entre iniciantes na programação seria fazer uma divisão como (flat resultado = 3/2;). Neste caso, como o valor 3 e 2 são inteiros, a divisão será uma divisão inteira e o resultado será 1, e não 1.5 como se espera. Nesse tipo de situação, indicar que os valores da divisão são flat evitaria o erro. No caso das variáveis peso e idade, a conversão é de flat para int, a primeira de forma implícita e a segunda de forma explícita utilizando o conceito chamado de type cast. 1.4 OVERLOADING Neste tipo de polimorfismo, temos funções com o mesmo nome, mas com parâmetros de entrada diferentes, o que permite que executem códigos distintos. Em português, podemos chamar esse conceito de sobrecarga. 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 5/17 A primeira função recebe dois inteiros e retorna o maior, a segunda recebe três inteiros e da mesma forma retorna o maior também. Quase que a totalidade das linguagens modernas de programação permite este tipo de polimorfismo. TEMA 2 – POLIMORFISMO NA LINGUAGEM JAVA Neste tema, vamos discutir detalhes da implementação de polimorfismo na linguagem Java e os códigos propriamente ditos. Mas, primeiramente, é importante reforçar o conceito de referência em Java. 2.1 REFERÊNCIA E INSTANCIAÇÃO Quando criamos uma variável de uma classe na linguagem Java, esta se comporta como uma referência, semelhante ao conceito de ponteiro da C/C++. Em outras palavras, a variável indica (aponta) uma posição de memória. Para indicar que desejamos criar um novo objeto na memória reservando espaço e efetivamente instanciar o mesmo, utilizamos o comando new. No código abaixo, observe que criamos três variáveis da classe Aluno, porém, todas fazem referência ao mesmo objeto, o mesmo espaço de memória; dessa forma, se modificarmos uma, a mudança vale para todas as variáveis. Se desejarmos que cada uma seja uma variável independente, deveríamos criar três instâncias (comando new). 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 6/17 2.2 EXEMPLO PRÁTICO DE POLIMORFISMO Vamos partir para um exemplo prático para analisar o polimorfismo da categoria subtipagem, a forma mais comumente associada ao polimorfismo. Supomos a seguinte situação: temos uma classe para representar os funcionários de uma empresa, no entanto, os funcionários dessa empresa são contratados seguindo regimes diferentes. Existem assalariados, que recebem um valor fixo mensal, os comissionados, que recebem um percentual das vendas que realizam, e os horistas, que recebem baseado no número de horas trabalhadas. Uma forma elegante de representar essa situação utilizando orientação a objetos seria criar uma superclasse para funcionário e subclasses diferentes para as diferentes categoriasde funcionário. A seguir, o diagrama UML e código das classes. Figura 2 – Diagrama 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 7/17 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 8/17 Até aqui, nenhuma novidade no código, as classes específicas foram criadas cada uma com um método pagamento diferente e atributos próprios para representar a forma que esse pagamento é calculado. No entanto, observe que é possível declarar uma variável da superclasse Funcionario e fazer a mesma apontar para objetos instanciados como subclasses Assalariado, Comissionado e Horista. Nesse caso, a chamada do método pagamento tem duas opções, executar a implementação da superclasse ou a da subclasse, nessa situação, será sempre executado o método da subclasse, conforme a variável foi instanciada. Confira essa situação no código a seguir. Talvez você se pergunte: “qual é a grande vantagem do polimorfismo?”. Imagine que desejamos armazenar todos os nossos funcionários em uma estrutura de ArrayList. Sem polimorfismo, a única forma seria a criação de três ArrayLists, pois temos três subclasses de funcionários. Desse modo, seria necessário um array para cada, dando muito mais trabalho na hora de realizar uma busca ou 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 9/17 qualquer outro código. No entanto, com o uso de polimorfismo, podemos criar um único array da superclasse funcionário e adicionar objetos das diferentes subclasses que executarão sempre os métodos conforme foram instanciadas. No exemplo a seguir, vemos um código que adiciona alguns funcionários de subclasses distintas em um mesmo array e, na sequência, calcula o total do pagamento de todos os funcionários deste array. TEMA 3 – CLASSE ABSTRATA No contexto de orientação a objetos, eventualmente teremos classes que não desejamos instanciar. Se formos analisar, na vida real existem conceitos abstratos que não possuem um objeto físico ligado a eles, por exemplo, uma forma geométrica. Sem especificarmos que forma geométrica estamos nos referindo, fica impossível analisar qualquer uma de suas propriedades físicas. Desse modo, podemos afirmar que uma forma geométrica ocupa uma certa área, mas sem especificar que forma, é impossível dizer como calcular essa medida. A ideia de classe abstrata propõe um conceito análogo que, na prática, implica que uma classe que você especifique como abstrata não poderá ser instanciada. Mas de que serve uma classe que não pode ser instanciada? Propriedades das classes abstratas: 1. Pode referenciar objetos de subclasses graças ao polimorfismo. 2. Permite que criemos métodos desprovidos de implementação, mas que obrigatoriamente devem ser implementados por suas subclasses. 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 10/17 3. Não podem ser instanciadas, ocorre um erro ao tentarmos instanciar um objeto dessa classe. Erros de compilação, ao contrário do senso comum, são muito positivos pois são fáceis de se identificar e corrigir, diferentemente de um bug que só será identificado durante a execução do programa e não possui qualquer indicação de onde possa estar no código. Os erros de compilação caem em duas categorias: erros léxicos, quando um comando é escrito errado, e semânticos, quando o comando é escrito corretamente, mas em local que não faça sentido; o compilador detecta esses erros, que são fáceis de localizar. Erros semânticos são aqueles em que o comando está escrito certo, no local certo, mas executa instruções diferentes das desejadas, em outras palavras, o significado do que está escrito não é o que se imaginava. Poderíamos nunca adotar classes abstratas a princípio sem grandes prejuízos, mas ao pensarmos em projetos grandes com muitos programadores participando, fica muito fácil descrever uma classe abstrata para que sirva de modelo para subclasses feitas por outros programadores. Vamos analisar um exemplo prático: suponha uma classe FormaGeometrica que possua um método chamado calculaArea. Esse método não será implementado pela classe FormaGeometrica, mas será obrigatória para suas classes filhas. Veja o código a seguir. 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 11/17 No código anterior, observe que na linha 01 utilizamos a palavra reservada abstract para indicar que a classe é abstração e pode ser instanciada com o comando new. Na linha 03, indicamos que o método calculaArea é abstrato, o que implica que os filhos não abstratos de FormaGeometrica são obrigados a implementá-los; caso contrário, a IDE apontará errado, o que facilita que programadores que decidam criar classes filhas de FormaGeometrica sigam adequadamente os escopos das funções. 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 12/17 TEMA 4 – INTERFACE Neste tema, vamos debater um conceito bastante próximo ao de classe abstrata, as interfaces. Na linguagem Java, não é possível termos múltiplas superclasses para uma mesma subclasse, certas linguagens permitem isso, como o C++, porém, não é o caso do Java. No entanto, temos o que chamamos de Interface. A palavra interface diz respeito ao meio de ligação/comunicação entre dois sistemas. Por exemplo, é possível ligar uma televisão em um computador por meio de uma interface HDMI, o protocolo de comunicação envolvido define características elétricas, como tensão e corrente, bem como o padrão de transmissão, taxa de símbolos e velocidade de transmissão. Ela permite ligar um mesmo computador com diferentes televisores criados por fabricantes distintas, e tudo funciona de forma transparente, desde que o protocolo de Interface HDMI seja respeitado por ambos. A analogia aqui é a mesma para a programação. Suponha que temos uma classe BancoDeDados, que tem os seus métodos acessados por uma classe Principal. Se desejarmos substituir a classe BancoDeDados por uma outra classe BancoDeDadosAlternativo, que internamente se comunique com um banco diferente, teremos que adaptar a chamada dos métodos pela classe Principal, gerando um retrabalho muito grande que é desnecessário, visto que, em essência, as classes BancoDeDados e BancoDeDadosAlternativo fazem as mesmas coisas, porém utilizando estratégias internas diferentes. Esse é o cenário perfeito para o uso de interface, pois ela força que classes diferentes implementem métodos com os mesmos nomes, entradas e saídas, garantindo que a classe Principal possa acessar as diferentes classes de banco de dados da mesma forma. Em resumo, uma interface pode ser entendida como um protocolo que explica como deve ser a assinatura dos métodos de uma classe. As interfaces funcionam de forma semelhante a classes que não podem ser instanciadas, mas podem servir de referência para cenários de polimorfismo. Vejamos o código abaixo: 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 13/17 No Java, declaramos interface dentro de seu próprio arquivo semelhante a uma classe e, no lugar da palavra class, escrevemos interface (linha 01). Dentro do bloco de código associado à interface, declaramos a assinatura dos métodos que desejamos que sejam implementados pelas classes. No exemplo anterior, a classe Gato implementa a interface Animal (linha 05), obrigando a classe Gato a implementar os métodos da interface. Observe que a classe Gato poderia implementar mais de uma interface apenas separando elas por vírgula (por exemplo: class Gato implements Animal,Mamifero). Interface funciona de forma muito semelhante à herança, no entanto, em vez da palavra extens., utilizamos a palavra implements. Atente para a diferença na nomenclatura, entendemos que as classes implementam interfaces e herdam superclasses. Quais são as diferenças entre interface e classe abstrata? Quadro 2 – Interface e classe abstrata Propriedade Interface Classe abstrata 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 14/17 Herança Classes podem implementardiversas interfaces. Uma classe só pode herdar uma única superclasse. Métodos Interface só possui a assinatura dos métodos. Uma classe abstrata pode implementar códigos dentro de seus métodos, que serão ou não sobrescritos. Atributos Só pode possuir atributos estáticos. Pode ter tanto atributos estáticos quanto não estáticos. Adaptação Fácil adaptar uma classe existente para implementar uma interface, bastando implementar os métodos conforme a assinatura. Adaptar uma classe existente para herdar uma classe abstrata pode ser trabalhoso por ser necessário modificar a hierarquia já existente de heranças. Quando usar? Classes que compartilham mesmo comportamento, assinatura. Classes que compartilham os mesmos atributos e precisam ter seu estado avaliado de forma compatível. Modificações adicionais Ao adicionar um novo método a uma interface, todas as classes devem trazer suas implementações. Ao adicionar um novo método, é possível trazer uma implementação padrão que servirá para todas as classes filhas. TEMA 5 – ENUM Em diversas linguagens de programação, incluindo a Java, existe o conceito de enum, que consiste de uma classe especial de rápida e simples implementação que é capaz de representar um grupo de constantes enumerando-as geralmente por debaixo dos panos, com inteiros em sequência. Geralmente é adotado para representar estados. Para criar um enum, utilizamos a palavra reservada enum em vez de class ou interface. E dentro do corpo do enum, deve vir o nome das constantes separadas por vírgula, pela convenção estabelecida oficialmente pela Oracle. Ademais, as constantes devem sempre descritas em letras maiúsculas na linguagem Java. Suponha que desejamos implementar um sistema para um site que vende roupas, em que cada roupa é destinada a uma estação do ano. Como representarmos isso? Temos diversas estratégias: Sting: poderíamos utilizar uma string com o nome da estação, o que ocuparia um espaço de memória muito além do necessário para uma tarefa tão simples, tornando a comparação entre estações lenta. Sting estacaoRoupa = "inverno". Int: podemos criar uma estratégia na qual associamos um inteiro diferente para cada estação, (inverno=1, primavera=2,...). Rápido para comparar, ocupa pouca memória, mas gera códigos pouco legíveis. int estacaoRoupa = 1. 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 15/17 Constantes: semelhante à solução com uso de inteiros, porém, criamos uma coleção de constantes associando cada estação com um número, solução rápida e que deixa o código mais claro. int estacaoRoupa = INVERNO; Enum: semelhante ao uso de constantes, no entanto, facilmente podemos agrupar as constantes em um conjunto comum denominado Estacao e declararmos a variável do próprio tipo Estacao, deixando ainda mais claro o código. Estacao estacaoRoupa = Estacao.INVERNO. Veja a seguir o exemplo de como ficaria o enum Estacao. É possível ainda fazer um loop por todas as estações utilizando o método values() que retorna um array contendo todas as constantes de um enum. 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 16/17 Enum pode ser declarado no próprio arquivo como uma classe, e geralmente é, ou pode ser declarado internamente dentro de uma classe existente para ser utilizado apenas localmente. Enum, na linguagem Java, é representado como uma classe, que inclusive pode possuir métodos e atributos, no entanto, suas constantes são sempre entendidas como public, static, final. public: as constantes são acessíveis por qualquer classe que possua visibilidade do enum. static: existe apenas uma constante para a classe inteira, e não uma por instância. final: não pode ter o valor alterado em tempo de execução. Quando utilizar enum? Ele é recomendado especialmente em situações em que você tem que descrever uma coleção de valores fixos que não variam, como meses, cores, baralho de cartas etc. FINALIZANDO Nesta aula, debatemos sobre o conceito de polimorfismo, primeiramente de forma geral, dentro do contexto de orientação a objetos como um todo, e posteriormente suas especificidades na linguagem Java. Na sequência, vimos a questão da criação de superclasses abstratas e o conceito bastante semelhante da interface, discutimos as suas diferenças e aplicações. Por fim, discutimos o conceito de enum, que facilita o trabalho de agruparmos e classificarmos conjunto de constantes, geralmente adotado para representar estados de alguma situação. Nas próximas aulas, vamos finalizar nosso conteúdo teórico debatendo as exceções, tratamento de erros e alguns conceitos adicionais dentro da linguagem Java. REFERÊNCIAS BARNES, D. J.; KÖLLING, M. Programação orientada a objetos com Java. 4. ed. São Paulo: Pearson Prentice Hall, 2009. 03/11/2022 11:29 UNINTER https://univirtus.uninter.com/ava/web/roa/ 17/17 DEITEL, P.; DEITEL, H. Java Como programar. 10. ed. São Paulo: Pearson, 2017. LARMAN, C. Utilizando UML e Padrões: Uma Introdução à Análise e ao Projeto Orientados a Objetos e ao Desenvolvimento Iterativo. 3. ed. Porto Alegre: Bookman, 2007. MEDEIROS, E. S. de. Desenvolvendo software com UML 2.0: definitivo. São Paulo: Pearson Makron Books, 2004. PAGE-JONES, M. Fundamentos do desenho orientado a objetos com UML. São Paulo: Makron Book, 2001. PFLEEGER, S. L. Engenharia de software: teoria e prática. 2. ed. São Paulo: Prentice Hall, 2004. SINTES, T. Aprenda programação orientada a objetos em 21 dias. 5ª reimpressão. São Paulo: Pearson Education do Brasil, 2014. SOMMERVILLE, I. Engenharia de software. 9. ed. São Paulo: Pearson, 2011. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 1/21 PROGRAMAÇÃO ORIENTADA A OBJETOS AULA 6 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 2/21 Prof. Leonardo Gomes CONVERSA INICIAL Nesta aula, vamos abordar a princípio o tratamento de erros e exceções na linguagem Java, como um programa deve se portar diante de situações inesperadas utilizando os comandos try e catch. Na sequência, iremos debater formas de criar as próprias exceções e, por fim, vamos discutir outras palavras reservadas importantes dentro do Java. Objetivos da aula: ao final desta aula, esperamos atingir os seguintes objetivos, que serão avaliados ao longo da disciplina da forma indicada. Quadro 1 – Objetivos Objetivos Avaliação 1 - Entendimento do conceito de exceção e seu devido tratamento utilizando os try catch. Questionário e questões dissertativas 2 - Ser capaz de desenvolver as próprias exceções. Questionário e questões dissertativas 3 - Dominar o conceito de alguns métodos padrões importantes dentro do Java como toString e equals. Questionário e questões dissertativas TEMA 1 – TRATAMENTO DE EXCEÇÕES Neste tema, vamos abordar a questão das exceções e a forma de lidar com elas na linguagem Java. Primeiramente, vamos definir algumas questões. Dentro do Java, temos o conceito do erro e da exceção. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 3/21 Erro (Error): é um problema sério que ocorre em tempo de execução, impossível para o compilador detectar e que geralmente não tem tratamento para ele. No geral, são problemas na plataforma que está rodando o programa Java. Por exemplo, a falta de memória para alocar recursos necessários do programa gera um erro que, na maioria das aplicações, não existirá meio de se contornar e, na maioria das vezes, o programa deverá simplesmente ser interrompido. A solução real seria realmente agir na plataforma em que o software está rodando e cabe ao programa gerar um log (relatório), ou alerta descrevendo o evento. O Java, ao detectar esse problema, lança esse erro no formato java.lang.OutOfMemoryError. Exceção (Exception): também é um problema, mas que geralmente pode ser manejado pelo programa e tratado de alguma forma. Por exemplo, ao tentar ler dados em um arquivo que foi apagado e não existe mais,irá gerar a exceção chamada pelo Java de FileNotFoundException. Nesta situação, geralmente é possível em vez de simplesmente encerrar o programa repentinamente, apresentar uma mensagem para o usuário e solicitar alguma intervenção e seguir com o programa funcionando. Tanto os erros quanto as exceções no Java, ao ocorrerem, são identificadas e lançadas de uma forma que determinados comandos conseguem recuperar o controle do programa e impedir que ele seja encerrado subitamente. Erros e exceções geram subclasses de uma superclasse Throwable e respeitam a hierarquia da figura a seguir. Figura 1 – Representação da hierarquia das classes de erros e exceções do Java 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 4/21 Quando ocorre uma exceção/erro em um método, ele gera um objeto do tipo específico e envia para a máquina virtual Java (JVM). O objeto Exception contém o nome, uma descrição e o estado do programa no momento que ocorreu o problema. A ordenação das chamadas dos métodos é conhecida como Call Stack (pilha de chamadas). Depois isso, ocorre a seguinte sequência de passos. A JVM busca na call stack um método que contenha um bloco de código capaz de tratar a exceção, vamos chamar de bloco tratador de exceção. A busca inicia no método que gerou a exceção e segue ordem reversa de chamada. Ao encontrar um bloco tratador apropriado, ele recebe o controle do fluxo de código junto ao objeto exceção. Por apropriado, entendemos que ele trata um tipo de exceção compatível com a que foi gerada. Se a JVM não encontrar um bloco tratador de exceções na call stack, então a JVM utiliza um bloco tratador de exceções padrão que irá encerrar o programa imediatamente e imprimir uma mensagem no console dando todas as informações da exceção, nome, descrição e a call stack. 1.1 LIDANDO COM EXCEÇÕES Na linguagem Java, assim como nas principais linguagens de programação, existem os comandos try (tentar) e catch (capturar). O comando try é associado a um bloco de código que será executado e caso um erro ou exceção aconteça o programa não encerrará automaticamente com mensagem de alerta no console, mas, sim, o controle será devolvido ao programa dentro do bloco catch adequado. Opcionalmente, é possível colocar o comando finally (finalmente) que também é associado a um bloqueio de código que será executado independente de o código ter entrado ou não no bloco catch. Vamos ver um exemplo de código a seguir. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 5/21 Na linha 3, temos o início do bloco try, observe que, na linha 5, acessamos a posição 10 de um array com apenas três itens, isso gera uma ArrayIndexOutOfBoundsException sem o try, nesse momento, o programa iria interromper imediatamente, porém, com o try, a exceção gerada pelo sistema será capturada pelo catch e em vez de interromper a mensagem problema, será impressa na tela. Observe que é possível imprimir o objeto exceção, o que trará nome e descrição dela. Por fim, o bloco existente dentro do finally (linha 8) será executado. TEMA 2 – CRIANDO AS PRÓPRIAS EXCEÇÕES Além de tratar as exceções e erros que o sistema lança por padrão, podemos lançar exceções e até mesmo criar nossas próprias exceções. A ideia por trás de lançar exceções está em evitar que o programa continue executando mediante situações anormais e devolver o controle para o método tratador adequado. Por exemplo, se o seu programa falhar em conectar com o banco de dados, o sistema lançará uma exceção de forma automática, provavelmente do tipo java.sql.SQLException. No entanto, se o banco de dados conectar adequadamente, mas um dado crítico para o funcionamento do seu programa não estiver presente na base dados, então não existirá uma exceção e, caso a situação não 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 6/21 seja devidamente identificada e tratada, o programa continuará em execução com um comportamento imprevisível. Este é o cenário adequado para se lançar uma exceção própria, identificando que o dado crítico não está presente na base de dados e isso pode ser feito por meio do comando throw. Esse comando é capaz de explicitamente lançar uma exceção qualquer. Confira o código a seguir. Nesse código, na linha 4, explicitamente criamos um objeto do tipo exceção NullPointerException, que geralmente é associado a um ponteiro nulo em uma situação inapropriada. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 7/21 No construtor do ponteiro, colocamos a mensagem Problema, podendo associar a mensagem que desejarmos nas exceções. Na linha 6, a exceção será capturada, e uma mensagem será impressa na tela e o erro lançado novamente. Como não existe outro bloco try englobando esse throw, a exceção sobe na pilha de chamadas de função até encontrar um try, no caso na main que chamou o método funcao() (linha 13) e, por estar em um bloco try/catch, também capturou na linha 15 e imprime uma mensagem final. 2.1 TIPOS DE EXCEÇÕES Na Figura 1, vemos que temos duas subclasses de Exception e são os dois tipos diferentes de exceções no Java: 1) Checked: são exceções verificadas em tempo de compilação, checked em inglês significa checada. Isso significa que ao lançar uma exceção desse tipo o método deve tratar ela com try/catch ou explicitamente anunciar na assinatura do método que pode lançar esse tipo de exceção com a palavra throws, caso não faça nenhuma das duas coisas, o compilador irá gerar um erro. Vamos analisar o código a seguir: 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 8/21 No código acima, três linhas são lidas e impressas na tela do arquivo “teste.txt”. No entanto, o código não funcionará, pois a classe FileReader lança uma exceção IOException do tipo checked. Para o compilador aceitar o código, será necessário utilizar bloco try/catch ou na assinatura do método explicitar que a exceção pode ser lançada. Todo o método A que explicitamente lance uma exception na assinatura obriga que, ao ser invocado por um outro método B, ele deva ou tratar com try/catch ou ele também lançar a exception na sua assinatura. No caso da main, se ela lançar a exceção para quem a invocou, o programa será interrompido imediatamente com mensagem no console informando a exceção. Unchecked: são exceções que não são checadas em tempo de compilação, dispensam o uso de throws na assinatura da função quando não tratadas, fica livre ao programador decidir se precaver com o uso de try/catch ou não. Problemas do tipo Error também não são checados, na hierarquia de classes presente na Figura 1, as exceções não checadas também são chamadas de RuntimeException. Esse código irá gerar uma RunTimeException do tipo java.lang.ArithmeticException. 2.2 DESENVOLVENDO SUA PRÓPRIA EXCEPTION 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 9/21 Além de lançar as exceções existentes, é possível criar a sua própria exceção subclasse de Exception ou RuntimeException e até mesmo Error. Ao criar uma exceção que estende Exception, ela será do tipo checada e, se ela estender RuntimeException, será do tipo não checada. Basta criar uma subclasse e ela poderá ser utilizada para descrever uma situação inesperada. No exemplo a seguir, temos um código que personaliza uma Exception para a situação de não encontrar um usuário em um banco de dados. Classe possui o método buscar que, caso não encontre o usuário, lança a exceção personalizada. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 10/21 Método principal aplicando o tratamento com try/catch. Dessa forma, outros problemas que no contexto de uma aplicação seja conveniente serem tratados como exceção também poderão ter suas exceções personalizadas. TEMA 3 – IGUALDADE Neste tema, vamos debater questões relativas à igualdade no Java. Quando desejamos comparar se dois elementos são iguais no Java, é comum pensarmos no comando ==. Por exemplo: x == 10 ‘a’== ‘b’ No entanto, esse comando embora compare adequadamente primitivas (que são variáveis básicas: int, char, float etc.), ele tem uma funcionalidade diferente ao comparar objetos, o comando retornará verdadeiro apenas se o endereço dos objetos for o mesmo, ou seja, se são exatamente a mesma instância e não se o conteúdo é o mesmo. Por exemplo, no Java as Strings são objetos e não primitivas, portanto, não é possível comparar o conteúdo com ==, para isso utilizamos um método chamado equals. Vejamos o código a seguir. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 11/21 Nesse exemplo, quando comparamos s1 com s2 utilizando ==, observe que o resultado é falso justamente pelo fato de serem instâncias diferentes. Agora s1 e s3 são iguais, pois tratam da mesma instância, o exato mesmo objeto, se realizarmos uma mudança em s3, vai afetar s1 e vice-versa, pois ambos apontam para a mesma região na memória. O método equals é um padrão dentro do Java e serve justamente para comparar conteúdos, não é obrigatório implementar o método equals para todas as classes, mas é uma convenção para aquelas classes que faça sentido a comparação entre objetos. Essa padronização garante a compatibilidade com certas classes do sistema também. A seguir, criamos uma classe simples para representar um usuário de um sistema e um método equals que compara ambos: 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 12/21 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 13/21 Na linha 11, repare que o método equals não recebe um Usuário como parâmetro, mas, sim, um Object, que é a classe mais geral de todas em Java. Por padrão todas as classes que não tenham uma superclasse são subclasses de Object, o que garante que todo o objeto instanciado pode ser entendido como um Object. Em outras palavras, é possível, dessa forma, comparar uma instância de Usuário com qualquer coisa. Com isso, até mesmo uma instância de uma subclasse de Usuário poderia ser comparada a uma outra instância de Usuário e se tiverem os mesmos valores será retornado verdadeiro. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 14/21 Na linha 21, observe o uso da técnica chamada de type cast, quando temos um objeto de uma classe e queremos indicar aquele objeto, pode ser interpretado como uma outra classe, a colocamos entre parênteses da forma que foi feita para evitar que ocorra um erro de compilação. Observe que isso não executa nenhuma transformação nos dados, apenas evita o erro de compilação, se a transição funcionará é responsabilidade do programador que realizou o comando. Diversas IDE Java, incluindo o Eclipse, conseguem gerar o código do equal automaticamente na opção Source/Generate hashCode() and equals()... Figura 2 – Menu Eclipse para criação de código automático TEMA 4 – MÉTODOS ESPECIAIS: TOSTRING Neste tema, vamos apresentar mais um método padrão que podemos sobrescrever no Java e que apresenta facilidades ao lidar com texto o método toString(). 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 15/21 Supondo a situação em que temos um objeto e desejamos descrevê-lo em formato texto para realizar um log, depuração ou mesmo apresentar o conteúdo de um objeto diretamente para o usuário do programa. O comum seria acessar cada atributo individualmente, no entanto, podemos acessar o objeto diretamente no formato string e especificar por meio do método toString(), como essa string será, indicando até mesmo atributos e resultados de métodos. Como já foi discutido anteriormente, todo o objeto Java é diretamente ou indiretamente subclasse de Object. E a classe Object possui o método toString() que, por padrã,o retorna uma String com o endereço na memória daquele objeto. Esse método toString() é implicitamente acessado sempre que tentamos ler o objeto no formato de uma string, por exemplo, ao tentar imprimir um objeto. Ao executarmos esse código, o endereço de memória no qual foi instanciado o objeto usr aparecerá na tela. Para que, ao fazermos isso, apareçam os dados do objeto usuário, podemos sobrescrever o método toString, como no exemplo a seguir. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 16/21 //Na main() No caso geral, ao tentarmos imprimir um objeto da classe Usuário, teríamos uma mensagem como Usuario@232204a1, o nome da classe seguido de um endereço de memória em que o objeto foi instanciado. Mas ao criarmos o método toString, essa mensagem é substituída pelo resultado do método. No código anterior, na linha 6, vemos o método toString, que retorna uma string que condiz a como desejamos representar o objeto no formato String. Na linha 5, temos o comando @Override, que serve para indicar que o método está sobrescrevendo outro de sua superclasse, esse comando não é necessário, no entanto, ele possui dois papéis: indicar aos programadores que lerão o código que aquele é um método sobrescrevendo outro e fazer com que o compilador verifique a adequação da assinatura do método. Também é possível em diversas IDEs Java, incluindo eclipse, gerar o código automaticamente para o método toString, indo na opção Source/Generate toString(). TEMA 5 – MODIFICADORES JAVA (SINGLETON) Neste tema, vamos abordar um padrão de desenvolvimento muito importante dentro das linguagens de programação orientadas a objeto, o padrão Singleton. Certos problemas dentro da programação, de tão clássicos e recorrentes, possuem soluções que se tornam referência e são usadas de forma padrão em projetos profissionais, na literatura, essas soluções padronizadas de problemas recorrentes são chamadas de design pattern. Um problema recorrente é a necessidade de termos um objeto que seja instanciado uma única vez e sempre que seja solicitado pelas classes de um projeto seja direcionado ao mesmo objeto. Um conceito semelhante ao de uma variável global, porém, utilizando uma instância. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 17/21 Dentro da literatura de design pattern, a solução para essa situação é a utilização do que chamamos de Singleton. Alguns exemplos comuns de uso do padrão Singleton é quando temos algum tipo de recurso compartilhado. Por exemplo, diversas classes requisitando acesso a uma impressora, banco de dados ou um mesmo arquivo não é interessante que esses recursos sejam acessados paralelamente por múltiplas instâncias, poderia causar conflitos entre as requisições, nesse tipo de situação, Singleton seria bem aplicado. Outro exemplo comum é a classe responsável por efetuar log de atividades. Essa tarefa é constantemente invocada em diferentes etapas de um sistema e ter uma única instância realizando-a minimiza o overhead (trabalho extra de gestão) da classe, visto que essa operação é realizada de forma constante e relativamente independentemente do restante do projeto. Na prática, para criar uma classe Singleton, são necessários dois passos: 1. Ter um construtor privado. 2. Criar um método estático que retorna um objeto da classe em questão instanciado. Conceito conhecido como Lazy initialization (inicialização preguiçosa), em que o objeto é criado depois de declarado. A seguir, o código apresenta uma classe Singleton que pode ser instanciada uma única vez: 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 18/21 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 19/21 Nesse código, o método getInstance() é utilizado para recuperar a instância, já que o construtor é privado e, portanto, inacessível de fora da classe. Observe que a variável número é a mesma para as variável x, y, z, afinal, trata-se da mesma instância, mesma posição de memória internamente. O método getInstance verifica se o objeto já foi instanciado. Caso positivo, ele retorna o objeto e, caso contrário, ele efetivamente cria a instância antes de retorná-la. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/20/21 FINALIZANDO Nesta aula, abordamos temas diversos, o tópico principal foi a gestão de erros e exceções. Como evitar que uma situação imprevisível faça perder o controle do programa e como gerar as próprias exceções para informar situações fora da normalidade. Na sequência, discutimos dois métodos importantes que existem na classe Object, tradicionalmente sobrescrita, sendo eles o método equals, que compara o conteúdo de objetos distintos, e o método toString, que permite de forma prática tratar objetos como strings personalizadas. Por fim, debatemos o conceito de Singleton, um design pattern bastante popular, aplicado a diversas linguagens de programação e que serve para garantir a existência de uma única instância. REFERÊNCIAS BARNES, D. J.; KÖLLING, M. Programação orientada a objetos com Java. 4. ed. São Paulo: Pearson Prentice Hall, 2009. DEITEL, P.; DEITEL, H. Java: como programar. 10. ed. São Paulo: Pearson, 2017 LARMAN, C. Utilizando UML e padrões: uma introdução à análise e ao projeto orientados a objetos e ao desenvolvimento iterativo. 3. ed. Porto Alegre: Bookman, 2007. MEDEIROS, E. S. de. Desenvolvendo software com UML 2.0: definitivo. São Paulo: Pearson Makron Books, 2004. PAGE-JONES, M. Fundamentos do desenho orientado a objetos com UML. São Paulo: Makron Book, 2001 PFLEEGER, S. L. Engenharia de software: teoria e prática. 2. ed. São Paulo: Prentice Hall, 2004. SINTES, T. Aprenda programação orientada a objetos em 21 dias. 5. reimpressão. São Paulo: Pearson Education do Brasil, 2014. SOMMERVILLE, I. Engenharia de software. 9. ed. São Paulo: Pearson, 2011. 03/11/2022 11:30 UNINTER https://univirtus.uninter.com/ava/web/roa/ 21/21