3. Classes e interfaces
3.1. O objeto explicado com exemplos
3.1.1. Visão geral
Vamos agora explorar a programação orientada a objetos através de exemplos. Um objeto é uma entidade que contém dados que definem o seu estado (chamados atributos ou propriedades) e funções (chamadas métodos). Um objeto é criado com base num modelo chamado classe:
public class C1{
type1 p1; // property p1
type2 p2; // property p2
…
type3 m3(…){ // m3 method
…
}
type4 m4(…){ // m4 method
…
}
…
}
A partir da classe anterior C1, podemos criar muitos objetos O1, O2, … Todos terão as propriedades p1, p2, … e os métodos m3, m4, … Terão valores diferentes para as suas propriedades pi, tendo assim cada um o seu próprio estado.
Se O1 é um objeto do tipo C1, então O1.p1 refere-se à propriedade p1 de O1, e O1.m1 refere-se ao método m1 de O1.
Vamos considerar um primeiro modelo de objeto: a classe Pessoa.
3.1.2. Definição da classe Pessoa
A definição da classe Person é a seguinte:
import java.io.*;
public class personne{
// attributes
private String prenom;
private String nom;
private int age;
// method
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
// method
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
}
Aqui temos a definição de uma classe, que é um tipo de dados. Quando criamos variáveis deste tipo, chamamos-lhes objetos. Uma classe é, portanto, um modelo a partir do qual os objetos são construídos.
Os membros ou campos de uma classe podem ser dados ou métodos (funções). Estes campos podem ter um dos três atributos seguintes:
private: Um campo privado é acessível apenas pelos métodos internos da classe
público: Um campo público é acessível por qualquer função, independentemente de estar ou não definido dentro da classe
protected: Um campo protegido é acessível apenas pelos métodos internos da classe ou por um objeto derivado (ver o conceito de herança mais adiante).
Geralmente, os dados de uma classe são declarados como privados, enquanto os seus métodos são declarados como públicos. Isto significa que o utilizador de um objeto (o programador)
a: não terá acesso direto aos dados privados do objeto
b: poderá chamar os métodos públicos do objeto, incluindo aqueles que fornecem acesso aos seus dados privados.
A sintaxe para declarar um objeto é a seguinte:
public class nomClasse{
private donnée ou méthode privée
public donnée ou méthode publique
protected donnée ou méthode protégée
}
Notas
- A ordem em que os atributos privados, protegidos e públicos são declarados é arbitrária.
3.1.3. O método initialize
Voltemos à nossa classe Person, declarada da seguinte forma:
import java.io.*;
public class personne{
// attributes
private String prenom;
private String nom;
private int age;
// method
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
// method
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
}
Qual é a função do método initialize? Como lastName, firstName e age são membros privados da classe Person, as instruções
são inválidas. Temos de inicializar um objeto do tipo Pessoa utilizando um método público. Esta é a função do método initialize. Escrevemos:
A sintaxe p1.initialize é válida porque initialize é público.
3.1.4. O operador new
A sequência de instruções
está incorreta. A afirmação
declara p1 como uma referência a um objeto do tipo pessoa. Este objeto ainda não existe, pelo que p1 não é inicializado. É como se tivéssemos escrito:
onde indicamos explicitamente com a palavra-chave null que a variável p1 ainda não faz referência a nenhum objeto.
Quando escrevemos então
estamos a chamar o método initialize do objeto referenciado por p1. No entanto, este objeto ainda não existe, e o compilador irá reportar um erro. Para que p1 refira um objeto, temos de escrever:
Isto cria um objeto Person não inicializado: os atributos name e first_name, que são referências a objetos String, terão o valor null, e age terá o valor 0. Existe, portanto, uma inicialização por defeito. Agora que p1 faz referência a um objeto, a instrução de inicialização para este objeto
é válida.
3.1.5. A palavra-chave this
Vejamos o código do método initialize:
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
A instrução this.firstName = P significa que ao atributo firstName do objeto atual (this) é atribuído o valor P. A palavra-chave this refere-se ao objeto atual: aquele no qual reside o método que está a ser executado. Como sabemos isto? Vejamos como o objeto referenciado por p1 é inicializado no programa de chamada:
É o método initialize do objeto p1 que é chamado. Quando referenciamos o objeto this dentro deste método, estamos, na verdade, a referenciar o objeto p1. O método initialize também poderia ter sido escrito da seguinte forma:
public void initialise(String P, String N, int age){
prenom=P;
nom=N;
this.age=age;
}
Quando um método de um objeto faz referência a um atributo A desse objeto, a notação this.A está implícita. Deve ser usada explicitamente quando houver um conflito de identificadores. É o caso da instrução:
this.age=age;
onde age se refere tanto a um atributo do objeto atual como ao parâmetro age recebido pelo método. A ambiguidade deve então ser resolvida referindo-se ao atributo age como this.age.
3.1.6. Um programa de teste
Eis um programa de teste:
public class test1{
public static void main(String arg[]){
personne p1=new personne();
p1.initialise("Jean","Dupont",30);
p1.identifie();
}
}
A classe Person está definida no ficheiro fonte person.java e é compilada:
E:\data\serge\JAVA\BASES\OBJETS\2>javac personne.java
E:\data\serge\JAVA\BASES\OBJETS\2>dir
10/06/2002 09:21 473 personne.java
10/06/2002 09:22 835 personne.class
10/06/2002 09:23 165 test1.java
Fazemos o mesmo para o programa de teste:
E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java
E:\data\serge\JAVA\BASES\OBJETS\2>dir
10/06/2002 09:21 473 personne.java
10/06/2002 09:22 835 personne.class
10/06/2002 09:23 165 test1.java
10/06/2002 09:25 418 test1.class
Pode parecer surpreendente que o programa test1.java não importe a classe person com uma instrução:
Quando o compilador encontra uma referência a uma classe no código-fonte que não está definida nesse mesmo ficheiro-fonte, procura a classe em vários locais:
- nos pacotes importados pelas instruções import
- no diretório a partir do qual o compilador foi iniciado
No nosso exemplo, o compilador foi iniciado a partir do diretório que contém o ficheiro personne.class, o que explica por que razão encontrou a definição da classe personne. Neste cenário, adicionar uma instrução de importação causa um erro de compilação:
E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java
test1.java:1: '.' expected
import personne;
^
1 error
Para evitar este erro, mas garantir que a classe Person é importada, escreveremos o seguinte no início do programa no futuro:
Agora podemos executar o ficheiro test1.class:
É possível combinar várias classes num único ficheiro fonte. Vamos combinar as classes person* e test1 no ficheiro fonte test2.java*. A classe test1 é renomeada para test2 para refletir a alteração no nome do ficheiro fonte:
// imported packages
import java.io.*;
class personne{
// attributes
private String prenom; // first name
private String nom; // its name
private int age; // his age
// method
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}//initialize
// method
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}//identifies
}//class
public class test2{
public static void main(String arg[]){
personne p1=new personne();
p1.initialise("Jean","Dupont",30);
p1.identifie();
}
}
Note que a classe Person já não tem o atributo public. Na verdade, num ficheiro de código-fonte Java, apenas uma classe pode ter o atributo public. Trata-se da classe que contém o método main. Além disso, o ficheiro de código-fonte deve ter o nome dessa classe. Vamos compilar o ficheiro *test2.java*:
E:\data\serge\JAVA\BASES\OBJETS\3>dir
10/06/2002 09:36 633 test2.java
E:\data\serge\JAVA\BASES\OBJETS\3>javac test2.java
E:\data\serge\JAVA\BASES\OBJETS\3>dir
10/06/2002 09:36 633 test2.java
10/06/2002 09:41 832 personne.class
10/06/2002 09:41 418 test2.class
Note que foi gerado um ficheiro .class para cada uma das classes presentes no ficheiro fonte. Agora vamos executar o ficheiro test2.class:
A partir de agora, utilizaremos ambos os métodos de forma intercambiável:
- classes agrupadas num único ficheiro fonte
- uma classe por ficheiro fonte
3.1.7. Outro método inicializa
Vamos continuar com a classe Person e adicionar-lhe o seguinte método:
public void initialise(personne P){
prenom=P.prenom;
nom=P.nom;
this.age=P.age;
}
Temos agora dois métodos chamados *initialize*: isto é permitido desde que aceitem parâmetros diferentes. É o que acontece aqui. O parâmetro é agora uma referência P a uma pessoa. Os atributos da pessoa P são então atribuídos ao objeto atual (this). Note-se que o método initialize tem acesso direto aos atributos do objeto P, mesmo que estes sejam do tipo privado. Isto é sempre verdade: os métodos de um objeto O1 de uma classe C têm sempre acesso aos atributos privados de outros objetos da mesma classe C.
Aqui está um teste da nova classe Person:
// import nobody;
import java.io.*;
public class test1{
public static void main(String arg[]){
personne p1=new personne();
p1.initialise("Jean","Dupont",30);
System.out.print("p1=");
p1.identifie();
personne p2=new personne();
p2.initialise(p1);
System.out.print("p2=");
p2.identifie();
}
}
e os seus resultados:
3.1.8. Construtores da classe Pessoa
Um construtor é um método que tem o nome da classe e é chamado quando o objeto é criado. É geralmente utilizado para inicializar o objeto. É um método que pode aceitar argumentos, mas não devolve qualquer resultado. O seu protótipo ou definição não é precedido por qualquer tipo (nem mesmo void).
Se uma classe tiver um construtor que aceita n argumentos args, a declaração e a inicialização de um objeto dessa classe podem ser feitas da seguinte forma:
classe objet =new classe(arg1,arg2, ... argn);
ou
classe objet;
…
objet=new classe(arg1,arg2, ... argn);
Quando uma classe tem um ou mais construtores, um desses construtores deve ser utilizado para criar um objeto dessa classe. Se uma classe C não tiver construtores, ela possui um construtor padrão, que é o construtor sem parâmetros: public C(). Os atributos do objeto são então inicializados com valores padrão. Foi isso que aconteceu quando, nos programas anteriores, escrevemos:
Vamos criar dois construtores para a nossa classe Pessoa:
public class personne{
// attributes
private String prenom;
private String nom;
private int age;
// manufacturers
public personne(String P, String N, int age){
initialise(P,N,age);
}
public personne(personne P){
initialise(P);
}
// method
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
public void initialise(personne P){
this.prenom=P.prenom;
this.nom=P.nom;
this.age=P.age;
}
// method
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
}
Os nossos dois construtores limitam-se a chamar os métodos initialize correspondentes. Recorde-se que, quando, num construtor, encontramos a notação initialize(P), por exemplo, o compilador traduz-a para this.initialize(P). No construtor, o método initialize é, portanto, chamado para operar sobre o objeto referenciado por this, ou seja, o objeto atual, aquele que está a ser construído.
Eis um programa de teste:
// import nobody;
import java.io.*;
public class test1{
public static void main(String arg[]){
personne p1=new personne("Jean","Dupont",30);
System.out.print("p1=");
p1.identifie();
personne p2=new personne(p1);
System.out.print("p2=");
p2.identifie();
}
}
e os resultados obtidos:
3.1.9. Referências de objetos
Continuamos a utilizar a mesma classe Pessoa. O programa de teste fica assim:
// import nobody;
import java.io.*;
public class test1{
public static void main(String arg[]){
// p1
personne p1=new personne("Jean","Dupont",30);
System.out.print("p1="); p1.identifie();
// p2 references the same object as p1
personne p2=p1;
System.out.print("p2="); p2.identifie();
// p3 references an object that will be a copy of the object referenced by p1
personne p3=new personne(p1);
System.out.print("p3="); p3.identifie();
// change the state of the object referenced by p1
p1.initialise("Micheline","Benoît",67);
System.out.print("p1="); p1.identifie();
// as p2=p1, the object referenced by p2 must have changed state
System.out.print("p2="); p2.identifie();
// as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
System.out.print("p3="); p3.identifie();
}
}
Os resultados são os seguintes:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
Ao declarar a variável p1 utilizando
p1 faz referência ao objeto person("Jean","Dupont",30), mas não é o próprio objeto. Em C, diríamos que é um ponteiro, ou seja, o endereço do objeto criado. Se escrevermos então:
Não é o objeto person("Jean","Dupont",30) que é modificado; em vez disso, é a referência p1 que altera o seu valor. O objeto person("Jean","Dupont",30) será "perdido" se não for referenciado por nenhuma outra variável.
Quando escrevemos:
inicializamos o ponteiro p2: ele «aponta» para o mesmo objeto (refere-se ao mesmo objeto) que o ponteiro p1. Assim, se modificarmos o objeto «apontado» (ou referenciado) por p1, modificamos aquele referenciado por p2.
Quando escrevemos:
é criado um novo objeto, que é uma cópia do objeto referenciado por p1. Este novo objeto será referenciado por p3. Se modificar o objeto «apontado» (ou referenciado) por p1, não modifica de forma alguma o objeto referenciado por p3. É isso que os resultados mostram.
3.1.10. Objetos temporários
Numa expressão, pode chamar explicitamente o construtor de um objeto: o objeto é criado, mas não pode aceder-lhe (para o modificar, por exemplo). Este objeto temporário é criado com o objetivo de avaliar a expressão e, em seguida, descartado. O espaço de memória que ocupou será automaticamente recuperado mais tarde por um programa chamado «garbage collector», cuja função é recuperar o espaço de memória ocupado por objetos que já não são referenciados pelos dados do programa.
Considere o seguinte exemplo:
// import nobody;
public class test1{
public static void main(String arg[]){
new personne(new personne("Jean","Dupont",30)).identifie();
}
}
e vamos modificar os construtores da classe Pessoa para que exibam uma mensagem:
// manufacturers
public personne(String P, String N, int age){
System.out.println("Constructeur personne(String, String, int)");
initialise(P,N,age);
}
public personne(personne P){
System.out.println("Constructeur personne(personne)");
initialise(P);
}
Obtemos os seguintes resultados:
mostrando a construção sucessiva dos dois objetos temporários.
3.1.11. Métodos para ler e escrever atributos privados
Adicionamos os métodos necessários à classe Pessoa para ler ou modificar o estado dos atributos dos objetos:
public class personne{
private String prenom;
private String nom;
private int age;
public personne(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
public personne(personne P){
this.prenom=P.prenom;
this.nom=P.nom;
this.age=P.age;
}
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
// accessors
public String getPrenom(){
return prenom;
}
public String getNom(){
return nom;
}
public int getAge(){
return age;
}
//modifiers
public void setPrenom(String P){
this.prenom=P;
}
public void setNom(String N){
this.nom=N;
}
public void setAge(int age){
this.age=age;
}
}
Testamos a nova classe com o seguinte programa:
// import nobody;
public class test1{
public static void main(String[] arg){
personne P=new personne("Jean","Michelin",34);
System.out.println("P=("+P.getPrenom()+","+P.getNom()+","+P.getAge()+")");
P.setAge(56);
System.out.println("P=("+P.getPrenom()+","+P.getNom()+","+P.getAge()+")");
}
}
e obtemos os seguintes resultados:
3.1.12. Métodos e atributos da classe
Suponhamos que queremos contar o número de objetos Pessoa criados numa aplicação. Poderíamos gerir um contador nós próprios, mas corremos o risco de esquecer objetos temporários que são criados aqui e ali. Pareceria mais seguro incluir uma instrução nos construtores da classe Pessoa que incremente um contador. O problema é passar uma referência a este contador para que o construtor o possa incrementar: precisamos de lhes passar um novo parâmetro. Também podemos incluir o contador na definição da classe. Uma vez que se trata de um atributo da própria classe e não de um objeto específico dessa classe, declaramo-lo de forma diferente utilizando a palavra-chave static:
Para fazer referência a ele, escrevemos person.nbPeople para indicar que se trata de um atributo da própria classe Person. Aqui, criámos um atributo privado que não pode ser acedido diretamente a partir do exterior da classe. Criamos, portanto, um método público para fornecer acesso ao atributo de classe nbPersonnes. Para devolver o valor de nbPersonnes, o método não necessita de um objeto específico: na verdade, nbPersonnes não é o atributo de um objeto específico; é o atributo de toda a classe. Por conseguinte, precisamos de um método de classe que também seja declarado estático:
que será chamado a partir do exterior utilizando a sintaxe person.getNbPeople(). Aqui está um exemplo.
A classe Person fica da seguinte forma:
public class personne{
// class attribute
private static long nbPersonnes=0;
// object attributes
…
// manufacturers
public personne(String P, String N, int age){
initialise(P,N,age);
nbPersonnes++;
}
public personne(personne P){
initialise(P);
nbPersonnes++;
}
// method
…
// class method
public static long getNbPersonnes(){
return nbPersonnes;
}
}// class
Com o seguinte programa:
// import nobody;
public class test1{
public static void main(String arg[]){
personne p1=new personne("Jean","Dupont",30);
personne p2=new personne(p1);
new personne(p1);
System.out.println("Nombre de personnes créées : "+personne.getNbPersonnes());
}// hand
}//test1
Obtemos os seguintes resultados:
3.1.13. Passar um objeto para uma função
Já mencionámos que o Java passa os parâmetros reais da função por valor: os valores dos parâmetros reais são copiados para os parâmetros formais. Uma função não pode, portanto, modificar os parâmetros reais.
No caso de um objeto, não se deve deixar enganar pelo uso incorreto comum da linguagem que ocorre quando as pessoas se referem sistematicamente a um «objeto» em vez de a uma «referência a um objeto». Um objeto é manipulado apenas por meio de uma referência (um ponteiro) a ele. O que é passado para uma função, portanto, não é o próprio objeto, mas uma referência a esse objeto. É, assim, o valor da referência — e não o valor do próprio objeto — que é copiado para o parâmetro formal: nenhum novo objeto é criado.
Se uma referência a um objeto R1 for passada para uma função, ela será copiada para o parâmetro formal correspondente R2. Assim, as referências R2 e R1 apontam para o mesmo objeto. Se a função modificar o objeto apontado por R2, obviamente modificará aquele referenciado por R1, uma vez que são o mesmo.

Isto é ilustrado pelo seguinte exemplo:
// import nobody;
public class test1{
public static void main(String arg[]){
personne p1=new personne("Jean","Dupont",30);
System.out.print("Paramètre effectif avant modification : ");
p1.identifie();
modifie(p1);
System.out.print("Paramètre effectif après modification : ");
p1.identifie();
}// hand
private static void modifie(personne P){
System.out.print("Paramètre formel avant modification : ");
P.identifie();
P.initialise("Sylvie","Vartan",52);
System.out.print("Paramètre formel après modification : ");
P.identifie();
}// modify
}// class
O método modify é declarado como estático porque é um método de classe: não é necessário prefixá-lo com um objeto para o chamar. Os resultados obtidos são os seguintes:
Constructeur personne(String, String, int)
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
Podemos ver que apenas um objeto é construído: o da pessoa p1 na função principal, e que o objeto foi efetivamente modificado pela função modify.
3.1.14. Encapsulando os parâmetros de saída de uma função num objeto
Como os parâmetros são passados por valor, não é possível escrever uma função Java com parâmetros de saída do tipo int, por exemplo, uma vez que não podemos passar uma referência a um tipo int que não seja um objeto. Podemos, portanto, criar uma classe que encapsule o tipo int:
public class entieres{
private int valeur;
public entieres(int valeur){
this.valeur=valeur;
}
public void setValue(int valeur){
this.valeur=valeur;
}
public int getValue(){
return valeur;
}
}
A classe anterior possui um construtor para inicializar um inteiro e dois métodos para ler e modificar o valor desse inteiro. Testamos esta classe com o seguinte programa:
// import integer;
public class test2{
public static void main(String[] arg){
entieres I=new entieres(12);
System.out.println("I="+I.getValue());
change(I);
System.out.println("I="+I.getValue());
}
private static void change(entieres entier){
entier.setValue(15);
}
}
e obtemos os seguintes resultados:
3.1.15. Um conjunto de pessoas
Um objeto é um dado como qualquer outro e, como tal, vários objetos podem ser agrupados numa matriz:
// import nobody;
public class test1{
public static void main(String arg[]){
personne[] amis=new personne[3];
System.out.println("----------------");
amis[0]=new personne("Jean","Dupont",30);
amis[1]=new personne("Sylvie","Vartan",52);
amis[2]=new personne("Neil","Armstrong",66);
int i;
for(i=0;i<amis.length;i++)
amis[i].identifie();
}
}
A instrução person[] friends = new person[3]; cria uma matriz de 3 elementos do tipo person. Estes 3 elementos são inicializados aqui com o valor null, o que significa que não referenciam quaisquer objetos. Mais uma vez, num sentido técnico, referimo-nos a uma «matriz de objetos» quando, na realidade, se trata apenas de uma matriz de referências a objetos. A criação da matriz de objetos — uma matriz que é ela própria um objeto (conforme indicado pelo uso de new) — não cria, por si só, quaisquer objetos do tipo dos seus elementos: isto deve ser feito posteriormente.
Os seguintes resultados são obtidos:
----------------
Constructeur personne(String, String, int)
Constructeur personne(String, String, int)
Constructeur personne(String, String, int)
Jean,Dupont,30
Sylvie,Vartan,52
Neil,Armstrong,66
3.2. Herança através de exemplos
3.2.1. Visão geral
Aqui discutimos o conceito de herança. O objetivo da herança é «personalizar» uma classe existente para que ela atenda às nossas necessidades. Suponhamos que queremos criar uma classe Professor: um professor é um tipo específico de pessoa. Possui atributos que outras pessoas não têm: a disciplina que leciona, por exemplo. Mas também possui os atributos de qualquer pessoa: nome próprio, apelido e idade. Um professor é, portanto, um membro de pleno direito da classe Pessoa, mas possui atributos adicionais. Em vez de escrever uma classe Professor do zero, preferimos basear-nos na classe Pessoa existente e adaptá-la às características específicas dos professores. É o conceito de herança que nos permite fazer isso.
Para expressar que a classe Professor herda as propriedades da classe Pessoa, escreveríamos:
public class enseignant extends personne
A classe Pessoa é chamada de classe pai (ou base), e a classe Professor é a classe derivada (ou filha). Um objeto Professor possui todas as qualidades de um objeto Pessoa: tem os mesmos atributos e métodos. Esses atributos e métodos da classe pai não são repetidos na definição da classe filha; especificamos simplesmente os atributos e métodos adicionados pela classe filha:
class enseignant extends personne{
// attributes
private int section;
// manufacturer
public enseignant(String P, String N, int age,int section){
super(P,N,age);
this.section=section;
}
}
Partimos do princípio de que a classe Pessoa está definida da seguinte forma:
public class personne{
private String prenom;
private String nom;
private int age;
public personne(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
public personne(personne P){
this.prenom=P.prenom;
this.nom=P.nom;
this.age=P.age;
}
public String identite(){
return "personne("+prenom+","+nom+","+age+")";
}
// accessors
public String getPrenom(){
return prenom;
}
public String getNom(){
return nom;
}
public int getAge(){
return age;
}
//modifiers
public void setPrenom(String P){
this.prenom=P;
}
public void setNom(String N){
this.nom=N;
}
public void setAge(int age){
this.age=age;
}
}
O método identifiant foi ligeiramente modificado para devolver uma cadeia de caracteres que identifica a pessoa e passa a chamar-se identite. Aqui, a classe Teacher acrescenta aos métodos e atributos da classe Person:
- um atributo `section`, que é o número da secção a que o professor pertence no corpo docente (basicamente uma secção por disciplina)
- um novo construtor que inicializa todos os atributos de um professor
3.2.2. Criar um objeto Professor
O construtor da classe Professor é o seguinte:
// constructeur
public enseignant(String P, String N, int age,int section){
super(P,N,age);
this.section=section;
}
A instrução super(P, N, age) é uma chamada ao construtor da classe pai, neste caso a classe Person. Sabemos que este construtor inicializa os campos first_name, last_name e age do objeto Person contido no objeto Student. Isto parece bastante complicado, e talvez prefiramos escrever:
// constructeur
public enseignant(String P, String N, int age,int section){
this.prenom=P;
this.nom=N
this.age=age
this.section=section;
}
Isso é impossível. A classe Person declarou os seus três campos — first_name, last_name e age — como privados. Apenas objetos da mesma classe têm acesso direto a esses campos. Todos os outros objetos, incluindo objetos filhos, como neste caso, devem usar métodos públicos para aceder aos mesmos. Isto teria sido diferente se a classe Person tivesse declarado os três campos como protegidos: teria então permitido que as classes derivadas tivessem acesso direto aos três campos. No nosso exemplo, utilizar o construtor da classe pai foi, portanto, a solução correta, e esta é a abordagem padrão: ao construir um objeto filho, chamamos primeiro o construtor do objeto pai e, em seguida, completamos as inicializações específicas do objeto filho (section no nosso exemplo).
Vamos experimentar um primeiro programa:
// import nobody;
// import teacher;
public class test1{
public static void main(String arg[]){
System.out.println(new enseignant("Jean","Dupont",30,27).identite());
}
}
Este programa limita-se a criar um objeto Teacher (new) e a identificá-lo. A classe Teacher não possui um método de identificação, mas a sua classe pai possui um, que também é público: através da herança, este torna-se um método público da classe Teacher.
Os ficheiros fonte das classes são colocados no mesmo diretório e, em seguida, compilados:
E:\data\serge\JAVA\BASES\OBJETS\4>dir
10/06/2002 10:00 765 personne.java
10/06/2002 10:00 212 enseignant.java
10/06/2002 10:01 192 test1.java
E:\data\serge\JAVA\BASES\OBJETS\4>javac *.java
E:\data\serge\JAVA\BASES\OBJETS\4>dir
10/06/2002 10:00 765 personne.java
10/06/2002 10:00 212 enseignant.java
10/06/2002 10:01 192 test1.java
10/06/2002 10:02 316 enseignant.class
10/06/2002 10:02 1 146 personne.class
10/06/2002 10:02 550 test1.class
O ficheiro test1.class é executado:
3.2.3. Sobrecarga de métodos
No exemplo anterior, tínhamos a identidade da pessoa como parte do professor, mas faltam algumas informações específicas da classe Professor (a secção). Por isso, precisamos de escrever um método para identificar o professor:
class enseignant extends personne{
int section;
public enseignant(String P, String N, int age,int section){
super(P,N,age);
this.section=section;
}
public String identite(){
return "enseignant("+super.identite()+","+section+")";
}
}
O método identity da classe Teacher baseia-se no método identity da sua classe pai (super.identity) para apresentar a sua parte "pessoa" e, em seguida, adiciona o campo section, que é específico da classe Teacher.
A classe Teacher tem agora dois métodos identity:
- aquele herdado da classe pai Pessoa
- o seu próprio
Se E for um objeto Teacher, E.identity refere-se ao método de identidade da classe Teacher. Dizemos que o método de identidade da classe pai é «sobrescrito» pelo método de identidade da classe filha. Em geral, se O for um objeto e M for um método, para executar o método O.M, o sistema procura um método M na seguinte ordem:
- na classe do objeto O
- na sua classe pai, se tiver uma
- na classe pai da sua classe pai, se existir
- e assim por diante…
A herança permite, portanto, que métodos com o mesmo nome na classe pai sejam substituídos na classe filha. É isso que permite que a classe filha seja adaptada às suas próprias necessidades. Combinada com o polimorfismo, que discutiremos em breve, a substituição de métodos é o principal benefício da herança.
Vamos considerar o mesmo exemplo de antes:
// import nobody;
// import teacher;
public class test1{
public static void main(String arg[]){
System.out.println(new enseignant("Jean","Dupont",30,27).identite());
}
}
Os resultados obtidos desta vez são os seguintes:
3.2.4. Polimorfismo
Considere uma hierarquia de classes: C0 C1 C2 … Cn
onde Ci Cj indica que a classe Cj é derivada da classe Ci . Isto implica que a classe Cj possui todas as características da classe Ci , além de outras. Sejam Oi objetos do tipo Ci . É válido escrever:
De facto, por herança, a classe Cj possui todas as características da classe Ci, além de outras adicionais. Portanto, um objeto Oj do tipo Cj contém em si mesmo um objeto do tipo Ci. A operação
significa que Oi é uma referência ao objeto de tipo Ci contido no objeto Oj.
O facto de uma variável Oi da classe Ci poder, na verdade, referir-se não só a um objeto da classe Ci, mas a qualquer objeto derivado da classe Ci, é chamado de polimorfismo: a capacidade de uma variável referir-se a diferentes tipos de objetos.
Vamos dar um exemplo e considerar a seguinte função, que é independente de qualquer classe:
A classe Object é a «classe pai» de todas as classes Java. Por isso, quando escrevemos:
estamos implicitamente a escrever:
Assim, todos os objetos Java contêm um componente *Object*. Portanto, podemos escrever:
O parâmetro formal do tipo Object na função display receberá um valor do tipo Teacher. Uma vez que Teacher deriva de Object, isto é válido.
3.2.5. Sobrecarga e Polimorfismo
Vamos completar a nossa função display:
O método obj.toString() devolve uma cadeia de caracteres que identifica o objeto obj na forma nome_da_classe@endereço_do_objeto. O que acontece no caso do nosso exemplo anterior:
O sistema deve executar a instrução System.out.println(e.toString()), em que e é um objeto Teacher. Ele procura um método toString na hierarquia de classes que conduz à classe Teacher, começando pela última:
- na classe Professor, não encontra um método toString()
- na classe pai Person, não encontra um método toString()
- na classe pai Object, encontra o método toString() e executa-o
É isto que o programa seguinte demonstra:
// import nobody;
// import teacher;
public class test1{
public static void main(String arg[]){
enseignant e=new enseignant("Lucile","Dumas",56,61);
affiche(e);
personne p=new personne("Jean","Dupont",30);
affiche(p);
}
public static void affiche(Object obj){
System.out.println(obj.toString());
}
}
Os resultados são os seguintes:
Ou seja, class_name@object_address. Como isto não é muito claro, poderíamos sentir-nos tentados a definir um método toString para as classes Person e Student que substituísse o método toString da classe pai Object. Em vez de escrever métodos que seriam semelhantes aos métodos de identidade já existentes nas classes Person e Teacher, vamos simplesmente renomear esses métodos de identidade para toString:
public class personne{
...
public String toString(){
return "personne("+prenom+","+nom+","+age+")";
}
...
}
class enseignant extends personne{
int section;
…
public String toString(){
return "enseignant("+super.toString()+","+section+")";
}
}
Utilizando o mesmo programa de teste de antes, os resultados são os seguintes:
3.3. Classes internas
Uma classe pode conter a definição de outra classe. Considere o seguinte exemplo:
// imported classes
import java.io.*;
public class test1{
// internal class
private class article{
// we define the structure
private String code;
private String nom;
private double prix;
private int stockActuel;
private int stockMinimum;
// manufacturer
public article(String code, String nom, double prix, int stockActuel, int stockMinimum){
// attribute initialization
this.code=code;
this.nom=nom;
this.prix=prix;
this.stockActuel=stockActuel;
this.stockMinimum=stockMinimum;
}//manufacturer
//toString
public String toString(){
return "article("+code+","+nom+","+prix+","+stockActuel+","+stockMinimum+")";
}//toString
}//item class
// local data
private article art=null;
// manufacturer
public test1(String code, String nom, double prix, int stockActuel, int stockMinimum){
// attribute definition
art=new article(code, nom, prix, stockActuel,stockMinimum);
}//test1
// accessor
public article getArticle(){
return art;
}//getArticle
public static void main(String arg[]){
// create a test1 instance
test1 t1=new test1("a100","velo",1000,10,5);
// display test1.art
System.out.println("art="+t1.getArticle());
}//hand
}// fin class
A classe test1 contém a definição de outra classe, a classe article. Dizemos que article é uma classe interna da classe test1. Isto pode ser útil quando a classe interna é necessária apenas dentro da classe que a contém. Ao compilar o código-fonte test1.java acima, obtemos dois ficheiros .class:
E:\data\serge\JAVA\classes\interne>dir
05/06/2002 17:26 1 362 test1.java
05/06/2002 17:26 941 test1$article.class
05/06/2002 17:26 1 020 test1.class
Foi gerado um ficheiro test1$article.class para a classe article, que é interna à classe test1. Se executarmos o programa acima, obtemos os seguintes resultados:
3.4. Interfaces
Uma interface é um conjunto de protótipos de métodos ou propriedades que forma um contrato. Uma classe que decide implementar uma interface compromete-se a fornecer uma implementação de todos os métodos definidos na interface. O compilador verifica esta implementação.
Eis um exemplo da definição da interface java.util.Enumeration:
Resumo dos métodos | ||
boolean | hasMoreElements() Verifica se esta enumeração contém mais elementos. | |
Object | nextElement() Retorna o próximo elemento desta enumeração se este objeto de enumeração tiver pelo menos mais um elemento para fornecer. | |
Qualquer classe que implemente esta interface será declarada como
Os métodos hasMoreElements() e nextElement() devem ser definidos na classe C.
Considere o código seguinte, que define uma classe «student» que especifica o nome de um aluno e a sua nota numa disciplina:
// a student class
public class élève{
// public attributes
public String nom;
public double note;
// manufacturer
public élève(String NOM, double NOTE){
nom=NOM;
note=NOTE;
}//manufacturer
}//student
Definimos uma classe «notes» que recolhe as notas de todos os alunos numa disciplina:
// imported classes
// student import
// class notes
public class notes{
// attributes
protected String matière;
protected élève[] élèves;
// manufacturer
public notes (String MATIERE, élève[] ELEVES){
// student & subject memorization
matière=MATIERE;
élèves=ELEVES;
}//notes
// toString
public String toString(){
String valeur="matière="+matière +", notes=(";
int i;
// concatenate all the notes
for (i=0;i<élèves.length-1;i++){
valeur+="["+élèves[i].nom+","+élèves[i].note+"],";
};
//final note
if(élèves.length!=0){ valeur+="["+élèves[i].nom+","+élèves[i].note+"]";}
valeur+=")";
// end
return valeur;
}//toString
}//class
Os atributos subject e students são declarados como protegido para que possam ser acedidos a partir de uma classe derivada. Decidimos derivar a classe notes numa classe notesStats que teria dois atributos adicionais: a média e o desvio padrão das notas:
public class notesStats extends notes implements Istats {
// attributes
private double _moyenne;
private double _écartType;
A classe notesStats deriva da classe notes e implementa a seguinte interface Istats:
// an interface
public interface Istats{
double moyenne();
double écartType();
}//
Isto significa que a classe notesStats deve ter dois métodos denominados average e standardDeviation com as assinaturas especificadas na interface Istats. A classe notesStats é a seguinte:
// imported classes
// import notes;
// import Istats;
// student import;
public class notesStats extends notes implements Istats {
// attributes
private double _moyenne;
private double _écartType;
// manufacturer
public notesStats (String MATIERE, élève[] ELEVES){
// parent class construction
super(MATIERE,ELEVES);
// average score calculation
double somme=0;
for (int i=0;i<élèves.length;i++){
somme+=élèves[i].note;
}
if(élèves.length!=0) _moyenne=somme/élèves.length;
else _moyenne=-1;
// standard deviation
double carrés=0;
for (int i=0;i<élèves.length;i++){
carrés+=Math.pow((élèves[i].note-_moyenne),2);
}//for
if(élèves.length!=0) _écartType=Math.sqrt(carrés/élèves.length);
else _écartType=-1;
}//manufacturer
// ToString
public String toString(){
return super.toString()+",moyenne="+_moyenne+",écart-type="+_écartType;
}//ToString
// istats interface methods
public double moyenne(){
// makes the average score
return _moyenne;
}//average
public double écartType(){
// makes the standard deviation
return _écartType;
}//écartType
}//class
A média _mean e o desvio padrão _standardDev são calculados quando o objeto é criado. Portanto, os métodos mean e standardDev simplesmente devolvem os valores dos atributos _mean e _standardDev. Ambos os métodos devolvem -1 se a matriz de alunos estiver vazia.
A seguinte classe de teste:
// imported classes
// student import;
// import Istats;
// import notes;
// import notesStats;
// test class
public class test{
public static void main(String[] args){
// some students & notes
élève[] ELEVES=new élève[] { new élève("paul",14),new élève("nicole",16), new élève("jacques",18)};
// recorded in a notes object
notes anglais=new notes("anglais",ELEVES);
// and display
System.out.println(""+anglais);
// idem with mean and standard deviation
anglais=new notesStats("anglais",ELEVES);
System.out.println(""+anglais);
}//hand
}//class
retorna os resultados:
matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0])
matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0]),moyenne=16.0,écart-type=1.632993161855452
As diferentes classes neste exemplo estão todas contidas em ficheiros de código-fonte separados:
E:\data\serge\JAVA\interfaces\notes>dir
06/06/2002 14:06 707 notes.java
06/06/2002 14:06 878 notes.class
06/06/2002 14:07 1 160 notesStats.java
06/06/2002 14:02 101 Istats.java
06/06/2002 14:02 138 Istats.class
06/06/2002 14:05 247 élève.java
06/06/2002 14:05 309 élève.class
06/06/2002 14:07 1 103 notesStats.class
06/06/2002 14:10 597 test.java
06/06/2002 14:10 931 test.class
A classe notesStats poderia muito bem ter implementado os métodos average e standardDev por conta própria, sem indicar que implementava a interface Istats. Então, qual é o objetivo das interfaces? É o seguinte: uma função pode aceitar como parâmetro um valor do tipo I. Qualquer objeto da classe C que implemente a interface I pode, então, ser um parâmetro dessa função. Considere a seguinte interface:
// an Iexample interface
public interface Iexemple{
int ajouter(int i,int j);
int soustraire(int i,int j);
}//interface
A interface Iexemple define dois métodos: adicionar e subtrair. As seguintes classes, class1 e class2, implementam esta interface.
// imported classes
// import Iexample;
public class classe1 implements Iexemple{
public int ajouter(int a, int b){
return a+b+10;
}
public int soustraire(int a, int b){
return a-b+20;
}
}//class
// imported classes
// import Iexample;
public class classe2 implements Iexemple{
public int ajouter(int a, int b){
return a+b+100;
}
public int soustraire(int a, int b){
return a-b+200;
}
}//class
Para simplificar o exemplo, as classes não fazem nada além de implementar a interface Iexample. Agora, considere o seguinte exemplo:
// imported classes
// import class1;
// import class2;
// test class
public class test{
// a static function
private static void calculer(int i, int j, Iexemple inter){
System.out.println(inter.ajouter(i,j));
System.out.println(inter.soustraire(i,j));
}//calculate
// the main function
public static void main(String[] arg){
// creation of two objects class1 and class2
classe1 c1=new classe1();
classe2 c2=new classe2();
// static function calls calculate
calculer(4,3,c1);
calculer(14,13,c2);
}//hand
}//test class
A função estática calculate aceita um elemento do tipo Iexample como parâmetro. Pode, portanto, receber um objeto do tipo class1 ou do tipo class2 para este parâmetro. É isso que é feito na função main, com os seguintes resultados:
Podemos ver, portanto, que esta propriedade é semelhante ao polimorfismo observado nas classes. Assim, se um conjunto de classes Ci que não estão relacionadas entre si por herança (e, portanto, não podem usar o polimorfismo baseado em herança) tiver um conjunto de métodos com a mesma assinatura, pode ser útil agrupar esses métodos numa interface I da qual todas as classes relevantes herdariam. As instâncias destas classes Ci podem então ser utilizadas como parâmetros para funções que aceitam um parâmetro do tipo I, ou seja, funções que utilizam apenas os métodos dos objetos Ci definidos na interface I e não os atributos e métodos específicos das várias classes Ci.
No exemplo anterior, cada classe ou interface era objeto de um ficheiro de código-fonte separado:
E:\data\serge\JAVA\interfaces\opérations>dir
06/06/2002 14:33 128 Iexemple.java
06/06/2002 14:34 218 classe1.java
06/06/2002 14:32 220 classe2.java
06/06/2002 14:33 144 Iexemple.class
06/06/2002 14:34 325 classe1.class
06/06/2002 14:34 326 classe2.class
06/06/2002 14:36 583 test.java
06/06/2002 14:36 628 test.class
Por fim, note-se que a herança de interfaces pode ser múltipla, ou seja, é possível escrever
onde os ij são interfaces.
3.5. Classes anónimas
No exemplo anterior, as classes class1 e class2 poderiam ter sido omitidas. Considere o seguinte programa, que faz essencialmente o mesmo que o anterior, mas sem definir explicitamente as classes class1 e class2:
// imported classes
// import Iexample;
// test class
public class test2{
// an internal class
private static class classe3 implements Iexemple{
public int ajouter(int a, int b){
return a+b+1000;
}
public int soustraire(int a, int b){
return a-b+2000;
}
};//definition class3
// a static function
private static void calculer(int i, int j, Iexemple inter){
System.out.println(inter.ajouter(i,j));
System.out.println(inter.soustraire(i,j));
}//calculate
// the main function
public static void main(String[] arg){
// creation of two objects implementing the Iexemple interface
Iexemple i1=new Iexemple(){
public int ajouter(int a, int b){
return a+b+10;
}
public int soustraire(int a, int b){
return a-b+20;
}
};//definition i1
Iexemple i2=new Iexemple(){
public int ajouter(int a, int b){
return a+b+100;
}
public int soustraire(int a, int b){
return a-b+200;
}
};//definition i2
// another object Iexample
Iexemple i3=new classe3();
// static function calls calculate
calculer(4,3,i1);
calculer(14,13,i2);
calculer(24,23,i3);
}//hand
}//test class
A característica principal está no código:
// creation of two objects implementing the Iexemple interface
Iexemple i1=new Iexemple(){
public int ajouter(int a, int b){
return a+b+10;
}
public int soustraire(int a, int b){
return a-b+20;
}
};//definition i1
Criamos um objeto i1 cujo único objetivo é implementar a interface Iexample. Este objeto é do tipo Iexample. Podemos, portanto, criar objetos do tipo interface. Muitos métodos de classes Java devolvem objetos do tipo interface, ou seja, objetos cujo único objetivo é implementar os métodos de uma interface. Para criar o objeto i1, poderíamos sentir-nos tentados a escrever:
Iexemple i1=new Iexemple()
No entanto, uma interface não pode ser instanciada. Apenas uma classe que implemente essa interface pode ser instanciada. Aqui, definimos essa classe «em tempo real» dentro do corpo da definição do objeto i1:
Iexemple i1=new Iexemple(){
public int ajouter(int a, int b){
// définition de ajouter
}
public int soustraire(int a, int b){
// définition de soustraire
}
};//définition i1
O significado de tal instrução é análogo à sequência:
public class test2{
................
// an internal class
private static class classe1 implements Iexemple{
public int ajouter(int a, int b){
// definition of add
}
public int soustraire(int a, int b){
// definition of subtract
}
};//definition class1
.................
public static void main(String[] arg){
...........
Iexemple i1=new classe1();
}//hand
}//class
No exemplo acima, estamos, de facto, a instanciar uma classe, e não uma interface. Uma classe definida «na hora» é designada por classe anónima. Trata-se de um método frequentemente utilizado para instanciar objetos cujo único objetivo é implementar uma interface.
A execução do programa anterior produz os seguintes resultados:
O exemplo anterior utilizou classes anónimas para implementar uma interface. Estas também podem ser utilizadas para derivar classes que não possuem construtores parametrizados. Considere o seguinte exemplo:
// imported classes
// import Iexample;
class classe3 implements Iexemple{
public int ajouter(int a, int b){
return a+b+1000;
}
public int soustraire(int a, int b){
return a-b+2000;
}
};//definition class3
public class test4{
// a static function
private static void calculer(int i, int j, Iexemple inter){
System.out.println(inter.ajouter(i,j));
System.out.println(inter.soustraire(i,j));
}//calculate
// hand method
public static void main(String args[]){
// definition of an anonymized class deriving from class3
// to redefine subtract
classe3 i1=new classe3(){
public int ajouter(int a, int b){
return a+b+10000;
}//subtract
};//i1
// static function calls calculate
calculer(4,3,i1);
}//hand
}//class
Aqui temos uma classe classe3 que implementa a interface Iexemple. Na função main, definimos uma variável i1 de um tipo derivado de classe3. Esta classe derivada é definida «em tempo real» numa classe anónima e substitui o método ajouter da classe classe3. A sintaxe é idêntica à da classe anónima que implementa uma interface. No entanto, aqui o compilador deteta que classe3 não é uma interface, mas sim uma classe. Para o compilador, trata-se, portanto, de uma derivação de classe. Todos os métodos encontrados no corpo da classe anónima irão substituir os métodos com o mesmo nome na classe base.
A execução do programa anterior produz os seguintes resultados:
3.6. P ackages
3.6.1. Criação de classes num pacote
Para imprimir uma linha no ecrã, utilizamos a instrução
Se analisarmos a definição da classe System, verificamos que, na verdade, ela chama-se java.lang.System:

