Comentários

0%

Não pode faltar

TRATAMENTO DE EXCEÇÕES E USO DE CLASSES ABSTRATAS

Jesimar da Silva Arantes

Construção de aplicações com suporte a eventuais falhas

O tratamento de exceções é extremamente útil para a criação de um software mais robusto a falhas, pois pode dar soluções mais elegantes a uma série de problemas.

Fonte: Shutterstock.

Deseja ouvir este material?

Áudio disponível no material digital.

Convite ao estudo

Prezado estudante, bem-vindo à unidade do livro Linguagem Orientada a Objetos. Nesta unidade, vamos estudar alguns importantes tópicos dentro do desenvolvimento orientado a objetos (OO), bem como estabelecer as ideias por trás das exceções, asserções, classes abstratas, da implementação de interfaces e do desenvolvimento de interfaces gráficas.

Após a leitura desta unidade, espera-se que o leitor seja capaz de fazer os primeiros tratamentos de exceção, tenha aprendido a lançar exceções, criar suas próprias exceções, declarar assertivas, criar classes abstratas, definir métodos abstratos, criar interfaces, implementar interfaces e desenvolver aplicações com interfaces gráficas. 

Ao longo do capítulo, será incentivada a utilização da plataforma de compartilhamento de código GitHub. O livro apresentará diversos exemplos práticos que o auxiliarão, de forma direta, na compreensão da teoria, e as capacidades de reflexão, modelagem e abstração também serão treinadas ao longo deste livro.

Esta unidade, a fim de ajudá-lo na compreensão da OO na linguagem Java, encontra-se dividida nas seguintes seções: a Seção 3.1, em que serão abordados os conceitos por trás do tratamento de exceção, assertivas e classes abstratas; a Seção 3.2, em que trataremos do conceito de interfaces e de exemplos de como implementá-las; e, por fim, a Seção 3.3, em que serão mostrados os passos necessários para a criação de aplicações gráficas na linguagem Java.

Pelo fato desta unidade ser prática, convido você a implementar as atividades aqui propostas. Lembre-se de que a prática leva à perfeição. 

Bons estudos!

PRATICAR PARA APRENDER

Caro aluno, bem-vindo à primeira seção da terceira unidade dos estudos sobre linguagem orientada a objetos. 

Qual desenvolvedor nunca se perguntou como construir uma aplicação que dê um maior suporte a eventuais falhas? Acredito que todo desenvolvedor já ficou chateado por ver a sua aplicação travar e fechar. No geral, não desejamos ver as nossas aplicações travarem e fecharem simplesmente por termos digitado um valor literal em vez de um valor numérico. Pois bem, nesta seção, estudaremos o tratamento de exceções, que dará soluções mais elegantes a uma série de problemas. Saber realizar o tratamento de exceção é extremamente útil quando se deseja criar um software mais robusto a falhas. 

Nesta seção, estudaremos, também, como fazer a entrada de dados por meio de passagem de valores pela linha de comando, a leitura de dados pelo teclado e a compilação e execução de códigos e Java pelo terminal de comandos. Além disso, trabalharemos com as assertivas e criaremos classes abstratas que auxiliam na reusabilidade de código e facilitam a manutenção de aplicações.

Como forma a contextualizar sua aprendizagem, tenha em mente a startup em que você foi contratado. Mais uma vez, o seu chefe deseja dar continuidade à modelagem do robô que você iniciou na primeira seção da primeira unidade. Ele acredita que a modelagem desenvolvida pode ser melhorada ao serem incorporados novos recursos que embasarão, cada vez mais, o simulador de robô que você deve construir. Dessa maneira, ele pediu a você que fizesse uma leitura do teclado do computador para movimentar o robô, e percebendo que não fez um tratamento de exceção em seu código, solicitou que estudasse esse assunto e melhorasse a sua implementação, deixando-a mais tolerante a falhas. Além disso, ele também lhe pediu para criar uma classe robô abstrata que modelasse a ideia da entidade robô, pois viu uma classe que você havia criado, chamada CaixaIdeia, e percebeu que ela devia ser do tipo abstrata e não concreta. 

Diante do desafio que lhe foi apresentado, como você fará a leitura de dados do teclado e o tratamento de exceção que lhe foi pedido? O que é tratamento de exceção? Como você irá criar essas classes abstratas? O que é uma classe abstrata e uma classe concreta? Esta seção irá auxiliá-lo na resposta de tais perguntas.

Muito bem, agora que você foi apresentado à sua nova situação-problema, estude esta seção e compreenda como a linguagem Java trata exceções e como ela faz a modelagem de classes abstratas. Esses conceitos são fundamentais na OO e trarão uma maior sofisticação na construção do simulador de robô que você deve fazer. Vamos juntos compreender esses conceitos para poder resolver esse desafio?

Bom estudo!

CONCEITO-CHAVE

A linguagem Java possui suporte a alguns recursos muito importantes, que são tratamento de exceção, assertivas e classes abstratas. Esses são alguns dos conceitos que serão abordados nesta seção, porém, gostaríamos, em primeiro lugar, de explicar um pouco sobre como interagir com uma aplicação.

Entrada de dados ou leitura de valores em Java

Até este momento, você ainda não aprendeu nenhuma forma de entrada de dados ou leitura de valores em Java. Existem duas formas principais para se interagir com uma aplicação; a primeira delas se dá passando-se argumentos no momento da execução do programa, já a segunda se dá por meio da leitura de valores em tempo real. Estudaremos, a seguir, essas duas formas.

Passagem de argumentos via linha de comando

