Baixe o app para aproveitar ainda mais
Prévia do material em texto
Rafael de Moura Moreira PROGRAMAÇÃO ORIENTADA A OBJETOS E-book 4 Neste E-Book: INTRODUÇÃO ����������������������������������������������������������� 3 CLASSES ABSTRATAS ��������������������������������������������4 MÉTODOS ABSTRATOS ������������������������������������������������������������9 INTERFACES �������������������������������������������������������������13 HERANÇA VERSUS IMPLEMENTAÇÃO ���������������������������������18 TRATAMENTO DE EXCEÇÃO ��������������������������������������������������21 CONSIDERAÇÕES FINAIS ����������������������������������� 32 REFERÊNCIAS BIBLIOGRÁFICAS & CONSULTADAS ������������������������������������������������������� 33 2 INTRODUÇÃO A herança e o polimorfismo são conceitos importan- tíssimos que permitem a reutilização de códigos. Em razão dessa característica, neste módulo aprofunda- remos os estudos nesses conceitos ao incorporar ferramentas interessantes do Java a essa equação, como as classes abstratas e as interfaces. Estudaremos, em seguida, de que modo as classes abstratas permitem o planejamento das estruturas de herança mais eficientes, sem a necessidade de implementar métodos que deverão necessariamente ser sobrepostos a posteriori� Nesse sentido, verificaremos que as interfaces per- mitem que diversas classes concordem em oferecer um conjunto de funcionalidades em comum, mesmo que elas não estabeleçam nenhum tipo de herança entre si. Por fim, estudaremos o tratamento da exceção breve- mente, por ser uma maneira inteligente e padronizada de lidar com erros em nosso programa. 3 CLASSES ABSTRATAS Imagine que você esteja desenvolvendo um siste- ma que precisa modelar bichinhos de estimação. Todo bichinho terá um nome, um tutor, um gênero (M ou F) e uma cor. Quem tem um pet em casa sabe que animais de estimação costumam se comunicar bastante, seja por gestos corporais, seja por sons. Porém, cada bichinho se comunica de um jeito. Por exemplo, os cães têm um comportamento; os gatos têm outro; os pássaros, outro e assim sucessi- vamente. Então, você tem a ideia de criar uma clas- se Pet, definindo os atributos dos bichinhos e um método “fala”. Depois disso, deve criar classes para diferentes bichinhos que herdarem da classe Pet e sobreponham o método “fala”. Observe: public class Pet{ private String nome; private String dono; private String cor; private String raca; public Pet(String nome, String dono, String cor, String raca){ this.nome = nome; this.dono = dono; this.cor = cor; this.raca = raca; } public String getNome(){ return this.nome; } 4 public void fala(){ System.out.println(this.nome + " faz barulhos."); } } Tabela 1: Código 1. Arquivo Pet.java Fonte: Elaboração própria. public class Cachorro extends Pet { public Cachorro(String nome, String dono, String cor, String raca){ super(nome, dono, cor, raca); } public void fala(){ System.out.println(this.getNome() + " faz au au."); } } Tabela 2: Código 2. Arquivo Cachorro.java Fonte: Elaboração própria. public class Gato extends Pet { public Gato(String nome, String dono, String cor, String raca){ super(nome, dono, cor, raca); } public void fala(){ System.out.println(this.getNome() + " faz miau."); } } Tabela 3: Código 3. Arquivo Gato.java Fonte: Elaboração própria. 5 É fácil verificar que podemos instanciar livremente objetos das classes Cachorro e Gato, bem como que um método esperando um Pet qualquer não encon- tra dificuldades com qualquer uma dessas classes, pois ambas herdam de Pet e ambas têm os mesmos métodos. É possível ainda instanciar objetos Pet diretamente. Do ponto de vista computacional, não há nenhum impedimento, uma vez que Pet é uma classe e possui atributos e métodos, um construtor etc. E quanto ao ponto de vista lógico, ele faz sentido? Ninguém tem em casa um animal “genérico”, com formato e comportamento indefinidos. As pessoas têm cachorros, gatos, periquitos, hamsters, porqui- nhos-da-índia, papagaios... Se nosso sistema fosse um sistema de cadastro de pacientes de uma clínica veterinária, por exemplo, instanciar diretamente um Pet implicaria criar enti- dades sem nenhuma informação específica, o que é ruim. Diferentes animais sofrem com diferentes doenças, que necessitam de diferentes vacinas, me- dicamentos, dosagens etc. Para esses casos, convém criar computacionalmen- te um bloqueio para que ninguém possa instanciar objetos da classe Pet. O único objetivo da classe Pet é providenciar as características comuns a diversas outras classes, as quais não só podem, como devem ser instanciadas. 6 Com vistas a criar esse bloqueio, podemos tornar nossa classe Pet uma classe abstrata. Uma classe abstrata é uma classe que serve tão-somente para herança, pois instanciar objetos é proibido. Criar uma classe abstrata é, em outras palavras, criar uma clas- se normal, mas com a inserção de “abstract” antes da palavra “class”. Ao declarar a classe Pet como abstrata, os objetos das classes Cachorro e Gato continuam funcionan- do normalmente, pois seguem herdando todos os atributos e métodos, bem como podem continuar usando o super, sobrepor métodos da classe origi- nal, entre outros. A única mudança é, portanto, na própria classe Pet. Desse ponto em diante, não podemos mais usar new Pet(), mas podemos criar funções que recebem objetos Pet, ou seja, ponteiros para Pet. Na prática, sempre passaremos objetos Cachorro ou Gato a ela. public abstract class Pet{ private String nome; private String dono; private String cor; private String raca; public Pet(String nome, String dono, String cor, String raca){ this.nome = nome; this.dono = dono; this.cor = cor; this.raca = raca; } public String getNome(){ 7 return this.nome; } public void fala(){ System.out.println(this.nome + " faz barulhos."); } } Tabela 4: Código 4. Arquivo Pet.java com classe abstrata. Fonte: Elaboração própria. Note que a única diferença entre os códigos 1 e 4 é a palavra abstract. Todo o resto segue como estava. Porém, se em algum ponto você chamar new Pet() em seu programa, terá um erro de compilação. FIQUE ATENTO O papel do construtor é inicializar um objeto recém- -criado. Classes abstratas não podem ser instan- ciadas, o que pode induzir algumas pessoas a concluir que o código 4 não faz sentido: por que a classe abstrata teria um construtor? Pois bem, nós ainda temos herança e “super”. O construtor da nossa classe Pet ainda existe, por- que ele não só pode, como é utilizado pelas sub- classes Cachorro e Gato pelo super. 8 MÉTODOS ABSTRATOS Em nossa classe Pet, criamos um método chamado “fala”, porque esse é um método que todos os her- deiros de Pet deveriam ter. Funções que recebem Pet provavelmente utilizarão esse método, diante desse fato, é importante garantir que todas as subclasses tenham uma. No caso, nossas subclasses Cachorro e Gato sobrepuseram esse método. Caso isso não tivesse ocorrido, elas herdariam o método original da classe Pet. Então, pensemos novamente em nosso problema concreto. Há, pouco, concluímos que não faz senti- do lógico em nossa modelagem instanciar um Pet, porque ninguém tem um bichinho genérico, e sim cachorros, gatos etc. Sabemos que cada bicho possui uma fala diferen- te. Essa é uma característica do nosso problema concreto: saber que diferentes espécies de animal fazem sons diferentes. Não tem como reaproveitar uma forma genérica de comunicação para diferen- tes tipos de animal. Logo, sabemos que todas as classes herdeiras de nossa classe Pet devem criar suas próprias versões de “fala”, sem usar uma “fala” genérica de Pet. Com isso, passamos a ter um problema. Se remo- vermos o método “fala” da classe Pet, ficamos sem nenhuma garantia de que as classes herdeiras terão necessariamentealgum tipo de “fala”. Um programa- dor pode se esquecer de implementar esse método, o 9 que incorrerá em erros. Entretanto, manter esse méto- do não faz sentido, afinal, não está lá para ser usado, por isso deveria ser redefinido em todas as classes herdeiras, e aquela classe jamais será instanciada. Assim, temos como solução os métodos abstratos. Um método abstrato é o que esperamos do método “fala” de nossa classe Pet, ou seja, um método “va- zio” que só está lá para obrigar as classes herdeiras a implementar algo com esse nome. Um método abstrato não pode ser implementado em sua classe. Em vez disso, vamos apenas declarar a assinatura do método junto da palavra “abstract”. Por isso, é preciso colocar a palavra “abstract” entre o modificador de acesso (public/protected/private) e o tipo de retorno da função, assim como o ponto- -e-vírgula, ao invés de abrir chaves. Toda classe herdeira é obrigada a implementar o método. Se criarmos uma classe herdeira de Pet, por exemplo, PorquinhoDaIndia, e dentro dela não hou- ver um método “fala” com o mesmo tipo de retorno previsto na classe Pet, o programa não compila. Dessa maneira, conseguimos garantir o polimorfis- mo, visto que todos os herdeiros de Pet terão um método “fala”, e ao mesmo tempo evitamos os có- digos desnecessários ou redundantes, já que não implementamos um método “inútil” em nossa classe abstrata. 10 public abstract class Pet{ private String nome; private String dono; private String cor; private String raca; public Pet(String nome, String dono, String cor, String raca){ this.nome = nome; this.dono = dono; this.cor = cor; this.raca = raca; } public String getNome(){ return this.nome; } public String getDono(){ return this.dono; } public abstract void fala(); } Tabela 5: Código 5. Arquivo Pet.java com método abstrato. Fonte: Elaboração própria. 11 FIQUE ATENTO Um método abstrato não é feito para ser utilizado, mas para ser implementado pelas classes her- deiras. O que aconteceria, então, se nossa classe Cachorro tivesse um método abstrato? Afinal, po- demos instanciar os objetos dessa classe. Como ela se comportaria se tentássemos chamar o mé- todo abstrato, que por definição é um método não implementado? Essa situação simplesmente não ocorre, porque toda classe que possui pelo menos um método abstrato automaticamente se torna abstrata, mes- mo que você não a tenha declarado como “abstract class”. Portanto, tenha sempre em mente que, se você pretende instanciar objetos de uma classe, ela não pode ter métodos abstratos. 12 INTERFACES Nosso sistema está rodando bem, mas agora gosta- ríamos de acrescentar veterinários. E a classe terá como atributos nome, CRMV do veterinário, valor da consulta e um método para examinar um Pet. public class Veterinario { public String nome; public int crmv; public double preco; public Veterinario(String nome, int crmv, dou- ble preco){ this.nome = nome; this.crmv = crmv; this.preco = preco; } public void consulta(Pet paciente){ System.out.println(this.nome + " examinou " + paciente.getNome()); System.out.println(paciente.getDono() + " deverá pagar R$" + this.preco); } } Tabela 6: Código 6. Arquivo Veterinario.java Fonte: Elaboração própria. Podemos notar que um objeto da classe Veterinario não tem praticamente nada em comum com um Pet. Pensando no mundo real, são duas entidades muito diferentes. Computacionalmente, não estabelecemos nenhum tipo de hierarquia ou herança entre eles. Assim, sua única interação é que um dos métodos 13 de Veterinario interage com um objeto Pet, mas dado que Pet é uma classe abstrata, na prática, Veterinario sempre interage com um objeto herdeiro dela. Porém, queremos que o banco de dados guarde in- formações tanto sobre os animais que frequentam a clínica quanto sobre os veterinários que trabalham na clínica. E queremos que ambos tenham um método padronizado para mostrar sua ficha de cadastro. Nesse ponto, temos o mesmo problema que já enun- ciamos, quando falamos em tirar o método “fala” da classe Pet, ou seja, alguém implementa uma classe para o sistema e pode se esquecer de implementar o método para mostrar a ficha da entidade nova. Reestruturar todo o sistema para fazer Pet e Veterinario herdarem de uma mesma classe abs- trata é algo trabalhoso e que, ainda por cima, não faz sentido lógico. Veterinario e Pet não têm nada em comum, então por que deveriam herdar de uma mesma classe? É precisamente neste ponto que entra a interface. Uma interface lembra um pouco uma classe abstrata, porque possui métodos abstratos, bem como porque todas as classes que implementam essa interface são obrigadas a implementar os métodos. Uma classe pode herdar de apenas uma classe, mas pode implementar várias interfaces simultaneamen- te. Assim, criar uma interface é parecido com criar uma classe. Porém, trocamos a palavra “class” por “interface” e todos os métodos são abstratos. 14 public interface Cadastravel { public abstract void mostraFicha(); } Tabela 7: Código 7. Arquivo Cadastravel.java Fonte: Elaboração própria. Fazer uma classe “assinar o contrato” com a interface é bastante parecido com herdar de uma classe abstrata. Usaremos a palavra “implements”, em vez de “extends”, seguido do nome da interface; ainda, implementaremos todos os métodos previstos na interface. Ao fazer as classes Veterinario e Pet implementarem a interface Cadastravel, elas serão obrigadas a im- plementar o método mostraFicha(), ou teremos erro de compilação. Em contrapartida, agora expandimos seu polimorfismo. Assim, podemos fazer funções que recebem objetos Cadastravel, sendo que essa função pode receber objetos de qualquer classe que implemente a interface Cadastravel. Portanto, nosso sistema de cadastro consegue tra- balhar tanto com Veterinario quanto com Pet. Caso novas classes sejam adicionadas ao sistema, elas também podem ser compatíveis desde que imple- mentem a mesma interface. public abstract class Pet implements Cadastravel { private String nome; private String dono; private String cor; private String raca; public Pet(String nome, String dono, String cor, String raca){ 15 this.nome = nome; this.dono = dono; this.cor = cor; this.raca = raca; } public String getNome(){ return this.nome; } public String getDono(){ return this.dono; } public abstract void fala(); public void mostraFicha(){ System.out.println("Nome: " + this.nome); System.out.println("Dono: " + this.dono); System.out.println("Cor: " + this.cor); System.out.println("Raça: " + this.raca); } } Tabela 8: Código 8. Arquivo Pet.java implementando a in- terface Cadastravel Fonte: Elaboração própria. public class Veterinario implements Cadastravel{ public String nome; public int crmv; public double preco; public Veterinario(String nome, int crmv, dou- ble preco){ this.nome = nome; this.crmv = crmv; this.preco = preco; } 16 public void consulta(Pet paciente){ System.out.println(this.nome + " examinou " + paciente.getNome()); System.out.println(paciente.getDono() + " deverá pagar R$" + this.preco); } public void mostraFicha(){ System.out.println("Nome: " + this.nome); System.out.println("CRMV: " + this.crmv); System.out.println("Valor da consulta: " + this.preco); } } Tabela 9: Código 9. Arquivo Veterinario.java implementando a interface Cadastravel Fonte: Elaboração própria. public class Main { public static void main(String args[]){ Cachorro dog = new Cachorro("Dot", "Brenda", "Malhado", "SRD"); Gato cat = new Gato("Sheldon", "Rafael", "Laranja", "SRD"); Veterinariovet = new Veterinario("Dr. Dogtor", 123456, 150.0); cat.fala(); dog.fala(); verificaFicha(cat); verificaFicha(vet); vet.consulta(cat); } public static void verificaFicha(Cadastravel cad){ System.out.println("Consultando a ficha..."); cad.mostraFicha(); } } Tabela 10: Código 10. Arquivo Main.java com método estáti- co utilizando interface Cadastravel Fonte: Elaboração própria. 17 HERANÇA VERSUS IMPLEMENTAÇÃO Neste ponto, precisamos fazer uma distinção con- ceitual importante: implementar uma interface é di- ferente de herdar. Em uma herança, a classe filha recebe atributos e métodos da classe mãe. Em uma implementação, a classe se compromete a cumprir as especificações previstas na interface em troca de compatibilidade com código projetado para interagir com a interface. Uma classe também pode implementar várias inter- faces ao mesmo tempo, separando seus nomes por vírgula; a herança não. Uma classe pode herdar de apenas uma classe por vez. Por conta disso, muitos programadores usam interfaces para “simular” uma herança múltipla em Java. Na prática, isso não é he- rança, é implementação. Herança de interface Uma classe herda de outra classe e implementa uma ou mais interfaces. Uma classe não herda de uma interface. Porém, uma interface pode herdar de uma ou mais interfaces. A sintaxe, para isso, é a mesma da herança de classes, ou seja, utiliza-se a palavra “extends” seguida do nome da(s) interface(s) mãe(s). Caso haja mais de uma, separa-se os nomes por vírgula. A interface filha terá os mesmos métodos abstra- tos que a(s) mãe(s), podendo ainda incluir métodos novos. Uma classe que implemente a interface filha 18 deve implementar todos os métodos, tanto os herda- dos das interfaces mãe quanto os próprios da filha. Classes abstratas versus interfaces A esta altura, é normal ficar um pouco confuso e, possivelmente, se perguntar: por que existem classe abstrata e interface, se ambas têm métodos abs- tratos que devem ser implementados pelas classes que as herdam/implementam? Interface é só uma gambiarra para simular herança múltipla? A primeira grande diferença é que, no caso da clas- se abstrata, nem tudo é método abstrato. Podemos implementar métodos completos na classe abstrata e as classes filhas podem herdá-los e usá-los. Com isso, ao atualizar um sistema, podemos acrescentar um método novo na classe mãe sem precisar neces- sariamente atualizar as filhas, desde que o método não seja abstrato. Já no caso da interface, qualquer mudança impli- cará automaticamente que todas as classes imple- mentadas também sejam atualizadas, pois devem implementar os métodos. Por consequência, em sistemas que mudam com frequência, é preferível criar uma estrutura de herança de modo que se possa, dentro do possível, fazer o máximo de atualizações nas classes “superiores”, de modo que elas se propagem sozinhas para todas as subclasses. Uma diferença mais abstrata (ou “filosófica”): quando temos uma herança, a ideia que estamos passando 19 é a de que as classes envolvidas são casos particu- lares de uma mesma classe geral. Cachorros e gatos são casos particulares de pets. Não é a mesma ideia com a interface, pois as clas- ses que implementam uma interface não necessa- riamente têm qualquer coisa em comum. Elas po- dem representar entidades radicalmente diferentes, mas concordarem em fornecer uma funcionalidade específica. FIQUE ATENTO Apesar de usar a linguagem Java para exemplificar conceitos, é importante não misturar os conceitos gerais de programação orientada a objeto e limi- tações específicas do Java, ou de qualquer outra linguagem. Herança múltipla é proibida por algumas lingua- gens, como o Java e o C#, mas é permitido por outras, como C++ e Python. Interfaces também não são unanimidade, pois o próprio C++ não as oferece, sendo que sua funcio- nalidade é obtida por meio de outras ferramentas. Feita essa ressalva, várias linguagens orientadas a objeto são bastante semelhantes ao Java, como o C#. 20 TRATAMENTO DE EXCEÇÃO Quando um programa está sendo executado, é nor- mal ocorrer erros. Nem sempre as coisas saem con- forme planejamos, pois podemos tentar acessar um arquivo que não existe, realizar uma conexão com um servidor e a rede estar fora do ar, o usuário pode passar um valor inválido em algum campo de entra- da etc. Todos esses casos podem provocar falhas de exe- cução nos programas, podendo até mesmo fazer o programa ter sua execução interrompida e causar perda de dados para o usuário. Ou pior: imagine que é um sistema crítico, que deve estar sempre disponível. Por isso, é importante prever os possíveis erros e tratá-los. Nem sempre o tratamento ocorre dentro da função ou da classe em que o erro pode ocorrer. Como vários módulos diferentes do programa po- dem utilizar essa função ou classe, talvez diferentes módulos tenham diferentes alternativas. Por exemplo, se um arquivo não existe, talvez um módulo possa simplesmente criar um arquivo em branco. Já outro módulo que apenas lê dados de um arquivo não tem essa opção; logo, precisa do arquivo. Uma técnica bastante difundida para isso era utilizar os retornos de função. Em linguagens como a C, por exemplo, é comum que as funções que, a princípio, não retornariam nada retornem um booleano indi- cando se ocorreu um erro ou não. Em sistemas mais complexos, é normal que seja declarada uma lista de 21 constantes numéricas, cada uma representando um tipo de erro diferente, e as funções retornam essas constantes. O módulo que executou a função fica responsável por testar seu retorno e, a partir dele, realizar o trata- mento adequado para o erro que foi sinalizado. Essa abordagem, no entanto, tem duas desvantagens. A primeira é que, quando chamamos uma função ou método, não somos obrigados a armazenar ou testar seu retorno. É comum, em sistemas do tipo, que programadores se esqueçam de testar o retorno da função, ou até mesmo deixem de fazê-lo propo- sitalmente, por acreditarem erroneamente que os possíveis erros naquela função não terão impacto significativo no sistema. Com isso, o sistema pode tentar seguir a execução em uma situação poten- cialmente problemática, causando os problemas já discutidos. A outra desvantagem é a dificuldade de corrigir poste- riormente o código para entender um erro. O retorno da função é um número dentre vários possíveis, e o significado do número, ou seja, o tipo de erro que a função provocou, está “codificado” no nome da constante. Qualquer tipo de ferramenta de debugging ou log- ging vai mostrar o valor numérico retornado pela função, então o programador perde bastante tempo lendo manualmente as tabelas de constantes de erro ao tentar compreender o fluxo problemático de seu programa. 22 Exceções A solução padronizada para o problema descrito são as exceções. Exceção é um erro que ocorreu no pro- grama. Várias linguagens, como o Java, apresentam meios de representar um erro e comunicá-lo a outros níveis. Assim, se encontrar um problema, o método pode lançar uma exceção. O método que o chamou tem duas opções: (i) pode capturar a exceção e tratá-la; (ii) pode lançá-lo, de modo que o método que chamou esse método receba a exceção, que, por sua vez, também pode tratá-la ou lançá-la ao nível superior. Com “tratar” uma exceção, queremos dizer simples- mente criar um bloco de código com ações que se- jam tomadas quando o erro previsto for encontrado. Lançando exceções Quando implementamos um método, após os pa- rênteses de abrir chaves podemos usar a palavra “throws”, seguida da exceção que acreditamos que aquele método pode provocar. As exceções são definidas por classes também, sen- do que há uma classe genérica “Exception”, e temos várias exceções já prontas que herdam dessa clas- se. Caso o erro ocorra e seja relacionado à exceção prevista, ao invés do programa travar ou encerrar, o método é interrompido, e oJava busca no método que o chamou algum tratamento para aquela exce- ção. Em caso de não encontrar e o método também lançar a exceção, segue para o método que o cha- 23 mou e sucessivamente. Com isso, para de “subir de nível” na pilha de execução quando encontrar um tratamento ou encontrar um método que não trate nem lance a exceção. Nesse caso sim a execução do programa será interrompida por conta do erro. O bloco try/catch A fim de tratar uma possível exceção, precisamos começar pelo comando “try”, o qual abrirá um bloco de comandos. Dito de outra forma, após a palavra “try”, abriremos as chaves, dentro das quais coloca- remos todo o código que pode provocar a exceção. Por exemplo, uma chamada para um método que pode lançar uma exceção. Se qualquer uma daquelas linhas falhar, o bloco “try” é interrompido, e a execu- ção salta para os blocos “catch”. Os blocos “catch” surgem logo após o “try”. Podemos ter quantos blocos “catch” forem necessários. Cada bloco recebe entre parênteses uma exceção. A ideia de criar vários blocos catch é cada um lidar com um tipo diferente de exceção. Um bloco “catch” que simplesmente receba um objeto “Exception” lida com qualquer tipo de exceção. O Java executa, assim, o primeiro bloco “catch” cuja exceção corresponder à exceção que ocorreu no “try”. Finally Imagine que vários erros diferentes possam ocorrer em uma mesma função, e cada um exige tratamento específico. Ao final, existe uma ação comum que pode ser tomada independentemente de ter dado 24 erro ou não, ou de qual erro ocorreu. Por exemplo, imagine que leremos dois números do teclado e re- alizaremos uma divisão entre eles. Pelo menos duas exceções podem ocorrer: ● Se o valor do denominador for 0, teremos uma ArithmeticException na divisão. ● Se o usuário digitar algo que não é número, pode- mos ter uma InputMismatchException. Suponha que, em caso de erro, queremos mostrar uma mensagem amigável explicando qual foi o erro que ocorreu. Porém, independentemente do erro, queremos que a função retorne um resultado mes- mo assim. As mensagens que mostraremos variam conforme o erro. Por isso, faz sentido que cada uma entre em um “catch” diferente. A atribuição de valor é padrão, por isso faz sentido ficar repetindo código? Bem, a resposta é não. É aí que entra o bloco “finally”, que é empregado de- pois de todos os “catch”, e executado independente- mente de qual “catch” foi executado, ou até mesmo quando não ocorrerem exceções. O “finally” é tipicamente utilizado para realizar ope- rações de limpeza após uma possível operação ar- riscada, como fechar recursos (um arquivo aberto, por exemplo), reiniciar o valor de variáveis, limpar ponteiros etc. Vamos verificar na prática uma imple- mentação de tratamento de exceção: 25 import java.util.*; public class Main { public static void main(String args[]) { double resultado = 0; // Vamos executar linhas de código com possíveis problemas... try { resultado = fazDivisao(); } // Tratando os erros... catch (ArithmeticException e) { System.out.println("Você tentou dividir por zero!"); resultado = -1; } catch (InputMismatchException e) { System.out.println("Número inválido!"); resultado = -1; } // Já fizemos os tratamentos diferenciados nos catch... // Agora vem o comum a todos: finally { System.out.println("O resultado da di- visão foi: " + resultado); } } // A função não irá tratar essas duas exceções. // Ela irá jogar para ser tratada um nível acima. public static double fazDivisao() throws InputMismatchException, ArithmeticException { int numero1, numero2; Scanner scan = new Scanner(System.in); 26 // Nas próximas linhas: risco de InputMismatchException // Usuário pode digitar algo que não é int System.out.println("Digite o numerador: "); numero1 = scan.nextInt(); System.out.println("Digite o denominador: "); numero2 = scan.nextInt(); scan.close(); // próxima linha: risco de ArithmeticException // se o usuário fizer numero2 = 0 return (double)numero1/numero2; } } Tabela 11: Código 11. Tratamento de exceção de uma fun- ção com divisão e leitura de valores pelo teclado. Fonte: Elaboração própria. Nesse exemplo, o desenvolvedor já sabia da pos- sibilidade de ocorrerem exceções ao fazer leitura de inteiros pelo teclado e ao fazer a divisão, mas mesmo assim optou por não tratá-los. O método “fa- zDivisao” simplesmente joga as possíveis exceções para o nível superior, então o nível superior deve se responsabilizar por tratá-las ou repassá-las também. No nível superior (função main), note que a chamada para a função está dentro de um “try”. Se você execu- tar o programa e digitar valores bem-comportados, não há problema. A função executa até o final, e o programa salta do “try” direto para o “finally”. Mas caso faça algo indevido, como digitar “abc” para um dos números ou 0 para o denominador, a função não completa a execução. O “catch” correspondente ao 27 erro provocado, que é executado, em seguida, o pro- grama segue para o “finally”. Criando o próprio tipo de exceção Não precisamos utilizar apenas as exceções prontas do Java. Podemos criar nossas próprias exceções e implementar as verificações personalizadas de certas condições, utilizando a palavra “throw” para ordenar a interrupção da execução do método e o lançamento da sua exceção. A exceção personalizada pode herdar de uma das várias exceções existentes do Java, o que pode ser útil caso alguma função tenha previsto uma dessas exceções, mas não a sua em particular. Por exemplo, se a sua exceção se refere a uma falha específica de conta, ela pode ser herdeira da ArithmeticException. Caso nenhuma opção existente seja uma boa classe- -base para ela, pode simplesmente herdar da classe genérica Exception. Pode-se, inclusive, personalizar uma mensagem de erro para ela, e é essa a mensagem exibida caso o programa eventualmente dê erro sem tratar a exceção. Vamos modificar o exemplo anterior. Suponha que, por algum motivo, nossa função não deva trabalhar com números negativos. Portanto, criamos uma exceção específica de número negativo. A String passada no construtor é a mensagem de erro per- sonalizada que aparece, caso o programa dê erro sem ser tratado. 28 public class ValorNegativo extends Exception { public ValorNegativo(String mensagem) { super(mensagem); } } Tabela 12: Código 12. Arquivo ValorNegativo.java Fonte: Elaboração própria. import java.util.*; public class Main { public static void main(String args[]) { double resultado = 0; // Vamos executar linhas de código com possíveis problemas... try { resultado = fazDivisao(); } // Tratando os erros... catch (ArithmeticException e) { System.out.println("Você tentou dividir por zero!"); resultado = -1; } catch (InputMismatchException e) { System.out.println("Número inválido!"); resultado = -1; } // Tratando nossa exceção personalizada: catch (ValorNegativo e) { System.out.println("Valor negativo."); } // Já fizemos os tratamentos diferenciados nos catch... // Agora vem o comum a todos: finally { System.out.println("O resultado da di- visão foi: " + resultado); 29 } } // Nossa exceção personalizada é lançada pela função: public static double fazDivisao() thro- ws InputMismatchException, ArithmeticException, ValorNegativo { int numero1, numero2; Scanner scan = new Scanner(System.in); // Nas próximas linhas: risco de InputMismatchException // Usuário pode digitaralgo que não é int System.out.println("Digite o numerador: "); numero1 = scan.nextInt(); System.out.println("Digite o denominador: "); numero2 = scan.nextInt(); scan.close(); // lançando nossa exceção personalizada: if (numero1 < 0 || numero2 < 0) { throw new ValorNegativo("Erro! Você não deveria digitar valores negativos!"); } // próxima linha: risco de ArithmeticException // se o usuário fizer numero2 = 0 return (double)numero1/numero2; } } Tabela 13: Código 13. Exemplo do código 11 tratando a exceção ValorNegativo. Essa exceção se comporta como qualquer uma das exceções padrão do Java: o método que não a trata informa que vai jogá-la pelo “throws” em sua decla- 30 ração. O método que se propõe a tratá-la tem um bloco “catch” especial para ela. A mensagem interna aparece, caso dê errado, para os desenvolvedores sa- berem qual foi o erro e, com isso, possam rastreá-lo. SAIBA MAIS A documentação oficial do Java disponibilizada pela Oracle contém a documentação da classe “Exception” e de todas as suas subclasses, as quais são pré-definidas quando da instalação do Java. Saiba mais em: https://docs.oracle.com/ja- vase/8/docs/api/java/lang/Exception.html� 31 https://docs.oracle.com/javase/8/docs/api/java/lang/Exception.html https://docs.oracle.com/javase/8/docs/api/java/lang/Exception.html CONSIDERAÇÕES FINAIS Iniciamos este módulo expandindo a forma com que lidamos com a herança por meio de classes e mé- todos abstratos. Com isso, pudemos verificar que é possível impedir certas classes de serem instan- ciadas, caso não faça sentido lógico no programa, bem como que somos capazes de exigir que todas as classes herdeiras necessariamente implementem um certo método, sem precisar herdar alguma versão já pronta dele. Em seguida, estudamos novas possibilidades de po- limorfismo sem herança com a ajuda das interfaces. As interfaces permitem que diferentes classes que não tenham qualquer tipo de relação de herança se comprometam a implementar os mesmos métodos, permitindo que façamos polimorfismo entre objetos de todas essas classes. Por fim, aprendemos a utilizar as exceções em Java visando a transmitir as mensagens sobre os possí- veis erros entre os diferentes métodos do programa, de modo que eles sejam tratados adequadamente e evitem que o programa falhe. 32 Referências Bibliográficas & Consultadas ASCÊNCIO, A. F. G.; CAMPOS, E. A. V; Fundamentos da programação de computadores. 2.ed. São Paulo: Pearson Prentice Hall, 2007 [Biblioteca Virtual]. DEITEL, P.; DEITEL, H. Java: como programar. 10. ed. São Paulo: Pearson Education do Brasil, 2017 [Biblioteca Virtual]. DOWNEY, A. B. Pense em Python. 2. ed. São Paulo: Editora Novatec, 2016. ELLIOTT, E. Composing Software: An exploration of functional programming and object composition in JavaScript. São Francisco: [s.n.], 2018. HORSTMANN, C. S; CORNELL, G. Core Java 2. 8. ed. São Paulo: Pearson Prentice Hall, 2010. MELO, A. C. V.; SILVA, F. S. C. Princípios de linguagem de programação. São Paulo: Blücher, 2003 [Minha Biblioteca]. SANTOS, R; Introdução à programação orientada a objetos usando Java. 2. ed. Rio de Janeiro: Elsevier, 2013. SCHILDT, H. Java para iniciantes: crie, compile e execute programas Java rapidamente. 6. ed. Porto Alegre: Bookman, 2015 [Minha Biblioteca]. SOMMERVILLE, I. Engenharia de software. 10. ed. São Paulo: Pearson Education do Brasil, 2018 [Biblioteca Virtual]. _GoBack INTRODUÇÃO CLASSES ABSTRATAS MÉTODOS ABSTRATOS INTERFACES HERANÇA VERSUS IMPLEMENTAÇÃO TRATAMENTO DE EXCEÇÃO CONSIDERAÇÕES FINAIS Referências Bibliográficas & Consultadas
Compartilhar