Skip to content

3. Classes e interfaces

3.1. O objeto por meio de exemplos

3.1.1. Noções gerais

Abordaremos agora, por meio de exemplos, a programação orientada a objetos. Um objeto é uma entidade que contém dados que definem seu estado (chamados de atributos ou propriedades) e funções (chamadas de métodos). Um objeto é criado de acordo com um modelo chamado de classe:

public class C1{
    type1 p1;            // propriedade p1
    type2 p2;            // propriedade p2
    
    type3 m3(){        // método m3
        
    }
    type4 m4(){        // método m4
        
    }
    
}

A partir da classe C1 anterior, é possível criar vários objetos O1, O2,… Todos terão as propriedades p1, p2, … e os métodos m3, m4, … Eles terão valores diferentes para suas propriedades pi, possuindo, assim, cada um um estado próprio.

Se O1 é um objeto do tipo C1, O1.p1 designa a propriedade p1 de O1 e O1.m1 o método m1 de O1.

Consideremos um primeiro modelo de objeto: a classe personne.

3.1.2. Definição da classe “pessoa”

A definição da classe personne será a seguinte:


import java.io.*;

public class personne{
  // atributos
  private String prenom;
  private String nom;
  private int age;
  
  // método
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }

  // método
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

Temos aqui a definição de uma classe, ou seja, um tipo de dado. Ao criarmos variáveis desse tipo, elas serão chamadas de 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). Esses campos podem ter um dos três atributos a seguir:

privé: Um campo privado (private) só é acessível pelos métodos internos da classe

public: Um campo público é acessível por qualquer função, definida ou não dentro da classe

protégé: Um campo protegido (protected) só é acessível pelos métodos internos da classe ou de um objeto derivado (veja mais adiante o conceito de herança).

Em geral, os dados de uma classe são declarados privados, enquanto seus métodos são declarados públicos. Isso significa que o usuário 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 e, em especial, aqueles que darão 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
}

Remarques

  • A ordem de declaração dos atributos private, protected e public é arbitrária.

3.1.3. O método initialize

Voltemos à nossa classe **pessoa,** declarada como:


import java.io.*;

public class personne{
  // atributos
  private String prenom;
  private String nom;
  private int age;
  
  // método
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }

  // método
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

Qual é a função do método initialise? Como nome, sobrenome e idade são dados privados da classe Pessoa, as instruções

personne p1;
p1.prenom="Jean";
p1.nom="Dupont";
p1.age=30;

são inválidas. Precisamos inicializar um objeto do tipo personne por meio de um método público. Essa é a função do método initialise. Escreveremos:

personne p1;
p1.initialise("Jean","Dupont",30);

A sintaxe p1.initialise é válida, pois initialise é de acesso público.

3.1.4. O operador new

A sequência de instruções

personne p1;
p1.initialise("Jean","Dupont",30);

está incorreta. A instrução

    personne p1;

declara p1 como uma referência a um objeto do tipo personne. Esse objeto ainda não existe e, portanto, p1 não está inicializado. É como se escrevêssemos:

personne p1=null;

onde se indica explicitamente, com a palavra-chave **null, que a variável p1 ainda não faz referência a nenhum objeto.

Quando, em seguida, escrevemos

p1.initialise("Jean","Dupont",30);

, estamos chamando o método initialise do objeto referenciado por p1. No entanto, esse objeto ainda não existe e o compilador sinalizará o erro. Para que p1 refira um objeto, é preciso escrever:

personne p1=new personne();

Isso tem como efeito a criação de um objeto do tipo personne ainda não inicializado: os atributos nom e prenom, que são referências a objetos do tipo String, terão o valor null, e age terá o valor 0. Portanto, há uma inicialização padrão. Agora que p1 faz referência a um objeto, a instrução de inicialização desse objeto

p1.initialise("Jean","Dupont",30);

é válida.

3.1.5. A palavra-chave this

Vejamos o código do método initialise:


public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }

A instrução this.prenom=P significa que o atributo prenom do objeto atual (this) recebe o valor P. A palavra-chave this designa o objeto atual: aquele no qual se encontra o método executado. Como sabemos disso? Vejamos como ocorre a inicialização do objeto referenciado por p1 no programa chamador:

p1.initialise("Jean","Dupont",30);

É o método initialise do objeto p1 que é chamado. Quando, nesse método, fazemos referência ao objeto this, na verdade estamos fazendo referência ao objeto p1. O método initialise 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 é implícita. Deve-se utilizá-la explicitamente quando houver conflito de identificadores. Esse é o caso da instrução:


this.age=age;

onde age designa um atributo do objeto atual, bem como o parâmetro age recebido pelo método. É necessário, então, eliminar a ambiguidade, designando o atributo age como this.age.

3.1.6. Um programa de teste

Aqui está 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 personne está definida no arquivo-fonte personne.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

É surpreendente que o programa test1.java não importe a classe personne com a instrução:

import personne;

Quando o compilador encontra no código-fonte uma referência a uma classe não definida nesse mesmo arquivo-fonte, ele procura a classe em diversos 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 arquivo personne.class, o que explica por que ele encontrou a definição da classe personne. Incluir, nesse caso, uma instrução import provoca 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 esse erro, mas para lembrar que a classe Person deve ser importada, escreveremos daqui em diante no início do programa:

// classes importadas
// import pessoa;

Agora podemos executar o arquivo test1.class:

E:\data\serge\JAVA\BASES\OBJETS\2>java test1
Jean,Dupont,30

É possível agrupar várias classes em um mesmo arquivo-fonte. Vamos, então, agrupar as classes personne e test1 no arquivo-fonte test2.java. A classe test1 é renomeada como test2 para refletir a alteração no nome do arquivo-fonte:

// pacotes importados
import java.io.*;

class personne{
   // atributos
  private String prenom;    // nome da minha pessoa
  private String nom;            // sobrenome
  private int age;                // idade dela

   // método
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }//inicializa

   // método
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }//identifica
}//classe
public class test2{
  public static void main(String arg[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    p1.identifie();
  }
}

Observe-se que a classe personne não possui mais o atributo public. De fato, em um arquivo-fonte Java, apenas uma classe pode ter o atributo public. É aquela que possui a função main. Além disso, o arquivo-fonte deve ter o nome dessa última. Vamos compilar o arquivo 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

Observe que foi gerado um arquivo .class para cada uma das classes presentes no arquivo-fonte. Vamos agora executar o arquivo test2.class:

E:\data\serge\JAVA\BASES\OBJETS\2>java test2
Jean,Dupont,30

A partir de agora, utilizaremos indistintamente os dois métodos:

  • classes agrupadas em um único arquivo-fonte
  • uma classe por arquivo-fonte

3.1.7. Outro método é inicializado

Consideremos novamente a classe personne e adicionemos a ela o seguinte método:


public void initialise(personne P){
    prenom=P.prenom;
    nom=P.nom;
    this.age=P.age;
  }

Agora temos dois métodos com o nome initialise: isso é permitido, desde que aceitem parâmetros diferentes. É o que acontece neste caso. 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). Observe-se que o método initialise tem acesso direto aos atributos do objeto P, embora estes sejam do tipo private. Isso sempre é verdade: os métodos de um objeto O1 de uma classe C sempre têm acesso aos atributos privados dos outros objetos da mesma classe C.

Aqui está um teste da nova classe personne:


// import pessoa;
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 seus resultados:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

3.1.8. Construtores da classe Pessoa

Um construtor é um método que leva o nome da classe e é chamado durante a criação do objeto. Geralmente, ele é usado para inicializá-lo. É um método que pode aceitar argumentos, mas não retorna nenhum resultado. Seu protótipo ou sua definição não são precedidos por nenhum tipo (nem mesmo void).

Se uma classe tiver um construtor que aceita n argumentos argi, a declaração e a inicialização de um objeto dessa classe poderão ser feitas da seguinte forma:


        classe objet =new classe(arg1,arg2, ... argn);

ou


        classe objet;

        objet=new classe(arg1,arg2, ... argn);

Quando uma classe possui um ou mais construtores, um desses construtores deve ser obrigatoriamente utilizado para criar um objeto dessa classe. Se uma classe C não tiver nenhum construtor, 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:

    personne p1;
    p1=new personne();

Vamos criar dois construtores para nossa classe personne:


public class personne{
  // atributos
  private String prenom;
  private String nom;
  private int age;
  
  // construtores
  public personne(String P, String N, int age){
    initialise(P,N,age);
  }
  public personne(personne P){
    initialise(P);
  }

  // método
  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;
  }

  // método
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

Nossos dois construtores limitam-se a chamar os métodos initialise correspondentes. Vale lembrar que, quando em um construtor aparece a notação initialise(P), por exemplo, o compilador a traduz como this.initialise(P). No construtor, o método initialise é, portanto, chamado para trabalhar no objeto referenciado por this, ou seja, o objeto atual, aquele que está sendo construído.

Aqui está um programa de teste:


// import pessoa;
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:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

3.1.9. As referências dos objetos

Sempre utilizamos a mesma classe personne. O programa de teste fica da seguinte forma:


// import pessoa;
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 faz referência ao mesmo objeto que p1
    personne p2=p1;
    System.out.print("p2="); p2.identifie();
    // p3 aponta para um objeto que será uma cópia do objeto ao qual p1 aponta
    personne p3=new personne(p1);
    System.out.print("p3=");  p3.identifie();
    // altera-se o estado do objeto referenciado por p1
    p1.initialise("Micheline","Benoît",67);
    System.out.print("p1=");  p1.identifie();
    // como p2 = p1, o objeto referenciado por p2 deve ter mudado de estado
    System.out.print("p2=");  p2.identifie();
    // como p3 não aponta para o mesmo objeto que p1, o objeto ao qual p3 aponta não deve ter mudado
    System.out.print("p3=");  p3.identifie();
  }
}

Os resultados obtidos 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 por meio de

personne p1=new personne("Jean","Dupont",30);

p1 faz referência ao objeto personne("Jean","Dupont",30), mas não é o próprio objeto. Em C, diríamos que se trata de um ponteiro, c.a.d, que aponta para o endereço do objeto criado. Se, em seguida, escrevermos:

    p1=null

Não é o objeto personne("Jean","Dupont",30) que é modificado, e sim a referência p1 que muda de valor. O objeto pessoa("Jean", "Dupont", 30) será “perdido” se não for referenciado por nenhuma outra variável.

Quando se escreve:

personne p2=p1;

inicializa-se o ponteiro p2: ele “aponta” para o mesmo objeto (designa o mesmo objeto) que o ponteiro p1. Assim, se modificarmos o objeto “apontado” (ou referenciado) por p1, modificamos aquele referenciado por p2.

Quando se escreve:

personne p3=new personne(p1);

ocorre a criação de um novo objeto, que é uma cópia do objeto referenciado por p1. Esse novo objeto será referenciado por p3. Se modificarmos o objeto “apontado” (ou referenciado) por p1, não alteraremos em nada aquele referenciado por p3. É isso que mostram os resultados obtidos.

3.1.10. Os objetos temporários

Em uma expressão, é possível chamar explicitamente o construtor de um objeto: ele é criado, mas não temos acesso a ele (para modificá-lo, por exemplo). Esse objeto temporário é criado para fins de avaliação da expressão e, em seguida, descartado. O espaço de memória que ele ocupava será automaticamente recuperado posteriormente por um programa chamado “garimpo de lixo”, cuja função é recuperar o espaço de memória ocupado por objetos que não são mais referenciados pelos dados do programa.

Consideremos o seguinte exemplo:


// import pessoa;

public class test1{
  public static void main(String arg[]){
    new personne(new personne("Jean","Dupont",30)).identifie();
  }
}

e vamos modificar os construtores da classe personne para que exibam uma mensagem:


// fabricantes
  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:

Constructeur personne(String, String, int)
Constructeur personne(personne)
Jean,Dupont,30

mostrando a construção sucessiva dos dois objetos temporários.

3.1.11. Métodos de leitura e gravação de atributos privados

Adicionamos à classe personne os métodos necessários 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);
  }

  // acessórios
  public String getPrenom(){
    return prenom;
  }
  public String getNom(){
    return nom;
  }
  public int getAge(){
    return age;
  }

  //modificadores
  public void setPrenom(String P){
    this.prenom=P;
  }
  public void setNom(String N){
    this.nom=N;
  }
  public void setAge(int age){
    this.age=age;
  }
}

Estamos testando a nova classe com o seguinte programa:


// importar pessoa;

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:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)

3.1.12. Métodos e atributos de classe

Suponhamos que queiramos contar o número de objetos personne criados em um aplicativo. Podemos gerenciar um contador manualmente, mas corremos o risco de esquecer os objetos temporários que são criados aqui e ali. Parece mais seguro incluir nos construtores da classe personne uma instrução que incremente um contador. O problema é passar uma referência desse contador para que o construtor possa incrementá-lo: é preciso passar um novo parâmetro a eles. Também é possível incluir o contador na definição da classe. Como se trata de um atributo da própria classe e não de um objeto específico dessa classe, ele é declarado de maneira diferente com a palavra-chave static:

    private static long nbPersonnes;        // número de pessoas criadas

Para referenciá-lo, escreve-se personne.nbPersonnes para indicar que se trata de um atributo da própria classe personne. Aqui, criamos um atributo privado ao qual não teremos acesso direto fora da classe. Portanto, criamos um método público para dar acesso ao atributo da classe nbPersonnes. Para definir o valor de nbPersonnes, o método não precisa de um objeto específico: de fato, nbPersonnes não é um atributo de um objeto específico, mas sim um atributo de toda a classe. Portanto, é necessário um método de classe também declarado como static:

public static long getNbPersonnes(){
    return nbPersonnes;
}

que, externamente, será chamado com a sintaxe personne.getNbPersonnes(). Veja um exemplo.

A classe personne fica da seguinte forma:


public class personne{
  
  // atributo de classe
  private static long nbPersonnes=0;

  // atributos de objetos

  
  // construtores
  public personne(String P, String N, int age){
    initialise(P,N,age);
    nbPersonnes++;
  }
  public personne(personne P){
    initialise(P);
    nbPersonnes++;
  }

  // método


  // método de classe
  public static long getNbPersonnes(){
    return nbPersonnes;
  }

}// classe

Com o seguinte programa:


// import pessoa;

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());
  }// main
}//test1

obtêm-se os seguintes resultados:

    Nombre de personnes créées : 3

3.1.13. Passagem de um objeto para uma função

Já mencionamos que o Java passa os parâmetros efetivos de uma função por valor: os valores dos parâmetros efetivos são copiados para os parâmetros formais. Portanto, uma função não pode modificar os parâmetros efetivos.

No caso de um objeto, não se deve se deixar enganar pelo uso incorreto da linguagem, que ocorre sistematicamente quando se fala de “objeto” em vez de “referência de objeto”. Um objeto só é manipulado por meio de uma referência (um ponteiro) a ele. O que é, portanto, passado para uma função não é o próprio objeto, mas uma referência a esse objeto. É, portanto, o valor da referência — e não o valor do próprio objeto — que é copiado no parâmetro formal: não há criação de um novo objeto.

Se uma referência de objeto R1 for passada para uma função, ela será copiada para o parâmetro formal correspondente R2. Portanto, as referências R2 e R1 apontam para o mesmo objeto. Se a função modificar o objeto apontado por R2, ela obviamente modificará aquele referenciado por R1, uma vez que se trata do mesmo objeto.

Image

É o que mostra o exemplo a seguir:


// import pessoa;

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();
  }// main

  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();
  }// modifica
}// classe

O método modifie é declarado como static porque é um método de classe: não é necessário prefixá-lo com um objeto para chamá-lo. 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

Percebe-se que há apenas a criação de um objeto: o da pessoa p1 pela função main e que o objeto foi efetivamente modificado pela função modifie.

3.1.14. Encapsular os parâmetros de saída de uma função em um objeto

Devido à passagem de parâmetros por valor, não é possível escrever em Java uma função que tenha parâmetros de saída do tipo int, por exemplo, pois não é possível passar a referência de um tipo int, que não é um objeto. Portanto, é possível 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 que permite inicializar um inteiro e dois métodos que permitem ler e modificar o valor desse inteiro. Testamos essa classe com o seguinte programa:


// import inteiros;

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:

I=12
I=15

3.1.15. Uma matriz de pessoas

Um objeto é um dado como qualquer outro e, por isso, vários objetos podem ser agrupados em uma tabela:


// import pessoa;

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 pessoa[] amigos = new pessoa[3]; cria um array com 3 elementos do tipo personne. Esses três elementos são inicializados aqui com os valores null e c.a.d, pois não referenciam nenhum objeto. Mais uma vez, por convenção, fala-se em “matriz de objetos”, embora se trate apenas de uma matriz de referências a objetos. A criação da matriz de objetos — matriz que é ela própria um objeto (presença de new) — não cria, portanto, por si só, nenhum objeto do tipo de seus elementos: isso deve ser feito posteriormente.

Obtêm-se os seguintes resultados:

----------------
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. O legado pelo exemplo

3.2.1. Introdução

Abordamos aqui o conceito de herança. O objetivo da herança é “personalizar” uma classe existente para que ela atenda às nossas necessidades. Suponhamos que queiramos criar uma classe enseignant: um professor é uma pessoa específica. Ele possui atributos que outra pessoa não terá: a disciplina que leciona, por exemplo. Mas também possui os atributos de qualquer pessoa: nome, sobrenome e idade. Um professor, portanto, faz parte integralmente da classe personne, mas possui atributos adicionais. Em vez de criar uma classe enseignant do zero, seria preferível aproveitar o que já foi definido na classe personne 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 enseignant herda as propriedades da classe personne, escreveremos:


    public class enseignant extends personne

personne é chamada de classe pai (ou mãe) e enseignant, de classe derivada (ou filha). Um objeto enseignant possui todas as características de um objeto personne: ele tem os mesmos atributos e os mesmos métodos. Esses atributos e métodos da classe pai não são repetidos na definição da classe filha: basta indicar os atributos e métodos adicionados pela classe filha:


class enseignant extends personne{
// atributos
  private int section;

// construtor
  public enseignant(String P, String N, int age,int section){
    super(P,N,age);
    this.section=section;
  }
}

Supomos que a classe personne esteja 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+")";
  }

  // acessores
  public String getPrenom(){
    return prenom;
  }
  public String getNom(){
    return nom;
  }
  public int getAge(){
    return age;
  }

  //modificadores
  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 identifie foi ligeiramente modificado para retornar uma sequência de caracteres que identifica a pessoa e agora se chama identite. Aqui, a classe enseignant acrescenta aos métodos e atributos da classe personne:

  • um atributo section, que é o número da seção à qual o professor pertence no corpo docente (basicamente, uma seção por disciplina)
  • um novo construtor que permite inicializar todos os atributos de um professor

3.2.2. Criação de um objeto professor

O construtor da classe enseignant é o seguinte:


// construtor
  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 personne. Sabemos que esse construtor inicializa os campos prenome, sobrenome e age do objeto personne contido dentro do objeto étudiant. Isso parece bem complicado e talvez fosse melhor escrever:


// construtor
  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 personne declarou como privados (private) seus três campos prenom, nom e age. Apenas objetos da mesma classe têm acesso direto a esses campos. Todos os outros objetos, incluindo objetos derivados como neste caso, devem utilizar métodos públicos para ter acesso a eles. A situação teria sido diferente se a classe personne tivesse declarado os três campos como protegidos (protected): nesse caso, ela permitiria que classes derivadas tivessem acesso direto aos três campos. Em nosso exemplo, usar o construtor da classe pai era, portanto, a solução correta e é o método habitual: ao construir um objeto filho, primeiro chama-se o construtor do objeto pai e, em seguida, completam-se as inicializações específicas do objeto filho (section em nosso exemplo).

Vamos tentar um primeiro programa:


// importar pessoa;
// importar professor;

public class test1{
  public static void main(String arg[]){
    System.out.println(new enseignant("Jean","Dupont",30,27).identite());
  }
}

Este programa se limita a criar um objeto enseignant (novo) e a identificá-lo. A classe enseignant não possui o método identité, mas sua classe pai possui um, que, além disso, é público: por herança, ele se torna um método público da classe enseignant.

Os arquivos-fonte das classes são reunidos em um 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 arquivo test1.class é executado:

E:\data\serge\JAVA\BASES\OBJETS\4>java test1
personne(Jean,Dupont,30)

3.2.3. Sobrecarga de um método

No exemplo anterior, obtivemos a identidade da parte personne do professor, mas faltam algumas informações específicas da turma enseignant (a seção). Portanto, precisamos escrever um método que permita 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 identite da classe enseignant baseia-se no método identite de sua classe pai (super.identite) para exibir sua parte “personne” e, em seguida, complementa com o campo section, que é específico da classe enseignant.

A classe enseignant dispõe agora de dois métodos identite:

  • aquele herdado da classe pai personne
  • e o seu próprio

Se E for um objeto enseignant, E.identite designa o método identite da classe enseignant. Diz-se que o método identite da classe pai é “sobrecarregado” pelo método identite da classe filha. De modo geral, se O for um objeto e M 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 houver uma
  • na classe-mãe da sua classe-mãe, se ela existir
  • etc…

A herança permite, portanto, sobrecarregar na classe filha métodos com o mesmo nome que existem na classe pai. É isso que permite adaptar a classe filha às suas próprias necessidades. Associada ao polimorfismo, que veremos um pouco mais adiante, a sobrecarga de métodos é o principal benefício da herança.

Consideremos o mesmo exemplo de antes:


// importar pessoa;
// importar professor;

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:

    enseignant(personne(Jean,Dupont,30),27)

3.2.4. O polimorfismo

Consideremos uma hierarquia de classes: C0 C1 C2 … Cn

onde Ci Cj indica que a classe Cj é derivada da classe Ci. Isso 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:

    Oi=Oj avec j>i

De fato, por herança, a classe Cj possui todas as características da classe Ci, além de outras. Portanto, um objeto Oj do tipo Cj contém em si um objeto do tipo Ci. A operação

    Oi=Oj

faz com que Oi seja uma referência ao objeto do tipo Ci contido no objeto Oj.

O fato de queuma variável Oi da classe Ci possa, na verdade, referenciar não apenas um objeto da classe Ci, mas qualquer objeto derivado da classe Ci é chamado de polimorfismo: a capacidade de uma variável referenciar diferentes tipos de objetos.

Vamos dar um exemplo e considerar a seguinte função, independente de qualquer classe:

    public static void affiche(Object obj){
        ….
    }

A classe Object é a “classe-pai” de todas as classes Java. Assim, quando escrevemos:

    public class personne

estamos escrevendo implicitamente:

    public class personne extends Object

Portanto, todo objeto Java contém em sua estrutura uma parte do tipo Object. Assim, poderemos escrever:

    enseignant e;
    affiche(e);

O parâmetro formal do tipo Object da função affiche receberá um valor do tipo enseignant. Como enseignant deriva de *Object*, isso é válido.

3.2.5. Sobrecarga e polimorfismo

Vamos completar nossa função affiche:

    public static void affiche(Object obj){
        System.out.println(obj.toString());
    }

O método obj.toString() retorna uma sequência de caracteres que identifica o objeto obj no formato nom_de_la_classe@adresse_de_l'objeto. O que acontece no caso do nosso exemplo anterior:

    enseignant e=new enseignant(...);
    affiche(e);

O sistema deverá executar a instrução System.out.println(e.toString()), em que e é um objeto professor. Ele procura um método toString na hierarquia de classes que conduz à classe enseignant, começando pela última:

  • na classe enseignant, ele não encontra o método toString()
  • na classe pai personne, ele não encontra o método toString()
  • na classe pai Object, ele encontra o método toString() e o executa

É isso que mostra o programa a seguir:


// importar pessoa;
// importar professor;

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 obtidos são os seguintes:

enseignant@1ee789
personne@1ee770

Ou seja, o objeto nom\_de\_la\_classe@adresse\_de\_l. Como isso não é muito explícito, ficamos tentados a definir um método toString para as classes personne e etudiant, que sobrecarregariam o método toString da classe pai Object. Em vez de escrever métodos que seriam semelhantes aos métodos identite já existentes nas classes personne e enseignant, basta renomear esses métodos identite 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+")";    
  }    
}

Com o mesmo programa de teste de antes, os resultados obtidos são os seguintes:

enseignant(personne(Lucile,Dumas,56),61)
personne(Jean,Dupont,30)

3.3. Classes internas

Uma classe pode conter a definição de outra classe. Consideremos o exemplo a seguir:

// classes importadas
import java.io.*;

public class test1{

    // classe interna
    private class article{
         // definimos a estrutura
        private String code;
        private String nom;
        private double prix;
        private int stockActuel;
        private int stockMinimum;

     // construtor
    public article(String code, String nom, double prix, int stockActuel, int stockMinimum){
         // inicialização dos atributos
      this.code=code;
      this.nom=nom;
      this.prix=prix;
      this.stockActuel=stockActuel;
      this.stockMinimum=stockMinimum;
    }//construtor

    //toString
    public String toString(){
        return "article("+code+","+nom+","+prix+","+stockActuel+","+stockMinimum+")";
    }//toString
  }//classe de item

   // dados locais
  private article art=null;

   // fabricante
  public test1(String code, String nom, double prix, int stockActuel, int stockMinimum){
       // definição de atributo
    art=new article(code, nom, prix, stockActuel,stockMinimum);
  }//teste1

   // acessador
  public article getArticle(){
      return art;
  }//getArticle

    public static void main(String arg[]){
      // criação de uma instância test1
      test1 t1=new test1("a100","velo",1000,10,5);
    // exibição test1.art
    System.out.println("art="+t1.getArticle());
  }//main

}// fim da classe

A classe test1 contém a definição de outra classe, a classe article. Diz-se que article é uma classe interna da classe test1. Isso pode ser útil quando a classe interna só tem utilidade na classe que a contém. Ao compilar o código-fonte test1.java acima, obtêm-se dois arquivos .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

Um arquivo test1$article.class foi gerado para a classe article, interna à classe test1. Ao executar o programa acima, obtêm-se os seguintes resultados:

E:\data\serge\JAVA\classes\interne>java test1
art=article(a100,velo,1000.0,10,5)

3.4. As interfaces

Uma interface é um conjunto de protótipos de métodos ou propriedades que forma um contrato. Uma classe que decide implementar uma interface se compromete a fornecer uma implementação de todos os métodos definidos na interface. É o compilador que verifica essa implementação.

Veja, por exemplo, a 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, caso este objeto de enumeração tenha pelo menos mais um elemento a fornecer.
 

Qualquer classe que implemente esta interface será declarada como

public class C : Enumeration{
    ...
    boolean hasMoreElements(){....}
    Object nextElement(){...}
}

Os métodos hasMoreElements() e nextElement() deverão ser definidos na classe C.

Consideremos o código a seguir, que define uma classe élève para registrar o nome de um aluno e sua nota em uma disciplina:


     // uma classe aluno
public class élève{
     // atributos públicos
    public String nom;
    public double note;
     // construtor
    public élève(String NOM, double NOTE){
        nom=NOM;
        note=NOTE;
    }//construtor
}//aluno  

Definimos uma classe notes que reúne as notas de todos os alunos em uma disciplina:


// classes importadas
// importação de aluno

// classe de notas
public class notes{

     // atributos
    protected String matière;
    protected élève[] élèves;

     // construtor
    public notes (String MATIERE, élève[] ELEVES){
         // armazenamento de alunos e disciplinas
        matière=MATIERE;
        élèves=ELEVES;
    }//notas

    // toString
    public String toString(){
        String valeur="matière="+matière +", notes=(";
        int i;
         // concatenamos todas as notas
        for (i=0;i<élèves.length-1;i++){
            valeur+="["+élèves[i].nom+","+élèves[i].note+"],";
        };
         //última nota
        if(élèves.length!=0){ valeur+="["+élèves[i].nom+","+élèves[i].note+"]";}
        valeur+=")";
         // fim
        return valeur;
    }//toString
}//classe

Os atributos matière e élèves são declarados como protected para que possam ser acessados por uma classe derivada. Decidimos derivar a classe notes em uma classe notesStats, que teria dois atributos adicionais: a média e o desvio-padrão das notas:


public class notesStats extends notes implements Istats {
     // atributos
    private double _moyenne;
    private double _écartType;

A classe notesStats deriva da classe notes e implementa a seguinte interface Istats:


// uma interface
public interface Istats{
    double moyenne();
    double écartType();
}//

Isso significa que a classe notesStats deve ter dois métodos chamados moyenne e écartType com a assinatura indicada na interface Istats. A classe notesStats é a seguinte:


// classes importadas
// importar notas;
// import Istats;
// import aluno;

public class notesStats extends notes implements Istats {
     // atributos
    private double _moyenne;
    private double _écartType;

     // construtor
    public notesStats (String MATIERE, élève[] ELEVES){
         // construção da classe pai
        super(MATIERE,ELEVES);
         // cálculo da média das notas
        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;
         // desvio-padrão
        double carrés=0;
        for (int i=0;i<élèves.length;i++){
            carrés+=Math.pow((élèves[i].note-_moyenne),2);
        }//para
        if(élèves.length!=0) _écartType=Math.sqrt(carrés/élèves.length);
        else _écartType=-1;
    }//construtor

    
     // ToString
    public String toString(){
        return super.toString()+",moyenne="+_moyenne+",écart-type="+_écartType;
    }//ToString

     // métodos da interface Istats
    public double moyenne(){
         // calcula a média das notas
        return _moyenne;
    }//média
    public double écartType(){
         // retorna o desvio-padrão
        return _écartType;
    }//écartType
}//classe

A média _moyenne e o desvio-padrão _ecartType são calculados assim que o objeto é criado. Portanto, os métodos moyenne e écartType precisam apenas retornar o valor dos atributos _moyenne e _ecartType. Ambos os métodos retornam -1 se a tabela de alunos estiver vazia.

A seguinte classe de teste:


// classes importadas
// importação de aluno;
// importação de Istats;
// importar notas;
// importar notesStats;

// turma de teste
public class test{
    public static void main(String[] args){
        // alguns alunos e notas
        élève[] ELEVES=new élève[] { new élève("paul",14),new élève("nicole",16), new élève("jacques",18)};
         // que são gravadas em um objeto de notas
        notes anglais=new notes("anglais",ELEVES);
         // e que são exibidas
        System.out.println(""+anglais);
         // o mesmo com média e desvio-padrão
        anglais=new notesStats("anglais",ELEVES);
        System.out.println(""+anglais);
    }//main
}//turma

resulta em:


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

Todas as diferentes classes deste exemplo estão em arquivos-fonte distintos:

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 moyenne e écartType por conta própria, sem indicar que implementava a interface Istats. Qual é, então, a utilidade das interfaces? É a seguinte: uma função pode aceitar como parâmetro um dado do tipo de uma interface I. Qualquer objeto de uma classe C que implemente a interface I poderá, então, ser um parâmetro dessa função. Consideremos a seguinte interface:


// uma interface Iexample
public interface Iexemple{
    int ajouter(int i,int j);
    int soustraire(int i,int j);
}//interface

A interface Iexemple define dois métodos: ajouter e soustraire. As seguintes classes, classe1 e classe2, implementam essa interface.


// classes importadas
// import Iexemplo;

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;
    }
}//classe

// classes importadas
// import Iexemplo;

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;
    }
}//classe

Para simplificar o exemplo, as classes não fazem nada além de implementar a interface Iexemple. Agora, consideremos o seguinte exemplo:


// classes importadas
// import classe1;
// importar classe2;

// classe de teste
public class test{
    // uma função estática
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calcular

     // a função main
    public static void main(String[] arg){
        // criação de dois objetos classe1 e classe2
        classe1 c1=new classe1();
        classe2 c2=new classe2();
        // chamadas à função estática calcular
        calculer(4,3,c1);
        calculer(14,13,c2);
    }//main
}//classe test

A função estática calculer aceita como parâmetro um elemento do tipo Iexemple. Portanto, ela poderá receber para esse parâmetro tanto um objeto do tipo classe1 quanto do tipo classe2. É isso que é feito na função main, com os seguintes resultados:

17
21
127
201

Vemos, portanto, que temos aqui uma propriedade semelhante ao polimorfismo observado nas classes. Assim, se um conjunto de classes Ci não ligadas entre si por herança (portanto, não é possível utilizar o polimorfismo de herança) apresentar um conjunto de métodos com a mesma assinatura, pode ser interessante agrupar esses métodos em uma interface I da qual todas as classes envolvidas herdariam. Instâncias dessas classes Ci podem, então, ser utilizadas como parâmetros de funções que aceitam um parâmetro do tipo I, c.a.d. Essas funções utilizam apenas os métodos dos objetos Ci definidos na interface I e não os atributos e métodos específicos das diferentes classes Ci.

No exemplo anterior, cada classe ou interface era objeto de um arquivo-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, vale ressaltar que a herança de interfaces pode ser múltipla, c.a.d. O que pode ser escrito como

public class classeDérivée extends classeDeBase implements i1,i2,..,in{
...
}

onde ij são interfaces.

3.5. Classes anônimas

No exemplo anterior, as classes classe1 e classe2 poderiam não ter sido definidas explicitamente. Consideremos o seguinte programa, que faz basicamente a mesma coisa que o anterior, mas sem a definição explícita das classes classe1 e classe2:


// classes importadas
// import Iexample;

// classe de teste
public class test2{

  // uma classe interna
  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;
        }
    };//definição da classe3

     // uma função estática
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calcular

     // a função main
    public static void main(String[] arg){
        // criação de dois objetos que implementam a interface Iexample
        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;
        }
    };//definição de 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;
        }
    };//definição de i2
        // outro objeto Iexemple
    Iexemple i3=new classe3();

        // chamadas à função estática `calcular`
        calculer(4,3,i1);
        calculer(14,13,i2);
    calculer(24,23,i3);
    }//main
}//classe test

A particularidade está no código:


         // criação de dois objetos que implementam a interface Iexample
        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;
        }
    };//definição i1

Cria-se um objeto i1 cuja única função é implementar a interface Iexemple. Esse objeto é do tipo Iexemple. Portanto, é possível criar objetos do tipo interface. Inúmeros métodos de classes Java retornam objetos do tipo interface c.a.d — objetos cuja única função é implementar os métodos de uma interface. Para criar o objeto i1, poderíamos ficar 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, define-se essa classe “na hora” no próprio corpo da definição do objeto i1:


        Iexemple i1=new Iexemple(){
        public int ajouter(int a, int b){
            // definição de “adicionar”
        }
        public int soustraire(int a, int b){
            // definição de subtrair
        }
    };//definição de i1

O significado dessa instrução é análogo à sequência:

public class test2{
................
// uma classe interna
private static class classe1 implements Iexemple{
        public int ajouter(int a, int b){
            // definição de somar
        }
        public int soustraire(int a, int b){
            // definição de subtrair
        }
};//definição da classe1
.................
    public static void main(String[] arg){
...........
        Iexemple i1=new classe1();
}//main
}//classe

No exemplo acima, instanciamos uma classe e não uma interface. Uma classe definida “na hora” é chamada de classe anônima. Esse é um método frequentemente utilizado para instanciar objetos cuja única função é implementar uma interface.

A execução do programa anterior produz os seguintes resultados:

17
21
127
201
1047
2001

O exemplo anterior utilizava classes anônimas para implementar uma interface. Elas também podem ser usadas para derivar classes que não possuem construtores com parâmetros. Consideremos o exemplo a seguir:

// classes importadas
// 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;
    }
};//definição da classe3

public class test4{

     // uma função estática
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calcular

   // método main
    public static void main(String args[]){
       // definição de uma classe anônima derivada de classe3
     // para redefinir a função subtrair
    classe3 i1=new classe3(){
        public int ajouter(int a, int b){
          return a+b+10000;
      }//subtrair
    };//i1
          // chamadas à função estática calcular
        calculer(4,3,i1);
  }//main
}//classe 

Encontramos aqui uma classe classe3 que implementa a interface Iexemple. Na função main, definimos uma variável i1 cujo tipo é uma classe derivada de classe3. Essa classe derivada é definida “na hora” em uma classe anônima e redefine o método ajouter da classe classe3. A sintaxe é idêntica à da classe anônima que implementa uma interface. Só que, neste caso, o compilador detecta que classe3 não é uma interface, mas uma classe. Para ele, trata-se, portanto, de uma derivação de classe. Todos os métodos que ele encontrar no corpo da classe anônima substituirão os métodos com o mesmo nome da classe base.

A execução do programa anterior produz os seguintes resultados:

E:\data\serge\JAVA\classes\anonyme>java test4
10007
2001

3.6. Os pacotes

3.6.1. Criar classes em um pacote

Para escrever uma linha na tela, usamos a instrução

System.out.println(...)

Se analisarmos a definição da classe System, descobrimos que, na verdade, ela se chama java.lang.System:

Image

Vamos verificar isso com um exemplo:


public class test1{
    public static void main(String[] args){
        java.lang.System.out.println("Coucou");
    }//main
}//classe

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");

Isso porque, implicitamente, em todo programa Java, há uma importação sistemática do “pacote” java.lang. Assim, tudo ocorre como se, no início de todo programa, houvesse a instrução:

import java.lang.*;

O que significa essa instrução? Ela dá acesso a todas as classes do pacote java.lang. O compilador encontrará nele o arquivo System.class, que define a classe System. Ainda não sabemos onde o compilador encontrará o pacote java.lang nem como é a estrutura de um pacote. Voltaremos a esse assunto. Para criar uma classe em um pacote, escreve-se:

package paquetage;
// definição da classe
...

Para o exemplo, vamos criar em um pacote nossa classe personne, estudada anteriormente. Escolheremos istia.st como nome do pacote. A classe personne passa a ser:


// nome do pacote no qual a classe pessoa será criada
package istia.st;

// classe pessoa
public class personne{
    // nome, sobrenome, idade
    private String prenom;
    private String nom;
    private int age;
  
    // construtor 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+")";
    }

}//classe

Essa classe é compilada e, em seguida, colocada no diretório istia\st do diretório atual. Por que istia\st? Porque o pacote se chama 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 personne em uma 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);
        }//mão
    }//classe de teste

Observe-se que a classe personne agora é precedida pelo nome de seu pacote, istia.st. Onde o compilador encontrará a classe istia.st.personne? O compilador procura as classes de que precisa em uma lista predefinida de diretórios e em uma árvore de diretórios a partir do diretório atual. Neste caso, ele procurará a classe istia.st.personne no arquivo istia\st\personne.class. É por isso que colocamos o arquivo 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);

É possível importar a classe istia.st.personne com uma cláusula import:


import istia.st.personne;

Assim, podemos escrever


            personne p1=new personne("Jean","Dupont",20);

e o compilador traduzirá como


            istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);

O programa de teste fica, então, assim:


// espaços de nomes importados
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);
    }//main
}//classe test2

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)

Colocamos o pacote istia.st no diretório atual. Isso não é obrigatório. Vamos colocá-lo em uma pasta chamada mesClasses, ainda no diretório atual. Vale lembrar que as classes do pacote istia.st estão localizadas em uma pasta chamada istia\st. A estrutura 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 não consegue mais encontrar o pacote istia.st desde que ele foi movido. Observe que ele o procura por causa da instrução import. Por padrão, ele o procura a partir do diretório atual, em uma pasta chamada istia\st, que não existe mais. 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. Ela permite indicar ao compilador onde procurar suas classes e pacotes. Vamos tentar. Vamos compilar informando ao compilador que o pacote istia.st agora está 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 ocorre sem 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 encontrar a classe istia/st/personne. Ela procura a classe no diretório atual, mas agora ela está no diretório mesClasses. Vamos verificar 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

Vemos que o JVM também possui uma opção classpath, assim como o compilador. Vamos usá-la para indicar onde está localizado 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 avançamos muito. Agora é a própria classe test2 que não está sendo encontrada. Pelo seguinte motivo: na ausência da palavra-chave classpath, o diretório atual é sistematicamente vasculhado durante a busca por classes, mas isso não ocorre quando ela está presente. Consequentemente, a classe test2.class, que se encontra no diretório atual, não é encontrada. A solução? Adicionar 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)

Por que todas essas complicações? O objetivo dos pacotes é evitar conflitos de nomes entre classes. Consideremos duas empresas, E1 e E2, que distribuem classes empacotadas, respectivamente, nos pacotes com.e1 e com.e2. Suponhamos um cliente C que adquira esses dois conjuntos de classes, nos quais ambas as empresas definiram uma classe personne. O cliente C fará referência à classe personne daempresa E1 como com.e1.personne e a da empresa E2 como com.e2.personne, evitando assim um conflito de nomes.

