Université de Nice Sophia-Antipolis
L3 - Programmation orientée objet

Contrôle 1 - Février 2012 - Durée : 2h.

Seuls documents autorisés : photocopies des transparents distribués en cours. Les énoncés et corrections des TP sont interdits. Éteignez les téléphones portables.

Important : la présentation et la lisibilité du code compteront dans la note finale. Vous êtes autorisé à écrire le code (et seulement le code) avec un crayon à papier si c'est parfaitement lisible (pas de crayon trop clair).

Ajoutez des commentaires quand vous pensez que ça peut être utile au correcteur. Ne mettez pas de commentaires évidents qui n'ajoutent rien à votre code.

Respectez le découpage en questions et l'ordre des questions. Les numéros des questions devront apparaître clairement sur votre feuille. Vous pouvez répondre à une question même si vous n'avez pas répondu aux questions précédentes mais si vous sautez une question, vous devez l'indiquer clairement.

Question 1 (3 points)

Voici 3 classes (dans 3 fichiers différents) :

public class A {
  public static void f() {
    System.out.println("Méthode f() de A");
  }
  public void g(A a) {
    System.out.println("Méthode g(A a) de A");
  }
}

public class B extends A {
  public static void f() {
    System.out.println("Méthode f() de B");
  }
  public void g(A a) {
    System.out.println("Méthode g(A a) de B");
  }
  public void g(B b) {
    System.out.println("Méthode g(B b) de B");
  }
}

public class Test {
  public static void main(String[] args) {
    A a = new A();
    a.g(a);
    a = new B();
    a.g(a);
    B b = (B)a;
    b.g(a);
    b.g(b);
    a.f();
    b.f();
  }
}
Que s'affiche-t-il à l'exécution de la méthode main de la classe Test ?

Correction

L'exécution de la méthode main de la classe Test produit la sortie suivante:
Méthode g(A a) de A
Méthode g(A a) de B
Méthode g(A a) de B
Méthode g(B b) de B
Méthode f() de A
Méthode f() de B

Question 2 (3 points)

Voici 3 classes (dans 3 fichiers différents) :

public class A {
  public void m() {
    System.out.println("A");
  }
}

public class B extends A {
  public void m() {
    System.out.println("B");
  }
}

public class Test {
  public static void main(String[] args) {
    A a = new A();
    B b = (B)a;
    b.m();
  }
}
  1. Est-ce que la classe Test compile ? Si elle compile, que sera-t-il affiché par la méthode main ?
  2. Si on enleve le cast de la ligne B b = (B)a; (la ligne devient B b = a;), est-ce que la classe Test compile ? Si elle compile, que sera-t-il affiché ?

Correction

  1. La classe compile. Il y aura une erreur à l'exécution, à cause du cast car on caste en B un A qui n'est pas un B (il sera affiché "Exception in thread "main" java.lang.ClassCastException: A cannot be cast to B"). En effet, ça compile car un cast vers une classe fille ou mère est toujours accepté par le compilateur, mais à l'exécution Java s'aperçoit que a n'est pas de type B.
  2. Ca ne compile pas car un cast vers une classe fille doit être explicite (au contraire d'un cast vers une classe mère).

Question 3 (7 points)

Voici 3 classes et une interface (dans 4 fichiers différents) qui sont un début d'implémentation de listes (d'entiers) en Java:

public interface Liste {
  // Ajoute l'entier i en tête de liste.
  void add(int i);
  
  // Ajoute le contenu de la liste l à la liste courante.
  void addAll(Liste l);
  
  // Retourne le nombre d'entiers contenu dans la liste.
  int size();
  
  // Retourne l'entier à la position pos dans la liste.
  // Le premier entier de la liste est à la position 0.
  int get(int pos);
}

public abstract class AbstractListe implements Liste {
  public void addAll(Liste l) {
    // À compléter
  }
}

public class MaListe extends AbstractListe {
  private Maillon tete;
  public MaListe() {
    tete = null;
  }
  public void add(int i) {
    // À compléter
  }
  public int size() {
    // À compléter
  }
  public int get(int i) {
    // À compléter
  }
}

public class Maillon {
  private int contenu;
  private Maillon queue;
  public Maillon(int contenu, Maillon queue) {
    this.contenu = contenu;
    this.queue = queue;
  }
  public int getContenu() {
    return contenu;
  }
  public Maillon getQueue() {
    return queue;
  }
}
Par exemple si l est une Liste initialement vide, les instructions suivantes
l.add(37);
l.add(99);
l.add(12);
produisent la liste



