Baixe o app para aproveitar ainda mais
Prévia do material em texto
289S - TOP ESP PROGR ORIENT OBJETOS Introdução ao Java Um breve histórico A Sun Microssystems, em 1991, financiou um projeto de pesquisa corporativa interna com o codinome Green, que resultou no desenvolvimento de uma linguagem baseada em C++ que seu criador, James Gosling, chamou de Oak. Descobriu-se mais tarde que já havia uma linguagem de programação com este nome. Uma equipe da Sun, em uma visita a uma cafeteria local que servia café cuja origem era da ilha de Java na Indonésia sugeriu este nome e ele acabou pegando. O projeto passou por dificuldades e quase foi cancelado. Mas em 1993 a World Wide Web explodiu em popularidade e a equipe da Sun viu um grande potencial para utilizar o Java par adicionar conteúdo dinâmico, como interatividade e animações, às páginas da Web, o que deu nova vida ao projeto. O Java foi anunciado formalmente em 1995, ganhando muito interesse da comunidade do World Wide Web. Hoje o Java é popular no desenvolvimento de software para diversos aplicativos, como aplicativos corporativos de grande porte, aplicativos governamentais, no aprimoramento da funcionalidade de servidores Web assim como aplicativos para o consumo popular em microcomputadores pessoais e dispositivos móveis. Arquitetura de um programa em Java Um dos aspectos mais marcantes do Java e que esteve presente desde sua concepção é o fato desta linguagem ser multiplataforma. Plataforma é o conjunto formado por um computador e o sistema operacional instalado neste computador. Podemos, então, ter as seguintes plataformas como exemplo: • Computador padrão PC-Intel executando Windows; • Computador padrão PC-Intel executando Linux; • Computador padrão iMac executando Mac OS; • Computador padrão workstation executando Solaris; • etc. Dizer que o Java é multiplataforma significa dizer que um programa feito em Java e compilado em uma determinada plataforma, poderá ser executado nesta plataforma e também em todas as demais plataformas que dão suporte ao Java. Diversas linguagens, como o C++ por exemplo, estão disponíveis para diversas plataformas. Entretanto, um código escrito em C++ que seja compilado em uma determinada plataforma só poderá ser executado nesta mesma plataforma. Para que o código seja executado em uma outra plataforma, ele deverá ser compilado novamente, mas dessa vez na plataforma em que se deseja executar o código. Mas como é possível que um programa compilado em Java em uma determinada plataforma possa ser executado em outras plataformas? Um computador só consegue executar programas que estejam escritos em linguagem de máquina. Quando um programa é escrito em uma linguagem de alto nível, o código deste programa deve ser compilado (traduzido) para a linguagem de máquina da plataforma que irá executar o programa. O Java realiza esta façanha porque ele não é compilado diretamente para a linguagem de máquina de uma plataforma específica. Em vez disso, ele é compilado para uma linguagem de máquina de um computador fictício, uma máquina virtual. Para que o programa possa ser então executado, um computador deve utilizar um emulador deste computador fictício, que então, interpreta o código compilado Java. Em outras palavras: Um programa em Java é semi-compilado, o que significa que o código fonte é compilado para uma linguagem intermediária, conhecida como bytecode. Este bytecode é um código de computador escrito em linguagem de máquina da Máquina Virtual Java (Java Virtual Machine – JVM). A Máquina Virtual Java, então, interpreta o bytecode e executa o programa. Com esta estrutura, um programa compilado em Java pode ser executado diretamente em qualquer sistema que possua uma Máquina Virtual Java disponível. O que é necessário para se programar em Java O Java é disponibilizado por seu atual desenvolvedor, a Oracle, em diversas distribuições. Algumas delas são: • Java Standard Edition (Java SE); • Java Enterprise Edition (Java EE); • Java Micro Edition (Java ME); • Java Card; • Java TV; • etc. A mais importante distribuição é o Java SE (Java Edição Padrão). É nesta distribuição que estão as bibliotecas básicas do Java e as principais ferramentas de desenvolvimento, como o compilador Java. As demais distribuições são complementos ao Java SE que disponibilizam outras capacidades à linguagem. Por exemplo, o Java EE (Java Edição Corporativa) inclui bibliotecas e servidores capazes de permitir a criação e operação de sistemas corporativos distribuídos, o uso de persistência automatizada e a criação de páginas dinâmicas de Internet entre outras funcionalidades. O Java ME (Java Edição Micro) oferece a base para a programação de dispositivos móveis, como celulares, PDAs, receptores de TV, TVs inteligentes, etc. No caso de dispositivos como TVs, DVDs e Blu-Rays, há uma distribuição específica, o Java TV, que é uma especialização do Java ME para estas aplicações particulares. Para se desenvolver em qualquer tecnologia Java, a única distribuição obrigatória é o Java SE. Este kit de desenvolvimento (SDK – Software Development Kit) é a única distribuição que contém o compilador Java. Qualquer outra distribuição é um acessório que deve ser instalado junto ao SDK Java SE. Tendo o SDK Java SE (também conhecido como JDK – Java Development Kit) instalado, já é possível escrever programas usando algum editor de arquivos texto e usando o compilador Java por meio de linha de comando. O SDK Java SE pode ser obtido da página de seu desenvolvedor: Java SE. Disponível em http://www.oracle.com/technetwork/java/javase/overview/index.html Apesar do SDK Java SE ser suficiente para se programar em Java, a maioria dos estudantes e programadores admitem que o uso de um Ambiente Integrado de Desenvolvimento (IDE – Integrated Development Environment) torna o trabalho do desenvolvedor mais fácil. Um IDE é um sistema que reúne em um único pacote diversas ferramentas como: • Editor de código fonte; • Gerenciador de arquivos; • Corretor de sintaxe; • Ferramenta de automação de compilação; • Ferramenta de automação de depuração; • etc. Assim, mesmo sendo um opcional, vamos adotar uma IDE para desenvolver os exemplos deste livro-texto. Existem diversas IDEs disponíveis, tanto de código aberto quanto de código proprietário. Várias delas podem ser utilizadas, mas para não gastarmos tempo discutindo as diferenças entre elas, vamos utilizar o Eclipse (http://www.eclipse.org). O Eclipse não é o único IDE de código aberto de grande popularidade. Outro IDE bastante conhecido é o NetBeans (https://netbeans.org), mantido principalmente pela Oracle, mesmo desenvolvedor do Java. Há algumas diferenças entre ambos, mas não é possível dizer se um é melhor do que o outro. O que pode ser dito é que ambos são equivalentes, ou seja, tudo o que é feito com um pode ser feito com o outro. Programando em Java Vamos criar nosso primeiro programa, o famoso Olá mundo! Comece iniciando o Eclipse. Caso nunca o tenha iniciado antes, ele deve apresentar uma página de boas vindas semelhante à da seguinte figura: Esta janela pede para que você indique qual é a pasta que deseja utilizar para guardar os seus projetos Java. Utilize o local padrão ou indique o local que deseja utilizar. Se marcar a check box, o Eclipse não irá mais fazer esta pergunta ao inciciá-lo. A seguir, o Eclipse inicia. De novo, caso seja a primeira vez que o esteja executando, ele deve apresentar uma tela de boas vindas como vista na figura a seguir. Clique no ícone Workbench para exibir a área de trabalho. Vamos criar um projeto. Clique no menu File → New → Java Project. O seguinte assistente surge: Na caixa de texto Project name digiteo nome do seu projeto, “Ola mundo”. Certifique-se de que na área JRE você esteja usando alguma máquina virtual Java SE. Se por algum motivo a JRE padrão em seu computador não for um Java SE, selecione o primeiro botão de rádio e escolha uma versão J2SE ou Java SE (qualquer versão destas deve ser suficiente). Em qualquer linguagem de programação, sempre é recomendado não utilizar caracteres acentuados ou especiais. Ainda é possível (mesmo que incomum) que haja problemas tanto na compilação quanto na execução de códigos que utilizem tais símbolos. A única exceção é quando escrevemos algum texto em um String, por exemplo. Se desejamos escrever a mensagem “Olá mundo!” na tela, podemos usar acentos e caracteres especiais. As demais opções não precisam ser modificadas. Você já pode clicar no botão Finish. Neste momento, o Eclipse criou uma pasta chamada “Ola mundo” na pasta que você indicou como pasta de trabalho. Dentro dela serão armazenados seus códigos-fonte, classes compiladas, arquivos de configuração entre outros. Note que no Package Explorer (Explorador de Pacotes) agora há o seu projeto “Ola mundo”. Clique no sinal à esquerda do projeto para expandir sua estrutura interna. Agora vamos criar o arquivo onde será escrito o código-fonte do programa. Note que dentro do projeto “Ola mundo” há uma pasta chamada src. Ela é uma abreviação de source (fonte) e é nela que iremos colocar todos os códigos fonte de um projeto. Com o botão direito do mouse, clique na pasta src e selecione new → package. Package (pacote) é um conceito da orientação a objetos que permite que o desenvolvedor agrupe suas classes em pacotes. Os conceitos de classe e pacote serão vistos em maiores detalhes mais adiante. Na caixa para definir o nome do pacote, digite “primeiroPrograma”. Note que em Java, nomes devem ser compostos de apenas uma palavra. Clique em Finish. Em linguagens que derivam do C, como o C++, C# e Java, os nomes de classes, variáveis, métodos, atributos e demais elementos devem ter como nome um termo composto de uma única palavra. Recomenda-se que os nomes sejam sempre mnemônicos, ou seja, o próprio nome deve indicar qual é a natureza ou finalidade do elemento que ele batiza. Por exemplo, uma variável não mnemônica chamada “xpto12” não dá nenhuma indicação ao programador de sua finalidade. Já se ela for chamada de “contadorDeIterações”, sua finalidade torna-se evidente. Quando utiliza-se mais de uma palavra como nome, recomenda-se que a primeira letra de cada palavra, a partir da segunda palavra seja escrita em maiúscula. Esta notação é conhecida como camel case. Em Java, recomenda-se que a primeira letra da primeira palavra seja maiúscula se o nome for de uma classe ou de uma interface. Ela deve ser minúscula para os demais casos. A exceção à notação camel case ocorre quando criamos o nome de uma constante. Neste caso, recomenda-se que o nome seja escrito com todas as letras maiúsculas, utilizando o símbolo do sublinhado para separar as palavras. Por exemplo, os seguintes nomes são apropriados para constantes: NOTA_MINIMA, TAXA_MINIMA_DE_CRESCIMENTO, PI, CONSTANTE_GRAVITACIONAL. Dentro do pacote “primeiroPrograma” vamos criar a classe “OlaMundo”. Clique com o botão direito no pacote e selecione new → Class. Na caixa para definir o nome da classe, digite o nome “OlaMundo”. Clique em Finish. Neste momento, o Eclipse cria o arquivo OlaMundo.java e o exibe no editor. Já há um pouco de código escrito no arquivo, a declaração do pacote e da classe. A seguir, basta escrever o código do método principal main() como mostrado a seguir: A seguir, para compilar e executar seu programa, clique no botão indicado pela primeira seta (ou selecione o menu run → run). O Eclipse irá executar o compilador Java sobre seu código e executar o programa compilado. A saída do console é exibida na aba destacada pela segunda seta. Caso a janela para o seu código esteja pequena demais, você pode dar um duplo clique na aba da janela, o que faz com que ela seja maximizada. Para voltar à visualização anterior, basta dar um duplo clique na aba novamente. As abas também podem ser fechadas definitivamente, clicando no símbolo “X”. Caso você tenha fechado acidentalmente uma aba e não saiba como fazer com que ela seja reapresentada, você pode pedir ao Eclipse para restaurar a configuração padrão das janelas. Para tanto, chame o menu Window → Perspective → Reset Perspective... e responda Yes quando lhe for perguntado se quer realmente retornar as janelas para a configuração padrão. Entrada e saída de dados usando JOptionPane No exemplo anterior, utilizamos um método para criar uma saída de texto no console. Também é possível receber dados do usuário pelo console. Mas para tornar este livro-texto mais enxuto, vamos deixar de lado esta forma de interação homem-máquina e priorizar o uso de interfaces gráficas. O Java nos oferece uma ferramenta pronta para criar caixas de diálogo. As caixas de diálogo são um tipo especial de janela em ambiente gráfico. Elas servem para apresentar informações e também para receber dados do usuário. Estas funcionalidades estão disponíveis na classe JOptionPane que está definida no pacote javax.swing, como ilustrado na figura a seguir. Na figura acima, a classe JOptionPane é representada em um diagrama de classes, tendo seus atributos omitidos e apenas dois de seus métodos representados. A classe está definida no pacote swing que por sua vez está definido no pacote javax. A UML (Unified Modeling Language – Linguagem Unificada de Modelagem) define diversos diagramas úteis para a documentação, modelagem, análise e projeto de sistemas orientados a objetos. O diagrama de classes, um dos mais importantes da UML, representa as classes de um sistema em um retângulo dividido em três partes. Na primeira é anotado o nome da classe, na segunda seus atributos e na terceira seus métodos. O método showMessageDialog() permite a exibição de uma caixa de diálogo onde uma informação é exibida para o usuário. A sobrecarga mais simples deste método é: Este método requer dois parâmetros: o primeiro, chamado pai, recebe um objeto que representa o elemento gráfico pai da caixa de mensagem. Este pai costuma ser uma janela do aplicativo. Entretanto, ainda não sabemos como criar qualquer elemento gráfico, por isso, vamos utilizar a palavra reservada null para indicar que a caixa de diálogo não irá possuir qualquer componente gráfico como pai. Isto também fará com que a caixa de diálogo seja apresentada centralizada na tela. A palavra reservada null indica um objeto que não foi instanciado, um objeto nulo. O segundo parâmetro é a mensagem que será apresentada dentro da caixa de diálogo. Assim, em um programa, poderíamos utilizar o método showMessageDialog() da seguinte forma e obter o resultado exibido abaixo: O método showInputDialog() permite criar uma caixa de diálogo que apresenta uma pergunta e exibe um espaço para que o usuário digite sua resposta. Quando o método é encerrado, ele retorna um String contendo o texto digitado pelo usuário. A declaração de sua sobrecarga mais simples é: Note que o método showInputDialog() não requer a indicação de um componente pai. Entretanto, se você desejar, pode indicar um, mesmo que seja null. O trecho de código abaixo mostra como podemos usar este método para receber um texto do usuário. O texto digitado pelo usuário é armazenado na variável resposta: No exemplo abaixo, mostramos um programa inteiro que utiliza a classe JOptionPane para realizar entrada e saída de dados usando as caixas de diálogo. Note que, para se utilizar esta classe, é necessário importar a classe para que o programa possa utilizá-la. Isto é feito coma palavra reservada import, que deve ser inserida logo abaixo da palavra package. Assumindo-se que, ao executar o programa acima, o usuário digite o nome Maria, ele irá obter as seguintes mensagens: Recomendações de estilo Podemos dar nomes a diversos elementos de nossos programas, como variáveis, atributos, métodos, parâmetros, etc. Em Java e em linguagens orientadas a objeto em geral, recomenda-se o uso de nomes mnemônicos, ou seja, nomes que já indicam qual é a finalidade do elemento que ele batiza. Por exemplo, pode ser difícil saber qual é a finalidade de uma variável chamada xpto12. Por outro lado, se a mesma variável receber o nome contadorDeIteracoes, sua finalidade torna-se bastante clara. Para que se criem nomes mnemônicos, muitas vezes, como no exemplo anterior, desejamos usar mais de uma palavra. Mas um nome deve ser uma única palavra, sem espaços. A recomendação, neste caso, é escrever todas as palavras juntas, sem espaço. Para se marcar a separação entre as palavras, a partir da segunda palavra, a primeira letra deve ser grafada com letra maiúscula. Esta notação é conhecida como camel case. Exemplo: • contadorDeIteracoes; • respostaDoUsuario; • mediaFinal. O Java recomenda que a primeira letra do nome seja maiúscula caso o nome seja de uma classe ou de uma interface. Exemplos: • OlaMundo • ProgramaPrincipal • String Não confunda interface com interface gráfica. A interface gráfica é um recurso de entrada e saída de dados utilizando elementos gráficos e dispositivos de interação humana, como mouse e teclado. Já a interface é um conceito de orientação a objetos que será abordado mais adiante, mas que você já pode estar familiarizado caso já tenha estudado Análise Orientada a Objetos. Uma interface pode ser entendida, resumidamente, como uma classe sem atributos e que apenas declara as assinaturas dos métodos, sem implementar qualquer um deles. A exceção ao camel case está na declaração de nomes de constantes. Constantes são atributos cujo valor não pode ser alterado. O Java recomenda que constantes sejam escritas com todas as letras maiúsculas, separando as palavras pelo símbolo do sublinhado. Exemplos: • NOTA_MINIMA; • TAMANHO_PADRAO; • PI • CONSTANTE_GRAVITACIONAL; Tipos primitivos Tipos são a natureza dos dados que podem ser armazenados em uma variável, um atributo, que podem ser transferidos por um parâmetro ou retorno de método. Tipo primitivo é o tipo de dado que está definido na própria linguagem Java. Por isso, esses tipos têm um excelente desempenho tanto em economia de memória quanto em desempenho de processamento. Estes tipos são: Tipo Tamanho em bits Natureza Valores booleanDepende da JVM de cada plataforma Booleanos true ou false char 16 Caractere alfanumérico ‘\u0000’ a ‘\uffff’ (0 a 65535) byte 8 Número inteiro -128 a 127 short 16 Número inteiro -32.768 a 32.767 int 32 Número inteiro -2.147.483.648 a 2.147.483.647 long 64 Número inteiro -9223372036854775808 a 9223372036854775807 float 32 Número em ponto flutuante Intervalo negativo: -3,4028234663852886E+38 a -1,40129846432481707e-45 Intervalo positivo: 1,40129846432481707e-45 a 3,4028234663852886E+38 double 64 Número em ponto flutuante Intervalo negativo: -1,7976931348623157E+308 a -4,94065645841246544e-324 Intervalo positivo: 4,94065645841246544e-324 a 1,7976931348623157E+308 Na figura a seguir, criamos algumas variáveis e atribuímos a elas valores definidos por literais, ou seja, valores definidos no próprio código fonte: Quando se define um literal numérico inteiro, ele é tratado como sendo um inteiro. Já quando se define um literal numérico com um ponto decimal, ele é tratado como um double. Para se definir um literal caractere, ele deve ser circundado por aspas simples. Um literal booleano só pode assumir os valores representados pelas palavras reservadas true ou false. Quando se deseja definir um literal inteiro maior do que a capacidade de representação do int, deve-se indicar que o literal é um long, adicionando-se o sufixo L ou l. Analogamente, ao se representar um literal como um float, deve-se adicionar o sufixo F ou f: Conversão de tipos Quando se trabalha com mais de um tipo primitivo, podemos atribuir o valor armazenado em uma variável de tipo de menor capacidade em uma variável de maior capacidade. De uma certa forma, podemos entender que um tipo menor “cabe” dentro de um tipo maior. Desta forma, o seguinte método, se executado, irá gerar a mensagem representada na Figura 25. Na linha 10 é declarado o método atribuicoesSemCast(), o qual realiza o exemplo. Na linha 11 é declarada a variável b do tipo byte com o valor 8. Da linha 12 até a linha 16, o valor da variável b é transferido sucessivamente para variáveis de maior capacidade. Na linha 18 é declarada uma variável do tipo String chamada valores. A esta variável é atribuído o retorno do método format() da classe String. O método format() recebe pelo menos dois parâmetros. O primeiro é um String de formatação. Este String é composto de caracteres que são simplesmente retornados, como “b = “ e parâmetros de formatação que são compostos de um sinal % e uma letra. Esta letra indica se o parâmetros será substituído por um número decimal (d) ou um número de ponto flutuante (f). A seguir, o método format() recebe os valores que serão inseridos nos parâmetros do String de formatação. As sequências “\n” indicam que será inserido no String uma quebra de linha. Por fim, para que o código não fique demasiadamente largo, pode-se quebrar uma linha no meio do String. Para tanto, ele deve ser encerrado na primeira linha fechando as aspas e, na linha seguinte, deve ser concatenado com o segundo String usando o operador +. Já para se fazer as atribuições na direção oposta é necessário realizar um cast, ou conversão de tipo. Um cast é representado pelo tipo de destino entre parênteses antes do valor a ser convertido. O programa da Figura 24 foi alterado para realizar atribuições de variáveis de maior capacidade a variáveis de menor capacidade. Na figura acima está representado o mesmo programa Atribuicoes. Repare que na linha 11 há um sinal + e a próxima linha é a 24. Este sinal indica que a implementação do método atribuicoesSemCast() foi ocultada. Este recurso está disponível no Eclipse (e em praticamente qualquer IDE moderna). Para se exibir novamente o código oculto, basta clicar no sinal +. Para se ocultar um trecho de código, clica-se no sinal -. A outra modificação está no método main(), que agora chama também o método atribuicoesComCast(). Na linha 26 é declarada uma variável double com um valor em ponto flutuante. Nas linhas 27 a 31, este valor é convertido sucessivamente em tipos cada vez de menor capacidade. O resultado pode ser visto na figura a seguir. Note como os valores, ao serem convertidos para tipos de menor capacidade, foram perdendo precisão, como se estivessem sendo corrompidos. Isto ocorre porque o cast não faz arredondamento, ele faz truncamento em binário. Isso significa que, quando um número é convertido para um tipo de capacidade menor em bits, apenas os bits menos significativos são mantidos. Os bits excedentes do número são desprezados, o que causa a perda de informação. A partir de agora, os exemplos apresentados neste livro-texto não serão mais programas inteiros. Para poupar espaço, os exemplos serão apresentados como métodos que podem simplesmente ser inseridos em um programa qualquer que você já tenha criado. Basta chamar o método a partir do seu método main(). Classes wrapper (invólucro) Cada tipo primitivopossui uma classe wrapper associada. Uma classe wrapper pode ser utilizada para instanciar um objeto que armazena um valor do tipo primitivo associado a ela. Além disso, estas classes apresentam diversos métodos para se fazer a conversão de e para diversos tipos. As classes wrapper são: Tipo primitivo Classe wrapper boolean Boolean char Character byte Byte short Short int Integer long Long float Float double Double Estas classes podem ser utilizadas para se instanciar objetos com um valor do tipo primitivo associado a elas: A partir do Java 5 (Java versão 1.5), o valor de uma variável de tipo primitivo pode ser atribuído diretamente a um objeto de sua classe wrapper associada e vice-versa. Este recurso é chamado de autoboxing: As classes wrapper oferecem métodos parse que são úteis para se converter um String em um valor de tipo primitivo associado. Cada classe wrapper possui um método parse: As classes wrapper numéricas (todas exceto Boolean e Character) possuem métodos value para converter o valor armazenado no objeto para outros tipos primitivos numéricos. Desta vez, a conversão é feita com arredondamento, não mais com truncamento como o que ocorre quando se utiliza um cast. O exemplo a seguir utiliza os métodos value para converter de um objeto Integer para diversos outros tipos, mas o mesmo pode ser feito com qualquer outro wrapper numérico. Repare que na primeira linha, foi usado um construtor da classe Integer que recebe um String e não um int. Todas as classes wrapper possuem um construtor que recebe um String. Este construtor faz a conversão apropriada (usando um método parse adequado) antes de terminar a instanciação do objeto. Veja o programa a seguir que utiliza uma classe wrapper para receber dois números do usuário e, a seguir, realizar um cálculo matemático: Orientação a Objetos Uma das diferenças mais óbvias entre uma linguagem estruturada e uma linguagem orientada a objetos são justamente as classes e objetos. Pode-se entender, de maneira bastante simplificada, que uma classe é um trecho de código de computador que modela algum conceito útil para o sistema que se está desenvolvendo. Este conceito pode ser algo tangível, como um cliente, um funcionário, um produto, uma venda, mas também pode ser um conceito mais abstrato, como um conjunto de ferramentas de cálculo ou um conjunto de funcionalidades de acesso a banco de dados. Quando se faz a análise de um sistema, deve-se modelar classes que encapsulem, isolem, coloquem uma “cápsula” em torno deste conceito. Para tanto, deve-se ter em mente as seguintes regras para um bom projeto de classe: • Uma classe deve modelar apenas um conceito; • Uma classe não deve modelar qualquer aspecto de outro conceito; • Não deve haver em nenhuma outra parte do sistema qualquer aspecto do conceito modelado pela classe. Uma classe modela um determinado conceito reunindo em um único elemento os dados que definem este conceito e os comportamentos que este conceito pode apresentar. Uma boa maneira de se representar classes é usando o Diagrama de Classes da UML (Linguagem Unificada de Modelagem em inglês). Neste diagrama, uma classe é representada com um retângulo dividido em três partes. Na primeira parte é registrado o nome da classe. Na segunda, os atributos da classe que armazenam os dados da classe. Na terceira parte, os métodos da classe que implementam os comportamentos da classe. A figura acima registra uma classe em UML. Esta classe tem o nome Pessoa, possui dois atributos: nome e telefone. O primeiro atributo armazena dados do tipo String enquanto que o segundo armazena dados do tipo int. A classe também apresenta o método apresente() que será usado para exibir as informações armazenadas nos atributos. Este método não recebe parâmetros, pois não há nenhum parâmetro representado entre os parêntesis. Ele também não devolve nenhum valor, pois seu tipo de retorno é void. Esta classe pode ser codificada em Java da seguinte maneira: Nas linhas 7 e 8 estão declarados os atributos nome e telefone. Eles são declarados como se fossem variáveis, mas são atributos por não estarem dentro de qualquer método. Suas declarações são precedidas pela palavra reservada public, representada pelo sinal + no diagrama UML da Figura 34. Esta palavra reservada indica que estes atributos podem ser acessados por qualquer parte do sistema que tenha acesso à classe Pessoa. Na linha 10 é declarado o método apresente() que imprime em uma caixa de diálogo os dados armazenados nos atributos. Os modificadores de acesso serão abordados em maiores detalhes mais adiante neste livro-texto. Uma classe é uma definição de um conceito, como o simples cadastro de uma pessoa apresentado no exemplo acima. Mas um programa não pode utilizar esta definição diretamente, ele precisa instanciar esta classe em um objeto. Imagine que uma classe seja uma receita de bolo. Com esta receita pode-se preparar vários bolos e todos acabarão sendo mais ou menos parecidos. Desta mesma forma, uma classe é usada para se instanciar vários objetos. É o objeto que realmente possui os dados e os comportamentos modelados. O programa abaixo instancia um objeto da classe Pessoa, atribui valores aos seus atributos e chama o método apresente(). O resultado de sua execução também é apresentado: Na linha 7 é criada uma variável chamada p cujo tipo é Pessoa. Nesta variável é armazenado um objeto da classe Pessoa. O objeto é instanciado com o uso da palavra reservada new seguida do “nome da classe”. Na verdade, o termo Pessoa() que segue a palavra new não é o nome da classe, mas um método construtor. Métodos construtores serão apresentados logo a seguir. Nas linhas 9 e 10 os atributos nome de p e telefone de p têm seus valores atribuídos. Note que o objeto p têm seus próprios atributos nome e telefone. Caso um outro objeto fosse instanciado, ele poderia ter valores diferentes em seus atributos nome e telefone. Na linha 12 é chamado o método apresente() de p, o qual faz ser exibida a caixa de mensagem com as informações armazenadas no objeto p. Instruções de controle Note que, da maneira que a classe Pessoa no item anterior foi escrita, qualquer valor pode ser atribuído aos atributos da classe, mesmo que eles sejam inválidos. Há uma maneira de se evitar que isso aconteça: nós podemos encapsular os atributos, ou seja, torna-los privados e oferecer acesso a eles por métodos acessores que fazem a validação dos dados, de forma que apenas dados válidos sejam armazenados. Abaixo, modificamos a classe Pessoa para que só possam ser armazenados telefones com pelo menos oito dígitos. Vamos também encapsular o atributo nome, mesmo que nenhuma validação seja feita: Nas linhas 7 e 8, os atributos nome e telefone foram alterados para privados, o que significa que agora eles só podem ser acessados de dentro da própria classe Pessoa. Para permitir que eles tenham seus valores lidos, foram criados os métodos getters, nas linhas 10 e 18. Estes métodos, quando executados, retornam o valor armazenado no atributo correspondente. Na linha 14 é criado o método setNome(), que recebe como parâmetro um nome e o atribui ao atributo nome. Como tanto o parâmetro quanto o atributo possuem o mesmo nome, o programa não teria como saber qual é o atributo e qual é o parâmetro. Para resolver este problema, utiliza-se a referência this na linha 15. Desta forma, pode-se ler a linha 15 como sendo “o nome deste objeto recebe o valor de nome”. Assim, this.nome é o atributo do objeto enquanto que nome é o parâmetro. Já no método setTelefone() na linha 22, só é feita a atribuição se o valor do parâmetrotelefone tiver ao menos oito dígitos. Esta verificação é feita com o desvio condicional if. Sua sintaxe completa é: A condição pode ser qualquer expressão que tenha como valor final um booleano. Isso significa que a condição pode ser uma simples variável booleana ou uma expressão booleana, que é o resultado de alguma operação lógica. As operações lógicas são: == igual a != diferente de < menor que > maior que <= menor ou igual a >= maior ou igual a ! não (inverte o resultado de uma expressão booleana) Quando se deseja realizar vários testes, pode-se encadear outro if dentro do conjunto de instruções da cláusula else. Mas também pode-se utilizar a estrutura switch-case, a qual tem a sintaxe abaixo: A estrutura switch-case usa como cláusula de teste uma variável que pode ser dos tipos primitivos char, int, short ou byte. Cada cláusula case define um conjunto de instruções que será executado caso seu valor corresponda ao valor da variável de teste. A cláusula default, que é opcional, é executada se nenhuma outra cláusula case foi executada. A classe Pessoa ainda não teve um método construtor definido explicitamente. Método construtor é um método que é executado sempre que um objeto é instanciado. Este método deve possuir exatamente o mesmo nome da classe e não deve apresentar nenhum tipo de retorno, nem mesmo void. Quando não criamos um construtor em uma classe, o próprio compilador cria um para nós, é o construtor padrão. O método construtor padrão é um construtor sem parâmetros e que não executa nenhuma ação em sua implementação. Quando escrevemos “Pessoa p = new Pessoa();”, o termo Pessoa(), na verdade, é o construtor padrão da classe. Nós podemos criar os nossos próprios construtores. Uma vez que a classe tenha um construtor qualquer, o compilador não irá criar o construtor padrão. Caso queiramos um construtor sem parâmetros, devemos declará-lo explicitamente, como do exemplo abaixo: Na linha 28 é declarado o método construtor sem parâmetros. Note que o método deve ter exatamente o mesmo nome da classe e não deve apresentar nenhum tipo de retorno. Na linha 32 é criado um segundo método construtor, desta vez ele recebe como parâmetros o nome e o telefone da pessoa. Em sua implementação, ele faz a atribuição dos valores utilizando a validação de dados por meio dos métodos acessores que já foram criados na classe. A classe TestaPessoa pode ser modificada da seguinte maneira para refletir as mudanças feitas na classe Pessoa: Na linha 7, é instanciado o objeto p1 utilizando o construtor sem parâmetros. Nas linhas 8 e 9, os valores dos atributos agora precisam ser definidos utilizando os métodos acessores. Já não é mais possível acessar os atributos diretamente pois eles foram definidos como privados. Na linha 12, um outro objeto, p2, é instanciado, desta vez utilizando o construtor que recebe como parâmetros os valores do nome e do telefone. Durante a instanciação os valores são armazenados nos atributos e o objeto já está pronto para exibir suas informações. Agora, digamos que queiramos que o programa leia e exiba os dados de dez pessoas. Nós poderíamos simplesmente repetir a lógica do programa anterior dez vezes, mas esta não é a melhor escolha. O computador existe para realizar o trabalho árduo para nós. Sempre que nos pegamos pensando algo como “vou ter que fazer a mesma coisa de novo”, muito provavelmente há algo errado em nosso algoritmo. Sempre há alguma maneira de fazer como que o programa faça a tarefa repetitiva para nós. Abaixo vamos modificar o programa TestePessoa para repetir dez vezes a rotina de receber e exibir os dados de dez pessoas: O método recebePessoa() entre as linhas 7 e 19 recebe do usuário os dados de uma pessoa, instancia um objeto da classe Pessoa com estes dados e o retorna. O método main() entre as linhas 21 e 30 utiliza o método recebePessoa() para receber os dados de dez pessoas. Na linha 25 é iniciado um laço de repetição for para repetir dez vezes a rotina definida nas linhas 26 e 27. O laço for tem três parâmetros: o primeiro é uma definição de uma variável contadora; o segundo é a condição que, se verdadeira, permitirá que o laço seja repetido mais uma vez; o terceiro é a lógica de incremento (ou decremento) da variável contadora. O Java ainda define os laços while e do-while, que possuem a seguinte sintaxe: O laço while inicia testando a condição de parada. Se ela for verdadeira, ele irá executar o conjunto de instruções de repetição. Em algum momento durante a execução, a condição de parada deve ser alterada para falsa, de modo a fazer com que o laço encerre. O laço do-while inicia executando uma vez o conjunto de instruções de repetição. A seguir ele verifica a condição de parada. Se ela estiver verdadeira, o loop reinicia. Novamente, em algum momento a condição de parada deve ser alterada para falsa para que o laço termine. Arrays O programa do exemplo anterior conseguia ler e exibir os dados de dez pessoas, mas não era capaz de armazená-los. Para tanto, deveríamos ter criado dez variáveis. Entretanto, criando dez variáveis, seria muito difícil continuar utilizando o laço for para automatizar o processo. Para resolver este problema, podemos utilizar um array (vetor). Um array é um conjunto de variáveis de mesmo tipo. Vejamos como o programa pode ser modificado para utilizar um array de Pessoa: Na linha 22 é declarada a variável p, que desta vez é um array de objetos da classe Pessoa. Isto é indicado pelo símbolo []. Um array deve ser instanciado como se fosse um objeto, o que é feito na linha 23 com o operador new, seguido do tipo de dado de cada elemento do vetor (Pessoa) e da quantidade de elementos do vetor ([10]). Na linha 25 é declarada a variável contadora que será usada nos dois laços for a seguir. Na linha 27 é iniciado o laço de leitura das dez pessoas. Na linha 28, cada pessoa é lida pelo método recebePessoa() e é armazenada no elemento de ordem i do vetor p (p[i]). Note que os índices de vetores em Java iniciam com o valor 0. Na linha 31 é iniciado o laço de apresentação das dez pessoas. Cada elemento do vetor p é um objeto da classe Pessoa e, por isso, possui o método apresente(). Assim, na linha 32, cada elemento do vetor tem o seu método apresente() chamado. Coleções O uso de arrays foi útil para armazenar uma quantidade definida de objetos da classe Pessoa. Entretanto, para utilizar arrays deve-se saber de antemão quantos elementos serão necessários para se criar o vetor. Quando não se sabe quantos elementos serão necessários, podemos utilizar estruturas dinâmicas de armazenamento de dados. O Java nos oferece um conjunto de estruturas deste tipo, as coleções. Uma coleção é um conjunto de variáveis semelhante a um array, mas que pode ter o seu tamanho modificado conforme a necessidade. Vamos modificar o programa TestaPessoa para receber uma quantidade indefinida de pessoas. Este programa irá armazenar objetos da classe Pessoa enquanto o usuário desejar: Na linha 24 é declarada a estrutura dinâmica de armazenamento de dados pessoas, que é um ArrayList que irá armazenar elementos da classe Pessoa. Na mesma linha, a estrutura pessoas é instanciada. Por enquanto, a estrutura já existe, mas ainda não possui nenhum elemento dentro dela. Nas linhas 27 a 32 é feita a leitura de uma quantidade não conhecida de pessoas. Na linha 27 é declarada a variável que irá conter a resposta do usuário quando lhe for perguntado se ele deseja continuar cadastrando pessoas. Sua resposta será na forma de um número inteiro, como será visto adiante. Na linha 28 é iniciado o laço do-while, o que significa que o laço será executado ao menos uma vez. Nalinha 29, o método recebePessoa() é executado para ler os dados de uma pessoa. Ele devolve um objeto da classe Pessoa, este objeto é, então, passado como parâmetro para o método add() do ArrayList pessoas. Este método adiciona seu parâmetro como um novo elemento do vetor dinâmico pessoas. Na linha 30 é usado o método showConfirmDialog da classe JOptionPane. Este método exibe uma caixa de diálogo onde o usuário pode dar sua resposta por meio de botões. A versão do método utilizada pede quatro parâmetros: o primeiro é a janela pai (continuamos usando nenhuma); o segundo é a pergunta que será exibida para o usuário; o terceiro é o título da caixa de diálogo; o quarto define quais são os botões exibidos. As opções são YES_NO_CANCEL_OPTION e YES_NO_OPTION. Na linha 32, a resposta do usuário é verificada. Para que não tenhamos que decorar qual é o valor de cada resposta, utilizamos uma das constantes disponíveis para a resposta: YES-OPTION, NO_OPTION ou CANCEL_OPTION. Nas linhas 35 a 37 um novo laço é feito, desta vez para exibir os dados de todas as pessoas cadastradas. A linha 35 utiliza uma forma alternativa do laço for. Esta forma recebe dois parâmetros separados por dois pontos (ao invés de ponto e vírgula): O primeiro é uma definição de cada elemento da estrutura definida pelo segundo parâmetro. Esta linha pode ser lida como “para cada Pessoa p em pessoas”. O segundo parâmetro pode ser uma coleção ou um vetor. Este laço, então, irá percorrer todos os elementos dentro de pessoas, irá chamar cada um de p e irá executar a linha 36, onde é chamado o método apresenta() de p. Tratamento de exceções Se você esteve testando os programas discutidos até aqui, deve ter encontrando algumas situações em que o programa parou abruptamente devido a alguma situação inesperada. Por exemplo, em qualquer um dos programas que cadastra o telefone de uma Pessoa, se o usuário digitar uma letra (ou nada) no lugar de um número inteiro quando lhe é pedido para digitar o número de um telefone, o programa é encerrado informando uma mensagem de erro. Experimente fazer isso agora e observe a mensagem de erro que é apresentada na janela do Console. Na primeira linha da mensagem de erro é dito que ocorreu uma exceção do tipo NumberFormatException (em inglês). Isso indica que foi tentada a conversão para inteiro de um String que não representava um número inteiro. Nós podemos modificar o programa para que ele perceba esta exceção e peça ao usuário para inserir novamente o número de telefone até que um número válido seja inserido. Isto é feito com o tratamento de exceções, que é composto de blocos try-catch ou try-catch-finally. No bloco try é implementado o código que pode provocar uma exceção. No bloco catch é implementado o código que será executado caso uma exceção ocorra. No bloco opcional finally é implementado o código que deve ser executado tendo ocorrido uma exceção ou não. Ele é útil para fechar recursos que estavam sendo usados no bloco try, como arquivos, conexões com bancos de dados, conexões de rede, etc. Vamos modificar o método recebePessoa() da classe TestaPessoa para que ele se recupere de uma entrada de dados errada no número do telefone. O trecho que recebe o número de telefone de uma pessoa agora está entre as linhas 19 e 29. Na linha 19 é definida uma variável booleana que é verdadeira enquanto o usuário precisa inserir um número de telefone válido. Na linha 20 é iniciado o laço que será repetido enquanto o usuário insere caracteres inválidos para um número de telefone. Na linha 21 é iniciado o bloco try, que é o trecho de código que pode causar uma exceção. A exceção de formatação numérica pode ocorrer na linha 22, quando o método parseInt() tenta converter um String inválido para um número inteiro. Se a conversão for bem sucedida, a linha 24 é executada e armazena o valor false na variável repete. Caso uma exceção de formatação numérica tenha sido lançada no bloco try, ela será capturada pelo bloco catch na linha 25. Na linha 26 é exibida uma mensagem informativa ao usuário e a variável repete não terá seu valor alterado. Na linha 29 o laço do-while decide se ele deve reiterar ou não, dependendo do valor armazenado pela variável repete. Ela contém o valor true se ocorreu uma exceção, false caso contrário. As principais exceções são: • ArithmeticException: erro aritmético, como uma divisão por zero; • ArrayIndexOutOfBoundsException: foi acessado um índice de vetor fora do seu limite; • NullPointerException: foi acessado um objeto que ainda não foi instanciado; • NumberFormatException: foi tentada a conversão de um String em um formato numérico inválido. Também é possível fazer o tratamento de mais de uma exceção em um mesmo bloco try: Nas linhas 16 e 18 é feita a conversão da entrada de dados do usuário para int, o que pode causar uma exceção NumberFormatException, a qual é tratada pela cláusula catch da linha 23. Na linha 20 é feita a divisão de dois números inteiros, o que pode causar uma exceção ArithmeticException, a qual é tratada pela cláusula catch da linha 26. Programação orientada a objetos No item anterior nós conhecemos a tecnologia Java e vimos, de maneira bastante superficial, que esta tecnologia é orientada a objetos. Nesta unidade, vamos abordar em maiores detalhes como o Java implementa os recursos que tornam o paradigma orientado a objetos o principal paradigma de desenvolvimento de sistemas. O aspecto mais importante da orientação a objetos é o polimorfismo. O termo polimorfismo origina-se do grego e significa muitas formas (poli = muitas, morphos = formas). As principais manifestações deste recurso são a sobrecarga, a sobrescrita e o polimorfismo de objetos. Sobrecarga A sobrecarga de métodos já foi vista rapidamente na unidade anterior, quando abordamos os métodos construtores de uma classe. Naquela ocasião, apenas dissemos que uma classe pode apresentar mais de um método construtor, desde que estes tenham uma lista de parâmetros diferentes. A sobrecarga pode ser feita com qualquer método, ou seja, podemos criar quantos métodos queiramos em uma classe com o mesmo nome, desde que eles tenham uma lista de parâmetros que seja diferente em quantidade, em tipo ou em quantidade e tipo. Quando sobrecarregamos um método, estamos dizendo que todos os métodos sobrecarregados realizam a mesma tarefa, mas de maneiras diferentes. Considere a seguinte classe: Note que a classe da Figura 48 define quatro versões do método soma(), cada uma com uma lista de parâmetros diferentes. A primeira sobrecarga recebe dois int; a segunda três int; a terceira dois double e a quarta três float. Note que não faz a menor diferença os nomes dos parâmetros, apenas os seus tipos. Se na classe acima for criada uma nova sobrecarga como mostrado abaixo, o código apresenta erros e não pode ser compilado. O próprio editor de código indica que há um erro na declaração dos métodos nas linhas 5 e 21. Ao se flutuar o mouse por cima do erro (flutuar significa mover o cursor sobre o erro e não clicar) o editor indica que o método soma(int, int) está duplicado, mesmo que os nomes dos parâmetros sejam diferentes. Ao se flutuar o mouse sobre um erro no código, pode ser apresentada uma solução automática para o erro. Cuidado! Apenas execute a solução automática se você entende o que ela está fazendo. Caso não saiba o que o editor está fazendo, a situação pode até piorar! Isto ocorre porque a solução automática realiza alguma mudança no código para que ele possa ser compilado, mas a mudança pode não ser a que você espera. Neste caso, encontrar o erro pode ser ainda mais difícil, pois você já não terá mais uma indicação visual do erro. Herança Um dos recursos maisimportantes da orientação a objetos é a herança. Ela é um mecanismo que permite que uma classe “herde” os atributos e métodos de uma outra classe. No jargão de programação, chamamos a classe que herda de classe filha, enquanto que chamamos a classe da qual a filha herda de classe pai. Outros nomes para classe pai são superclasse e classe geral. A classe filha também pode ser chamada de subclasse e classe especializada. Considere a classe Pessoa vista na unidade I. Ela é útil para armazenar os dados de uma pessoa genérica. Agora considere que desejamos armazenar os dados de um aluno. Muitos dos elementos da classe Aluno serão os mesmos da classe Pessoa. Sempre que passar pela sua cabeça a sensação de que algo que você está programando é igual a algo que já programou, tenha certeza de que há uma maneira melhor de realizar a mesma tarefa. Em orientação a objetos, sempre que um programador repete um código, ele está errando! Não se repete código em orientação a objetos! Ao se criar a classe Aluno, pode-se aproveitar o que já foi feito na classe Pessoa, herdando da classe Pessoa. A partir daí, a classe Aluno já possui todos os atributos e métodos da classe Pessoa. Ela só precisa declarar atributos e métodos que são específicos de um aluno. Na linha 3, a classe Aluno é declarada com a palavra reservada extends, que indica que a classe herda da classe Pessoa. A partir daqui, a classe já possui todos os atributos e métodos da classe Pessoa. Note que só foram declarados os atributos e métodos que pertencem apenas a um aluno. Na linha 12, o método setRa() agora faz consistência do seu parâmetro, para ter certeza de que ele não é um String vazio. Entretanto, não se pode escrever a comparação ra != “”, pois os operadores lógicos não funcionam como se espera quando os operandos são objetos, apenas quando eles são de tipos primitivos. Quando a comparação é feita com objetos, como é o caso de Strings, utilize o método equals(), que retorna verdadeiro se o objeto que executa o método tem o mesmo conteúdo que o objeto no parâmetro do método. Na linha 15, caso o parâmetro recebido pelo método não seja válido, o método irá lança uma exceção do tipo IllegalArgumentException com a mensagem de erro “RA inválido”. Desta forma, o método que tentou atribuir o RA inválido pode tratar a exceção e tentar obter um valor válido para o RA. É uma boa prática de programação lançar uma exceção quando alguma rotina de validação de dados não for bem sucedida. Modifique a classe Pessoa para que ela se comporte desta maneira. A partir de agora, pode-se instanciar um objeto da classe Aluno, atribuir valores ao seu nome, telefone e RA e executar o método apresente(). Sobrescrita Note que o método apresente() apenas informa o nome e o telefone do aluno. Desta forma, ele deve ser modificado. Nós podemos sobrescrever, ou seja, modificar o método na classe filha para que ele se comporte da maneira desejada. Esta é mais uma modalidade de polimorfismo. O método apresente() na linha 21 possui exatamente a mesma assinatura do método herdado da superclasse, ou seja, ele possui o mesmo nome, os mesmos parâmetros e o mesmo tipo de retorno. Isso caracteriza uma sobrescrita do método. Note também que, na linha 23, não podemos mais acessar diretamente os atributos nome e telefone, pois ambos são privados na classe Pessoa. Por isso, fazemos o acesso por meio de seus métodos acessores. Nas linhas 27 a 30 é definido o construtor da classe que recebe três parâmetros, o nome, o telefone e o Ra. Na linha 28 poderíamos ter feito a atribuição dos valores do nome e telefone diretamente usando os métodos acessores dos atributos. Mas a classe Pessoa já realiza esta tarefa em um de seus construtores. Nós podemos invocar diretamente tal construtor de dentro de nosso construtor, na primeira linha, usando a palavra reservada super, que referencia a superclasse. Esta referência funciona de maneira análoga à referência this, mas enquanto this referencia o próprio objeto, super referencia a superclasse. O programa abaixo ilustra a instanciação e uso de um objeto da classe Aluno. Polimorfismo O polimorfismo de classes é o aspecto mais importante da orientação a objetos. Com este recurso, quando bem utilizado, podemos criar soluções simples e eficientes para problemas que teriam soluções grandes, trabalhosas e ineficientes se fosse utilizado apenas o paradigma estruturado de programação. O polimorfismo de classes é um recurso que permite que um objeto de uma classe seja tratado como se fosse de outra classe. Para que isto seja possível, ambas as classes devem estar na mesma estrutura hierárquica e, além disso, uma delas deve ser um ancestral da outra. Considere o seguinte exemplo: Uma loja de rua permite a entrada de qualquer pessoa por sua porta principal. A loja não faz distinção se a pessoa que entra é um aluno, um professor, um funcionário, motorista ou qualquer outra especialização de pessoa. Por outro lado, uma escola possui catracas que só permitem a entrada de alunos. Outros tipos de pessoas não têm a sua entrada permitida. Em termos de orientação a objetos, a catraca da escola pode ser considerada como um método que recebe apenas objetos da classe Aluno como parâmetro. Já a entrada da loja é um método que recebe como parâmetro objetos da classe Pessoa. Como Aluno é uma subclasse de Pessoa, é uma especialização, é um tipo de Pessoa, o Aluno também pode ser enviado como parâmetro para a loja por polimorfismo. Nas linhas 7 a 10 é definido o método catracaDaEscola() que recebe como parâmetro um objeto da classe Aluno. Nas linhas 12 a 15 é definido o método entradaDaLoja() que recebe como parâmetros um objeto da classe Pessoa. Por polimorfismo, como Aluno é um tipo de Pessoa, ele também pode ser passado como parâmetro para este método. Nas linhas 17 a 25 é feito o código de teste. Nas linhas 18 e 19 são instanciados objetos das classes Aluno e Pessoa respectivamente. Na linha 21, o aluno é passado para o método catracaDaEscola(). Na linha 22, a pessoa é passada para o método entradaDaLoja(). Já na linha 24 é que ocorre o polimorfismo: o aluno é passado para o método entradaDaLoja(). Mesmo que este método receba como parâmetro um objeto da classe Pessoa, ele aceita o objeto aluno, pois ele é um tipo de Pessoa. Repare que na terceira saída do programa, o aluno Joaquim é tratado como uma pessoa, não como um aluno. Modificadores de acesso Agora que já abordamos os principais aspectos da orientação a objetos, podemos dar mais atenção aos modificadores de acesso. Utilizamos os modificadores de acesso private e public para encapsular atributos, ou seja, protegemos os atributos tornando-os inacessíveis, permitindo o acesso a eles por métodos acessores públicos que fazem a consistência de dados. A orientação a objetos define quatro modificadores de acesso para os membros (atributos e métodos) da classe, os quais são adotados pela linguagem Java. Eles são: • public (público): Qualquer classe que tenha acesso à classe onde o membro está declarado terá acesso ao membro. • protected (protegido): Apenas a própria classe e suas subclasses têm acesso ao membro. Em Java, o modificador protected também permite acesso ao membro por classes do mesmo pacote, mesmo que não sejam subclasses. • padrão (ou pacote): Membros definidos sem nenhuma palavra reservada são de acesso padrão ou pacote, ou seja, a própria classe e qualquer classe do mesmo pacote têm acesso ao membro. • private (privado): Apenas a própria classe onde o membro é declarado tem acesso a ele. É uma boa prática de programação definir todos os membros inicialmente com o modificador mais restritivo (privado). Conforme a necessidadede acesso ao membro vai surgindo, o modificador de acesso deve ser alterado gradativamente na direção do modificador público. Escopo Os membros de uma classe normalmente pertencem aos seus objetos. Por exemplo, o atributo nome da classe Pessoa pertence aos objetos da classe Pessoa. Cada um dos objetos pode ter um valor diferente para o atributo nome. Da mesma maneira, o método apresente() só pode ser chamado de um objeto. Isto é chamado de escopo de objeto. Um membro pode, por outro lado, pertencer à classe, não aos seus objetos, caracterizando o escopo de classe. O escopo de classe é definido pela palavra reservada static. Um método de classe pode ser chamado diretamente da sua classe, não sendo necessário instanciar nenhum objeto da classe. Os métodos showMessageDialog(), showInputDialog() e showConfirmDialog() puderam ser chamados diretamente da classe JOptionPane, sem a necessidade de instanciar um objeto da classe para executar estes métodos. Da mesma forma o método main() também é de classe, porque para executar um programa, a máquina virtual não irá instanciar qualquer objeto, ela irá simplesmente executar o método da classe onde ele foi declarado. Um atributo de escopo de classe terá o mesmo valor para a classe e para todos os seus objetos. Se o seu valor for modificado em qualquer um deles, o valor será modificado em todos. Um método de escopo de objeto é executado em um objeto. Por isso, ele terá acesso a qualquer método e qualquer atributo da classe, sejam eles de qualquer um dos dois escopos. Por outro lado, um método de escopo de classe, quando executado, não tem acesso a qualquer objeto, pois ele só “conhece” a sua classe. Por isso, ele não consegue acessar membros que sejam de escopo de objeto, ele só pode acessar membros de classe como ele próprio. O exemplo a seguir utiliza um atributo de classe para contar quantas vezes ele foi instanciado. Na linha 5 é declarado o atributo contador de escopo de classe, com o valor inicial 0. Nas linhas 7 a 9 é definido o construtor da classe. O construtor é executado toda vez que um objeto da classe é instanciado, e quando isso acontece, o atributo contador é incrementado. Nas linhas 11 a 13 é definido um método acessor para o atributo contador. Nas linhas 9 a 11 a classe Contador é instanciada 100 vezes. Nas linhas seguintes ela é instanciada mais duas vezes. O conteúdo do atributo contador é apresentado nas linhas 15 a 17. Classes abstratas e interfaces Considere que estamos desenvolvendo um programa de desenho por computador. Este programa usa figuras geométricas como o retângulo, o triângulo e o círculo. Uma das funcionalidades deste programa é calcular a área de todas as figuras no desenho. Para aproveitar o polimorfismo, vamos criar uma estrutura hierárquica de classes para modelar as figuras geométricas do sistema. Assim, as classes Retangulo, Triangulo e Circulo serão todas subclasses da classe FiguraGeometrica. A classe FiguraGeometrica irá definir o método calculaArea() que será sobrescrito por todas as subclasses. As sobrescritas do método, então, irão realizar o cálculo correto da área de cada figura. Para que o código não fique muito extenso, não vamos encapsular os atributos. Nas linhas 6 a 8 é definido o método calculaArea(), o qual existe apenas para permitir o polimorfismo. Como não é possível calcular a área de uma figura geométrica genérica, o método retorna um valor negativo para indicar que ele não deve ser usado. O mesmo ocorre com o método getNome() entre as linhas 7 e 9, que irá retornar o nome da figura geométrica. As três subclasses sobrescrevem o método calculaArea() entre as linhas 7 e 9, cada uma com a equação apropriada. Note que na classe Circulo, o cálculo é feito com a ajuda da classe Math, a qual define uma constante com o valor de π (pi) e o método pow(), o qual realiza a potência de raio elevado a 2. O método getNome() entre as linhas 11 e 13 retorna o nome da figura geométrica apropriada. As subclasses também definem seus construtores, os quais já fazem a atribuição de valores aos seus atributos. A seguir, o programa de teste: Entre as linhas 7 e 11, o método formataFigura() recebe como parâmetro um objeto da classe FiguraGeometrica e de qualquer uma de suas especializações. Na linha 9 é feita a formatação de um String com o nome da figura e sua área. No método main(), entre as linhas 13 e 27, é feita a rotina de teste. Na linha 14 é declarado e instanciado um vetor de três posições de FiguraGeometrica. Nas linhas 16 a 18, cada uma das posições recebe um objeto de uma de cada das classes de figuras geométricas especializadas. Neste ponto já ocorre polimorfismo. Entre as linhas 22 e 24 é feito um laço for aprimorado para percorrer o vetor e obter a sua representação em texto. Cada representação é anexada à variável mensagem, com o caracter de escape \n, que representa uma quebra de linha. Na linha 26 é exibida a caixa de diálogo. O programa acima realiza a sua tarefa, mas não muito bem. O problema está na classe FiguraGeometrica, a qual define métodos que não pode implementar e, por isso, faz uma implementação propositalmente errada. A orientação a objetos oferece uma alternativa: nós podemos declarar métodos abstratos, ou seja, métodos sem implementação. Quando se criam métodos abstratos em uma classe, esta classe também deve ser abstrata. Assim, podemos modificar a classe FiguraGeometrica da seguinte forma e o restante do programa pode permanecer inalterado. Nas linhas 5 e 7, as declarações dos métodos são agora de métodos abstratos. Por isso, eles agora não possuem implementação e são terminados com ponto-e-vírgula. A classe agora também deve ser abstrata. Uma classe abstrata pode definir métodos e atributos concretos (não abstratos). Mas por conter métodos sem implementação, uma classe abstrata não pode ser instanciada. Ela só pode ser usada com polimorfismo, como foi feito neste exemplo. Além disso, as classes que herdam de uma classe abstrata devem implementar todos os seus métodos abstratos se quiserem ser classes concretas, caso contrário, elas também serão abstratas. Isto é útil para garantir que as subclasses implementaram todos os métodos que deveriam ser sobrescritos, caso eles fossem concretos, como na primeira versão deste exemplo. Considere agora uma classe abstrata que só declara métodos abstratos (como no exemplo acima). Uma classe desse tipo pode ser melhor modelada como uma interface. Uma interface é uma classe abstrata que só define métodos abstratos. Uma classe que realize uma interface (não se usa o termo herdar de uma interface) deve implementar todos os seus métodos. Assim, poderíamos ter: Na linha 3 estamos declarando uma interface, não mais uma classe. Como não se pode herdar de uma interface, apenas realiza-la, as classes Retangulo, Triangulo e Circulo devem ser alteradas, trocando a palavra reservada extends por implements: Interfaces Gráficas As interfaces gráficas em Java são normalmente criadas utilizando as bibliotecas AWT e SWING. A biblioteca AWT foi a primeira a ser desenvolvida e diversos de seus componentes continuam em uso até hoje. Entretanto, alguns componentes dessa biblioteca utilizam recursos do ambiente gráfico no qual o aplicativo esteja sendo executado (como o Windows, o Linux e o MacOS). Isso faz com que aplicativos que utilizem estes componentes do AWT tenham comportamentos ligeiramente diferentes em cada plataforma. Para resolver este problema, a biblioteca SWING foi desenvolvida para oferecer componentes gráficos que não dependem do ambiente de execução. Assim, aplicativos Java com interfaces gráficas costumam usar essas duas bibliotecas.Há duas maneiras principais para se escrever uma interface gráfica em Java. A primeira é escrever o código todo no editor de códigos, como vínhamos fazendo até agora. Inicialmente isso pode parecer difícil, mas com um pouco de experiência e conhecimento das bibliotecas você vai acabar percebendo que isso não é tão difícil. Ainda bem, pois há situações em que devemos escrever manualmente o código de uma interface gráfica. A outra maneira de se criar uma interface gráfica é usando algum editor de GUI (Graphical User Inteface - Interface Gráfica com o Usuário). Enquanto o Eclipse normalmente não vem equipado com uma, é possível instalar algum plug-in que ofereça esta funcionalidade. Existem vários na loja de plug-ins do Eclipse. Já o NetBeans é equipado com um editor de interfaces gráficas. Por isso, para este módulo vamos utilizar esta IDE. Um aplicativo simples com interface gráfica Vamos criar um pequeno aplicativo separado em classes de estereótipos entity, boundary e controller com acoplamento por interface. Este aplicativo terá a arquitetura representada pelo diagrama de classes abaixo: Vamos criar três pacotes, o pacote entidades, controle e fronteira. A interface será criada no pacote controle. Vamos iniciar codificando as classes de entidade Endereco e Pessoa, as quais são as mesmas já usadas no último laboratório. Elas apenas foram refatoradas ao serem declaradas no pacote entidades: A seguir, vamos codificar a interface ICadastroPessoa, a qual define qual é o método que a interface gráfica irá chamar quando for pressionado o botão para o cadastro de uma nova pessoa: Uma interface pode ser entendida como um cardápio de métodos disponíveis em uma determinada classe. Quando uma classe realiza uma interface, temos certeza de que a classe oferece os serviços definidos pelos métodos declarados na interface. No nosso exemplo, a interface gráfica irá utilizar o método cadastraPessoa() definido acima e que será implementado por outra classe. O método cadastraPessoa() recebe como parâmetros um objeto que representa a janela ativa do usuário (pai) e um objeto da classe Pessoa com os dados a serem cadastrados. Abaixo, vamos criar a classe CadastroPessoa, a qual realiza a interface acima e implementa seu método: Neste momento, vamos usar o editor de GUI incorporado do NetBeans. Note que o mesmo pode ser feito ao se baixar um plug-in editor de GUI para o Eclipse. No pacote fronteira, clique com o botão direito e selecione Novo -> Outros e então selecione Forms GUI Swing -> Form JFrame: Dê o nome TelaCadastroDePessoa à sua classe e clique em Finish. O assistente cria uma nova classe que implementa uma janela. Desta vez, o editor de código também possui uma aba que permite a edição gráfica da janela. Clique na aba Design: A seguir, clique em JLabel na palheta de componentes e adicione o rótulo na janela. Digite “Nome:” em sua propriedade text. Altere o nome do objeto JLabel para lblNome: Repita o processo e crie os rótulos para telefone, rua e número: Insira agora os componentes JTextFields para receber os dados. Não se esqueça de trocar os nomes dos objetos: Insira agora o botão “Cadastrar”: Até este momento, criamos a aparência básica da janela. Vamos preparar a janela para que ela possa executar o método cadastraPessoa da classe CadastroPessoa. Clique na aba Código-Fonte e modifique o código da janela inserindo três novas linhas de importação e um novo atributo. Vamos tornar o construtor sem parâmetros privado para que a janela só possa ser instanciada com um tratador de cadastro.Vamos criar também um novo construtor que já atribui um objeto com o método definido pela interface que criamos: Vamos criar um método que será chamado quando o botão “Cadastrar” for clicado: Volte para o editor de design e dê um clique duplo no botão “Cadastrar”. O editor irá criar um método que será chamado quando o botão for clicado. Inclua na implementação do método btnCadastrarActionPerformed() uma chamada ao método cadastrar() que acabamos de criar: No código fonte, mude o modificador de acesso do método main() para privado, pois não queremos que este método seja mais utilizado: Agora só falta criar o método main() na classe Programa que irá iniciar a janela da maneira correta: Agora o programa pode ser executado: Você pode baixar este aplicativo compilado e o código fonte a partir do GitHub no endereço https://goo.gl/Ww5RGH. Se ao clicar em sua cópia local de AplicativoTresCamadas.jar o aplicativo não for iniciado, abra uma linha de comando na pasta onde o aplicativo foi gravado e execute o comando java -jar AplicativoTresCamadas.jar.
Compartilhar