A forma de interação por meio da passagem de argumentos via linha de comando é similar à linguagem C. A linguagem Java possui um ponto de entrada da aplicação com a seguinte assinatura: public static void main(String[] args). O argumento args é, na verdade, um vetor de Strings, ou seja, pode passar quantos argumentos quiser, e a aplicação Java poderá utilizá-los. Analise o Código 3.1 a seguir para entender como isso funciona.

Código 3.1 | Implementação básica para passagem de argumentos via linha de comando
public class ArgsLinhaDeComandoBasico {
    public static void main(String[] args) {
        System.out.printf("qtd de argumentos = %d%n", args.length);
        for (int i = 0; i < args.length; i++) {
            System.out.printf("\targs[%d] = %s%n", i, args[i]);
        }
    }
}
Fonte: elaborado pelo autor.

O Código 3.1 apresenta cinco pequenas novidades, que são:

Analise o Quadro 3.1 a seguir para aprender/relembrar as principais sequências de escape presentes na linguagem Java.

Quadro 3.1 | Síntese de sequências de escape da linguagem Java
Sequência de Escape
Descrição
Exemplo de utilização
\n Insere nova linha. System.out.print("Introdução\na\nProgramação\ncom\nJava");
\t Insere tabulação na horizontal. System.out.print("Col A\tCol B\tCol C\tCol D");
\\ Insere barra invertida. System.out.print("C:\\Windows\\system32");
\” Insere aspa dupla. System.out.print("Nome do livro \"Dom Quixote\" de Miguel de Cervantes");
\r Realiza retorno do carro. System.out.print("Texto Não Mostrado \rEsse Texto Aparece\n");
Fonte: elaborado pelo autor.

Analise o Quadro 3.2 a seguir para aprender/relembrar os principais especificadores de formato presentes na linguagem Java.

Quadro 3.2 | Síntese de especificadores de formato na linguagem Java
Especificador
Descrição
Exemplo de Utilização 
Saída
%d Valor inteiro em decimal com sinal (pode ser usado para byte, short, int e long). System.out.print("%d", 127); 127
%o Valor inteiro em octal com sinal. System.out.print("%o", 127); 177
%x Valor inteiro em hexadecimal com sinal (minúsculo). System.out.printf("%x", 127); 7f
%X Valor inteiro em hexadecimal com sinal (maiúsculo). System.out.printf("%X", 127); 7F
%f Valor real (float ou double). System.out.printf("%f", 3.141592); 3,141592
%e Valor real (notação exponencial) (minúsculo). System.out.printf("%e", 3.14); 3,14e+00
%E Valor real (notação exponencial) (maiúsculo). System.out.printf("%E", 3.14); 3,14E+00
%X Valor inteiro em hexadecimal com sinal (maiúsculo). System.out.printf("%X", 10); 7F
%b Valor lógico (boolean) (minúsculo). System.out.printf("%b", 3 > 2); true
%B Valor lógico (boolean) (maiúsculo). System.out.printf("%B", 2 > 3); FALSE
%c Caractere (normal). System.out.printf("%c", ‘v’); v
%C Caractere (maiúsculo). System.out.printf("%C", ‘v’); V
%s String (normal). System.out.printf("%s", "String"); String
%S String (maiúscula). System.out.printf("%S", "String"); STRING
%% Imprime símbolo porcentagem. System.out.printf("%d%%", 68); 68%
%n Insere uma nova linha portável. System.out.printf("L1%nL2"); L1

L2
Fonte: elaborado pelo autor.
DICA

Uma dica muito importante para fixar e compreender as sequências de escape e os especificadores de formato é criar exemplos simples em que estes são utilizados. Gostaríamos de destacar que os especificadores de formato mostrados no Quadro 3.2 são utilizados principalmente no comando System.out.printf. Para complementar o seu estudo, assista ao vídeo Curso de Java 63: printf.

Lembre-se

Ao utilizar os comandos System.out.println() ou System.out.print(), utilize o \n para pular linhas; já para os comandos System.out.printf(), System.out.format(), String.format() ou para a classe Formatter, utilize o %n para pular linhas. O %n é um pulador de linhas portável entre diferentes sistemas operacionais. Em muitos casos, o \n funcionará dentro do comando System.out.printf(), porém é bom evitá-lo, a fim de que não tenha problemas quando utilizar seu código em outras plataformas.

Explicamos, brevemente, o funcionamento do Código 3.1, porém não mencionamos como executá-lo; logo, a fim de simplificarmos as coisas, recomendamos a criação de apenas um arquivo, chamado ArgsLinhaDeComandoBasico.java. (Como dica, não coloque esse arquivo dentro de nenhum pacote, a fim de simplificar a compilação.) Em seguida, abra um terminal de comandos no diretório em que está salvo esse arquivo e compile o código utilizando o compilador do Java chamado javac. 

$ javac ArgsLinhaDeComandoBasico.java

ou

$ javac NomeDoPrograma.java

Após esse comando mostrado acima, será gerado um arquivo com o nome ArgsLinhaDeComandoBasico.class. Para executar esse arquivo, digite o seguinte comando no terminal (repare que os argumentos chamados arg1 arg2 e arg2 são parâmetros que são passados ao programa principal). 

$ java ArgsLinhaDeComandoBasico arg1 arg2 arg3

ou

$ java NomeDoPrograma lista_de_argumentos_separados_por_espaço

Dessa maneira, o Código 3.1 produzirá a seguinte saída. 

quantidade de argumentos = 3

  args[0] = arg1

  args[1] = arg2

  args[2] = arg3

Pronto, agora você já sabe como criar um simples programa em Java que é compilado e executado por meio do terminal, bem como passar argumentos via linha de comando para interagir com a aplicação.

Assimile