et l'instruction l.get(0) renvoie 12.
  1. Complétez les classes AbstractListe et MaListe (vous n'avez pas à gérer les cas exceptionnels dans la méthode get).
  2. Quel peut être l'avantage d'associer une classe abstraite à une interface comme on l'a fait ici pour l'interface Liste?
  3. Supposons que nous voulions écrire une classe MonAutreListe qui implémente l'interface Liste et dans laquelle nous allons redéfinir la méthode addAll (pour des questions d'efficacité par exemple). Est-il utile de la faire hériter de AbstractListe? Pourquoi?

Correction

  1. public abstract class AbstractListe implements Liste {
      public void addAll(Liste l) {
        for(int i = 0; i < l.size(); i++) {
          add(l.get(i));
        }
        // Ou mieux pour préserver l'ordre
        // for(int i = l.size() - 1; i >= 0; i--) {
        //   add(l.get(i));
        // }
      }
    }
    
    public class MaListe extends AbstractListe {
      private Maillon tete;
      public MaListe() {
        tete = null;
      }
      public void add(int i) {
        tete = new Maillon(i, tete);
      }
      public int size() {
        Maillon maillon = tete;
        int size = 0;
        while (maillon != null) {
          maillon = maillon.getQueue();
          size++;
        }
        return size;
      }
      public int get(int i) {
        Maillon maillon = tete;
        for (int j = 0; j < i; j++) {
          maillon = maillon.getQueue();
        }
        maillon.getContenu();
      }
    }
            
  2. Associer une classe abstraite à une interface permet d'implémenter de manière générale certaines méthodes de l'interface en fonction des autres. Dans l'exemple, les classes voulant implémenter l'interface Liste n'ont pas à écrire la méthode addAll si elles héritent de AbstractListe. Et celle-ci utilisera la méthode add de la classe fille.
  3. Même en implémentant toutes les méthodes de la classe abstraite, il est intéressant d'hériter de celle-ci car, au cours du développement, elle est susceptible de s'enrichir. Par exemple, on pourrait ajouter à posteriori la méthode toString dans la classe AbstractListe.
    public String toString() {
      StringBuilder builder = new StringBuilder();
      builder.append("[ ");
      for (int i = 0; i < size() - 1; i++) {
        builder.append(get(i));
        builder.append(", ");
      }
      builder.append(get(size() - 1));
      builder.append(" ]");
      return builder.toString();
    }
            
    La question étant ouverte, d'autres réponses seront acceptées si elles sont justifiées.

Question 4 (7 points)

IMPORTANT : vous n'avez pas le droit d'utiliser des collections (listes ou autre) pour résoudre cet exercice. Vous devez utiliser des tableaux.

Une classe Employe représente les employés d'une entreprise. Elle a une méthode
public double getSalaire()
qui renvoie le salaire de l'employé.

Une classe Entreprise représente une entreprise qui emploie des employés. Elle a une méthode
public String getNom()
qui renvoie le nom de l'entreprise, et une méthode
public Employe[] getEmployes()
qui renvoie un tableau qui contient les employés de l'entreprise (le tableau est complètement rempli ; il ne contient pas d'éléments null).

Écrivez une méthode de classe qui prend en paramètre un tableau d'entreprises (complètement rempli) et retourne un tableau qui contient les noms des entreprises qui ont plus de n employés qui gagnent plus qu'un certain salaire. n et le seuil pour le salaire doivent être des paramètres de la méthode. Le tableau renvoyé devra être complètement rempli (pas d'éléments null). La méthode devra renvoyer un tableau de longueur 0 si aucune entreprise ne convient.

Dans quelle classe allez-vous écrire cette méthode ?

Correction

On peut écrire cette méthode dans une classe de test ou dans la classe Entreprise (ça dépend du contexte et de l'utilité de la méthode).

package fr.unice.entreprise;

public class Main {
  
  public static String[] entreprisesAvecEmployesBienPayes(Entreprise[] entreprises, int n, double seuilSalaire) {
    Entreprise[] entreprisesChoisies = new Entreprise[entreprises.length];
    // L'indice pour ranger les entreprises sélectionnées
    int nCourant = 0;
    for (Entreprise entreprise : entreprises) {
      int nbGrosSalaires = 0;
      for (Employe employe : entreprise.getEmployes()) {
        if (employe.getSalaire() > seuilSalaire) {
          nbGrosSalaires++;
        }
      }
      if (nbGrosSalaires > n) {
        entreprisesChoisies[nCourant++] = entreprise;
      }
    }
    // Construit le tableau à renvoyer
    String[] resultat = new String[nCourant];
    for (int i = 0; i < nCourant; i++) {
      resultat[i] = entreprisesChoisies[i].getNom();
    }
    return resultat;
  }