Prévia do material em texto
W BA 13 62 _V 1. 0 PROGRAMAÇÃO ORIENTADA A OBJETOS 2 Anderson da Silva Marcolino São Paulo Platos Soluções Educacionais S.A 2023 PROGRAMAÇÃO ORIENTADA A OBJETOS 1ª edição 3 2023 Platos Soluções Educacionais S.A Alameda Santos, n° 960 – Cerqueira César CEP: 01418-002— São Paulo — SP Homepage: https://www.platosedu.com.br/ Head de Platos Soluções Educacionais S.A Silvia Rodrigues Cima Bizatto Conselho Acadêmico Alessandra Cristina Fahl Ana Carolina Gulelmo Staut Camila Braga de Oliveira Higa Camila Turchetti Bacan Gabiatti Giani Vendramel de Oliveira Gislaine Denisale Ferreira Henrique Salustiano Silva Mariana Gerardi Mello Nirse Ruscheinsky Breternitz Priscila Pereira Silva Coordenador Henrique Salustiano Silva Revisor Fabiano Gonçalves dos Santos Editorial Beatriz Meloni Montefusco Carolina Yaly Márcia Regina Silva Paola Andressa Machado Leal Dados Internacionais de Catalogação na Publicação (CIP)_____________________________________________________________________________ Marcolino, Anderson da Silva Programação orientada a objetos/ Anderson da Silva Marcolino. – São Paulo: Platos Soluções Educacionais S.A. 2023 32 p. ISBN 978-65-5356-441-1 1. Programação. 2. Software. 3. Orientada a objetos. I. Título. 3. Técnicas de speaking, listening e writing. I. Título. CDU 004.3 _____________________________________________________________________________ Raquel Torres – CRB 8/10534 M321p © 2023 por Platos Soluções Educacionais S.A. Todos os direitos reservados. Nenhuma parte desta publicação poderá ser reproduzida ou transmitida de qualquer modo ou por qualquer outro meio, eletrônico ou mecânico, incluindo fotocópia, gravação ou qualquer outro tipo de sistema de armazenamento e transmissão de informação, sem prévia autorização, por escrito, da Platos Soluções Educacionais S.A. https://www.platosedu.com.br/ 4 SUMÁRIO Apresentação da disciplina __________________________________ 05 Histórico e pilares da programação orientada a objetos _____ 07 Abstração e herança em orientação a objetos _______________ 19 Encapsulamento e instanciação de objetos __________________ 39 Implementação de interfaces gráficas com Java ______________ 56 PROGRAMAÇÃO ORIENTADA A OBJETOS 5 Apresentação da disciplina No contexto de desenvolvimento de software, temos diversas tecnologias para desenvolver soluções para diversos problemas. Dentre tais tecnologias, destacamos linguagens de programação orientada a objetos. É neste contexto que, nesta disciplina, entenderemos o surgimento do paradigma orientado a objetos, seus fundamentos e como podemos utilizar todo o seu poder de reuso para desenvolver softwares utilizando a linguagem de programação Java. A disciplina está dividida em quatro módulos. No primeiro, veremos como a orientação a objetos surgiu a partir de um breve histórico. Conceituaremos os pilares fundamentais da orientação a objetos e como estes se integram, permitindo usufruir de diversos benefícios, os quais serão discutidos e apresentados também. Neste mesmo módulo, veremos conceitos sobre a linguagem de modelagem unificada, que permite representarmos graficamente representações dos elementos contidos em um programa orientado a objetos. No segundo módulo, veremos a utilização dos pilares para programar em Java. Utilizaremos o pilar de abstração para entender como criamos os objetos em Java, seus estados e seus comportamentos. Adicionalmente, utilizaremos o pilar de herança, ainda que em menor profundidade. No terceiro módulo, utilizaremos os pilares de encapsulamento e nos aprofundaremos nos conceitos de herança. Entenderemos como instanciar objetos de classes e os segredos por trás dos métodos construtores, bem como o que são a generalização e a especialização. 6 No quarto e último módulo, utilizaremos um pacote Java específico para criação de interfaces gráficas de usuário, por meio do desenvolvimento de uma aplicação que nos permitirá colocar em prática tudo que foi aprendido nos módulos anteriores e avançar ainda mais na linguagem. Assim, conseguiremos formar uma base sólida para que possamos nos aventurar ainda mais em Java e na sua vasta gama de bibliotecas, ou migrarmos para outras linguagens de programação orientada a objetos. Desejo bons estudos! 7 Histórico e pilares da programação orientada a objetos Autoria: Anderson da Silva Marcolino Leitura crítica: Fabiano Gonçalves dos Santos Objetivos • Compreender o histórico e a evolução dos paradigmas de programação até o paradigma de orientação a objetos. • Estabelecer os pilares que fundamentam a programação orientada a objetos. • Compreender via código e representações da Linguagem de Modelagem Unificada (UML) os conceitos fundamentais do paradigma orientado a objetos. 8 1. Introdução e histórico do paradigma de programação orientada a objetos A linguagem de programação orientada a objetos (OO) tem seu foco dirigido à representação de objetos reais, suas características e seus comportamentos. Essa representação, além de permitir uma melhor abstração de objetos, garante o reuso. Neste contexto, a criação e o desenvolvimento, bem como a evolução das linguagens de programação OO, são alicerçados em benefícios, com destaque para o reuso. Deste modo, torna-se importante conhecer o motivo de tal concepção e como ela atingiu o estágio em que se encontra hoje, por meio de um breve histórico. Inicialmente, o conceito de orientação a objetos estava voltado à representação de objetos reais por meio de um paradigma de programação. Um paradigma pode ser definido com um conjunto de elementos, no caso de uma linguagem de programação, que criam um modelo, mas, antes de OO, temos outros paradigmas adotados (MICHAELIS, 1998). Os primeiros paradigmas de programação foram os funcionais e imperativos, utilizados na área de Exatas no contexto da Máquina de Turing e do cálculo Lambda, por volta de 1030. Enquanto a Máquina de Turing utilizava cartões perfurados para processamento de baixo para cima (bottom-up), passo a passo, o cálculo Lambda representa um processamento que vai de cima para baixo (top-down), em uma abordagem voltada à utilização de funções. Deste modo, em muitos livros, temos a adoção do termo top-down para as linguagens de programação procedurais. Nos anos seguintes, por volta de 1940, linguagens de programação de baixo nível, como 9 Assembly, foram criadas. Na sequência, nos anos de 1950, linguagens de alto nível, como COBOL e FORTRAN, ambas imperativas, foram criadas. Somente na década de 1960 que o termo programação orientada a objetos foi cunhado por Alan Kay, em seu trabalho de graduação. No entanto, foi em 1965 que foi criada a primeira linguagem reconhecida, de fato, como uma linguagem orientada a objetos: a Simula. Essa linguagem introduziu conceitos, como classes, herança, subclasses e métodos virtuais (aqueles que serão sobrescritos nas subclasses). O mesmo idealizador do conceito de OO criou, junto a outros pesquisadores, a linguagem Smalltalk, contudo esta era mais funcional do que orientada a objetos. Somente quando intitulada de Smalltalk-72 que se tornou uma linguagem mais orientada a objetos que a Simula. Alan classifica as características essenciais para que uma linguagem de programação seja assim designada, sendo elas a passagem de mensagens, o encapsulamento e a vinculação dinâmica. Em 1991, a Sun Microsystems criou a linguagem que seria uma das mais famosas no contexto de programação orientada a objetos, a linguagem Java, a qual, hoje, se encontra na sua versão 18 (JECK, 2021). Considerando as características preconizadas por Alan e as integradas em Java, podemos destacar o que torna o paradigma de OO tão importante (ELLIOTT, 2020): • Representação de objetos do mundo real por meio de suas características ou propriedades e de suas ações ou comportamentos. • Capacidade de troca de mensagens e alteração de estados por meio de meiosespecíficos, sem que os estados mutáveis estejam disponíveis a todo o código. 10 • Estabelecimentos claros dos objetos, seus papéis e, consequentemente, a dissociação entre eles, tornando-os únicos. • Capacidade de adaptabilidade e resiliência a mudanças, em especial, no tempo de execução, ou seja, quando o programa já está sendo executado. Porém, para que tais conceitos e benefícios possam ser adotados, precisamos entender de fato os conceitos iniciais que integram o paradigm de OO. É o que estudaremos a seguir. 2. Conceitos essenciais de OO Considerando o próprio nome do paradigma – orientação a objetos –, temos um elemento essencial e central: o objeto. Um objeto é uma entidade do mundo real, seja esta entidade concreta (uma pessoa) ou abstrata (um personagem de uma história), que pode ser descrita por meio de características e estados e que pode realizar ações ou possuir determinados comportamentos (PRESSMAN; MAXIM, 2021). Deste modo, um objeto em OO é representado por meio de atributos, que refletem o estado e as propriedades, e por métodos, que refletem as ações ou os comportamentos que pode realizar. Por exemplo, um objeto do tipo pessoa poderia possuir os seguintes atributos e valores: • Nome: João. • Idade: 42 anos. • Altura: 180 cm. • Peso: 80 kg. 11 • Raça: Parda. E os seguintes comportamentos: • Mover(). • Subir(). • Pular(). • Comer(). Contudo, para que tais objetos sejam criados, temos um elemento a ser criado previamente. Ele permite que possamos criar mais de um objeto do mesmo tipo, ou seja, poderíamos criar mais de uma Pessoa, e esta possuir valores de seus atributos diferentes dos do objeto “João”. Este elemento são as classes. Enquanto o objeto é uma instância, ou seja, a definição de um objeto por meio da especificação de seus atributos e métodos, e como esses interagem ou realizam as ações do objeto Pessoa instanciado, a classe serve de molde, e designa quais atributos uma pessoa terá, bem como seus métodos. A representação de objetos do mundo real, por meio de objetos instanciados e sua instanciação, considerando as classes, é um dos quatro pilares da OO, sendo conhecido como abstração. Assim, a abstração é a capacidade de conseguirmos expressar via propriedades e comportamentos, ou seja, atributos e métodos, objetos do mundo real. Como já mencionados, tais objetos podem ser abstratos ou concretos, como uma conta a pagar, um personagem de jogo, uma pessoa, um prédio etc. (JECK, 2021). Considerando tal pilar, temos o segundo pilar, que é originário pela possibilidade de especificarmos os “moldes” que permitirão a instância 12 de um ou mais objetos: a herança. É importante frisar que o termo instância é padronizado e serve para indicar que estamos criando um objeto a partir de uma classe. A herança ocorre na relação de uma classe com outra, ou entre múltiplas classes de modo encadeado. Em linguagens como Java, não podemos ter uma classe que herde características e métodos de mais de uma classe, ou seja, não há heranças múltiplas, devido às dificuldades de se determinar à qual classe um método pertence. Este problema é tratado em tópicos mais avançados sobre OO. O importante, neste momento, é nos atentarmos ao conceito de herança. A herança permite, então, que uma classe estenda os atributos e métodos de uma classe à outra, permitindo a criação de objetos especialistas. Dizemos que a classe pai é uma superclasse, enquanto a classe filha é uma subclasse. Dentre elas, temos os conceitos de generalização e especialização. É algo similar quando temos uma família que passa, por herança, os seus bens aos filhos, e os filhos, por si só, utilizam-se de tais bens como lhes convir. Na generalização, a classe pai é mais genérica, ou por analogia, podemos dizer que a classe pai não possui tantos atributos ou métodos quanto a classe filha, a qual, por ser mais “nova”, se torna especialista em algo e pode, portanto, aprender mais coisas que o pai. A Figura 1 apresenta a relação de herança entre duas classes filhas por meio de um diagrama de classes da Linguagem de Modelagem Unificada (UML) (PRESSMAN; MAXIM, 2021). 13 Figura 1 – Classe Pessoa e Subclasses Funcionário e Gerente Fonte: elaborada pelo autor. A superclasse, ou classe pai, na Figura 1, é representada pela classe “Pessoa”. Por padronização, sempre um nome de classe deverá ser iniciado por letra maiúscula, já atributos e métodos iniciam com letra minúscula. Além disso, os métodos são, geralmente, verbos no infinitivo. Quando um método possuir um nome composto, como é o caso do método “realizarTreinamento()”, a segunda, ou as demais palavras unidas, deverá iniciar com letra maiúscula (GUERRA, 2014). As padronizações auxiliam na leitura e na identificação dos elementos, tanto nos diagramas quanto no código, e devem ser adotadas independentemente da linguagem de programação adotada. Na superclasse, temos cinco atributos e quatro métodos. Os atributos ficam representados na parte seguinte ao título da classe, seguidos pelos métodos. Em alguns diagramas, com menos especificidade, temos o ocultamento dos métodos e atributos, mas a visão mais comum é a apresentada na Figura 1. 14 Seguidas da superclasse, temos duas classes filhas, a classe “Funcionario” e a classe “Gerente”. Note que, por padronização, não se utiliza acentuação no nome de classes, métodos ou atributos. Tais subclasses estão herdando as características da classe pai “Pessoa” e adicionam, considerando suas especificidades, um atributo e um método a mais para cada. No caso da subclasse “Funcionario”, ela é uma classe especialista da classe “Pessoa”, que, por sua vez, é uma generalização para a classe “Pessoa”. Logo, a classe filha “Funcionario” possui um atributo especifico dela, que é “especializacao” e um método específico, o “realizarTreinamento()”. Este estado e comportamento não são reconhecidos pela classe pai, por isso diz-se que a classe filha é especialista. Por analogia, o filho estudou mais que o pai e, portanto, sabe mais que ele. No contexto de herança, é importante destacar que a linguagem de modelagem unificada (UML), por meio de sua notação, destaca o relacionamento de herança existente, devido aos relacionamentos por meio de setas com pontas vazadas que relacionam “Funcionario” e “Gerente” à superclasse “Pessoa”. A UML foi criada justamente para auxiliar na representação dos diferentes elementos e características que um software orientado a objetos pode possuir, auxiliando na criação do projeto e dos diagramas que servirão para o desenvolvimento posterior, garantindo melhor qualidade e padronização do produto em diferentes fases de desenvolvimento (WAZLAWICK, 2019). Ao considerar a OO e a representação no diagrama de Figura 1, temos a possibilidade de instanciar objetos da classe “Funcionario” e “Gerente” apenas. Isso ocorre porque, também por padronização da UML, a classe pai é, geralmente, especificada como sendo uma classe abstrata, ou seja, ela não permite instanciar um objeto dela. 15 O conceito de classe abstrata designa um contrato de herança, em que a classe pai pode não ter código no interior dos seus métodos, sendo estes implementados especificamente nas classes filhas, ou seja, as classes que herdam os atributos e métodos da classe pai. Temos, deste modo, o que chamamos de classes concretas, que permitem instanciar objetos – sendo este o caso das classes filhas –, e a classe abstrata, que é representada pela classe pai, no contexto apresentado, que não permite a instanciação de um objeto. A classe abstrata atua como um contrato que obriga as classes filhas a implementarem seus métodos. Na perspectiva de métodos abstratos das classes abstratas, temos um terceiro pilar de OO, o que chamamos de polimorfismo, ou seja, várias formas. O polimorfismo condiz com a capacidade das classes filhas de implementarem de modo diferente o método da classe pai. Se um método da classe pai possuir um comportamentoou não o implementar, quando o método for abstrato, a classe filha, que herdou seus métodos, terá de implementá-los, reescrevendo-os, o que dá nome ao conceito de polimorfismo. No total, há dois tipos de polimorfismo: o de sobrescrita e o de sobrecarga. O de sobrescrita reescreve o código do corpo do método e, assim, realiza um comportamento totalmente diferente da classe da qual herdou tal método. Já no contexto da sobrecarga, a relação de polimorfismo não ocorre entre classes pais e filhas, mas, sim, no corpo da classe filha, no qual a assinatura do método, por exemplo, “gerenciarDepartamento()”, é reutilizado quantas vezes forem necessárias, porém com uma quantidade e tipos de parâmetros específicos para cada reutilização. Deste modo, a sobrecarga ocorre na passagem de parâmetros. O método “gerenciarDepartamento(int i)” e o método “gerenciarDepartamento(int i, String x)” são sobrecarregados, visto que 16 um recebe apenas um parâmetro do tipo inteiro (o que está sendo passado dentro dos parâmetros da assinatura do método) e o outro recebe dois parâmetros. No caso da linguagem Java, por exemplo, o código saberá qual método chamar, de acordo com a quantidade e os tipos de parâmetros passados para o método. Essa característica permite a reutilização de métodos e redução de escopo no contexto dos métodos, propiciando reuso (JECK, 2021). Ainda considerando a Figura 1, temos o último dos pilares da OO, o encapsulamento. O encapsulamento permite que os estados (atributos) dos objetos não sejam alterados sem utilizar os próprios métodos do objeto que se deseja modificar. Isso garante a integridade de autonomia dos objetos, trazendo garantia de que os valores não serão alterados por qualquer objeto ou método que estiver no código. O encapsulamento está representado no digrama de classes da Figura 1 por meio dos sinais de “+” e “-” na frente dos atributos e métodos. No caso, os atributos são privados (do termo em inglês private) ou públicos (do termo em inglês public), quanto aos métodos. Deste modo, não será possível fazer uma atribuição de valores para os atributos sem a chamada de um método, bem como obter o valor atual mantido em um estado do objetos. Para isso, são utilizados os métodos padronizados e intitulados getters e setters, que, em português, seriam traduzidos para “buscadores” e “alteradores”, mas são chamados nos contextos de desenvolvimento com o termo em inglês. Os métodos getters e setters não são, geralmente, apresentados nos diagramas da UML, pois estes são obrigatórios, tão logo que um atributo é declarado ou representado como private. Por exemplo, para a classe “Funcionario”, seriam necessários seis métodos get e seis métodos set, para que seja possível obter os valores dos seis atributos e alterar o valor dos seis atributos, respectivamente. Por exemplo, para o atributo “nome”, o objeto instanciado, seja ele da classe “Funcionario” ou “Gerente”, terá de implementar o método 17 “getNome(): String” e o método “setNome(String nome)”. No primeiro, há um retorno de valor do tipo String, ou seja, cadeia de caracteres, enquanto, no segundo, há uma passagem de um parâmetro, também do tipo cadeia de caracteres, para que o atribuo “nome” possa ser retornado ou alterado, respectivamente. Esta declaração deve se repetir para os demais atributos existentes, os quais, por padrão, devem ser declarados como privados. O Quadro 1 resume os tipos de visibilidade no contexto do modificador de acesso e encapsulamento. Quadro 1 – Resumo dos modificadores de acesso em Java Visibilidade public (público) protected (protegido) default (padrão) private (privado) Na mesma classe. Visível Visível Visível Visível Nível de qualquer classe no mesmo pacote. Visível Visível Visível Restrito Nível de classe filha no mesmo pacote. Visível Visível Visível Restrito Nível de classe filha em pacotes diferentes. Visível Visível Restrito Restrito Nível de qualquer classe em pacotes diferentes. Visível Restrito Restrito Restrito Fonte: elaborado pelo autor. Os tipos de modificadores de visibilidades apresentados no Quadro 1 compõem e ampliam o encapsulamento de código no contexto de Java e outras linguagens orientadas a objetos. Há modificadores específicos que limitam a visibilidade de código entre as classes e, consequentemente, entre os objetos delas instanciados. O modificador public dá permissão de acesso geral em qualquer tipo de visibilidade. O protected restringe a visibilidade entre classes em pacotes diferentes. O modificador default, isto é, quando não há indicação de modificador, restringe o acesso entre classes, sejam estas filhas ou pais, no contexto de pacotes diferentes. Já o mais restrito, o private, permite a visibilidade do que for marcado como privado ao nível da mesma classe. 18 Finalmente, os modificadores de visibilidade podem ser aplicados para classes, atributos e métodos. Deste modo, conseguimos identificar e observar os quatro principais pilares que definem a OO: abstração, herança, polimorfismo e encapsulamento. Todos os pilares estão relacionados diretamente com as classes, os atributos e os métodos e, consequentemente, os objetos que serão instanciados destas classes (PRESSMAN; MAXIM, 2021; WAZLAWICK, 2019). Notam-se, ainda, padrões para a nomenclatura de cada elemento, apresentados, em especial, na Figura 1, que, seguindo a Linguagem de Modelagem Unificada (UML) utilizada para a representação de projetos com linguagens orientadas a objetos, utiliza notação própria e padronizada para facilitar a conversação entre as partes durante a concepção de um projeto de software. Os conceitos dos quatro pilares podem ser identificados e aplicados em diferentes tipos de linguagens orientadas a objetos, deste modo, carece de estudo aprofundado para seu entendimento e aplicação, em especial, nos documentos que especificam a linguagem a ser utilizada. Desejo bons estudos! Referências ELLIOTT, E. Composing software: an exploration of functional programming and object composition in JavaScript. [S. l.]: Lean Publishing, 2020. GUERRA, E. Design Patterns com Java: projeto orientado a objetos guiado por padrões. São Paulo, SP: Casa do Código, 2014. JECK, D. Introduction to programming using Java. Geneva, NY: Hobart and William Smith Colleges, 2021. MICHAELIS. Moderno dicionário da língua portuguesa. São Paulo, SP: Melhoramentos, 1998. PRESSMAN, R. S.; MAXIM, B. R. Engenharia de software. São Paulo, SP: McGraw Hill Brasil, 2021. WAZLAWICK, R. Engenharia de software: conceitos e práticas. São Paulo, SP: Elsevier, 2019. 19 Abstração e herança em orientação a objetos Autoria: Anderson da Silva Marcolino Leitura crítica: Fabiano Gonçalves dos Santos Objetivos • Compreender de modo mais avançado o conceito de abstração na orientação a objetos. • Estabelecer como o conceito de abstração é aplicado na herança. • Compreender, via código e representações de herança, o funcionamento da herança, considerando a linguagem Java. 20 1. Abstraindo o mundo real A orientação a objetos (OO) transforma objetos abstratos ou concretos em representações que possam ser controladas nas mais diferentes aplicações, desenvolvidas com uma linguagem de programação que seja, consequentemente, orientada a objetos. O ato de abstrair algo é a ação ou o efeito de isolar algo de um todo para considerá-lo individualmente (MICHAELIS, 1998). A abstração em OO nos permite representar um objeto, ocultando seus dados ou propriedades internas, bem como sua implementação do mundo externo, sendo, portanto, capaz de representar um objeto do mundo real para uma aplicação a ser utilizada por usuários (JECK, 2021). Podemos comparar o exemplo de um robô aspirador de pó. Ele possui um controle via aplicativo, no qual você pode configurar e gerenciar o seu funcionamento. Para isso, você não precisa saber que peças estão internamente no robô. A complexidade lógica e suas peças estão completamente ocultas paranós, usuários finais. É deste modo que um objeto é representado em OO para quem o utiliza, mas, internamente, o desenvolvedor se preocupa em representar todos os elementos lógicos necessários para o funcionamento do equipamento. Considerando o mesmo exemplo do robô aspirador, teremos diversas especificidades, porém, em uma linguagem orientada a objetos, buscamos representar um modelo genérico, o qual, com a atribuição de valores específicos às propriedades e com comportamentos também específicos, conseguirá “atuar” de modo diferente dos outros. É aí que temos as classes. A Figura 1 representa um esquema de abstração do nosso exemplo do aspirador de pó robô. 21 Figura 1 – Abstração do objeto aspirador de pó robô Fonte: adaptada de https://www.flaticon.com/free-icons/robot-vacuum-cleaner. Acesso em: 2 maio 2023. Na Figura 1, podemos observar o processo de abstração de um objeto real, no caso, o aspirador de pó robô, a lista de suas propriedades e ações e, ao final, sua representação em uma classe. Note que, para criarmos a lista de propriedades e ações, podemos fazer diversos questionamentos sobre o objeto a ser abstraído, por exemplo: qual é a cor do objeto? Qual é o modelo? O objeto para ao se deparar com um obstáculo? De um modo mais genérico, podemos questionar quais são as propriedades, características ou atributos do objeto? Quais são as ações, as funcionalidades ou os comportamentos do objeto? E assim por diante. Note, ainda, que as perguntas são genéricas e não se referem a um modelo específico, o que indica que estamos abstraindo em nível de classe. O modelo será especificado somente quando instanciarmos um objeto. Vejamos como fica esta representação via código Java. Consideraremos também os getters e setters, ou seja, os métodos que tratam do encapsulamento dos atributos, visto que todos estão encapsulados para manter a privacidades dos atributos existentes. 22 1. public class Aspirador { 2. private String cor; 3. private String modelo; 4. private String tipoControle; 5. private String tipoAplicativo; 6. private String tipoLimpeza; 7. private String voltagem; 8. public String getCor() { 9. return cor; 10. } 11. public void setCor(String cor) { 12. this.cor = cor; 13. } 14. public String getModelo() { 15. return modelo; 16. } 17. public void setModelo(String modelo) { 18. this.modelo = modelo; 19. } 20. public String getTipoControle() { 21. return tipoControle; 22. } 23. public void setTipoControle(String tipoControle) { 24. this.tipoControle = tipoControle; 25. } 26. public String getTipoAplicativo() { 27. return tipoAplicativo; 28. } 29. public void setTipoAplicativo(String tipoAplicativo) { 30. this.tipoAplicativo = tipoAplicativo; 31. } 32. public String getTipoLimpeza() { 33. return tipoLimpeza; 23 34. } 35. public void setTipoLimpeza(String tipoLimpeza) { 36. this.tipoLimpeza = tipoLimpeza; 37. } 38. public String getVoltagem() { 39. return voltagem; 40. } 41. public void setVoltagem(String voltagem) { 42. this.voltagem = voltagem; 43. } 44. public boolean parar() { 45. return false; 46. } 47. public void mapear() { 48. System.out.println(“Implementar!”); 49. } 50. public void voltarBase() { 51. if(this.verificarBateria()) { 52. System.out.println(“Permitido voltar a base!”); 53. } 54. } 55. public boolean ativarSensorColisao() { 56. return false; 57. } 58. private boolean verificarBateria() { 59. return false; 60. } 61. } Na linha 1, temos a definição de nossa classe. Trata-se de uma classe pública, intitulada “Aspirador”. Na sequência, temos os atributos de nossa classe. Por padrão, os atributos serão sempre declarados no início do corpo da classe. A classe tem um “corpo” que se inicia com a chave 24 (“{“) e que termina com outra chave (“}”). Este padrão também é utilizado na declaração dos métodos, como no método getCor(). Como, por padrão, todos os atributos estão declarados como privados, ou seja, possuem restrição de acesso – o que permite que tais atributos só possam ser modificados pela própria classe –, é necessário criarmos métodos que permitam a outros objetos e classes a alteração ou consulta dos valores dos atributos da classe “Aspirador”. Para isso, declaramos um conjunto de métodos, os quais, geralmente, são chamados de getters e setters. Enquanto um método get busca ou “pega” algo, um método set configura ou altera algo. No caso, o atributo “cor”, por exemplo, para ser alterado precisa do método “setCor(String cor)”, declarado da linha 11 até a linha 13. Este método não possui retorno, por isso, em sua assinatura, temos a palavra void, e recebe como atributo uma String, ou seja, uma cadeia de caracteres que serão atribuídos para o atributo da classe. Quando falamos de um retorno do método, indicamos que é esperado que, ao final da execução do corpo do método, ou seja, suas funcionalidades, o método retorne um resultado. Esse resultado deverá ser exatamente do mesmo tipo que foi indicado na sua assinatura. Um exemplo de assinatura de método é “public boolean parar()”, declarado na linha 44. Neste caso, o método deverá retornar um valor booleano, ou seja, verdadeiro (true) ou falso (false). Quando um método tem sua assinatura indicando a palavra void entre o tipo de encapsulamento e seu nome, indicará que o método não terá retorno. Os métodos que exigem retorno precisam ter a palavra return e o dado/informação indicados obrigatoriamente. No caso do método da linha 44, o seu retorno é indicado na linha 45: “return false;”. Como ainda não temos uma implementação completa do método, este será o retorno, se executarmos o código. 25 Retornando aos métodos getters e setters, para consultarmos um valor de atributo, devemos usar o método getCor(), declarado, por sua vez, da linha 8 até a linha 10. Este método tem um retorno – e faz todo sentido possuir –, já que retornará o valor do atributo “cor”, que é do tipo String. É por isso que temos a palavra String no cabeçalho do método. Da linha 14 até a linha 43, temos os demais métodos gets e sets. Note que o nome de tais métodos é sempre formado pela palavra get ou set, seguida pelo nome do atributo com letra maiúscula. Esta é uma boa prática e uma padronização para se nomear métodos. No contexto da nomeação dos atributos, quando algum deles é formado por uma ou mais palavras, utilizamos também letra maiúscula para iniciar a palavra concatenada. Contudo, um atributo ou método sempre iniciará com letra minúscula. Já para o nome da classe, sempre utilizaremos a letra maiúscula. Este padrão de nomenclatura é chamado CamelCase, que corresponde à denominação, em inglês, para a prática de escrever as palavras compostas ou frases, em que cada palavra é iniciada com maiúsculas e unidas sem espaços (GUERRA, 2014). Voltando ao código de nossa classe “Aspirador”, temos outros métodos que não estão inseridos no grupo dos métodos de setters e getters. O método da linha 44, “parar()”, por exemplo, deve retornar um booleano, ou seja, verdadeiro ou falso, mas não temos uma implementação do método, apenas um retorno (“return false;”) no seu corpo. Por tal método ser público, ele poderá ser chamado por objetos e classes que queiram interagir como um objeto instanciado da classe “Aspirador”. O método “mapear()”, por exemplo, é do tipo void, ou seja, sem retorno e não está implementado também. No seu corpo, há apenas uma mensagem, a qual será exibida no terminal quando tal código for executado. Ainda sobre os métodos da classe “Aspirador”, temos o método declarado da linha 58 até a linha 60, “verificarBateria()”. Esse método, assim como os demais, precisa de alguma implementação de 26 comportamento em seu corpo, para poder realizar a funcionalidade esperada. Contudo, o que nos chama a atenção nesse método é que ele é privado, pois está identificado com a palavra private, assim como osatributos. Logo, o método está encapsulado, o que implica dizer que ele só poderá ser chamado internamente na própria classe. Definir um método como privado requer uma análise, em especial, de seu comportamento e do que se espera que ele faça. Considerando que ele pode alterar algo específico e interno à classe, opta-se por declará-lo como privado. Adicionalmente, a questão de privar ou não o acesso de métodos ou atributos está envolvida com a responsabilidade do que se quer encapsular e a boa prática de não permitir que outros objetos/classes alterem valores de objetos sem a utilização dos devidos métodos criados para isso (getters e setters). Se observarmos o código do método “voltarBase()”, perceberemos que, ainda que incompleto, ele faz uma chamada do método “verificarBateria()”. Assim, é possível perceber que, por estar dentro de uma estrutura condicional if, espera-se que o método retorne verdadeiro para que o conteúdo interno ao bloco condicional – no caso, o que está declarado na linha 52 – seja executado. Agora que temos um exemplo claro de como abstrair um objeto do mundo real, precisamos de um código adicional para executar nossa classe, ainda que seus métodos não estejam todos implementados. Para isso, criaremos uma classe chamada “Principal”, a qual permitirá executar nossa classe “Aspirador”. Essa classe é apresentada no código a seguir: 1. public class Principal { 2. public static void main(String[] args) { 3. Aspirador a = new Aspirador(); 4. a.setCor(“Vermelho”); 5. a.setModelo(“Skynet”); 27 6. System.out.println(“A cor do aspirado é: “ + a.getCor()); 7. System.out.println(“A modelo do aspirado é: “ + a.getModelo()); 8. } 9. } Para que nossa classe “Aspirador” pudesse ser executada, criamos uma classe que contenha o método “main”. Esse método, que traduzido quer dizer “principal”, inicia qualquer programa em Java. No exemplo de nosso código, na linha 3, temos a instanciação de um objeto em uma variável de referência chamada “a”. Essa variável é do tipo “Aspirador” e é instanciada por meio da palavra reservada “new”, seguida pela chamada do método construtor da classe. O nome “a” poderia ser qualquer nome, mas, ao definirmos, devemos usar o que foi definido em todo o restante do código. O método construtor, sobre o qual falaremos a seguir, apesar de não aparecer no código da classe “Aspirador”, é implicitamente criado pelo Java para permitir a instância de objetos. Resumidamente, é o método construtor responsável por “construir”, ou melhor, instanciar os objetos de uma classe concreta. Na sequência, nas linhas 4 e 5, temos a atribuição de valores para os atributos “cor” e “modelo”, respectivamente; nas linhas 6 e 7, a impressão de cor e modelo, utilizando os métodos de get de cada atributo dentro da chamada “System.out.println()”, que é um método que imprime conteúdo no terminal. Se quiséssemos alterar mais atributos, bastaria chamarmos os métodos sets respectivos. Poderíamos, ainda, criar outras variáveis de referência e, assim, instanciarmos mais de um robô aspirador de pó. Agora, ampliaremos nossos conhecimentos no contexto de herança. 28 2. Herança com Java Considerando o próprio nome do paradigma – orientação a objetos –, temos um elemento essencial e central: o objeto. Como vimos, a existência dos objetos só será realizada a partir da instanciação deles. A prática, considerando que OO visa ao reuso, é criarmos classes genéricas que permitam criar objetos que compartilhem comportamentos e propriedades, sendo o conjunto destas últimas chamado de estado. Mas, geramos de fato uma classe genérica para instanciar nossos aspiradores de pó robô? E se eu quisesse instanciar também aspiradores de pó convencionais, seria possível? A resposta é sim, mas precisamos realizar algumas modificações, como considerar aspiradores que possuem alimentação de energia direto com tomada e que não voltarão a uma base para recarregar, já que não se tratam de robôs autônomos. E como fazer isso? Como podemos criar dois tipos de objetos e derivá-los de um modelo comum, já que compartilham funcionalidades específicas? Primeiramente, precisamos nos aprofundar ainda mais no contexto da abstração, voltada ao desenvolvimento de software. Com a abstração, devemos pensar também no contexto de dividir para conquistar. Muitas especificidades podem ser trabalhadas de modo isolado, por exemplo, ao pensarmos em um objeto carro, o motor e outras partes são também candidatos a se tornarem classes e objetos delas instanciados. Neste contexto, temos a adição da modularização (JECK, 2021). Na modularização, dividimos o todo em partes bem definidas, as quais interagem de modo bem definido, tendo, assim, seu papel bem identificado. Colocando a abstração e a modularização juntos, temos que, enquanto a modularização restringe-se ao processo de dividir problemas em partes menores, a abstração é a capacidade de ignorar detalhes e focalizar em cenários mais abrangentes (JECK, 2021; PRESSMAN; MAXIM, 2021). 29 Deste modo, utilizaremos da herança para permitir modularizar e abstrair o que precisamos: instanciar diferentes tipos de aspiradores de pó, tanto do tipo robô quanto convencionais. A Figura 2 apresenta a separação dos atributos e métodos das classes envolvidas e o mecanismo de herança. Figura 2 – Herança com Classe Aspirador Fonte: elaborada pelo autor. A Figura 2 adapta a nossa necessidade para permitir instanciarmos objetos do tipo aspirador robô e aspirador vertical. Note que os atributos comuns entre os dois tipos de objetos foram mantidos na classe pai, e os atributos que eram específicos para cada tipo de aspirador foram incluídos nas suas respectivas classes, e ambas herdam as características em comum da classe pai. 30 A ideia principal do conceito de herança é permitir a reutilização de elementos em comum, retirando a necessidade de se duplicar código e permitir estender uma classe de outra (JECK, 2021). Os conceitos de superclasse e subclasse correspondem à classe pai, que é superior, por isso chamada de superclasse, e à classe filha, intitulada subclasse. Poderíamos, se necessário, ter uma outra superclasse, tornando a classe “Aspirador” uma classe filha de outra classe e, ao final, a classe “Robo”, por exemplo, herdaria as características da classe herdada pela classe “Aspirador”, além dos próprios elemento de tal classe. É importante mencionar que o encapsulamento de elementos da classe pai faz com que a classe filha utilize-se de métodos públicos para alterar estados ou chamar comportamentos. O que for privado não estará disponível para a classe filha. Se o atributo, método ou mesmo classe for encapsulado como protected, a classe filha terá acesso total ao elemento que assim o for marcada. Outro detalhe importante, que pode parecer irrelevante a princípio, é o fato de nossa classe pai estar com o nome “Aspirador” em itálico. Isso indica que a classe é abstrata, ou seja, não conseguiremos instanciar um objeto dela, somente das classes concretas (“Robo” e “Vertical”). Adicionalmente, podemos ter métodos marcados também como abstratos, que deverão ser implementados especificamente nas classes filhas, que estenderem à classe “Aspirador”. É o caso do método “verificarBateria()”. Analisaremos, agora, o código de tais classes. Para fins de representação, o código dos getters e setters será suprimido, já que o vimos no código da classe “Apirador” anteriormente. Código classe “Aspirador” atualizado, com getters e setters ocultados. 1 public abstract class Aspirador { 2 private String cor; 31 3 private String modelo; 4 private String tipoLimpeza; 5 private String voltagem; 6 private boolean bateria; 7 8 a 46 getters e setters 47 48 public boolean desligar() { 49 System.out.println(“Aparelho desligado!”); 50 return false; 51 } 52 53 public boolean ligar() { 54 System.out.println(“Aparelholigado!”); 55 return true; 56 } 57 58 public abstract boolean verificarBateria(); 59 60 } Código classe “Robo” 1 public class Robo extends Aspirador { 2 private String tipoAplicativo; 3 private String tipoControle; 4 5 public Robo() { 6 super.setCor(“vermelho”); 7 super.setModelo(“Zyon”); 8 super.setBateria(true); 32 9 super.setTipoLimpeza(“A seco”); 10 super.setVoltagem(“220 voltz”); 11 this.tipoAplicativo = “Android”; 12 this.tipoControle = “Por aplicativo apenas.”; 13 } 14 15 public String getTipoAplicativo() { 16 return tipoAplicativo; 17 } 18 19 public void setTipoAplicativo(String tipoAplicativo) { 20 this.tipoAplicativo = tipoAplicativo; 21 } 22 23 public String getTipoControle() { 24 return tipoControle; 25 } 26 27 public void setTipoControle(String tipoControle) { 28 this.tipoControle = tipoControle; 29 } 30 31 public void mapear() { 32 System.out.println(“Mapeando Ambiente”); 33 } 34 35 public void voltarBase() { 36 if(this.verificarBateria()) { 37 System.out.println(“Voltando a base!”); 33 38 } else { 39 System.out.println(“Não é possível voltar a base, a bateria está fraca!”); 40 } 41 } 42 43 public boolean ativarSensorColisao() { 44 return true; 45 } 46 47 @Override 48 public boolean verificarBateria() { 49 if(super.getBateria()) { 50 return true; 51 }else { 52 return false; 53 } 54 } 55 56} Código classe “Vertical” 1 public class Vertical extends Aspirador { 2 private boolean hasteAjustavel; 3 private float tamanhoCabo; 4 5 public boolean isHasteAjustavel() { 6 return hasteAjustavel; 7 } 8 34 9 public void setHasteAjustavel(boolean hasteAjustavel) { 10 this.hasteAjustavel = hasteAjustavel; 11 } 12 13 public float getTamanhoCabo() { 14 return tamanhoCabo; 15 } 16 17 public void setTamanhoCabo(float tamanhoCabo) { 18 this.tamanhoCabo = tamanhoCabo; 19 } 20 21 public void recolherCabo() { 22 System.out.println(“O cabo de “ + this. getTamanhoCabo() + “ foi recolhido!” ); 23 } 24 25 @Override 26 public boolean verificarBateria() { 27 if(super.getBateria()) { 28 System.out.println(“Bateria carregada!”); 29 return true; 30 }else { 31 System.out.println(“Bateria fraca!”); 32 return false; 33 } 34 } 35 } Classe Principal 35 1. public class Principal { 2. 3. public static void main(String[] args) { 4. Robo a = new Robo(); 5. Vertical v = new Vertical(); 6. 7. System.out.println(“A cor do aspirado é: “ + a.getCor()); 8. System.out.println(“A modelo do aspirado é: “ + a.getModelo()); 9. a.ligar(); 10. a.desligar(); 11. 12. v.setCor(“Azul”); 13. v.setModelo(“Arnold”); 14. v.ligar(); 15. v.desligar(); 16. v.verificarBateria(); 17. 18. } 19. 20. } A classe “Aspirador” é abstrata, logo não pode ser instanciada. Além disso, por ser abstrata, ela pode declarar métodos abstratos, como é o caso do método “verificarBateria()” na linha 58. Esse método não pode ser chamado e precisa, por polimorfismo, ser implementado nas classes que estenderem a classe “Aspirador”. E mesmo sendo abstrata, a classe pode continuar tendo métodos com declarações convencionais. A classe “Robo”, por ser classe filha da classe “Aspirador”, apresenta esta herança na primeira linha, em “extends Aspirador”. A palavra reservada extends permite às classes filhas estenderem a classe pai desejada. Nas linhas 2 e 3, temos a declaração de mais dois atributos além dos já 36 existentes na classe pai. Da linha 5 até a linha 13, temos a declaração explícita do método construtor da classe “Robo”. Esse é o método responsável por instanciar objetos de tais classes e que, em muitos casos, pode não ser declarado explicitamente. O método construtor tem uma característica específica: é o único método que começa com letra maiúscula, pois deve ser escrito exatamente com a mesma grafia do nome da classe. No caso de nossa classe “Robo”, o método construtor foi declarado, pois ele modifica atributos da classe pai e da classe filha, para permitir instanciar um objeto com valores já pré-atribuídos e que poderão, ao longo da execução do programa, ter seu estado modificado sem empecilhos. O uso da palavra “super” para chamar os setters se deve ao fato de os atributos, como já mencionado anteriormente, estarem declarados como privados. Deste modo, ainda que tenham sido herdados da classe pai, precisam ser modificados somente via métodos. Já a palavra this, utilizada nas linhas 11 e 12, auxilia na identificação de que estamos tratando de um atributo ou método, se utilizado junto a um método, de elementos da própria classe. No caso, não utilizamos o método “setTipoAplicativo()”, por exemplo, para modificar o valor do atributo “tipoAplicativo”. Estamos modificando diretamente o valor no atributo. O mesmo ocorre com o atributo “tipoControle”. Um outro trecho de código que requer atenção é a sobrescrita do método “verificarBateria()” declarado como abstrato na superclasse “Aspirador”. A classe abstrata serve como um contrato que obriga a subclasse a implementar os métodos indicados como abstratos. Logo, caso um método não seja pertinente de ser implementado, ele ficará com o seu corpo vazio, na classe filha, o que não é o caso de nosso exemplo, em que o método “verificarBateria()” foi primeiramente indicado com a marcação “@Overrride”, sinalizando que está ocorrendo uma sobrescrita do método vindo da classe pai. 37 Considerando a sobrescrita, temos a implementação de código no corpo do método, determinando o comportamento dele. Ao compararmos o mesmo método para a classe “Vertical”, notamos que ele foi implementado de modo diferente, e isso é o esperado. Como temos objetos diferentes, os quais se comportarão de modos diferentes, faz sentido termos métodos que serão diferentes entre si, mas a assinatura permanecerá a mesma. Deste modo, estamos utilizando de mais um pilar de OO, o polimorfismo, que será abordado em outros momentos. Finalmente, para executarmos nosso código, temos a classe “Principal” alterada, instanciando dois objetos: “a” do tipo “Robo” e “v” do tipo “Vertical”. Note que a chamada dos métodos construtores deles são idênticas, mas, no caso da classe “Robo”, temos a declaração explícita do método construtor, o que não temos na classe “Vertical”. No entanto, como já foi mencionado, o método construtor não declarado é chamado implicitamente por Java, pois é ele que permite instanciar os objetos. Nas linhas 7 a 16 da classe Principal, temos o chamamento dos métodos dos objetos instanciados. Destarte, percorremos novamente os pilares de OO, com ênfase na abstração e herança. Vários testes e modificações podem e são recomendados serem feitos nos códigos disponibilizados, para que o entendimento seja amplo e mais aprofundado. Recomenda-se a identificação dos pilares no código e testes específicos com os objetos e seus métodos, a fim de permitir uma compreensão mais completa dos exemplos dados. Bons estudos! Referências 38 GUERRA, E. Design Patterns com Java: projeto orientado a objetos guiado por padrões. São Paulo, SP: Casa do Código, 2014. JECK, D. Introduction to programming using Java. Geneva, NY: Hobart and William Smith Colleges, 2021. MICHAELIS. Moderno dicionário da língua portuguesa. São Paulo, SP: Companhia Melhoramentos, 1998. PRESSMAN, R. S.; MAXIM, B. R. Engenharia de software. São Paulo, SP: McGraw Hill Brasil, 2021. 39 Encapsulamento e instanciação de objetos Autoria: Anderson da Silva Marcolino Leitura crítica: FabianoGonçalves dos Santos Objetivos • Compreender a instanciação e o encapsulamento de classes, pacotes, métodos e atributos em projetos orientados a objetos. • Estabelecer como os métodos construtores podem definir o estado do objeto no momento de sua instanciação. • Compreender, via código, como e quando utilizar interfaces no lugar de herança com classes abstratas e como os objetos instanciados se comunicam entre si, considerando a linguagem Java. 40 1. Protegendo elementos na programação orientada a objetos Na programação orientada a objetos (OO), temos diversos recursos, e estes são reflexos de seus quatro pilares. Um desses recursos é o encapsulamento, por meio do qual podemos privar o acesso de determinados elementos em nosso código, como classes, atributos e métodos (JECK, 2021). O termo encapsulamento é chamada de data hidding, em inglês. O objetivo, ao restringir o acesso, está associado ao contexto de abstração. O usuário ou, no caso do código-fonte, o desenvolvedor não sabe o que de fato ocorre dentro do elemento encapsulado, mas, por meio de seus métodos, atributos etc., é possível identificar como poderá usar o elemento restrito em seu código e como poderá interagir com ele (JECK, 2021; FURGERI, 2018). Alguns autores conceituam o encapsulamento como a acessibilidade, já que permitirá tornar ou não os elementos restritos em uma classe, ou até ela inteira. Dentre as vantagens de seu uso, estão: ocultar detalhes do código e do comportamento; aumentar a legibilidade do código; reduzir erros de programação; restringir conteúdos de variáveis e facilitar novas atualizações (FURGERI, 2018). Há quatro modificadores de acesso ou qualificadores de acesso, sendo eles: public – não aplica nível de restrições; private – apenas a própria classe pode ter acesso aos atributos ou variáveis e aos métodos; protected – as variáveis e os métodos podem ser acessados pela própria classe e suas subclasses, no caso de herança; package-private – a classe só pode ser acessada por outras classes no mesmo pacote, é o encapsulamento padrão, ao não se utilizar um qualificador (ORACLE, 2022). 41 Comumente, declaramos os atributos de nossas classes como privados, e os métodos para manipularmos os atributos como públicos, os gets e sets, os quais assim asseguram que certas variáveis só possam ter seus valores alterados via métodos públicos ou a partir da própria classe (FURGERI, 2018). Como o encapsulamento envolve também os pacotes em Java, consideraremos um exemplo para entender um pouco mais sobre tal pilar, sobre os modificadores protected e package-private e sobre outros conceitos importantes de Java e OO. Classe SomarSubtrair 1. package br.com.tema3.operacoesbasicas; 2. 3. import javax.swing.JOptionPane; 4. 5. public class SomarSubtrair { 6. 7. public void SomarESubtrair() { 8. Somar s = new Somar(1); 9. JOptionPane.showMessageDialog(null, “O valor da da soma de dois doubles é: “ + s.somarDoisDoubles(2.4, 2.6)); 10. } 11. 12. } Classe Somar 1. package br.com.tema3.operacoesbasicas; 2. 3. public class Somar { 4. 5. private int valor1; 6. 42 7. public Somar(int valor1){ 8. this.valor1 = valor1; 9. } 10. public Somar(){ 11. } 12. 13. public int somarDoisInteiros(int valor2) { 14. return (this.valor1 + valor2); 15. } 16. 17. protected double somarDoisDoubles(double valor1, double valor2) { 18. return (valor1 + valor2); 19. } 20. 21. } Classe Multiplicar 1. package br.com.tema3.operacoes; 2. 3. import javax.swing.JOptionPane; 4. 5. import br.com.tema3.operacoesbasicas.Somar; 6. 7. public class Multiplicar { 8. protected final float PI = 3.1416f; 9. public static final int CONSTATE = 1; 10. 11. public double MultiplicarComPI(double valor) { 12. return valor*PI; 13. } 14. 15. public void SomarEMultiplicar() { 43 16. Somar s = new Somar(); 17. //O método somarDoiDoubles não está visível, mesmo importando a classe Somar! 18. //JOptionPane.showMessageDialog(null, “O valoda da soma de dois doubles é: “ + s.somarDoisDoubles(2.4, 2.6)); 19. JOptionPane.showMessageDialog(null, “O valor da da soma de dois inteiros é: “ + s.somarDoisInteiros(CONSTATE)); 20. } 21. } 22. Classe DividirNumeros 1. package br.com.tema3.operacoes; 2. 3. public class Dividir { 4. 5. public double DividirNumeros(double valor, double divisor) { 6. return valor/divisor; 7. } 8. 9. } Classe Principal 1. package br.com.tema3.main; 2. 3. import java.text.DecimalFormat; 4. 5. import br.com.tema3.operacoes.Dividir; 6. import br.com.tema3.operacoes.Multiplicar; 44 7. import br.com.tema3.operacoesbasicas.Somar; 8. import br.com.tema3.operacoesbasicas.SomarSubtrair; 9. 10. public class Principal { 11. 12. public static void main(String[] args) { 13. Somar s = new Somar(); 14. Somar sn = new Somar(3); 15. SomarSubtrair ss = new SomarSubtrair(); 16. Dividir d = new Dividir(); 17. Multiplicar m = new Multiplicar(); 18. 19. DecimalFormat formatacao = new DecimalFormat(); 20. formatacao.applyPattern(“0,00”); //Se utilizar “#,##” valores de zero serão ocultados 21. 22. System.out.println(“Resultado da soma de dois inteiros (constante = 1): “ + s.somarDoisInteiros(Multiplicar.CONSTATE)); 23. System.out.println(“Resultado da soma de dois inteiros: “ + s.somarDoisInteiros(2)); 24. System.out.println(“Resultado da soma de dois inteiros (método construtor recebeu 3): “ + sn.somarDoisInteiros(2)); 25. 26. ss.SomarESubtrair(); 27. System.out.println(“Resultado da divisão: “ + d.DividirNumeros(2.4e2, 2.4e2)); 28. System.out.println(“Resultado da multiplicação por PI: “ + formatacao.format(m.MultiplicarComPI(1.0e2))); 29. m.SomarEMultiplicar(); 30. 31. } 45 32. 33. } Considerando o código apresentado e as cinco classes, entenderemos os conceitos do encapsulamento e, na sequência, outros conceitos fundamentais para se programar em OO com Java. O código apresentado corresponde a um projeto com três pacotes: o pacote br.com.tema3.operacoesbasicas, o pacote br.com.tema3. operacoes e o pacote br.com.tema3.main. As classes, em projetos Java, são mantidas em pacotes. Como padrão de nomenclatura, utiliza-se o domínio da empresa ou instituição à qual o projeto será destinado. A título de exemplo, usamos o domínio que seria www.tema3.com.br para compor o nome dos pacotes que ficaram, seguindo a padronização como “br.com.tema3”, sem o “www” e escrito de trás para frente. Considerando que temos diversos elementos encapsulados, notamos que as classes “SomarSubtrair” e “Dividir” contêm todos os seus métodos públicos, bem como suas classes. A classe “Somar” apresenta um atributo privado que só é modificado quando um novo objeto é instanciado e o método construtor, que sofre sobrecarga, é utilizado. Falaremos sobre o método construtor mais adiante. Na mesma classe, temos, ainda, o método “somarDoisDoubles()”, que é qualificado como protected. Ao indicarmos o encapsulamento como protected, estamos restringindo os métodos e atributos para serem acessados pela mesma classe ou por subclasses que estejam no mesmo pacote. Ainda que importemos o pacote “Somar” em uma classe de outro pacote, não será possível utilizar tal método. A importação de pacotes permite o reuso, visto que podemos utilizar métodos de classes específicas emoutras, mas isso dependerá exclusivamente de como utilizarmos o encapsulamento. No caso, 46 como visto, o protected limita o uso do método. Você pode fazer o teste retirando as duas barras (“//”) da linha 18, que está comentando o código, e tentar executar o projeto. Dependendo do seu ambiente de desenvolvimento integrado (IDE)(e.g. Netbeans, Eclipse), seu código não será nem executado. Sobre comentários, é possível criar comentários e uma única linha, como visto nas linhas 17 e 18 da classe “Multiplicar”, e em blocos, iniciando com barra + asterisco “/*” e terminando o bloco com asterisco + barra “*/”. Há, ainda, o comentário em blocos utilizados pelo JavaDoc, que inclui um asterisco a mais no comentário em bloco: “/**” com fechamento “*/”. Voltando à remoção do comentário da linha 17 e tentando executar, notaremos que o método “somarDoisDoubles()” ficará grifado em vermelho, indicando erro. Na realidade, não se trata de um erro, pois encapsulamos o método com protected. Outra classe que possui encapsulamento é a classe “Multiplicar”, para variável estática PI. Ainda na mesma classe, temos uma constante, que é declarada com o nome de “CONSTANTE”, apenas para facilitar a visualização. Uma variável declarada como final corresponde a algo que não queremos que o valor seja modificado após a sua atribuição, que não precisa ser no momento de sua declaração – normalmente, são utilizadas dentro dos métodos –, diferentemente do exemplo. Já uma constante precisa ser declarada, e o seu valor atribuído no momento de sua declaração. Tanto em uma variável final quanto static, o programa apresentará erros, se tentar realizar a modificação de seus valores durante a execução. Sobre a nomenclatura de variáveis, estas devem começar com letra, caractere de sublinhado (_) ou cifrão ($). Não é permitido iniciar o nome 47 de uma variável com número. Já as constantes, por padronização, são escritas com letras maiúsculas e as palavras se separam com underline. Voltando ao código, se tentássemos mudar a chamada da CONSTANTE pela variável final PI na linha 22 da classe “Principal”, teríamos também um erro, visto que tal variável, por estar declarada como protected, só poderá ser acessada por classes que estejam dentro do mesmo pacote. No exemplo do código, a classe “Dividir” pode acessar a variável PI, sem problemas. Para isso, precisará apenas instanciar um objeto da classe “Dividir”. Quanto ao package, ele é utilizado para definir o pacote para o qual as classes que ali forem mantidas possam se comunicar, considerando os qualificadores para os diversos elementos que estas mantêm. Assim, conseguimos entender os conceitos relacionados ao encapsulamento e como este pode ser utilizado no contexto da programação em Java e em OO. É importante mencionar que há encapsulamento em outras linguagens de programação, não sendo algo específico do Java. Voltando ao nosso código e analisando os outros elementos apresentados, vamos nos aprofundar em alguns elementos que podemos usar em nossos programas! 2. Métodos construtores, polimorfismo e Java Ao observarmos mais atentamente o código da classe “Somar”, notamos dois métodos construtores com o mesmo nome, porém com quantidades de parâmetros diferentes: “Somar(int valor1)” e “Somar()”. O método construtor nos permite instanciar um objeto, o qual é chamado da linha 13 até a linha 17 para instanciar as demais classes na classe ‘Principal”. 48 Nem todas as classes que possuem seus objetos instanciados possuem a declaração do método construtor, pois esse é implícito. Todavia, isso não ocorre com a classe “Somar”. Nela, queremos inicializar o valor da variável “valor1”, logo que o objeto de tal classe concreta for instanciado. Para isso, incluímos um parâmetro na assinatura do método construtor. Adicionalmente, para permitir que executemos a instanciação sem a atribuição de um inteiro para a variável “valor1”, temos o método construtor convencional, que seria declarado de modo implícito por Java. Mas, por que ter dois métodos? O método com a assinatura na linha 7, na classe “Somar”, é uma sobrecarga do método construtor da linha 10, ou seja, está ocorrendo um polimorfismo no contexto da assinatura do método e trata-se deum polimorfismo de sobrecarga, no qual o objeto usa o mesmo nome do método, mas modifica a quantidade de parâmetros. Se não houvesse a necessidade de permitirmos instanciar um objeto sem a atribuição de um valor para a variável “valor1”, poderíamos deixar apenas o método das linhas 10 e 11 declarado, ou remover a declaração, já que Java garante a declaração do método construtor de modo implícito. Em outras palavras, o método construtor existe, só não temos o código dele especificado. É o que ocorre com a classe “Dividir”, por exemplo. É importante lembrar sobre a sobrescrita, outro tipo de polimorfismo, no qual se mantém a mesma assinatura do método, contudo o seu corpo – o que é delimitado entre “{“ e “}” – será diferente. A sobrescrita ocorre quando uma classe concreta estende uma classe, ou seja, temos a relação de herança, ou a implementa, quando consideramos uma interface. Veremos, a seguir, um exemplo considerando herança e interfaces. Antes, ainda, precisamos analisar alguns itens específicos do código. 49 Na classe “SomarSubtrair”, na linha 9, temos a utilização de um elemento proveniente do pacote Swing de Java: “JOptionPane. showMessageDialog(null, “O valor da da soma de dois doubles é: “ + s.somarDoisDoubles(2.4, 2.6)); pacote Swing. A importação ocorre na linha 3. O “JOptionPane.showMessageDialog()” cria uma caixa de diálogo, a qual servirá para impressão de valores, tal como ocorre quando utilizamos o “System.out.println()”, presente na linha 22, por exemplo, do código da classe “Principal”. A diferença é que, ao usarmos o JOptionPane, utilizamos um elemento que cria uma interface gráfica comumente utilizada em aplicações. Já a saída do System.out. println() apresentará o resultado somente no terminal de saída da IDE utilizada, não sendo exibida para um usuário final que não estiver executando o código diretamente. O uso do System.out.println() é, ainda, uma boa prática para realizarmos pequenos testes no nosso código, saídas etc. No entanto, a migração para elementos gráficos presentes no pacote chamado de Swing, em Java, é tendência natural no contexto do desenvolvimento de aplicações desktop. No código a seguir, além de explorarmos o contexto de herança, interfaces e polimorfismos, utilizaremos um outro tipo de JOptionPane, capaz de permitir a leitura de dados via teclado. Interface IBase 1. package br.com.calculos; 2. 3. public interface IBase { 4. public abstract double Soma(double a, double b); 5. public abstract double Subtracao(double a, double b); 6. public abstract double Divisao(double a, double b); 7. public abstract double Multiplicacao(double a, double b); 8. } 50 Classe Impressao 1. package br.com.calculos; 2. 3. import javax.swing.JOptionPane; 4. 5. public abstract class Impressao { 6. 7. public void testeImpressao() { 8. JOptionPane.showMessageDialog(null, “Este é um teste de impressão do método testeImpressao()!”); 9. } 10. 11. public abstract void imprimirResultado(); 12. } Classe Nucleo 1. package br.com.calculos; 2. 3. import javax.swing.JOptionPane; 4. 5. public class Nucleo extends Impressao implements IBase { 6. 7. @Override 8. public double Soma(double a, double b) { 9. return a + b; 10. } 11. 12. @Override 13. public double Subtracao(double a, double b) { 14. return a–b; 15. } 51 16. 17. @Override 18. public double Divisao(double a, double b) { 19. return a / b; 20. } 21. 22. @Override 23. public double Multiplicacao(double a, double b) { 24. returna * b; 25. } 26. 27. @Override 28. public void imprimirResultado() { 29. // TODO 30. } 31. 32. public void calcular() { 33. double a = Double. parseDouble(JOptionPane.showInputDialog(“Entre com o primeiro número:”)); 34. double b = Double. parseDouble(JOptionPane.showInputDialog(“Entre com o segundo número:”)); 35. Object[] op = {“Soma”, “Subtração”, “Divisão”, “Multiplicação”}; 36. String tipo = (String) JOptionPane. showInputDialog(null, “Selecione o cálculo:\n”, “Pesquisa”, JOptionPane.PLAIN_MESSAGE, null, op, “Soma”); 37. switch (tipo) { 38. case “Soma”: { 39. JOptionPane. showMessageDialog(null, “O resultado da soma é: “+ this. Soma(a, b)); 52 40. break; 41. } 42. case “Subtração”: { 43. JOptionPane. showMessageDialog(null, “O resultado da subtração é: “+ this.Subtracao(a, b)); 44. break; 45. } 46. case “Divisão”: { 47. JOptionPane. showMessageDialog(null, “O resultado da divisão é: “+ this. Divisao(a, b)); 48. break; 49. } 50. case “Multiplicação”: { 51. JOptionPane. showMessageDialog(null, “O resultado da multiplicação é: “+ this.Multiplicacao(a, b)); 52. break; 53. } 54. default: 55. throw new IllegalArgumentException(“Valor inválido: “ + tipo); 56. } 57. } 58. } Classe Calculadora 1. package br.com.calculadora; 2. 3. import br.com.calculos.Nucleo; 4. 5. public class Calculadora { 53 6. 7. public static void main(String[] args) { 8. Nucleo n = new Nucleo(); 9. n.calcular(); 10. } 11. } A interface “IBase” define a base para a implementação dos cálculos. Como toda interface, não há métodos implementados, todos são especificados com abstratos e possuem apenas suas assinaturas. Logo, a classe “Nucleo”, que implementa tal interface, é obrigada a implementar todos os métodos ali presentes. Os métodos sobrescritos são anotados com a indicação da palavra @Override, ou seja, sobrescrita – como pode ser visto na linha 7, por exemplo. Adicionalmente, a classe “Nucleo” estende, ou seja, é subclasse da classe abstrata “Impressao”, que é, por sua vez, superclasse da classe “Nucleo”. Nesta superclasse, temos dois métodos: um abstrato, que obrigará a sua implementação na classe filha, e um normal. O método tido como normal só será chamado, se necessário, pelo objeto instanciado da classe filha, no caso, um objeto do tipo “Nucleo”. É preciso lembrar que Java permite que a herança seja realizada de modo hierárquico, sem um limite, mas que uma classe só pode herdar de outra classe, ou seja, não há classes múltiplas. Isso se deve ao fato de que, ao coincidir nomes de métodos ou atributos entre classes pai, a classe filha não saberia qual método ou atributo utilizar, e isso implicaria um problema chamado losango da morte. Para resolver isso, foram definidas as interfaces, que podem ser implementadas por um número ilimitado de classes, e tais classes podem implementar uma ou mais interfaces. 54 No código de exemplo, se nossa classe “Nucleo” necessitasse implementar mais de uma interface, colocaríamos uma “,” após “IBase” e incluiríamos ali a outra interface, e assim por diante. Ainda no último exemplo de código, temos um novo tipo de JOptionPane, o “showInputDialog”, que cria uma caixa de diálogo, na qual podemos digitar um texto e este ser armazenado em uma variável. Os valores solicitados para o usuário são os números do tipo double, que, para isso, precisam ser convertidos pelo método da classe Double, como ocorre nas linhas 33 e 34 da classe “Nucleo”. Na classe “Nucleo”, temos a utilização de uma estrutura de seleção chamada Switch, a qual permite que se verifique o valor de uma variável em várias opções: se o valor foi igual, no caso, se o tipo de cálculo for igual a uma das operações definidas, o código após os dois pontos (“:”) será executado e o “break;” finaliza a execução do programa ali. Esta estrutura pode ser vista das linhas 37 a 56. Nesta mesma estrutura, temos também o que chamamos de um lançamento de exceção, presente na linha 55. Nesta mesma linha, temos a exibição de uma mensagem de valor inválido, caso o valor digitado pelo usuário seja diferente de uma das operações esperadas na estrutura do Switch. Finalmente, para executarmos nosso programa, temos uma classe principal, assim indicada por possuir o método “main()”, que será o método a inicializar a execução de nossa aplicação. Dentro desse método, temos, então, a instanciação de um objeto do tipo “Nucleo” (linha 8), por meio de seu método construtor implícito e a chamada do método “calcular()”, que de fato executa toda a aplicação. Destarte, percorremos os pilares de OO, com ênfase, no primeiro código, no encapsulamento, e no segundo, no contexto de herança. Utilizamos, ainda, a conversão de valores para Double, a biblioteca Swing e estruturas condicionais e de seleção, como o Switch. Há outros elementos e recursos da linguagem Java, mas aqueles que 55 foram apresentados permitem a criação de aplicações iniciais e, em especial, compreender o poder de reuso e delimitações mais precisas de aplicações orientadas a objetos. Estar atento à documentação oficial de Java é fortemente recomendado para que se entenda ainda mais tais elementos e se aprendam novos. Bons estudos! Referências FURGERI, S. Java 8 Ensino Didático: desenvolvimento e implementação de aplicações. São Paulo, SP: Saraiva Educação S.A., 2018. JECK, D. Introduction to programming using Java. Geneva, NY: Hobart and William Smith Colleges, 2021. ORACLE. Controlling Access to Members of a Class. Oracle, 2022. Disponível em: https://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html Acesso em: 17 fev. 2023. 56 Implementação de interfaces gráficas com Java Autoria: Anderson da Silva Marcolino Leitura crítica: Fabiano Gonçalves dos Santos Objetivos • Compreender a integração de todos os pilares da orientação a objetos para a construção de uma aplicação com interface gráfica. • Estabelecer uma estrutura precisa de uma aplicação, considerando a utilização de pacote de interface gráfica, como Swing, do Java. • Compreender, via código, a implementação de sobrescrita de métodos. 57 1. Primeiros passos com interface gráfica em Java A programação orientada a objetos (OO) é aplicada no desenvolvimento de diferentes aplicações e softwares. Para que estas se tornem mais robustas e facilitem a utilização aos usuários, adotam-se as interfaces gráficas dos usuários. É importante não confundirmos as interfaces, que são implementadas por classes concretas com estas interfaces gráficas. Sempre que vamos nos referenciar a interfaces que refletem um contexto visual, utilizaremos o termo “gráfico” junto. Portanto, uma interface gráfica é aquilo que será exibido nas telas, nas quais os usuários interagirão. Deste modo, quando utilizarmos interfaces gráficas de usuário em Java, aplicamos todos os pilares de tal paradigma: abstração, herança, polimorfismo e encapsulamento. Este uso ocorre com bibliotecas específicas. Nas versões mais antigas, Java nos fornecia um pacote para o desenvolvimento de interfaces gráficas chamado Abstract Window Toolkit (AWT), ou na tradução direta, kit de ferramentas para janelas abstratas. Com novas versões, o AWT foi estendido, dando origem a uma nova e mais robusta biblioteca, a Swing (JECK, 2021; FURGERI, 2018). Atualmente, a Swing está com solicitação de retirada das especificações Java, visto que novos arcabouçospara a criação de interfaces gráficas estão sendo utilizados, como o JavaFX. Contudo, é importante mencionar que o JavaFX pode ser integrado a aplicações Swing já existentes (ORACLE, 2014). Considerando que a biblioteca Swing é uma evolução, ela traz benefícios, como melhor aparência às interfaces gráficas, melhores tratamentos de eventos, recursos, entre outros. Adicionalmente, por ser uma extensão 58 da AWT, para diferenciar seus elementos, temos a adição do J no nome de cada elemento. Por exemplo, para inserirmos um botão no AWT, adicionávamos um objeto da Classe Button; com a biblioteca Swing, adicionaremos um objeto da Classe JButton (FURGERI, 2018). Ao falarmos de bibliotecas, aquelas que são mantidas e disponibilizadas pela Oracle – a empresa mantenedora de Java – são seguras e são importadas automaticamente pelos ambientes de desenvolvimento integrados, como Eclipse e NetBeans. Contudo, há também pacotes e bibliotecas de terceiros, como versões específicas da Java DataBase Connectivity, que, na tradução direta, indica um pacote de conectividade Java com banco de dados. Um driver JDBC deverá ser específico para o banco que se deseja fazer a conexão, sendo disponibilizado pelas empresas mantenedoras de tais bancos de dados. É importante mencionar que, ainda que um driver JDBC seja desenvolvido para atuar com bancos específicos, ele segue as especificações da Oracle. O uso de pacotes é essencial para que o reuso ocorra de fato. Ademais, para que possamos usá-los, precisamos olhar a documentação dos pacotes e das bibliotecas, pois ela nos oferecerá as classes e os métodos – em especial, as interfaces – que podemos utilizar em nossos projetos, bem como informações adicionais para nos ajudar a explorar, de modo mais fácil, todo o potencial dos pacotes utilizados. Deste modo, os exemplos que serão apresentados do uso do Swing servirão de base para a utilização de outras bibliotecas, mas devemos levar em conta a documentação de cada uma delas. É importante frisar que nem o mais experiente dos desenvolvedores Java saberão todos os métodos e pacotes existentes, por isso é mais importante termos fontes confiáveis para pesquisa do que tentar decorar tais mecanismos. Retornando à nossa biblioteca Swing, é por meio dela que criaremos as interfaces gráficas de usuário (GUI), do inglês graphical user interface. Quando estamos pensando na criação de uma aplicação com interfaces gráficas, um bom planejamento é necessário. Quando em uma 59 empresa de grande porte, haverá uma equipe com designers gráficos e especialistas em experiência de usuário (UX), do inglês user experience, para auxiliar na criação dessas interfaces gráficas. Em projetos menores, podemos nos basear nas interfaces que vemos nos sistemas operacionais e em aplicações mais atuais. Isso garantirá uma maior aproximação da solução a ser desenvolvida com o que os usuários comumente usam. Essa prática garantirá uma melhor experiência de quem usará nosso software e maior garantia de que estamos criando um projeto bom. No contexto de Java, ao construirmos nossas interfaces ou uma aplicação que possua uma GUI, deveremos seguir alguns passos: • Definir um desenho inicial ou um esboço do que pretendermos desenvolver no contexto da interface – e isso não retira a necessidade de se analisar e conceber todo o projeto da arquitetura de software. • Identificar e definir quais componentes (classes e objetos) serão utilizados em cada componente desenhado. • Importar o pacote para o projeto (import javax.swing.* e import java.awt.event.*). • Definir a disposição de tais elementos em tela. • Programar os comportamentos dos componentes, por meio do controle de eventos. Apesar de utilizarmos o Swing para que os eventos ocorram por meio de tais componentes, deveremos importar o pacote de eventos da biblioteca AWT. Adicionalmente, como o Swing é uma evolução de elementos do AWT, em alguns projetos precisaremos importar e utilizar o pacote AWT: import java.awt.*. Em um contexto geral, quando 60 queremos importar todas as classes que estão dentro de um pacote Java, podemos utilizar o asterisco “*”. Se queremos importar alguma classe específica, esta poderá ser escrita substituindo o “*”, mas, neste caso, ao identificarmos a necessidade de importar mais uma classe – e seu componente –, teremos de inserir uma nova linha de importação ou substituir a classe que havíamos importado anteriormente, por asterisco, reduzindo a quantidade de linhas de importação no cabeçalho de nossas classes no projeto. A Figura 1 apresenta uma interface de aplicação Java. Para a criação de GUIs como esta, podemos utilizar algumas ferramentas para facilitar a criação, como o editor de Design do NetBeans, que permite arrastar e soltar componentes e, assim, “montar” nossa tela como desejado. Figura 1 – Tela da aplicação Fonte: elaborada pelo autor. Na GUI da Figura 1, notamos que a aplicação deve calcular o índice de massa corporal (IMC). Para isso, precisaremos que o usuário digite o peso, a altura e, depois, clique no botão Calcular. O resultado será exibido na caixa de texto que não permite edição. O botão Limpar servirá para limpar os campos, para que um novo cálculo possa ser realizado. Adicionalmente, uma imagem aleatória pode ser inserida para 61 deixar a interface mais atrativa, como é o caso do desenho utilizado, que indica uma pessoa com massa corpórea elevada e outra baixa, já que nosso aplicativo faz, justamente, o cálculo do IMC. Deste modo, considerando nossa interface, podemos identificar os elementos que poderemos utilizar do Swing. 2. Criando nossa aplicação com os componentes do Swing O primeiro elemento a ser criado para que possamos montar nossas telas é o JFrame. Ele permite a criação de uma janela com barra de títulos, bordas e permite inserir outros elementos em seu interior para exibição. Dentre estes itens, um muito utilizado são os JPanels. Como o nome sugere, trata-se de um painel que pode, assim como o JFrame, receber outros elementos. Deste modo, poderíamos criar em um projeto apenas um JFrame e todas as outras interfaces de usuário serem montadas em painéis, sendo chamadas ou ocultadas de acordo com a navegação do usuário. Uma outra questão importante ao usarmos JFrame e JPanels são os layouts que ajudam a posicionar os elementos. Diversas classes do AWT e do Swing oferecem gerenciadores de layout para auxiliar a posicionar os elementos na tela, a compor nossas GUIs. Alguns dos mais utilizados são o AbsoluteLayout, BorderLayout, BoxLayout, CardLayout, FlowLayout, GridBagLayout, GridLayout, GroupLayout e SpringLayout. Dentre estes, podemos destacar o AbsoluteLayout, BorderLayout, o FlowLayout e o GroupLayout. No AbsoluteLayout, nossos objetos ficarão na posição em que forem designados, necessitando de valores para um posicionamento horizontal e vertical. Os demais layouts seguem 62 uma direção inicial para posicionar os elementos, com exceção do GroupLayout. O GroupLayout foi criado para auxiliar na criação de GUIs via ferramenta de construção de interfaces gráficas, como a existente no NetBeans e em plug-ins no Eclipse, Visual Studio Code e outros. Claro que se pode utilizá-lo para posicionar elementos de modo manual, contudo a especificidade para o posicionamento dos elementos é mais complexa, visto que ele trata o posicionamento considerando um layout horizontal e outro vertical. A Figura 2 apresenta a tela com a ferramenta de construção gráfica do NetBeans em uma interface com GridBagLayot. Figura 2 – Ferramenta de criação de GUI no NetBeans Fonte: captura de tela de Apache NetBeans IDE 13. O Quadro 1 apresenta os principais componentes (classes e seus objetos) utilizados em projetos com Swing. É importante destacar que estes são somente alguns dos existentes e que se faz necessário consultar a documentação de Java, para que possa identificar outros possíveis componentes. 63 Quadro 1 – Métodos mais comuns dosprincipais componentes do Swing Componente Método Função JLabel JLabel() Cria um rótulo (label) sem texto. JLabel(String, Image, int) Cria um rótulo (label) com um texto, uma imagem e o alinhamento dos dados. JTextField JTextField() Cria uma caixa de texto sem conteúdo. setEditable(boolean) Ao receber um valor boleano (true ou false), indicará se o JTextField será (true) ou não (false) editável. getText() Retorna o valor inserido no campo. setText() Atribui um valor para o campo. JButton Button(String) Cria um botão com texto. setEnabled(booblean) Recebe um valor para ativar (true) ou desati- var (false) o botão. JOptionPane showMessageDialog(Componente, String, String, int ) Cria uma caixa de diálogo com a indicação do componente que o criou (geralmente, usa- do a palavra this), a mensagem a ser exibida, o texto da barra de títulos e o tipo de mensa- gem (JOptionPane.INFORMATION_MESSA- GE, por exemplo). Fonte: adaptado de Jeck (2021) e Furgeri (2018). Com base nestes elementos, criaremos nossa tela seguindo o modelo especificado na Figura 1. Para isso, usaremos a ferramenta de construção de telas do NetBeans, apresentada na Figura 2. Para conseguirmos utilizá-la, devemos iniciar um projeto no NetBeans e adicionar, no pacote principal, um novo “JFrame Form..”, que criará um JFrame e abrirá o NetBeans diretamente na tela de criação de GUIs, indicado pela aba “Design”. Para podermos editar e incluir código, devemos selecionar a opção “Source”, que corresponde ao código. Todos os projetos que utilizam tal ferramenta apresentam uma parte do código destacada em cor cinza no editor de código. Logo perceberá que 64 tal texto não pode ser modificado, isso porque este código foi gerado pelo NetBeans, por meio do uso da ferramenta de criação da interface gráfica. Há a possibilidade de alterarmos estruturas específicas, em especial, métodos de eventos dos elementos gráficos que inserirmos, por isso, o código a seguir apresenta somente os trechos editáveis. Antes de começarmos a modificar o código, precisamos inserir, via “Palette”, também visualizada na Figura 2, no canto superior direito, os seguintes componentes: três JTexfFields (estarão identificados na ferramenta como “Text Field” sem o J, mas, ao criarmos o componente, como já mencionado, será exibido o J para diferenciar o pacote Swing do AWT; adicionalmente, as palavras não terão espaços, pois estamos nos referindo a objetos Java); dois botões (identificados como “Button”); três labels (identificados por “Label”) e, opcionalmente, uma imagem, que será também um componente modificado do tipo “Label”. Após a inserção destes elementos, poderemos fazer as modificações no código, gerando a aplicação com o código apresentado a seguir. Código da Classe TelaPrincipal 1. package br.aula.imc_swing; 2. 3. import java.awt.Color; 4. import java.awt.Container; 5. import java.awt.Dimension; 6. import java.awt.FlowLayout; 7. import java.awt.Toolkit; 8. import javax.swing.JButton; 9. import javax.swing.JDialog; 10. import javax.swing.JLabel; 11. import javax.swing.JOptionPane; 12. import javax.swing.JPanel; 13. import javax.swing.SwingConstants; 14. 65 15. public class TelaPrincipal extends javax.swing.JFrame { 16. 17. public TelaPrincipal(){ 18. initComponents(); 19. } 20. 21. float peso, altura, imc; 22. JDialog dialog; 23. 24. private void initComponents() { 25. java.awt.GridBagConstraints gridBagConstraints; 26. 27. jLabel4 = new javax.swing.JLabel(); 28. jLabel1 = new javax.swing.JLabel(); 29. tfPeso = new javax.swing.JTextField(); 30. jLabel2 = new javax.swing.JLabel(); 31. tfAltura = new javax.swing.JTextField(); 32. jLabel3 = new javax.swing.JLabel(); 33. tfResultado = new javax.swing.JTextField(); 34. btnLimpar = new javax.swing.JButton(); 35. btnCalcular = new javax.swing.JButton(); 36. 37. jLabel4.setText(“jLabel4”); 38. 39. setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ ON_CLOSE); 40. setTitle(“Calculadora de IMC”); 41. getContentPane().setLayout(new java.awt. GridBagLayout()); 42. 43. jLabel1.setLabelFor(tfPeso); 66 44. jLabel1.setText(“Peso:”); 45. 4gridBagConstraints = new java.awt. GridBagConstraints(); 46. gridBagConstraints.gridx = 2; 47. gridBagConstraints.gridy = 0; 48. gridBagConstraints.gridwidth = 2; 49. gridBagConstraints.ipadx = 34; 50. gridBagConstraints.anchor = java.awt. GridBagConstraints.NORTHWEST; 51. gridBagConstraints.insets = new java.awt.Insets(24, 6, 0, 0); 52. getContentPane().add(jLabel1, gridBagConstraints); 53. gridBagConstraints = new java.awt. GridBagConstraints(); 54. gridBagConstraints.gridx = 5; 55. gridBagConstraints.gridy = 0; 56. gridBagConstraints.gridwidth = 6; 57. gridBagConstraints.gridheight = 2; 58. gridBagConstraints.ipadx = 59; 59. gridBagConstraints.anchor = java.awt. GridBagConstraints.NORTHWEST; 60. gridBagConstraints.insets = new java.awt.Insets(21, 4, 0, 18); 61. getContentPane().add(tfPeso, gridBagConstraints); 62. 63. jLabel2.setLabelFor(tfAltura); 64. jLabel2.setText(“Altura:”); 65. gridBagConstraints = new java.awt. GridBagConstraints(); 66. gridBagConstraints.gridx = 2; 67. gridBagConstraints.gridy = 2; 68. gridBagConstraints.anchor = java.awt. GridBagConstraints.NORTHWEST; 67 69. gridBagConstraints.insets = new java.awt.Insets(11, 6, 0, 0); 70. getContentPane().add(jLabel2, gridBagConstraints); 71. gridBagConstraints = new java.awt. GridBagConstraints(); 72. gridBagConstraints.gridx = 5; 73. gridBagConstraints.gridy = 2; 74. gridBagConstraints.gridwidth = 6; 75. gridBagConstraints.gridheight = 2; 76. gridBagConstraints.ipadx = 59; 77. gridBagConstraints.anchor = java.awt. GridBagConstraints.NORTHWEST; 78. gridBagConstraints.insets = new java.awt.Insets(11, 4, 0, 18); 79. getContentPane().add(tfAltura, gridBagConstraints); 80. 81. jLabel3.setLabelFor(tfResultado); 82. jLabel3.setText(“Resultado:”); 83. gridBagConstraints = new java.awt. GridBagConstraints(); 84. gridBagConstraints.gridx = 2; 85. gridBagConstraints.gridy = 4; 86. gridBagConstraints.gridwidth = 3; 87. gridBagConstraints.ipadx = 23; 88. gridBagConstraints.anchor = java.awt. GridBagConstraints.NORTHWEST; 89. gridBagConstraints.insets = new java.awt.Insets(28, 6, 0, 0); 90. getContentPane().add(jLabel3, gridBagConstraints); 91. 92. tfResultado.setEditable(false); 93. tfResultado.setHorizontalAlignment(javax.swing.JTextField. TRAILING); 68 94. gridBagConstraints = new java.awt. GridBagConstraints(); 95. gridBagConstraints.gridx = 5; 96. gridBagConstraints.gridy = 4; 97. gridBagConstraints.gridwidth = 5; 98. gridBagConstraints.gridheight = 2; 99. gridBagConstraints.ipadx = 57; 100. gridBagConstraints.anchor = java.awt. GridBagConstraints.NORTHWEST; 101. gridBagConstraints.insets = new java.awt.Insets(25, 4, 0, 0); 102. getContentPane().add(tfResultado, gridBagConstraints); 103. 104. btnLimpar.setText(“Limpar”); 105. btnLimpar.addActionListener(new java.awt.event. ActionListener() { 106. public void actionPerformed(java.awt.event. ActionEvent evt) { 107. btnLimparActionPerformed(evt); 108. } 109. }); 110. gridBagConstraints = new java.awt. GridBagConstraints(); 111. gridBagConstraints.gridx = 2; 112. gridBagConstraints.gridy = 7; 113. gridBagConstraints.gridwidth = 4; 114. gridBagConstraints.anchor = java.awt. GridBagConstraints.NORTHWEST; 115. gridBagConstraints.insets = new java.awt.Insets(11, 36, 17, 0); 116. getContentPane().add(btnLimpar, gridBagConstraints); 117. btnLimpar.getAccessibleContext(). setAccessibleName(“btnLimpar”); 69 118. 119. btnCalcular.setText(“Calcular”); 120. btnCalcular.addActionListener(newjava.awt.event. ActionListener() { 121. public void actionPerformed(java.awt. event.ActionEvent evt) { 122. btnCalcularActionPerformed(evt); 123. } 124. }); 125. gridBagConstraints = new java.awt. GridBagConstraints(); 126. gridBagConstraints.gridx = 0; 127. gridBagConstraints.gridy = 7; 128. gridBagConstraints.gridwidth = 2; 129. gridBagConstraints.anchor = java.awt. GridBagConstraints.NORTHWEST; 130. gridBagConstraints.insets = new java.awt.Insets(11, 103, 17, 0); 131. getContentPane().add(btnCalcular, gridBagConstraints); 132. btnCalcular.getAccessibleContext(). setAccessibleName(“btnCalcular”); 133. 134. pack(); 135. } 136. private void btnCalcularActionPerformed(java.awt.event. ActionEvent evt) { 137. if(!(tfPeso.getText().isEmpty() || tfAltura.getText().isEmpty())){ 138. peso = Float.parseFloat(tfPeso. getText().replace(“,” , “.”)); 139. altura = Float. parseFloat(tfAltura.getText().replace(“,” , “.”)); 140. imc = peso / (altura * altura); 70 141. tfResultado.setText(String.valueOf(imc)); 142. } else { 143. mensagemDialogo(“Campos em branco. Por favor informe!”, “Erro”); 144. //mensagemDialogoCriado(); 145. } 146. 147. } 148. 149. private void mensagemDialogo(String mensagem, String titulo){ 150. JOptionPane.showMessageDialog(this, mensagem, 151. titulo, JOptionPane.INFORMATION_MESSAGE); 152. } 153. 154. private void mensagemDialogoCriado(){ 155. //Aqui vamos configurar, de fato o JDialog 156. dialog.setResizable(false); 157. dialog.getContentPane().add(this.createPane()); 158. dialog.pack(); 159. dialog.setSize(300, 200); 160. Dimension size = Toolkit.getDefaultToolkit(). getScreenSize(); 161. dialog.setLocation(new Double((size. getWidth()/2)–(dialog.getWidth()/2)).intValue(), 162. new Double((size.getHeight()/2)–(dialog. getHeight()/2)).intValue()); 163. dialog.setVisible(true); 164. } 165. 166. protected Container createPane(){ 167. JPanel telaDialogo = new JPanel(); 168. telaDialogo.setLayout(new FlowLayout()); 71 169. JLabel lbl = new JLabel(“Campos em branco. Por favor informe!”, SwingConstants.CENTER); 170. JButton jb = new JButton(“Ok”); 171. jb.addActionListener(e -> telaDialogo. setVisible(false)); 172. telaDialogo.setBackground(Color.GREEN); 173. telaDialogo.add(lbl); 174. telaDialogo.add(jb); 175. return telaDialogo; 176. } 177. 178. private void btnLimparActionPerformed(java.awt.event. ActionEvent evt) { 179. tfPeso.setText(“”); 180. tfAltura.setText(“”); 181. tfResultado.setText(“”); 182. } 183. 184. public static void main(String args[]) { 185. java.awt.EventQueue.invokeLater(new Runnable() { 186. public void run() { 187. new TelaPrincipal().setVisible(true); 188. } 189. }); 190. } 191. 192. // Variables declaration–do not modify 193. private javax.swing.JButton btnCalcular; 194. private javax.swing.JButton btnLimpar; 195. private javax.swing.JLabel jLabel1; 196. 1private javax.swing.JLabel jLabel2; 197. private javax.swing.JLabel jLabel3; 198. private javax.swing.JLabel jLabel4; 72 199. private javax.swing.JTextField tfAltura; 200. private javax.swing.JTextField tfPeso; 201. 2private javax.swing.JTextField tfResultado; 202. // End of variables declaration 203. } Na linha 1, temos a indicação do pacote onde nossa classe está localizada. Da linha 3 até a linha 13, temos a importação dos elementos utilizados na nossa aplicação e, na linha 15, temos o início de nossa classe, a qual, por ser um JFrame, faz herança da classe javax.swing. JFrame. Na linha 17, temos a declaração de um método que se chama initComponents(), que, como o nome sugere, chama o método declarado a partir da linha 24 até a linha 135, e que, por estar destacado em cinza, indica que o código foi gerado automaticamente pelo NetBeans. Este método é implementado obrigatoriamente pelas classes que implementam a classe JFrame. Assim, trata-se de uma sobrescrita de método. A aplicação de cálculo do IMC, após a entrada das informações pelos usuários, ocorrerá no método btnCalcularActionPerformed(java.awt. event.ActionEvent evt)). Este método é o que chamamos de um evento, uma ação realizada, da tradução do termo ActionPerformed pelo elemento que recebeu uma interação. No caso, o JButton, que recebeu o nome de btnCalcular. Note que nossos JTextFields foram renomeados também, usando nomes mais fáceis de serem lembrados para se trabalhar no código. No método da ação do btnCalcular, que vai da linha 136 até a linha 147, temos uma estrutura condicional (“if”) que busca validar os campos de peso (tfPeso) e altura (tfAltura), identificando se estão vazios. Não estando vazios, segue para o cálculo do IMC, que é armazenado em uma variável chamada imc e, depois, é convertida em String, na linha 141, 73 e atribuída ao JTextField txResultado, que exibe o resultado; se algum dos campos estiver vazio, segue para o bloco do “senão” (else), que exibe uma caixa de diálogo, cujo método é implementado da linha 149 até a linha 152. Na linha 144, temos uma alternativa para esta caixa de diálogo, na qual uma caixa é criada no método definido da linha 154 até a linha 164. O método mensagemDialogoCriado() utiliza um método específico para criar um container, via método privado, declarado da linha 166 até a linha 176. Este método é privado, pois não deve ser chamado pelo objeto instanciado, e sim somente pelos métodos da própria classe, exemplificando o uso do encapsulamento. Da linha 178 até a linha 182, temos a definição de nosso método que limpa nossos campos de texto. Tal ação está vinculada ao nosso segundo botão, intitulado btnLImpar. Note que, no corpo do método, se utiliza o método setText(), para cada um dos JTextFields, para inserir um texto vazio, limpando o texto que o usuário possa ter inserido nos campos de texto ativos. Finalmente, na linha 184, temos o método main(), que inicia nossa aplicação, e da linha 193 até a linha 201, a declaração das variáveis de referência para todos os objetos do Swing utilizados no projeto de nossa interface. Destarte, percorremos os pilares de OO no contexto prático, com o uso da biblioteca Swing. Há, ainda, outros elementos e recursos da linguagem Java. Todavia, os apresentados permitem a criação de aplicações iniciais e, em especial, compreender o poder de reuso e delimitações mais precisas de aplicações orientadas a objetos. Estar atento à documentação oficial de Java é fortemente recomendado para que se entenda mais tais elementos e se aprendam novos. Bons estudos! 74 Referências FURGERI, S. Java 8 Ensino Didático: desenvolvimento e implementação de aplicações. São Paulo, SP: Saraiva Educação S.A., 2018. JECK, D. Introduction to programming using Java. Geneva, NY: Hobart and William Smith Colleges, 2021. ORACLE. JavaFX: Interoperability. Oracle, 2014. Disponível em: https://docs.oracle. com/javase/8/javafx/interoperability-tutorial/swing-fx-interoperability.htm. Acesso em: 28 fev. 2023. 75 BONS ESTUDOS! Sumário Apresentação da disciplina Histórico e pilares da programação orientada a objetos Objetivos 1. Introdução e histórico do paradigma de programação orientada a objetos 2. Conceitos essenciais de OO Referências Abstração e herança em orientação a objetos Objetivos 1. Abstraindo o mundo real 2. Herança com Java Referências Encapsulamento e instanciação de objetos Objetivos 1. Protegendo elementosna programação orientada a objetos 2. Métodos construtores, polimorfismo e Java Referências Implementação de interfaces gráficas com Java Objetivos 1. Primeiros passos com interface gráfica em Java 2. Criando nossa aplicação com os componentes do Swing Referências