Ao observarmos o Quadro 3.2, percebemos que a saída do comando “System.out.printf("%e", 3.141592);” foi o valor 3,141592, utilizando-se a vírgula como separador decimal e não o ponto. Na verdade, a saída pode utilizar qualquer uma das formas, tanto o ponto (utilizado, por exemplo, nos EUA) quanto a vírgula (utilizada, por exemplo, no Brasil). De forma a compreender como alterar o formatador de impressão utilizado, implemente o Código 3.2 mostrado a seguir e alterne a execução do código comentando e descomentando as linhas 5 e 6.

Código 3.2 | Implementação básica que define a localização para formatar a saída usada da impressão
import java.util.Locale;
public class ExemploLocalizacao {
    public static void main(String[] args) {
        //alterne a execução com essas duas linhas
        Locale.setDefault(new Locale("pt", "BR"));
        //Locale.setDefault(new Locale("en", "US"));
        double pi = 3.141592;
        System.out.printf("Valor Pi: %f%n", pi);        
    }
}
Fonte: elaborado pelo autor.

Ao utilizar a linha 5 e comentar a linha 6, a localização do Brasil é especificada, assim, os números são impressos utilizando-se o separador vírgula. Ao utilizar a linha 6 e comentar a linha 5, a localização dos EUA é especificada, assim, os números são impressos utilizando-se o separador ponto.

Agora que vimos o básico de passagem de argumentos via linha de comando, podemos construir uma aplicação mais complexa que utiliza esse recurso. Dessa forma, crie um novo projeto com o nome AppCmdMath. Dentro desse projeto, crie uma única classe com o nome ArgsLinhaDeComandoMath.java. O Código 3.3 mostra o conteúdo dessa classe, veja:

Código 3.3 | Aplicação matemática que utiliza argumentos via linha de comando
import java.util.Locale;
public class ArgsLinhaDeComandoMath {
    public static void main(String[] args) {
        Locale locale = new Locale("en", "US");
        if (args.length == 0) {
            System.err.println("Precisa de Argumentos");
            System.exit(0);
        }
        String resp = "ERROR";
        args[0] = args[0].toLowerCase();
        if (args[0].equals("--help")) {
            resp = "Programa: Cmd Math via args\n" + 
                   "Funções:\n\tsqrt x\n\tpow x y\n\tlog10 x\n" +
                   "Constantes:\n\tPI\n\tE\n\tPHI\n";
        } else if (args[0].equals("--author")) {
            resp = "Autor: Jesimar da Silva Arantes";
        } else if (args[0].equals("--version")) {
            resp = "Versão: 1.0";
        } else if (args[0].equals("sqrt")) {
            resp = Math.sqrt(Double.parseDouble(args[1])) + "";
        } else if (args[0].equals("pow")) {
            double x = Double.parseDouble(args[1]);
            double y = Double.parseDouble(args[2]);
            resp = Math.pow(x, y) + "";
        } else if (args[0].equals("log10")) {
            resp = Math.log10(Double.parseDouble(args[1])) + "";
        } else if (args[0].equals("pi")) {
            resp = Math.PI + "";
        } else if (args[0].equals("e")) {
            resp = Math.E + "";
        } else if (args[0].equals("phi")) {
            resp = 1.618033988749895 + "";
        }
        System.out.println(resp);
    }
}
Fonte: elaborado pelo autor.

Ao executar esse código, os seguintes argumentos são válidos:

Algumas considerações sobre o Código 3.3 são necessárias: na linha 4, foi especificado que desejamos utilizar o sistema de medidas dos EUA, assim, a impressão dos valores numéricos decimais será dada utilizando-se ponto e não vírgula. Nas linhas 5 a 8, o programa verifica se foi passado argumentos via linha de comando, e caso não haja argumentos, o programa imprimirá uma mensagem na saída de erro invocada com o System.err. Até então, os nossos programas apenas utilizavam a saída padrão que é invocada com o System.out. Na linha 10, foi convertido o primeiro argumento para minúsculo, dessa maneira, dará na mesma digitar --help ou --HELP ou --HeLp. A próxima novidade está nas linhas 20, 22, 23 e 26, que contêm a chamada para o método Double.parseDouble(args[1]). O método parseDouble da classe Double é um método estático que recebe uma String como argumento e a converte para o tipo primitivo double.

No Código 3.1, foi sugerido compilar o arquivo .java, gerando um arquivo .class, e executar o .class via linha de comando. Agora, faremos um procedimento um pouco diferente: geraremos um arquivo .jar e o executaremos por meio da linha de comando, passando os argumentos necessários. Dessa maneira, procure como gerar um arquivo .jar utilizando o seu IDE. Em geral, é só apertar um botão na interface do IDE (no Netbeans tem um botão chamado “Limpar e Construir Projeto”) ou, então, apertar Shift+F11. Após isso, um arquivo .jar será gerado dentro de um diretório chamado dist. Abra um terminal de comandos nesse diretório dist e, então, mande executar o código da seguinte forma: 

$ java -jar AppCmdMath.jar --help

ou

$ java -jar NomeDoProjeto lista_de_argumentos_separados_por_espaço

Após esse comando, a seguinte saída será impressa na tela:

Programa: Cmd Math via args

Funções:

  sqrt x

  pow x y

  log10 x

Constantes:

  PI

  E

  PHI

Experimente fazer a execução do programa acima conforme sugerido nas linhas abaixo. Em cada uma das execuções, tente entender o resultado que será gerado.

Execução 1: $ java -jar AppCmdMath.jar --author

Execução 2: $ java -jar AppCmdMath.jar --version

Execução 3: $ java -jar AppCmdMath.jar sqrt 25

Execução 4: $ java -jar AppCmdMath.jar pow 2 4