3.6.2. Pesquisa de pacotes

Quando escrevemos em um programa

import java.util.*;

para ter acesso a todas as classes do pacote java.util, onde ele é encontrado? Já mencionamos que os pacotes são procurados, por padrão, no diretório atual ou na lista de diretórios declarados na opção classpath do compilador ou na JVM, caso essa opção esteja presente. Eles também são procurados nos diretórios lib do diretório de instalação do JDK. Consideremos este diretório:

Image

Neste exemplo, as árvores de diretórios jdk14\lib e jdk14\jre\lib serão exploradas para localizar arquivos .class, .jar ou .zip, que são arquivos compactados de classes. Vamos, por exemplo, fazer uma busca pelos arquivos .jar localizados no diretório jdk14 anterior:

Image

Existem várias dezenas deles. Um arquivo .jar pode ser aberto com o utilitário winzip. Vamos abrir o arquivo rt.jar acima (rt=RunTime). Encontramos várias centenas de arquivos .class, incluindo aqueles que pertencem ao pacote java.util:

Image

Uma maneira simples de gerenciar os 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, e é prático agrupá-las em um único arquivo .jar (JAR = arquivo Java ARchive). O executável jar.exe está localizado 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

É possível obter ajuda sobre o uso do programa jar executando-o sem 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''archives existant
    -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 pour les fichiers jar 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 doivent Ûtre spÚcifiÚs
dans l''ordre des indicateurs ''m'' et ''f''.

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 ''monmanifest'' pour archiver tous les fichiers du

           rÚpertoire foo/ dans ''classes.jar'':
       jar cvfm classes.jar monmanifest -C foo/ .

Voltemos à classe personne.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 arquivo istia.st.jar que armazene todas as classes do pacote istia.st, ou seja, todas as classes da árvore istia\st 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, com o winzip, o conteúdo do arquivo istia.st.jar:

Image

Vamos colocar o arquivo 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)

Percebemos que bastou indicar o nome do arquivo a ser pesquisado, sem precisar especificar explicitamente onde ele se encontrava. Todos os diretórios da árvore de diretórios <jdk>\jre\lib são pesquisados para localizar o arquivo .jar solicitado.

3.7. O exemplo IMPÔTS

Retomamos o cálculo do imposto já estudado no capítulo anterior e o processamos utilizando uma classe. Recorde-se o problema:

Consideramos o caso simplificado de um contribuinte que tem apenas seu salário para declarar:

  • calcula-se o número de cotas do empregado nbParts = nbEnfants/2 + 1 se ele for solteiro, nbEnfants/2 + 2 se for casado, onde nbEnfants é o número de filhos.
  • se tiver pelo menos três filhos, recebe mais meia parcela
  • calcula-se sua renda tributável R = 0,72 * S, onde S é seu salário anual
  • calcula-se seu coeficiente familiar QF = R / nbParts
  • calcula-se seu imposto I. Consideremos a tabela a seguir:
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 possui 3 campos. Para calcular o imposto I, procura-se a primeira linha em que QF ≤ campo1. Por exemplo, se QF = 23000, será encontrada a linha

    24740        0.15        2072.5

O imposto I é, então, igual a 0,15*R - 2072,5*nbParts. Se QF for tal que a relação QF <= campo1 nunca for verificada, então serão utilizados os coeficientes da última linha. Aqui:

    0                0.65        49062

o que resulta no imposto I = 0,65 * R - 49.062 * nbParts.

A classe impots será definida da seguinte forma:


// criação de uma classe de impostos

public class impots{

    // os dados necessários para o cálculo do imposto
     // provêm de uma fonte externa

    private double[] limites, coeffR, coeffN;

     // fabricante
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
         // verifica-se se as três tabelas têm o mesmo tamanho
        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+")");
        // tudo certo
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//criador

     // cálculo do imposto
    public long calculer(boolean marié, int nbEnfants, int salaire){
         // cálculo do número de cotas
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
         // cálculo do rendimento tributável e do quociente familiar
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // cálculo do imposto
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // retorno do resultado
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calcular
}//classe

É criado um objeto “impostos” com os dados necessários para o cálculo do imposto de um contribuinte. Essa é a parte estável do objeto. Uma vez criado esse objeto, é possível chamar repetidamente seu método “calcular”, que calcula o imposto do contribuinte com base em seu estado civil (casado ou solteiro), no número de filhos e no salário anual.

Um programa de teste poderia ser o seguinte:


//classes importadas
// importação de impostos;
import java.io.*;

    public class test
    {
        public static void main(String[] arg) throws IOException
        {
             // programa interativo de cálculo de impostos
             // o usuário digita três dados no teclado: casado nbEnfants salário
             // o programa exibe então o imposto a pagar

            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";

             // tabelas de dados necessárias para o cálculo do imposto
            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};

       // criação de um fluxo de leitura
      BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
            // criação de um objeto de imposto
            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

            // loop infinito
            while(true){
                 // solicitação dos parâmetros para o cálculo do imposto
                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();
                // há algo a ser feito?
                if(paramètres==null || paramètres.equals("")) break;
                // verificação do número de argumentos na linha inserida
                String[] args=paramètres.split("\\s+");
                int nbParamètres=args.length;
                if (nbParamètres!=3){
                    System.err.println(syntaxe);
                    continue;
                }//if
                 // verificação da validade dos parâmetros
                // casado
                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
                 // salário
                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
                 // os parâmetros estão corretos — calcula-se o imposto
                System.out.println("impôt="+objImpôt.calculer(marié.equals("o"),nbEnfants,salaire)+" F");
                 // próximo contribuinte
            }//enquanto
        }//main
    }//classe

Aqui está um exemplo de execução do programa anterior:

E:\data\serge\MSNET\c#\impostos\3>teste em Java

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 :