Programação por Objectos Java Parte 10: Pacotes, excepções e asserções LEEC@IST Java – 1/54 Pacotes – revisão (1) • Um pacote é um mecanismo de agrupamento de informação: – Os pacotes podem conter outros pacotes, classes, interfaces e objectos. – O pacote forma um espaço de nomes (namespace), logo os seus membros têm de ter identificadores únicos (por exemplo, num pacote não pode haver duas classes com o mesmo nome). – O identificador dum pacote pode consistir num nome simples ou num nome qualificado. O nome qualificado corresponde ao nome simples prefixado pelo nome do pacote onde este reside, se existir. É usual usar-se :: para separar os nomes simples. LEEC@IST Java – 2/54 Pacotes – revisão (2) – A importação adiciona o conteúdo do pacote importado ao espaço de nomes do pacote importador, de tal forma que passa a ser desnecessário usar o seu nome qualificado. – A importação não é transitiva. • • Se o pacote B importar o pacote A, e o pacote C importar o pacote B, no pacote C não são importados os elementos do pacote A. Caso o pacote C queira importar igualmente os elementos do pacote A, devem ser inseridas duas directivas de importação, uma para o pacote A e outra para o pacote B. • O conjunto de métodos de um pacote é referido por API (Application Programmer Interface). LEEC@IST Java – 3/54 Pacotes (1) • Os pacotes são úteis por várias razões: – Agrupam interfaces e classes relacionadas num mesmo pacote (que depois pode ser disponibilizado num ficheiro jar, juntamente com o MANIFEST.MF descrevendo o pacote). – Criam um espaço de nomes que evita conflito de nomes entre tipos definidos fora do pacote (possível uso de nomes populares, por exemplo, List). – Oferecem um domínio protegido para o desenvolvimento de aplicações (o código dentro do pacote pode cooperar, usando o acesso que lhe é oferecido aos membros das classes e interfaces do pacote, acesso esse que normalmente é mais restrito para código externo). LEEC@IST Java – 4/54 Pacotes (2) • Uma classe é inserida num pacote pela directiva package IdPacote; • Uma declaração package tem de ser a primeira coisa a aparecer num ficheiro fonte, antes da declaração de qualquer classe ou interface (e mesmo antes de qualquer directiva de import). • Só pode existir uma declaração package por código fonte. • O nome do pacote é implicitamente prefixado ao nome de cada tipo contido no pacote. LEEC@IST Java – 5/54 Pacotes (3) • Se um tipo não é declarado como pertencendo a um pacote, é colocado num pacote sem nome (facilita a implementação de pequenos programas). LEEC@IST Java – 6/54 Pacotes (4) • A importação das classes é feita pela directiva: import IdPacote[.IdSubPacote]*.(*|IdTipo); • Importação a pedido (on demand): import java.util.*; O * importa as definições de todos os tipos públicos do pacote. • Importação simples: import java.util.Set; LEEC@IST Java – 7/54 Pacotes (5) • A directiva de import deve ser usada depois da declaração package, mas antes da declaração de qualquer tipo. • O Java importa automaticamente o pacote java.lang (subpacote lang do pacote java). – O separador “.” no Java corresponde ao “/” do Unix e “\” do Windows. LEEC@IST Java – 8/54 Pacotes (6) • Código escrito fora dum pacote, e que precisa de tipos definidos nesse pacote, tem duas opções: – Utilizar o nome qualificado do tipo. – Importar parte ou todo o pacote. package xpto; public class Xpto { java.util.Set<String> strings; //... package xpto; } import java.util.Set; public class Xpto { Set<String> strings; //... } LEEC@IST Java – 9/54 Pacotes (7) • • As directivas de import apenas dizem ao compilador como determinar o nome qualificado dos tipos que são usados na aplicação e que não são encontradas localmente. O compilador procura pelo tipo na ordem: – – – – – • O tipo corrente, incluindo tipos herdados. Tipos aninhados no tipo corrente. Tipos importados explicitamente através de importações simples. Outros tipos declarados no mesmo pacote. Tipos importados implicitamente através de importações a pedido. Se depois de todos estes passos o tipo ainda não é encontrado, dá um erro de compilação. LEEC@IST Java – 10/54 Pacotes (8) • Quando dois pacotes contém um tipo com o mesmo nome o programador pode: – Referir-se a ambos através do nome qualificado, i.e., pacote1.IdTipo e pacote2.IdTipo. – Importar apenas pacote1.IdTipo, ou pacote1.*, e usar apenas IdTipo para se referir a pacote1.IdTipo, e usar sempre o nome qualificado para pacote2.IdTipo. – Fazer o contrário, i.e., importar apenas pacote2.IdTipo, ou pacote2.*, e usar apenas IdTipo para se referir a pacote2.IdTipo, e usar sempre o nome qualificado para pacote1.IdTipo. – Importar tudo de ambos os pacotes pacote1.* e pacote2.*, e usar o nome qualificado pacote1.IdTipo e pacote2.IdTipo no código (se um tipo com o mesmo identificador existe numa importação a pedido, não é possível usar o nome simples em ambos os tipos). LEEC@IST Java – 11/54 Pacotes (9) • Existem apenas duas opções de visibilidade para classes e interfaces (não aninhadas) num pacote: pacote e public. – Uma classe ou interface pública é acessível em código fora do pacote. – Por omissão do qualificador de visibilidade, uma classe ou interface é acessível apenas no código dentro do mesmo pacote. • Os tipos são escondidos para fora do pacote. • Os tipos são escondidos para subpacotes. LEEC@IST Java – 12/54 Pacotes (10) • Por omissão do qualificador de visibilidade, um membro de uma classe é visível dentro do correspondente pacote, e apenas dentro deste. • Membros de uma classe não declarados private num pacote são visíveis em todo o pacote. • Todos os membros de uma interface são implicitamente public. LEEC@IST Java – 13/54 Pacotes (11) • Um método só pode ser redefinido numa subclasse se for acessível na superclasse. • Quando um método é chamado, em tempo de execução o sistema tem de considerar a acessibilidade do método para decidir qual a implementação a usar... LEEC@IST Java – 14/54 Pacotes (12) package p1; public abstract class SuperClasseAbstracta { private void pri() {print(“SuperClasseAbstracta.pri()”);} void pac() {print(“SuperClasseAbstracta.pac()”);} protected void pro() {print(“SuperClasseAbstracta.pro()”);} public void pub() {print(“SuperClasseAbstracta.pub()”);} public final void imprimir() { pri(); pac(); pro(); pub(); } } LEEC@IST Java – 15/54 Pacotes (13) package p2; import p1.SuperClasseAbstracta; public class SubClasseConcreta1 extends SuperClasseAbstracta { public void pri() {print(“SubClasseConcreta1.pri()”);} public void pac() {print(“SubClasseConcreta1.pac()”);} public void pro() {print(“SubClasseConcreta1.pro()”);} public void pub() {print(“SubClasseConcreta1.pub()”);} } A chamada de new SubClasseConcreta1().imprimir(); imprime no terminal SuperClasseAbstracta.pri() SuperClasseAbstracta.pac() SubClasseConcreta1.pro() SubClasseConcreta1.pub() LEEC@IST Java – 16/54 Pacotes (14) package p1; import p2.SubClasseConcreta1; public class SubClasseConcreta2 extends SubClasseConcreta1 { public void pri() {print(“SubClasseConcreta2.pri()”);} public void pac() {print(“SubClasseConcreta2.pac()”);} public void pro() {print(“SubClasseConcreta2.pro()”);} public void pub() {print(“SubClasseConcreta2.pub()”);} } A chamada de new SubClasseConcreta2().imprimir(); imprime no terminal SuperClasseAbstracta.pri() SubClasseConcreta2.pac() SubClasseConcreta2.pro() SubClasseConcreta2.pub() LEEC@IST Java – 17/54 Pacotes (15) package p3; import p1.SubClasseConcreta2; public class SubClasseConcreta3 extends SubClasseConcreta2 { public void pri() {print(“SubClasseConcreta3.pri()”);} public void pac() {print(“SubClasseConcreta3.pac()”);} public void pro() {print(“SubClasseConcreta3.pro()”);} public void pub() {print(“SubClasseConcreta3.pub()”);} } A chamada de new SubClasseConcreta3().imprimir(); imprime no terminal SuperClasseAbstracta.pri() SubClasseConcreta3.pac() SubClasseConcreta3.pro() SubClasseConcreta3.pub() LEEC@IST Java – 18/54 Excepções – revisão (1) • Frequentemente as aplicações informáticas são sujeitas a situações anómalas: – Erros matemáticos (por exemplo, divisões por 0). – Dados indicados em formato inválido (por exemplo, inteiro inserido com caracteres inválidos). – Tentativa de acesso a uma referência nula. – Abertura para leitura de ficheiro inexistente. – … LEEC@IST Java – 19/54 Excepções – revisão (2) • Uma excepção é um sinal lançado ao ser identificada uma condição que impede a execução normal do programa. • As excepções podem ser tratadas de formas diversas: – Termina o programa com mensagem de aviso e impressão do estado (inaceitável para sistemas críticos). – Geridas em locais específicos, designados por manipuladores (handlers). LEEC@IST Java – 20/54 Tratamento de excepções (1) • No Java, uma excepção é um objecto da classe Exception, derivada da classe Throwable. • A gestão de excepções é feita nos seguintes passos: 1. Delimitar código onde podem ser geradas excepções (numa cláusula try). 2. Se for detectada situação anómala, lançar uma excepção (numa cláusula throw). 3. Inserir manipulador para as excepções lançadas (numa cláusula catch). LEEC@IST Java – 21/54 Tratamento de excepções (2) • Formato típico dos métodos: try { instruções com testes tipo: if (teste) throw new IdExcepção(String); } catch(IdExcepção1 e) { instruções } catch (IdExcepção2 e) { instruções //... tantos catches quantos necessários } finally { instruções } LEEC@IST Java – 22/54 Tratamento de excepções (3) • Uma cláusula try tem de ter pelo menos uma cláusula catch, ou finally. • Podem estar associadas a uma cláusula try qualquer número de cláusulas catch, incluindo zero, desde que cada cláusula apanhe diferentes tipos de excepções. • O corpo do try é executado até que uma excepção é lançada. Caso nenhuma excepção seja lançada o corpo do try termina com sucesso. LEEC@IST Java – 23/54 Tratamento de excepções (4) • Se uma excepção é lançada, cada cláusula catch é examinada, da primeira para a última, até que se encontre uma cujo tipo da excepção tratada seja compatível com a excepção lançada. – Se a cláusula catch é encontrada, é executado o seu corpo. Mais nenhuma cláusula catch é executada. – Se nenhuma cláusula catch é encontrada no respectivo método, a excepção passa de método para método na pilha de execução, até que uma cláusula try trate a excepção lançada. • LEEC@IST Caso esta cláusula nunca seja encontrada o programa termina abruptamente. Java – 24/54 Tratamento de excepções (5) • Não é possível definir numa cláusula catch para apanhar excepções duma superclasse, antes duma cláusula catch para apanhar excepções duma subclasse. – A primeira cláusula catch apanharia sempre a excepção, e a segunda cláusula catch nunca seria atingida. – Por prevenção, existe um erro de compilação nestas situações. • Se o try tem uma cláusula finally, o seu código é executado após todo o processamento no try estar completo (independentemente de o ter feito com sucesso, através do lançamento de uma excepção, ou com instruções return ou break). LEEC@IST Java – 25/54 Tratamento de excepções (6) • Apenas uma excepção é tratada numa cláusula try. Se uma cláusula catch ou finally lançarem outra excepção, as cláusulas catch da cláusula try não são reexaminadas. – As cláusulas catch e finally encontram-se fora da protecção da correspondente cláusula try. – Tais excepções são passadas de método para método na pilha de execução e poderão, contudo, vir a ser tratadas noutra cláusula try. LEEC@IST Java – 26/54 Tratamento de excepções (7) • Tipicamente, no tratamento de excepções são executadas as seguintes acções: 1. Registar mensagem de erro. 2. Recuperar o estado do objecto. 3. Chamar novamente o método de onde foi gerada a excepção. LEEC@IST Java – 27/54 Tratamento de excepções (8) • Se o programa gerar uma excepção, e no código não se indicar o bloco catch para tratamento da excepção, o JVM: 1. Aborta execução do programa. 2. Imprime no System.err a excepção gerada e a pilha de execução. LEEC@IST Java – 28/54 Classes de excepções <<abstract>> Object <<abstract>> Throwable Error LinkageError LEEC@IST Exception ... RuntimeException ... Java – 29/54 Classe Throwable (1) • Superclasse de todos os erros e excepções. • Apenas objectos desta classe, ou subclasses, podem ser lançados na instrução throw e usados como argumentos da clásula catch . LEEC@IST Java – 30/54 Classe Throwable (2) • Construtores: Throwable() Constrói um novo Throwable sem mensagem detalhada. Throwable(String message) Constrói um novo Throwable com a mensagem detalhada recebida. Throwable(String message, Throwable cause) Constrói um novo Throwable com a mensagem detalhada e causa recebidas. Throwable(Throwable cause) Constrói um novo Throwable com a causa recebida e mensagem detalhada (cause==null ? null : cause.toString()) (que tipicamente contém a classe e a mensagem detalhada da causa). LEEC@IST Java – 31/54 Classe Throwable (3) • Alguns métodos: Throwable initCause(Throwable cause) Inicializa a causa deste Throwable com a causa recebida como parâmetro. Este método pode ser chamado apenas uma vez, normalmente é chamado directamente no construtor, ou imediatamente após a sua construção. Se este Throwable foi criado com Throwable(Throwable) ou Throwable(String,Throwable) este método nunca pode ser chamado. Throwable getCause() Retorna a causa deste Throwable, ou null. String getMessage() Retorna a messagem detalhada associada. void printStackTrace() Imprime no System.err a pilha de chamada dos métodos. LEEC@IST Java – 32/54 Classe Error • Usada para lançar excepções por falha do JVM. • Normalmente não são tratadas pelo programador. LEEC@IST Java – 33/54 Classe Exception (1) • J2SE dispõe 55 subclasses: – ClassNotFoundException – IOException (contém 21 subclasses) – ... • Excepções do programador são subclasses de Exception. LEEC@IST Java – 34/54 Classe Exception (2) • Construtores: Exception() Constrói uma nova Exception sem mensagem detalhada. Exception(String message) Constrói uma nova Exception com a mensagem detalhada recebida. Exception(String message, Throwable cause) Constrói uma nova Exception com a mensagem detalhada e causa recebidas. Exception(Throwable cause) Constrói uma nova Exception com a causa recebida e mensagem detalhada (cause==null ? null : cause.toString()) (que tipicamente contém a classe e a mensagem detalhada da causa). • A classe Exception não introduz novos métodos. LEEC@IST Java – 35/54 Exemplo (1) • Exemplo de excepção do utilizador (divisão por zero): public class ExDivisãoPorZero extends Exception { public ExDivisãoPorZero() { super(“Divisão por zero”); } public ExDivisãoPorZero(String message) { super(message); } } LEEC@IST Java – 36/54 Exemplo (2) public class Divide { protected int op1; public Divide() { op1=20; } public int divide(int op2) { try { if (op2==0) throw new ExDivisãoPorZero(); return op1/op2; } catch (ExDivisãoPorZero e) { System.out.println(e); return –1; } } } LEEC@IST Java – 37/54 Exemplo (3) public static void main (String args[]){ if (args.length!=1) { System.out.println(“Um numero apenas!”); System.out.exit(0); } Divide d = new Divide(); int result = d.divide(Integer.parseInt(args[0],10)); if (result==-1) System.exit(1); else { System.out.println(d.val()+“/”+args[0]+”=“+result); System.exit(0); } } LEEC@IST Java – 38/54 Transferência de excepções (1) • Frequentemente é de interesse a excepção ser tratada pelo objecto que chama o método (normalmente a recuperação depende do objecto que o chama). • O método, onde a excepção pode ser gerada, deve indicar no cabeçalho throws IdExcepção • No exemplo da divisão por zero, o método divide seria alterado para: public int divide(int op2) throws ExDivisãoPorZero { if (op2==0) throw new ExDivisãoPorZero(); return op1/op2; } LEEC@IST Java – 39/54 Transferência de excepções (2) public static void main(String args[]) { if (args.length!=1) { System.out.println(“Um numero apenas!”); System.out.exit(0); } int result; Divide d = new Divide(); try { result = d.divide(Integer.parseInt(args[0],10)); System.out.println(d.op1()+”/”+args[0]+”=“+result); System.exit(0); } catch (ExDivisãoPorZero e) { System.out.println(e); System.exit(1); } } LEEC@IST Java – 40/54 Outro exemplo (1) public void lerFicheiroDados(String nomeFicheiro) throws FicheiroDadosInválido { String ficheiro = nomeFicheiro + “.fasta”; FileInputStream in = null; try { in = new FileInputStream(ficheiro); parsarFicheiroDados(in); } catch (IOException e) { throw new FicheiroDadosInválido(); } finally { try { if (in!=null) in.close(); } catch (IOException e) { //ignorar: ou lemos os dados correctamente // ou lançamos FicheiroDadosInválido } } } LEEC@IST Java – 41/54 Outro exemplo (2) • • No exemplo anterior tanto o construtor FileInputStream, como o método parsarFicheiroDados podem lançar excepções do tipo IOException. Quando é criada a excepção FicheiroDadosInválido é perdida informação da excepção que foi lançada. – Criar novas excepções em substituição de outras é um mecanismos importante para subir o nível de abstracção. – Mas como fazê-lo sem perder informação que pode ser necessária para se proceder à recuperação da excepção? • Há duas soluções: 1. Usar o método initCause da classe Throwable. 2. Definir construtores da nova excepção que aceite um Throwable. LEEC@IST Java – 42/54 Outro exemplo (3) } catch (IOException e) { FicheiroDadosInválido fdi = new FicheiroDadosInválido(); fdi.initCause(e); throw fdi; } } catch (IOException e) { throw (FicheiroDadosInválido) new FicheiroDadosInválido().initCause(e); } LEEC@IST Java – 43/54 Outro exemplo (4) public class FicheiroDadosInválido extends Exception { public FicheiroDadosInválido() {} public FicheiroDadosInválido(String msg) { super(msg); } public FicheiroDadosInválido(Throwable causa) { super(causa); } public FicheiroDadosInválido(String msg, Throwable causa) { super(msg, causa); } } } catch (IOException e) { throw new FicheiroDadosInválido(e); } LEEC@IST Java – 44/54 Asserções – definição • Uma asserção serve para verificar um invariante. • Um invariante é uma condição que deve ser sempre verdadeira. LEEC@IST Java – 45/54 Asserções (1) Sintaxe: assert expr1 [: expr2] • expr1 é uma expressão boolean ou Boolean. – Quando é encontrado um assert no código: • A expr1 é avaliada: – Se expr1 for true, a asserção passa. – Se expr1 for false, a asserção falha, e um AssertionError é construído e lançado. LEEC@IST Java – 46/54 Asserções (2) • expr2 é uma expressão opcional que será passada ao construtor do AssertionError para descrever o problema encontrado. – Se expr2 for um Throwable será o valor retornado pelo método getCause do correspondente AssertionError. – Caso contrário, expr2 é convertida numa String e será a mensagem de detalhe do correspondente AssertionError. LEEC@IST Java – 47/54 Asserções (3) • Não devem ser usadas asserções para testar maus funcionamentos que às vezes acontecem (IOException, NullPointerException, ...). • As asserções devem ser usadas apenas para testar condições que nunca devem falhar num programa correcto. Por exemplo: – Testar se o estado corrente do objecto é correcto. – Verificar o fluxo do código. LEEC@IST Java – 48/54 Asserções (4) • Testar se o estado corrente do objecto é correcto: ao remover um objecto de um conjunto de objectos, se o objecto foi encontrado e removido é suposto o conjunto ter menos 1 elemento. public boolean remover(Object objecto) { assert numElementos >= 0; if (objecto==null) throw NullPointerException(“remover: objecto null”); int aux = numElementos; boolean objEncontrado = false; try { //remover objecto do conjunto (se existir) return objEncontrado; } finally { assert ((objEncontrado==false && numElementos==aux) || (objEncontrado==true && numElementos==aux-1); } } LEEC@IST Java – 49/54 Asserções (5) • Verificar o fluxo do código: neste caso, quer-se proceder à substituição do valor antigo da tabela valores pelo valor novo, assegurando-nos que o valor antigo está sempre presente na tabela valores. private void substituir(int antigo, int novo) { for (int i=0; i<valores.length; i++) { if (valores [i ] == antigo) { valores [i ] = novo; return; } } assert false : “substituir: valor ”+antigo+”não encontrado”; } LEEC@IST Java – 50/54 Asserções (6) • Por omissão, as asserções não são avaliadas. – É possível ligar e desligar as asserções (para pacotes e classes). – Quando as asserções estão desligadas não são avaliadas, por isso as expressões das asserções não devem produzir efeitos colaterias. assert ++i < max; LEEC@IST i++; assert i < max; Java – 51/54 Asserções (7) • Para ligar e desligar as asserções: – Correr a aplicação na linha de comando com as seguintes opções (no NetBeans Properties do projecto > Run > VM Options): • -ea[:IdPacote ][.IdSubPacotes ]*[.IdClasse ] Liga as asserções para os pacotes/classes dadas. Se não forem dados pacotes/classes as asserções são ligadas para todas as classes (excepto classes de sistema). • -da[:IdPacote ][.IdSubPacotes ]*[.IdClasse ] Desliga as asserções para os pacotes/classes dadas. Se não forem dados pacotes/classes as asserções são desligadas para todas as classes. – No caso dos pacotes pode usar-se IdPacote... para aplicar a mesma opção a todos os subpacotes. LEEC@IST Java – 52/54 Asserções (8) • Remover por completo as asserções: private static final asserçõesLigadas = false; if (asserçõesLigadas) assert false : “asserções não estão desligadas”; LEEC@IST Java – 53/54 Asserções (9) • Tornando as asserções obrigatórias (uso propositado de efeitos colaterais na asserção): static { boolean asserçõesLigadas = false; assert asserçõesLigadas = true; if (!asserçõesLigadas) throw new IllegalStateException(“Asserções necessárias”); } LEEC@IST Java – 54/54