Execução 5: $ java -jar AppCmdMath.jar log10 100

Execução 6: $ java -jar AppCmdMath.jar PI

Execução 7: $ java -jar AppCmdMath.jar E

Execução 8: $ java -jar AppCmdMath.jar PHI

No texto acima, foram mencionados os arquivos .class e .jar. Um arquivo .class é um arquivo binário gerado pelo compilador do Java. Um conjunto de arquivos .class pode ser agrupado para formar um arquivo .jar. Um Java ARchive (JAR) é, basicamente, um arquivo compactado usado para distribuir um conjunto de classes do Java (.class e .java), podendo-se ter também dentro do JAR arquivos de imagens, xml, json, txt, entre outros. Além disso, um arquivo .jar pode ser também pensado como um executável da aplicação, uma vez que, a partir dele, executamos o programa java.

Dica

O texto acima referiu-se a um diretório chamado dist, em que é gerado o arquivo .jar contendo o executável do projeto. O Quadro 3.3 sintetiza a organização geral em termos de estrutura de diretórios de um projeto qualquer feito na linguagem Java. Analise a descrição feita nesse quadro e navegue também pelos seus projetos, em seu computador, para conhecer melhor essa estrutura que os IDEs criam automaticamente.

Quadro 3.3 | Organização geral das pastas (diretório) em um projeto Java
Diretório
Descrição do Conteúdo
src/ Código-fonte da aplicação.
test/ Código de teste unitário (não utilizado neste livro).
lib/ Dependências do projeto.
dist/ Arquivos de distribuição como .jar e suas dependências.
build/ Arquivos gerados pelo processo de compilação.
Fonte: elaborado pelo autor.

Leitura de dados em tempo real

Acima, vimos como fazer a entrada de valores para a aplicação com argumentos via linha de comando. Uma outra forma de interagirmos com uma aplicação Java se dá por meio da leitura de dados em tempo real via classe Scanner, que fornece métodos de leitura com sintaxes diferentes, mas com funcionamento similar à função scanf presente na linguagem C. Analise o Código 3.4 mostrado a seguir.

Código 3.4 | Exemplo de leitura de dados em Java utilizando-se a classe Scanner
import java.util.Scanner; 
public class ExemploLeituraDados {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.print("Digite um valor inteiro (int): ");
        int entradaInt = scan.nextInt();
        System.out.print("Digite um valor real (double): ");
        double entradaDouble = scan.nextDouble();
        System.out.print("Digite um valor lógico (boolean): ");
        boolean entradaBoolean = scan.nextBoolean();
        System.out.print("Digite uma string (uma palavra): ");
        String entradaPalavra = scan.next();
        scan.nextLine();//comando para esvaziar o buffer do teclado
        System.out.print("Digite uma string (várias palavras): ");
        String entradaString = scan.nextLine();
        System.out.println("Saída dos valores lidos: ");
        System.out.printf("\tValorInteiro: %d%n", entradaInt);
        System.out.printf("\tValorReal: %f%n", entradaDouble);
        System.out.printf("\tValorLógico: %b%n", entradaBoolean);
        System.out.printf("\tValorPalavra: %s%n", entradaPalavra);
        System.out.printf("\tValorFrase: %s%n", entradaString);
    }
}
Fonte: elaborado pelo autor.

No Código 3.4, na linha 1, temos a importação da classe Scanner que será utilizada para fazer a leitura dos dados. Na linha 4, temos a criação de um objeto do tipo Scanner que fará a leitura de dados da entrada padrão (System.in). É importante ressaltarmos que a entrada padrão utiliza o dispositivo periférico teclado. A Figura 3.1 nos mostra o fluxo de entrada e saída básico de uma aplicação Java qualquer; já a saída padrão (System.out) utiliza a tela do computador (monitor) e a saída de erro (System.err) também (veremos mais sobre essa saída ainda nesta unidade).

Figura 3.1 | Fluxo de entrada e saída em qualquer aplicação na linguagem Java
A figura ilustra em formato de organograma o fluxo de entrada e saída na linguagem JAVA. No topo está Syste.ini como entrada padrão que segue para Programa Java com notação binária de zeros e uns, entre eles há um teclado. De programa java, à esquerda, segue para system.out como saída padrão, com uma tela, e à direita, segue para system.err como saída de erro, com uma tela.
Fonte: elaborada pelo autor.

Ainda no Código 3.4, o método nextInt() na linha 6 lê um valor do tipo inteiro (int) da entrada; já o método nextDouble() na linha 8 lê um valor double da entrada. A depender de como estiver configurado o seu Java, você terá de digitar o valor double utilizando o separador ponto ou o separador vírgula (dica: pode-se alterar essa forma de entrada de dados utilizando a classe Locale de forma semelhante ao Código 3.2). O método nextBoolean() na linha 10 lê um valor lógico da entrada (true ou false); o método next() na linha 12 lê uma String da entrada, mas apenas uma palavra pode ser lida (ao se digitar um espaço, a leitura com o next() é interrompida); e na linha 13, tivemos que inserir uma leitura com nextLine() para esvaziar o buffer do teclado. O problema de buffer do teclado está associado, neste caso, à leitura de um \n que não foi consumido na leitura anterior e se dá toda vez que se está lendo algum tipo de dado, como byte, short, int, long, float, double ou string (com o comando next()), e, em seguida, é solicitada uma leitura de uma linha completa utilizando-se o nextLine(). Já o método nextLine() na linha 15 lê uma String da entrada, podendo esta ser uma frase inteira com separador de espaço (sendo que a leitura com nextLine() é interrompida com o primeiro enter digitado.); e, por fim, nas linhas 17 a 21, temos a impressão na tela dos valores lidos do teclado. 