Vamos verificar isto com um exemplo:
public class test1{
public static void main(String[] args){
java.lang.System.out.println("Coucou");
}//hand
}//class
Vamos compilar e executar este programa:
E:\data\serge\JAVA\classes\paquetages>javac test1.java
E:\data\serge\JAVA\classes\paquetages>dir
06/06/2002 15:40 127 test1.java
06/06/2002 15:40 410 test1.class
E:\data\serge\JAVA\classes\paquetages>java test1
Coucou
Por que, então, podemos escrever
System.out.println("Coucou");
em vez de
java.lang.System.out.println("Coucou");
Porque, por predefinição, todos os programas Java importam automaticamente o pacote java.lang. Por isso, é como se tivéssemos a seguinte instrução no início de cada programa:
O que significa esta instrução? Ela fornece acesso a todas as classes do pacote java.lang. O compilador encontrará lá o ficheiro System.class, que define a classe System. Ainda não sabemos onde o compilador encontrará o pacote java.lang nem como é um pacote. Voltaremos a este assunto. Para criar uma classe num pacote, escrevemos:
Para este exemplo, vamos criar a nossa classe Person, estudada anteriormente, dentro de um pacote. Escolheremos istia.st como nome do pacote. A classe Person passa a ser:
// name of the package in which the person class will be created
package istia.st;
// class person
public class personne{
// last name, first name, age
private String prenom;
private String nom;
private int age;
// builder 1
public personne(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
// toString
public String toString(){
return "personne("+prenom+","+nom+","+age+")";
}
}//class
Esta classe é compilada e, em seguida, colocada no diretório istia\st do diretório atual. Porquê istia\st? Porque o pacote chama-se istia.st.
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 16:04 <DIR> istia
E:\data\serge\JAVA\classes\paquetages\personne>dir istia
06/06/2002 16:04 <DIR> st
E:\data\serge\JAVA\classes\paquetages\personne>dir istia\st
06/06/2002 16:28 675 personne.class
Agora vamos utilizar a classe «person» numa primeira classe de teste:
public class test{
public static void main(String[] args){
istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);
System.out.println("p1="+p1);
}//hand
}//test class
Note que a classe Person tem agora o prefixo do nome do seu pacote, istia.st. Onde é que o compilador irá encontrar a classe istia.st.Person? O compilador procura as classes de que necessita numa lista predefinida de diretórios e numa árvore de diretórios a partir do diretório atual. Aqui, irá procurar a classe istia.st.personne num ficheiro chamado istia\st\personne.class. É por isso que colocámos o ficheiro personne.class no diretório istia\st. Vamos compilar e, em seguida, executar o programa de teste:
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 16:06 246 test.java
06/06/2002 16:04 <DIR> istia
06/06/2002 16:06 738 test.class
E:\data\serge\JAVA\classes\paquetages\personne>java test
p1=personne(Jean,Dupont,20)
Para evitar escrever
istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);
pode importar a classe istia.st.personne utilizando uma instrução de importação:
import istia.st.personne;
Podemos então escrever
personne p1=new personne("Jean","Dupont",20);
e o compilador irá traduzir isto para
istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);
O programa de teste passa então a ser o seguinte:
// imported namespaces
import istia.st.personne;
public class test2{
public static void main(String[] args){
personne p1=new personne("Jean","Dupont",20);
System.out.println("p1="+p1);
}//hand
}//test2 class
Vamos compilar e executar este novo programa:
E:\data\serge\JAVA\classes\paquetages\personne>javac test2.java
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 16:06 246 test.java
06/06/2002 16:04 <DIR> istia
06/06/2002 16:06 738 test.class
06/06/2002 16:47 236 test2.java
06/06/2002 16:50 740 test2.class
E:\data\serge\JAVA\classes\paquetages\personne>java test2
p1=personne(Jean,Dupont,20)
Colocámos o pacote istia.st no diretório atual. Isto não é obrigatório. Vamos colocá-lo numa pasta chamada mesClasses, ainda no diretório atual. Recorde-se que as classes do pacote istia.st se encontram numa pasta chamada istia\st. A árvore de diretórios do diretório atual é a seguinte:
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 16:06 246 test.java
06/06/2002 16:06 738 test.class
06/06/2002 16:47 236 test2.java
06/06/2002 16:50 740 test2.class
06/06/2002 16:21 <DIR> mesClasses
E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses
06/06/2002 16:22 <DIR> istia
E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia
06/06/2002 16:22 <DIR> st
E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia\st
06/06/2002 16:01 1 153 personne.class
Agora vamos compilar novamente o programa test2.java:
E:\data\serge\JAVA\classes\paquetages\personne>javac test2.java
test2.java:2: package istia.st does not exist
import istia.st.personne;
O compilador já não consegue encontrar o pacote istia.st, uma vez que o movemos. Note-se que o procura devido à instrução import. Por predefinição, procura-o no diretório atual, numa pasta chamada istia\st, que já não existe. Vamos examinar as opções do compilador:
E:\data\serge\JAVA\classes\paquetages\personne>javac
Usage: javac <options> <source files>
where possible options include:
-g Generate all debugging info
-g:none Generate no debugging info
-g:{lines,vars,source} Generate only some debugging info
-O Optimize; may hinder debugging or enlarge class file
-nowarn Generate no warnings
-verbose Output messages about what the compiler is doing
-deprecation Output source locations where deprecated APIs are used
-classpath <path> Specify where to find user class files
-sourcepath <path> Specify where to find input source files
-bootclasspath <path> Override location of bootstrap class files
-extdirs <dirs> Override location of installed extensions
-d <directory> Specify where to place generated class files
-encoding <encoding> Specify character encoding used by source files
-source <release> Provide source compatibility with specified release
-target <release> Generate class files for specific VM version
-help Print a synopsis of standard options
Aqui, a opção -classpath pode ser útil. Permite indicar ao compilador onde procurar as suas classes e pacotes. Vamos experimentar. Vamos compilar indicando ao compilador que o pacote istia.st está agora na pasta mesClasses:
E:\data\serge\JAVA\classes\paquetages\personne>javac -classpath mesClasses test2.java
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:47 236 test2.java
06/06/2002 17:03 740 test2.class
06/06/2002 16:21 <DIR> mesClasses
Desta vez, a compilação é concluída sem quaisquer problemas. Vamos executar o programa test2.class:
E:\data\serge\JAVA\classes\paquetages\personne>java test2
Exception in thread "main" java.lang.NoClassDefFoundError: istia/st/personne
at test2.main(test2.java:6)
Agora é a vez da Máquina Virtual Java não conseguir encontrar a classe istia/st/personne. Ela está à procura dela no diretório atual, enquanto esta se encontra agora no diretório mesClasses. Vejamos as opções da Máquina Virtual Java:
E:\data\serge\JAVA\classes\paquetages\personne>java
Usage: java [-options] class [args...]
(to execute a class)
or java -jar [-options] jarfile [args...]
(to execute a jar file)
where options include:
-client to select the "client" VM
-server to select the "server" VM
-hotspot is a synonym for the "client" VM [deprecated]
The default VM is client.
-cp -classpath <directories and zip/jar files separated by ;>
set search path for application classes and resources
-D<name>=<value>
set a system property
-verbose[:class|gc|jni]
enable verbose output
-version print product version and exit
-showversion print product version and continue
-? -help print this help message
-X print help on non-standard options
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
enable assertions
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
disable assertions
-esa | -enablesystemassertions
enable system assertions
-dsa | -disablesystemassertions
disable system assertions
Podemos ver que a JVM também tem uma opção de classpath, tal como o compilador. Vamos usá-la para indicar à JVM onde se encontra o pacote istia.st:
E:\data\serge\JAVA\classes\paquetages\personne>java.bat -classpath mesClasses test2
Exception in thread "main" java.lang.NoClassDefFoundError: test2
Não fizemos muitos progressos. Agora, a própria classe test2 não consegue ser encontrada. Eis o motivo: quando a palavra-chave classpath está ausente, o diretório atual é sistematicamente pesquisado em busca de classes, mas isso não acontece quando ela está presente. Como resultado, o ficheiro test2.class localizado no diretório atual não é encontrado. A solução? Adicione o diretório atual ao classpath. O diretório atual é representado pelo símbolo .
E:\data\serge\JAVA\classes\paquetages\personne>java -classpath mesClasses;. test2
p1=personne(Jean,Dupont,20)
Porquê toda esta complexidade? O objetivo dos pacotes é evitar conflitos de nomes entre classes. Considere duas empresas, E1 e E2, que distribuem classes empacotadas nos pacotes com.e1 e com.e2, respetivamente. Suponha que um cliente, C, adquira ambos os conjuntos de classes, nos quais ambas as empresas definiram uma classe denominada Person. O cliente C referenciará a classe Person da empresa E1 como com.e1.Person e a da empresa E2 como com.e2.Person, evitando assim um conflito de nomes.
3.6.2. Pesquisar pacotes
Quando escrevemos num programa
para aceder a todas as classes do pacote java.util, onde é que este se encontra? Mencionámos que os pacotes são pesquisados, por predefinição, no diretório atual ou na lista de diretórios especificados na opção classpath do compilador ou da JVM, caso essa opção esteja presente. Também são pesquisados nos diretórios lib do diretório de instalação do JDK. Considere este diretório:

Neste exemplo, os diretórios jdk14\lib e jdk14\jre\lib serão pesquisados por ficheiros .class ou ficheiros .jar ou .zip, que são arquivos de classes. Por exemplo, vamos procurar ficheiros .jar localizados no diretório jdk14 mencionado acima:

Existem várias dezenas deles. Um ficheiro .jar pode ser aberto com o utilitário WinZip. Vamos abrir o ficheiro rt.jar acima (rt = RunTime). Este contém várias centenas de ficheiros .class, incluindo os que pertencem ao pacote java.util:

Uma forma simples de gerir pacotes é colocá-los no diretório <jdk>\jre\lib, onde <jdk> é o diretório de instalação do JDK. Geralmente, um pacote contém várias classes, sendo conveniente agrupá-las num único ficheiro .jar (JAR = ficheiro Java ARchive). O executável jar.exe encontra-se na pasta <jdk>\bin:
E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\bin\jar.exe"
07/02/2002 12:52 28 752 jar.exe
Pode obter ajuda sobre a utilização do programa jar chamando-o sem quaisquer parâmetros:
E:\data\serge\JAVA\classes\paquetages\personne>"e:\program files\jdk14\bin\jar.exe"
Syntaxe : jar {ctxu}[vfm0M] [fichier-jar] [fichier-manifest] [rÚp -C] fichiers ...
Options :
-c crÚer un nouveau fichier d'archives
-t gÚnÚrer la table des matiÞres du fichier d'archives
-x extraire les fichiers nommÚs (ou tous les fichiers) du fichier d'archives
-u mettre Ó jour le fichier d'existing archives
-v gÚnÚrer des informations verbeuses sur la sortie standard
-f spÚcifier le nom du fichier d'archives
-m inclure les informations manifest provenant du fichier manifest spÚcifiÚ
-0 stocker seulement ; ne pas utiliser la compression ZIP
-M ne pas crÚer de fichier manifest pour les entrÚes
-i gÚnÚrer l'index for jar files spÚcifiÚs
-C passer au rÚpertoire spÚcifiÚ et inclure le fichier suivant
Si un rÚpertoire est spÚcifiÚ, il est traitÚ rÚcursivement.
Les noms des fichiers manifest et d'archives must be spÚcifiÚs
dans l'order of ''m'' and ''f'' indicators.
Exemple 1 : pour archiver deux fichiers de classe dans le fichier d'archives classes.jar :
jar cvf classes.jar Foo.class Bar.class
Exemple 2 : utilisez le fichier manifest existant 'mymanifest'' to archive all files in the
rÚpertoire foo/ dans 'classes.jar'':
jar cvfm classes.jar monmanifest -C foo/ .
Voltemos à classe *person.class criada anteriormente no pacote *istia.st:
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 17:36 195 test.java
06/06/2002 16:04 <DIR> istia
06/06/2002 16:06 738 test.class
06/06/2002 16:47 236 test2.java
06/06/2002 18:15 740 test2.class
E:\data\serge\JAVA\classes\paquetages\personne>dir istia
06/06/2002 16:04 <DIR> st
E:\data\serge\JAVA\classes\paquetages\personne>dir istia\st
06/06/2002 16:28 675 personne.class
Vamos criar um ficheiro chamado istia.st.jar que arquive todas as classes do pacote istia.st, ou seja, todas as classes da árvore de diretórios istia\st mostrada acima:
E:\data\serge\JAVA\classes\paquetages\personne>"e:\program files\jdk14\bin\jar" cvf istia.st.jar istia\st\*
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 17:36 195 test.java
06/06/2002 16:04 <DIR> istia
06/06/2002 16:06 738 test.class
06/06/2002 16:47 236 test2.java
06/06/2002 18:15 740 test2.class
06/06/2002 18:08 874 istia.st.jar
Vamos examinar o conteúdo do ficheiro istia.st.jar utilizando o WinZip:

Vamos colocar o ficheiro istia.st.jar no diretório <jdk>\jre\lib\perso:
E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\jre\lib\perso"
06/06/2002 18:08 874 istia.st.jar
Agora vamos compilar o programa test2.java e, em seguida, executá-lo:
E:\data\serge\JAVA\classes\paquetages\personne>javac -classpath istia.st.jar test2.java
E:\data\serge\JAVA\classes\paquetages\personne>java -classpath istia.st.jar;. test2
p1=personne(Jean,Dupont,20)
Note-se que bastou especificar o nome do arquivo a procurar, sem necessidade de indicar explicitamente a sua localização. Todos os diretórios na árvore <jdk>\jre\lib são pesquisados para encontrar o ficheiro .jar solicitado.
3.7. O exemplo de cálculo de impostos
Revisamos o cálculo de impostos já abordado no capítulo anterior e processamo-lo utilizando uma classe. Vamos relembrar o problema:
Consideramos o caso simplificado de um contribuinte que tem apenas o seu salário para declarar:
- calculamos o número de escalões fiscais do empregado nbParts = nbEnfants/2 + 1 se for solteiro, nbEnfants/2 + 2 se for casado, onde nbEnfants é o número de filhos.
- Se tiver pelo menos três filhos, recebe uma meia quota adicional
- Calculamos o seu rendimento tributável R = 0,72 * S, em que S é o seu salário anual
- Calculamos o seu coeficiente familiar QF = R / nbParts
- Calculamos o seu imposto. I. Considere a seguinte tabela:
12620,0 | 0 | 0 |
13 190 | 0,05 | 631 |
15 640 | 0,1 | 1.290,5 |
24.740 | 0,15 | 2.072,5 |
31 810 | 0,2 | 3.309,5 |
39 970 | 0,25 | 4.900 |
48 360 | 0,3 | 6.898,5 |
55 790 | 0,35 | 9.316,5 |
92 970 | 0,4 | 12 106 |
127 860 | 0,45 | 16 754,5 |
151 250 | 0,50 | 23 147,5 |
172 040 | 0,55 | 30 710 |
195 000 | 0,60 | 39 312 |
0 | 0,65 | 49 062 |
Cada linha tem 3 campos. Para calcular o imposto I, encontre a primeira linha em que QF <= campo1. Por exemplo, se QF = 23.000, a linha encontrada será
O Imposto I é então igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a condição QF<=field1 nunca seja satisfeita, então são utilizados os coeficientes da última linha. Aqui:
o que dá o imposto I = 0,65*R - 49062*nbParts.
A classe impots será definida da seguinte forma:
// creation of an impots class
public class impots{
// data required for tax calculation
// come from an external source
private double[] limites, coeffR, coeffN;
// manufacturer
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// check that the 3 arrays have the same size
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// it's good
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//manufacturer
// tAX CALCULATION
public long calculer(boolean marié, int nbEnfants, int salaire){
// calculating the number of shares
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// calculation of taxable income & family quota
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// tAX CALCULATION
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// return result
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//calculate
}//class
É criado um objeto fiscal com os dados necessários para calcular o imposto de um contribuinte. Esta é a parte estável do objeto. Uma vez criado este objeto, o seu método calculate pode ser chamado repetidamente para calcular o imposto do contribuinte com base no seu estado civil (casado ou não), número de filhos e salário anual.
Um programa de teste pode ter o seguinte aspeto:
//imported classes
// import impots;
import java.io.*;
public class test
{
public static void main(String[] arg) throws IOException
{
// interactive tax calculator
// the user enters three data points on the keyboard: married nbEnfants salary
// the program then displays the tax payable
final String syntaxe="syntaxe : marié nbEnfants salaire\n"
+"marié : o pour marié, n pour non marié\n"
+"nbEnfants : nombre d'enfants\n"
+"salaire : salaire annuel en F";
// data tables required for tax calculation
double[] limites=new double[] {12620,13190,15640,24740,31810,39970,48360,55790,92970,127860,151250,172040,195000,0};
double[] coeffR=new double[] {0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65};
double[] coeffN=new double[] {0,631,1290.5,2072.5,3309.5,4900,6898.5,9316.5,12106,16754.5,23147.5,30710,39312,49062};
// create a reading flow
BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
// tax object creation
impots objImpôt=null;
try{
objImpôt=new impots(limites,coeffR,coeffN);
}catch (Exception ex){
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
System.exit(1);
}//try-catch
// infinite loop
while(true){
// tax calculation parameters are requested
System.out.print("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :");
String paramètres=IN.readLine().trim();
// anything to do?
if(paramètres==null || paramètres.equals("")) break;
// check the number of arguments in the input line
String[] args=paramètres.split("\\s+");
int nbParamètres=args.length;
if (nbParamètres!=3){
System.err.println(syntaxe);
continue;
}//if
// checking the validity of parameters
// married
String marié=args[0].toLowerCase();
if (! marié.equals("o") && ! marié.equals("n")){
System.err.println(syntaxe+"\nArgument marié incorrect : tapez o ou n");
continue;
}//if
// nbEnfants
int nbEnfants=0;
try{
nbEnfants=Integer.parseInt(args[1]);
if(nbEnfants<0) throw new Exception();
}catch (Exception ex){
System.err.println(syntaxe+"\nArgument nbEnfants incorrect : tapez un entier positif ou nul");
continue;
}//if
// salary
int salaire=0;
try{
salaire=Integer.parseInt(args[2]);
if(salaire<0) throw new Exception();
}catch (Exception ex){
System.err.println(syntaxe+"\nArgument salaire incorrect : tapez un entier positif ou nul");
continue;
}//if
// parameters are correct - tax is calculated
System.out.println("impôt="+objImpôt.calculer(marié.equals("o"),nbEnfants,salaire)+" F");
// next taxpayer
}//while
}//hand
}//class
Aqui está um exemplo do programa anterior em ação:
E:\data\serge\MSNET\c#\impots\3>java test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :q s d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument marié incorrect : tapez o ou n
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o s d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument nbEnfants incorrect : tapez un entier positif ou nul
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument salaire incorrect : tapez un entier positif ou nul
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :q s d f
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :