Examen de programmation Java

Partie 1 : Choix multiples (QCM) Question 1 - Instanciation et héritage des constructeurs Le résultat de ce code lorsqu'il est exécuté est la Réponse C : 123 . Explication détaillée : En Java, lorsqu'une classe enfant est instanciée, son constructeur appelle systématiquement le constructeur de sa classe parente avant d'exécuter ses propres instructions.

D'après le document Examen de programmation Java

Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Examen de programmation Java

Document source

Examen de programmation Java

Programmation, Mathématiques · PDF · 8 pages · 2015

Afficher l'aperçu du document

Consulter le document original →

Partie 1 : Choix multiples (QCM)

Question 1 - Instanciation et héritage des constructeurs

Le résultat de ce code lorsqu'il est exécuté est la Réponse C : 123.

Explication détaillée : En Java, lorsqu'une classe enfant est instanciée, son constructeur appelle systématiquement le constructeur de sa classe parente avant d'exécuter ses propres instructions. Si aucun appel explicite (comme super(arguments)) n'est écrit par le programmeur, le compilateur insère un appel implicite au constructeur par défaut (sans arguments) de la classe parente super().

Ainsi, lors de l'appel de new Three() :

  1. Le constructeur Three() est appelé. Il déclenche d'abord super() vers Two().
  2. Le constructeur Two() est appelé. Il déclenche d'abord super() vers One().
  3. Le constructeur One() s'exécute et affiche 1.
  4. Le contrôle revient à Two(), qui exécute son code et affiche 2.
  5. Le contrôle revient à Three(), qui exécute son code et affiche 3.

Question 2 - Héritage et constructeur par défaut manquant

Le résultat est la Réponse D : Compilation échoue en raison d'une erreur dans la ligne 7.

Explication détaillée : Comme expliqué dans la question précédente, le constructeur de la classe fille Employee (à la ligne 7) tente d'appeler implicitement le constructeur par défaut (sans argument) de sa classe parente Person via un super() invisible.

Cependant, en Java, si vous définissez explicitement un constructeur avec paramètres dans une classe (ici public Person(String nm) à la ligne 3), le compilateur ne génère plus automatiquement le constructeur par défaut sans argument. La classe Person n'a donc aucun constructeur vide. L'appel implicite à la ligne 7 échoue, provoquant une erreur de compilation.

Remarque sur le code source : Une faute de frappe est également présente dans le code de l'énoncé (la variable est déclarée emplD à la ligne 6 mais le constructeur utilise empID à la ligne 7). Toutefois, l'erreur liée à l'absence de constructeur parent empêche la compilation de toute façon, et c'est cette règle fondamentale d'héritage objet que la question évalue, comme le confirme la justification de la correction officielle.

Question 3 - Pointeurs de tableaux en mémoire

La bonne réponse est la Réponse B : Ligne 3 affiche la valeur 3.

Explication détaillée : La déclaration int y[] = x; à la ligne 2 ne crée pas un nouveau tableau indépendant. Elle copie uniquement la référence mémoire du tableau x dans la variable y. Les deux variables pointent donc vers exactement le même objet tableau en mémoire. En Java, l'indexation d'un tableau commence toujours à l'indice 0. Par conséquent, y[2] cible la troisième case du tableau {1, 2, 3, 4, 5}, ce qui correspond bien à la valeur numérique 3.

Partie 2 : Conception et implémentation orientée objet Java

Section I - Implémentation de la classe Membre

Pour se conformer strictement à la contrainte ("Les Getters et les setters ne sont à implémenter que si leur demande est explicite"), les attributs autres que id (comme nom) ne posséderont pas de getters. Ils recevront la visibilité par défaut (visibilité de paquetage, ou package-private) afin d'être consultables par les autres classes du même exercice sans nécessiter de méthodes d'accès.

À noter également : la signature exigée par l'énoncé pour la comparaison est boolean equals(Membre m). Bien que la norme Java classique redéfinisse boolean equals(Object obj), nous respectons ici scrupuleusement la signature imposée par le sujet.

public class Membre {
    private int id;
    String nom; // Visibilité 'package' par défaut car aucun getter n'est autorisé

    // Uniquement un constructeur paramétré
    public Membre(int id, String nom) {
        this.id = id;
        this.nom = nom;
    }

    // Getters et Setters seulement pour l'attribut identifiant
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    // La méthode toString
    @Override
    public String toString() {
        return "Membre [id=" + id + ", nom=" + nom + "]";
    }

    // La méthode equals avec la signature spécifique imposée par l'énoncé
    public boolean equals(Membre m) {
        if (m == null) {
            return false;
        }
        // Deux membres sont égaux s'ils ont le même id et le même nom
        return this.id == m.getId() && this.nom.equals(m.nom);
    }
}

Section II - Implémentation des classes Joueur et Entraineur

Ces deux classes héritent de Membre et font appel à super() dans leurs constructeurs.

public class Joueur extends Membre {
    int numero; // Visibilité 'package' pour y accéder plus tard sans getter

    public Joueur(int id, String nom, int numero) {
        super(id, nom);
        this.numero = numero;
    }

    @Override
    public String toString() {
        return "Joueur [id=" + getId() + ", nom=" + nom + ", numero=" + numero + "]";
    }
}

public class Entraineur extends Membre {
    int anneesExperience;

    public Entraineur(int id, String nom, int anneesExperience) {
        super(id, nom);
        this.anneesExperience = anneesExperience;
    }

    @Override
    public String toString() {
        return "Entraineur [id=" + getId() + ", nom=" + nom + ", anneesExperience=" + anneesExperience + "]";
    }
}

Section III - Implémentation de la classe Equipe

Afin de rendre le code compilable et de respecter les exigences de l'énoncé quant à la gestion des erreurs, nous devons d'abord déclarer les exceptions personnalisées.

// Déclaration des exceptions personnalisées requises
public class EquipeNonValideException extends Exception {
    public EquipeNonValideException(String message) {
        super(message);
    }
}

public class TransfertException extends Exception {
    public TransfertException(String message) {
        super(message);
    }
}

Voici l'implémentation complète de la classe Equipe. Nous utilisons un tableau natif et un compteur interne pour respecter techniquement la notion de "capacité maximale de 30" classique de ce niveau de programmation.

public class Equipe {
    private int id;
    private String nom;
    private Membre[] membres;
    private int nombreActuelMembres;

    public Equipe(int id, String nom) {
        this.id = id;
        this.nom = nom;
        this.membres = new Membre[30]; // Capacité maximale de 30 membres
        this.nombreActuelMembres = 0;
    }

    // 1. Rechercher un membre (retourne l'indice ou -1)
    public int rechercher(Membre membre) {
        if (membre == null) return -1;
        for (int i = 0; i < nombreActuelMembres; i++) {
            if (membres[i].equals(membre)) {
                return i;
            }
        }
        return -1;
    }

    // 2. Ajouter un membre (vérification capacité et unicité)
    public void ajouter(Membre membre) {
        if (nombreActuelMembres >= 30) {
            System.out.println("Ajout impossible : la capacité maximale de 30 est atteinte.");
            return;
        }
        if (rechercher(membre) != -1) {
            System.out.println("Ajout impossible : ce membre est déjà dans l'équipe.");
            return;
        }
        membres[nombreActuelMembres] = membre;
        nombreActuelMembres++;
    }

    // 3. Supprimer un membre
    public void supprimer(Membre membre) {
        int indice = rechercher(membre);
        if (indice != -1) {
            // On décale les membres restants vers la gauche pour éviter les espaces null
            for (int i = indice; i < nombreActuelMembres - 1; i++) {
                membres[i] = membres[i + 1];
            }
            membres[nombreActuelMembres - 1] = null;
            nombreActuelMembres--;
        }
    }

    // 4. toString pour l'équipe (id, nom, et uniquement les joueurs)
    @Override
    public String toString() {
        String chaine = "Equipe [id=" + id + ", nom=" + nom + "]\nListe des joueurs :\n";
        for (int i = 0; i < nombreActuelMembres; i++) {
            // L'opérateur instanceof permet de filtrer uniquement les objets de type Joueur
            if (membres[i] instanceof Joueur) {
                chaine += membres[i].toString() + "\n";
            }
        }
        return chaine;
    }

    // 5. Valider la composition de l'équipe
    public void valider() throws EquipeNonValideException {
        int compteurEntraineur = 0;
        for (int i = 0; i < nombreActuelMembres; i++) {
            if (membres[i] instanceof Entraineur) {
                compteurEntraineur++;
            }
        }
        
        if (compteurEntraineur == 1) {
            System.out.println("Equipe valide");
        } else {
            throw new EquipeNonValideException("Erreur de validation : l'équipe doit posséder un et un seul entraineur.");
        }
    }

    // 6. Afficher les numéros des joueurs
    public void afficherNumeros() {
        for (int i = 0; i < nombreActuelMembres; i++) {
            if (membres[i] instanceof Joueur) {
                // Conversion (cast) explicite pour accéder à l'attribut du Joueur
                Joueur j = (Joueur) membres[i];
                System.out.println("Numéro: " + j.numero); 
            }
        }
    }

    // 7. Transférer un joueur entre deux équipes
    // L'énoncé demande "void transfererJoueur". Nous la déclarons statique car la méthode 
    // orchestre des transferts entre deux instances externes distinctes (e1 et e2).
    public static void transfererJoueur(Joueur joueur, Equipe e1, Equipe e2) throws TransfertException {
        if (e1.rechercher(joueur) == -1) {
            throw new TransfertException("Transfert impossible");
        }
        e1.supprimer(joueur);
        e2.ajouter(joueur);
    }
}

Méthode

Face à une épreuve de programmation orientée objet sur papier, l'approche optimale se structure en trois étapes clés :

  1. Extraction du modèle de domaine : Avant de coder, dressez la liste des entités (Membre, Joueur, Equipe). Établissez leurs liens : qui hérite de qui ? (relation "est un" : un Joueur est un Membre), qui contient qui ? (relation "possède" : une Equipe possède des Membre).
  2. Identification des contraintes strictes : Repérez et soulignez les règles impératives. Dans cet examen, l'absence imposée de getters non sollicités est un piège classique ; il dicte immédiatement le choix de la visibilité des variables (passer de private au mode "package-private" par défaut).
  3. Sécurité des traitements en boucle : L'utilisation de tableaux à taille fixe (comme new Membre[30]) implique que toutes les cases ne sont pas nécessairement remplies à un instant T. Il est crucial d'utiliser une variable de contrôle (nombreActuelMembres) pour limiter les itérations plutôt que membres.length, ce qui évite l'apparition fatale d'exceptions de type NullPointerException en plein milieu de vos algorithmes de recherche ou d'affichage.

Partager

Commentaires

Aucun commentaire pour le moment. Posez la première question.

Les commentaires sont relus avant publication. Votre e-mail n'est jamais affiché.

← Toutes les révisions