Dica

Caro aluno, implemente o Código 3.4 acima e faça diversos testes com diferentes valores de entrada. Para cada um desses testes, analise os valores de saída, faça testes que falhem ou gerem erros e veja a saída impressa.

Tratamento de Exceções

Um importante recurso presente na linguagem Java é a sua capacidade de lidar com exceções, que são comportamentos fora do padrão e que, em geral, ocorrem poucas vezes. 

De forma a entender melhor a ideia de tratamento de exceção, vamos considerar o seguinte exemplo: imagine que você programou uma calculadora capaz de executar as seguintes operações básicas: soma, subtração, multiplicação e divisão. Agora, imagine que você quer que essa calculadora seja utilizada por diversas pessoas e, então, pede a um amigo para testá-la. O seu amigo, inicialmente, faz algumas operações básicas, como soma, subtração, multiplicação e divisão, e tudo funciona perfeitamente. Em seguida, ele decide verificar se a sua calculadora/implementação é realmente robusta; para tanto, divide 5 por 0 e aperta a tecla igual. Após isso, a sua aplicação trava e a única forma que encontra de fazê-la voltar é apertando o botão para desligar e ligar novamente. Esses passos descritos acima podem ser acompanhados na Figura 3.2. 

Figura 3.2 | Esquema didático que representa o lançamento de exceção sem tratamento
A figura mostra uma calculadora e ao lado indica o uso dos botões 5, / (símbolo de divisão), zero, igual e desligar. Saindo do botão igual há uma seta com um símbolo erro e o texto: Gera um erro. Do símbolo de erro sai uma seta que vai para o botão Desligar, com o texto: Trava a aplicação.
Fonte: elaborada pelo autor.

O seu amigo, então, diz que a calculadora precisa de alguns ajustes. Você, acuado, fala para ele que não, que a calculadora funciona perfeitamente e que o usuário é quem não deve fazer uma divisão por zero, afinal, todos sabemos que não existe divisão por zero. Bem, nesse exemplo, o programador está errado; de forma geral, não podemos assumir que o usuário não informará uma operação que não está definida. Você, então, deve tratar isso a nível de código, deixando-o “mais” tolerante a falhas. Em geral, construir um código totalmente livre de falhas é quase impossível, mas você deve buscar minimizar ao máximo esses equívocos. 

A Figura 3.3 nos mostra como seria um possível tratamento de exceção em alto nível. Após o usuário digitar 5 dividido por 0 e apertar a tecla igual, uma exceção é gerada e tratada em código; além disso, uma mensagem é exibida no visor da calculadora dizendo que o valor é indefinido. Após isso, para continuar a usá-la, o usuário deverá apertar o botão para limpar o conteúdo, e é essa a essência básica do tratamento de exceção em que damos um tratamento adequado (nesse caso, imprimir uma mensagem dizendo “INDEFINIDO”) a alguma eventual falha que, se não tratada, pode vir a travar a aplicação.

Figura 3.3 | Esquema didático que representa o tratamento de exceção
A figura mostra uma calculadora e ao lado indica o uso dos botões 5, / (símbolo de divisão), zero, igual e CE. Saindo do botão igual há uma seta com um símbolo erro e o texto: Gera uma exceção. Do símbolo de erro sai uma seta que vai para a mensagem INDEFINIDO, com o texto: Trava a exceção, da mensagem sai uma seta que vai para o botão CE.
Fonte: elaborada pelo autor.

Antes de explicarmos como tratar uma exceção, gostaríamos de propor ao leitor a criação de algumas aplicações simples em Java que lancem alguns tipos de exceção. Cada uma das aplicações deve possuir apenas a função principal main e uma das linhas de código do Quadro 3.4 a seguir, assim, será uma aplicação por linha do quadro. Ao executar a primeira aplicação, a divisão do valor inteiro 5 por 0 será feita e, então, uma exceção será gerada. O leitor verá que será impresso algo do tipo “Exception in thread "main" java.lang.ArithmeticException: / by zero”, que indica o tipo de exceção lançada, que foi, nesse caso, do tipo ArithmeticException, devido à divisão (/) por zero. Após essa mensagem, será mostrado o que chamamos de rastro de pilha ou stack trace, que indica o caminho percorrido pelo erro, passando por diversos métodos chamados até o método main, que iniciou a execução da aplicação.

Quadro 3.4 | Trechos de código que lançam diversas exceções

Linhas de Código 
Tipo de Exceção Lançada
1 int divPor0 = 5/0; ArithmeticException
2 int valorStr = Integer.parseInt("A"); NumberFormatException
3 int refNull = Integer.parseInt(null); NullPointerException
4 String indiceNegativo = args[-1]; ArrayIndexOutOfBoundsException
5 System.out.printf("%d", "5"); IllegalFormatConversionException
6 Scanner sc = new Scanner(System.in);

int vFloat = sc.nextInt();//ler 3.5
InputMismatchException
Fonte: elaborado pelo autor.

O exemplo 2 do Quadro 3.4 lança uma exceção do tipo NumberFormatException pois um valor String "A" não pode ser convertido para inteiro. O exemplo 3 do Quadro 3.4 lança uma exceção do tipo NullPointerException pois uma referência nula (utilizando-se a palavra-reservada null) não pode ser passada para o método parseInt(). No Quadro 3.4, todos os erros foram forçados a fim de que o leitor conhecesse algumas das possíveis exceções que podem ser lançadas. Na maioria das aplicações desenvolvidas, os erros não são tão evidentes quanto esses, mas podem ocorrer, eventualmente, devido à grande complexidade e grande dinamicidade das aplicações.

A seguir, serão estudadas as estruturas de código em Java que são necessárias para se fazer uma aplicação que trata exceções. 

O Quadro 3.5 apresenta uma síntese dos principais comandos capazes de lidar com o tratamento de exceção.

Quadro 3.5 | Síntese dos comandos: try-catch, try-catch-finally e try-multicatch-finally
cmd: try-catch
cmd: try-catch-finally
cmd: try-multicatch-finally
try {

  //tente executar

catch (ClassEx e) {

  //trate a exceção

}
try {

  //tente executar

catch (ClassEx e) {

  //trate a exceção

} finally {

  //execute sempre

}
try {

  //tente executar

catch (ClassEx e) {

  //trate a exceção

 
catch (ClassEx e) {

  //trate a exceção 

... //múltiplos catchs

finally {

  //execute sempre

}
Fonte: elaborado pelo autor.

O Código 3.5 contém uma possível forma de realizar o tratamento de exceção para a divisão por zero de números inteiros.

Código 3.5 | Implementação da operação de divisão que trata a divisão por zero
public static int div(int a, int b) {
    try {
        return a / b;
    } catch (ArithmeticException ex) {
        System.err.println("A divisão por zero é indefinida");
        return 0;
    }
}
Fonte: elaborado pelo autor.

Os métodos em Java podem também lançar exceções, pois, às vezes, desejamos que o tratamento seja feito não no método corrente, mas no método que o chamou, e assim, utilizamos a palavra reservada throw para lançar uma exceção. 

Analise o Código 3.6, que contém o lançamento de uma exceção, pois não existe o fatorial de um número inteiro negativo. Dessa maneira, a função que chama o método fatorial é que deve tratar números negativos e não o fatorial em si. 

Código 3.6 | Implementação do fatorial que lança uma exceção caso n seja negativo
public static long fatorial(int n) {
    if (n < 0) {
        throw new IllegalArgumentException("O n deve ser >= 0");
    }
    long fat = 1;
    for (int i = 1; i <= n; i++) {
        fat *= i;
    }
    return fat;
}
Fonte: elaborado pelo autor.

A Figura 3.4 nos mostra como é feita a organização das classes do Java em termos da hierarquia de exceções. Assim, no topo da hierarquia, temos a classe Throwable (lançável), que modela qualquer objeto que possa lançar uma exceção ou erro. Em seguida, temos, basicamente, dois tipos de problemas que podem ocorrer na linguagem Java: as exceções (classe Exception ou suas subclasses) e os erros (classe Error ou suas subclasses). Os erros são lançados pela JVM e costumam ser bem raros de ocorrer. Caso ocorra algum tipo de erro em sua aplicação, não tente tratá-lo; a recomendação é que você simplesmente deixe-o ocorrer e execute novamente a sua aplicação. Em relação às exceções, de forma geral, as subdividimos em checadas ou não checadas. As exceções que herdam RuntimeException são não checadas e ocorrem, essencialmente, em tempo de execução. As exceções que herdam Error também são não checadas; já as exceções que herdam Exception (exceto a RuntimeException) são checadas e podem ocorrer em tempo de compilação. As exceções do tipo checadas obrigam o programador a tratá-las com um bloco try-catch ou lançar a exceção para o método chamador utilizando-se o comando throws.

Existem milhares de classes nativas do Java que auxiliam no tratamento de exceção, no entanto, na Figura 3.4, veremos apenas as principais, assim como, a seguir, veremos como criar as nossas próprias classes que lançam e tratam exceções.

Figura 3.4 | Organização das classes na hierarquia de exceções em Java
a figura mostra um esquema hierárquico de exceções em JAVA. Há uma legenda: Verde: Exceções checadas e Exceções em tempo de compilação, Lilás: Exceções não checadas e Exceções em tempo de execução, e Laranja: Exceções não checadas (lançada pela JVM) e Exceções em tempo de execução.  Em verde há: Exception, java.lang com o texto explicativo: classe base de todas as exceções checadas. De Exception, java.lang sai uma seta que vai para Throwable, java.lang com o texto explicativo: todas as exceções herdam Throwable. De Throwabel sai uma seta que vai para Object, java.lang (em cinza). De IOException, java.io e SQLExcpetion, java.sql saem setas que vão para Exception, java.lang, de FileNotFoundException, java.io sai uma seta que vai para IOException, java.io e de SocketException, java.net, sai uma seta que vai para SQLException, java.sql. Em Lilás há: RuntimeException, java.lang com o texto explicativo: classe base para todas as exceções não checadas. De ArithmeticException, java.lang, IndexOfBoundsException, java.lang, IllegalArgumentException, java.lang, NullPointerException, java.lang, NosuchElementException, java.util saem setas que apontam para RuntimeException, java.lang. De ArrayIndexOf BoundsException, java.lang sai uma seta que aponta para IndexBoundsException, java.lang. De NumberFormatException, java.lang e IllegalFormat ConversationException, java.lang saem setas que apontam para IllegalArgumentException, java.lang. De InputMismachException, java.lang sai uma seta que aponta para NoSuchElementException, java.util. Em Laranja há: Error, java.lang com o texto explicativo Classe base de todos os erros em nível de JVM. De VirtualMachineError, java.lang, ThreadDeath, java.lang, AssertionError, java.lang, LinkageError, java.lang e IOError, java.io saem setas que apontam para Error, java.lang. De OutOFMemoryError, java.lang e StackOverflowError, java.lang, saem setas que apontam para Error, java.lang.
Fonte: elaborada pelo autor.

A seguir, serão brevemente descritas algumas das exceções mostradas na Figura 3.4.

A linguagem Java permite-nos criar nossas próprias classes que lançam exceções. Uma boa prática é utilizarmos as classes de exceções que já existem na plataforma, no entanto, nem sempre a linguagem possui uma exceção já programada que representa aquilo que desejamos, assim, devemos criar a nossa própria classe. 

Frente a isso, criaremos uma classe que modelará exceções não checadas e que será lançada toda vez que entrarmos com um valor negativo. Como queremos que essa exceção seja não checada, estenderemos a classe RuntimeException e, em seguida, três construtores bem simples serão criados. Faz-se importante ressaltar que esse exemplo tem um propósito didático. 

Código 3.7 | Classe que cria uma exceção personalizada para tratar valores negativos
public class ValorNegativoException extends RuntimeException {
    public ValorNegativoException() {
    }    
    public ValorNegativoException(String message) {
        super(message);
    }
    public ValorNegativoException(String msg, Throwable cause) {
        super(msg, cause);
    }
}
Fonte: elaborado pelo autor.

Agora, para utilizarmos a exceção criada acima, basta modificarmos, por exemplo, a linha 3 do Código 3.6 para lançarmos uma exceção do tipo ValorNegativoException em vez de lançarmos a exceção do tipo IllegalArgumentException. Gostaríamos de ressaltar a importância de se fazer as implementações e os testes mencionados acima e, só assim, prosseguir na leitura.

Assertivas

A linguagem Java possui um outro recurso chamado asserção ou assertiva. Esse recurso auxilia no desenvolvimento de aplicações e as mantém limpas para a etapa de produção na linha de desenvolvimento de software. Imagine que você precisa criar uma aplicação que receberá um valor inteiro que representará a idade de uma pessoa; todos nós concordamos que não existe idade negativa, mas um valor de idade negativa pode ser erroneamente informado no sistema, podendo acarretar algum tipo de erro no sistema como um todo. Frente a isso, a assertiva é um tipo de tratamento de exceção em nível de desenvolvimento de código, que, quando pronto, tem as assertivas automaticamente removidas. 

O Quadro 3.6 apresenta a sintaxe básica das assertivas em Java. Nele, percebemos que as assertivas utilizam a palavra reservada assert. Podemos perceber também que existem dois tipos de comandos assert: no primeiro, nenhuma mensagem é especificada, já no segundo comando, há uma mensagem especificada. A ideia de especificar uma mensagem é imprimi-la quando um erro é gerado. Caso a assertiva (ExprLógica) seja falsa, um erro do tipo AssertionError será lançado e a aplicação encerrada.

Quadro 3.6 | Síntese da sintaxe utilizada no comando assert

cmd: assert (sem msg)
cmd: assert (com mensagem)
Sintaxe assert (ExprLógica); assert (ExprLógica) : ”MsgDeAviso”;
Exemplo de Exceção Lançada
Exception in thread "main" java.lang.AssertionError Exception in thread "main" java.lang.AssertionError: MsgDeAviso
Fonte: elaborado pelo autor.

O Código 3.8 possui um exemplo básico de assertiva em que valores negativos de idade geram mensagens de alertas e interrompem a execução da aplicação. Uma questão muito importante sobre as asserções é que nunca devemos tratá-las; o ideal é deixarmos as exceções encerrarem a aplicação, logo, o programador deve fazer o devido tratamento para que esse tipo de exceção nunca ocorra. Lembre-se: as asserções não vão para o programa final, elas são usadas apenas durante o desenvolvimento e devem guiar o programador na construção de aplicações que não permitem que tais erros ocorram.

Código 3.8 | Implementação básica para mostrar o uso de assertivas em Java
public class ExemploAssercao {
    public static void main(String[] args) {  
        //faça testes com as idades 20, 12, -1, 84, -20
        int idade = 20;
        assert (idade >= 0) : "Aviso: não existe idade negativa";
        System.out.printf("Idade: %d%n", idade);
    }
}
Fonte: elaborado pelo autor.

Como mencionado anteriormente, as assertivas são automaticamente removidas do código quando vai para a produção. Assim, para a execução do programa com a assertiva, a forma de compilação é ligeiramente diferente. A fim de que consiga executar o código acima, o argumento -enableassertions (também aceita-se -ea) faz-se necessário para habilitar asserções. 

A seguir, temos, na primeira linha, a compilação do código ExemploAssercao.java (igual ao anterior) e, em seguida, a forma como devemos executar o código. 

$ javac ExemploAssercao.java

$ java –enableassertions ExemploAssercao

O Código 3.9 mostra um método desenvolvido em produção que verifica os argumentos recebidos de uma função que calcula o Índice de Massa Corporal (IMC). Os argumentos de massa e altura devem satisfazer a todos os requisitos, caso contrário, a aplicação encerra a sua execução.

Dica

Caro aluno, construa o método main que invoca calcularIMC(), faça diversos testes, passando valores para falhar, e perceba o funcionamento das asserções.

Código 3.9 | Método que calcula o IMC com uso de assertivas em Java
public float calcularIMC(float massa, float altura){
    assert (massa >= 0) : "Aviso: não existe massa negativa";
    assert (altura >= 0) : "Aviso: não existe altura negativa";
    assert (massa != 0) : "Aviso: não existe massa igual a 0";
    assert (altura != 0) : "Aviso: não existe altura igual a 0";
    return massa / (altura * altura);
}
Fonte: elaborado pelo autor.
Reflita

As assertivas geram erros do tipo AssertionError, que é uma subclasse de Error (consulte novamente a Figura 3.4); dessa maneira, reflita: por que não devemos tratar as exceções lançadas pelas assertivas? Por que a maneira de execução do código com as assertivas mudou? Por que o argumento –enableassertions não é o argumento default de compilação? Por fim, gostaria de propor também ao leitor uma reflexão sobre as diferenças entre as exceções e asserções em Java.

Uso de classes abstratas

A linguagem Java possui dois tipos de classes, que são: classes concretas e classes abstratas. As classes concretas são as classes que estudamos até aqui. A partir dessas classes, podemos criar instâncias e fazer a manipulação delas. Já as classes abstratas são apenas uma abstração de alguma entidade, assim, não podemos fazer a sua instanciação. As classes abstratas servirão de modelo para a criação de subclasses (concretas ou abstratas), e a declaração de uma classe abstrata utiliza a palavra reservada abstract

As classes abstratas podem possuir métodos abstratos, ou seja, métodos que não possuem nenhuma implementação, assim, nesses métodos, são declaradas apenas as suas assinaturas, e a classe que herdar uma classe abstrata é que deverá sobrescrever as assinaturas dos métodos abstratos. A declaração de um método abstrato utiliza a palavra reservada abstract. Os métodos do tipo estático não podem ser do tipo abstrato, pois não podem ser sobrescritos. Uma classe abstrata não pode ser declarada como final, pois uma classe final não pode ser herdada. 

Vamos olhar novamente a Figura 2.9 e o Código 2.12 da unidade 2. Perceba que, nesses exemplos, as classes Geom2D e Geom3D foram declaradas como classes concretas. As entidades figuras geométricas 2D e 3D são entidades abstratas e não deviam ser declaradas daquela forma; já as entidades Circulo, Triângulo, Retângulo, Esfera, Tetraedro e Cubo são concretas e foram declaradas de forma adequada ao nosso propósito. Dessa maneira, é necessário fazer uma reimplementação das classes Geom2D e Geom3D de forma mais consistente com o seu significado.

Exemplificando

De forma a exemplificar a criação de classes abstratas, analise o Código 3.10 que apresenta uma possível reimplementação das classes Geom2D e Geom3D de forma abstrata.

Código 3.10 | Implementação de Geom2D e Geom3D de forma abstrata
//declaração da classe abstrata
public abstract class Geom2D { 
    protected double perimetro;
    protected double area;
    //métodos abstratos calcPerimetro e calcArea
    public abstract double calcPerimetro(); 
    public abstract double calcArea();
    public double getPerimetro() {
        return perimetro;
    }
    public double getArea() {
        return area;
    }
}

//declaração da classe abstrata
public abstract class Geom3D { 
    protected double area;
    protected double volume;
    //métodos abstratos calcArea e calcVolume
    public abstract double calcArea();
    public abstract double calcVolume();
    public double getArea() {
        return area;
    }
    public double getVolume() {
        return volume;
    }
}
Fonte: elaborado pelo autor.

No Código 3.10, na linha 2, foi feita a declaração da classe Geom2D como abstrata; nas linhas 6 e 7, foram declarados os métodos calcPerimetro() e calcArea() como abstratos. Repare que colocamos apenas as assinaturas desses métodos, pois as classes que herdarem de Geom2D é que deverão implementar, de fato, esses métodos. O raciocínio é o mesmo para a classe Geom3D. Na implementação das classes concretas, não é necessário alterar nada nas classes Circulo, Triângulo, Retângulo, Esfera, Tetraedro e Cubo. Tente fazer essa adaptação de código e realize alguns testes para entender o seu funcionamento.

Caro estudante, nesta seção, você estudou os conteúdos relacionados a argumentos via linha de comando, à leitura de dados por meio da classe Scanner, ao tratamento de exceção, ao lançamento de exceção, à criação de exceção, a assertivas e a classes abstratas, bem como analisou diversos exemplos sobre como esses conceitos são implementados na linguagem Java. Nas seções seguintes, estudaremos melhor como funcionam as interfaces em Java e avançaremos ainda mais no entendimento dessa linguagem.

Referências

ARANTES, J. da S. Livro-POO-Java. 2020. Disponível em: https://bit.ly/3eiUMcF. Acesso em: 29 jul. 2020.

CAELUM. Java e orientação a objetos: apostila do curso FJ-11. [s.d.]. Disponível em: https://bit.ly/2ZpTqrZ. Acesso em: 29 jul. 2020.

DEITEL, P. J.; DEITEL, H. M. Java: como programar. 10. ed. São Paulo: Pearson Education, 2016.

LOIANE GRONER. Curso de Java 63: printf. 2016. Disponível em: https://bit.ly/3mf5Blx . Acesso em: 29 jul. 2020.

LOIANE GRONER. Curso de Java básico gratuito com certificado e fórum. 2016. Disponível em: https://bit.ly/2VUtDGT. Acesso em: 29 jul. 2020.

ORACLE. ArrayIndexOutOfBoundsException. [s.d.]. Disponível em: https://bit.ly/33ilAXf. Acesso em: 29 jul. 2020.

ORACLE. Class ArithmeticException. [s.d.]. Disponível em: https://bit.ly/2DTTRDC. Acesso em: 29 jul. 2020.

ORACLE. IllegalArgumentException. [s.d.]. Disponível em: https://bit.ly/3iqZfgs. Acesso em: 29 jul. 2020.

ORACLE. IndexOutOfBoundsException. [s.d.]. Disponível em: https://bit.ly/2RjhsAO. Acesso em: 29 jul. 2020.

ORACLE. NullPointerException. [s.d.]. Disponível em: https://bit.ly/2FnYt5v. Acesso em: 12 jul. 2020.

ORACLE. NumberFormatException. [s.d.]. Disponível em: https://bit.ly/2DPDUOE. Acesso em: 12 jul. 2020.

Bons estudos!

AVALIE ESTE MATERIAL

OBRIGADO PELO SEU FEEDBACK!