Questions d'entretien pour développeur Java
La préparation d'un entretien est la première étape pour obtenir l'emploi dont vous rêvez. Dans cet article, je vais essayer de vous donner les questions fréquemment posées dans le poste de développeur Java.
Java est l'un des langages de programmation les plus populaires au monde, utilisé par des millions de développeurs et des milliers d'entreprises. Voici une sélection de questions courantes avec leurs réponses détaillées.
Quelle est la différence entre une classe interne et une sous-classe ?
Question fréquente sur la hiérarchie et l'imbrication des classes.
Une classe interne est une classe imbriquée dans une autre classe. Une classe interne a des droits d'accès pour la classe externe et peut accéder à toutes les variables et méthodes définies dans la classe externe.
Une sous-classe est une classe qui hérite d'une autre classe appelée classe mère (superclasse). La sous-classe peut accéder à toutes les méthodes et champs publics et protégés de sa classe mère.
// Exemple de classe interne
class Outer {
private int x = 10;
class Inner {
void display() {
System.out.println("Accès à x depuis la classe interne : " + x);
}
}
}
// Exemple de sous-classe
class Parent {
protected void display() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override
public void display() {
System.out.println("Child");
super.display();
}
}À quoi servent les méthodes et variables statiques ?
Lorsqu'il est nécessaire de partager une méthode ou une variable entre plusieurs objets (instances) d'une classe au lieu de créer des copies séparées pour chaque objet, nous utilisons le mot-clé static pour créer une méthode ou une variable partagée pour tous les objets.
public class Compteur {
private static int compteurGlobal = 0; // Variable statique partagée
private int compteurInstance = 0; // Variable d'instance
public Compteur() {
compteurGlobal++;
compteurInstance++;
}
public static void afficherCompteurGlobal() {
System.out.println("Compteur global : " + compteurGlobal);
}
public void afficherCompteurInstance() {
System.out.println("Compteur instance : " + compteurInstance);
}
}
// Utilisation
Compteur c1 = new Compteur();
Compteur c2 = new Compteur();
Compteur c3 = new Compteur();
Compteur.afficherCompteurGlobal(); // Affiche 3
c1.afficherCompteurInstance(); // Affiche 1- Les membres statiques appartiennent à la classe, pas aux instances
- Ils sont chargés une seule fois en mémoire au moment du chargement de la classe
- On peut y accéder directement via le nom de la classe (pas besoin d'instance)
- Les méthodes statiques ne peuvent accéder qu'aux membres statiques
Qu'est-ce que l'encapsulation de données et quelle est sa signification ?
L'encapsulation est un concept de la programmation orientée objet permettant de combiner des propriétés et des méthodes dans une seule unité.
L'encapsulation aide les programmeurs à suivre une approche modulaire pour le développement de logiciels, car chaque objet possède son propre ensemble de méthodes et de variables et remplit ses fonctions indépendamment des autres objets.
L'encapsulation sert également à cacher des données (data hiding). En rendant les attributs privés et en fournissant des méthodes publiques pour y accéder (getters/setters), on contrôle la manière dont les données sont modifiées.
public class Personne {
private String nom; // Attributs privés
private int age;
// Constructeur
public Personne(String nom, int age) {
this.nom = nom;
this.age = age;
}
// Getters et Setters (accès contrôlé)
public String getNom() {
return nom;
}
public void setNom(String nom) {
if (nom != null && !nom.isEmpty()) {
this.nom = nom;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age > 0 && age < 150) { // Validation
this.age = age;
}
}
}- Contrôle d'accès aux données (sécurité)
- Validation des données avant modification
- Modularité et maintenabilité du code
- Masquage de l'implémentation interne
Qu'est-ce qu'une classe singleton ? Donnez un exemple pratique de son utilisation.
Une classe singleton en Java ne peut avoir qu'une seule instance et donc toutes ses méthodes et variables appartiennent à une seule instance. Le concept de classe Singleton est utile dans les situations où il est nécessaire de limiter le nombre d'objets pour une classe.
Le meilleur exemple de scénario d'utilisation singleton est lorsqu'il y a une limite d'avoir une seule connexion à une base de données en raison de certaines limitations de pilote ou en raison de problèmes de licence.
public class DatabaseConnection {
private static DatabaseConnection instance;
private Connection connection;
// Constructeur privé (empêche l'instanciation directe)
private DatabaseConnection() {
// Initialisation de la connexion
try {
connection = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/mabase",
"user",
"password"
);
} catch (SQLException e) {
e.printStackTrace();
}
}
// Méthode statique pour obtenir l'instance unique
public static synchronized DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public Connection getConnection() {
return connection;
}
}
// Utilisation
DatabaseConnection db1 = DatabaseConnection.getInstance();
DatabaseConnection db2 = DatabaseConnection.getInstance();
System.out.println(db1 == db2); // true (même instance)- Constructeur privé (interdit l'instanciation externe)
- Instance statique privée
- Méthode publique statique pour obtenir l'instance
- Initialisation paresseuse (lazy initialization) possible
- Thread-safe avec synchronized si nécessaire
Quelle est la différence entre les variables doubles et flottantes en Java ?
| Caractéristique | float | double |
|---|---|---|
| Taille en mémoire | 4 octets (32 bits) | 8 octets (64 bits) |
| Précision | Simple précision (environ 6-7 chiffres décimaux) | Double précision (environ 15-16 chiffres décimaux) |
| Suffixe | float f = 3.14f; | double d = 3.14; (ou 3.14d) |
En Java, float prend 4 octets en mémoire tandis que double prend 8 octets. Float est un nombre décimal à virgule flottante simple précision tandis que Double est un nombre décimal double précision.
float prix = 19.99f; // Le suffixe 'f' est obligatoire
double pi = 3.14159265359; // Pas de suffixe nécessaire (d par défaut)
// Attention aux calculs avec des flottants
double resultat = 0.1 + 0.2; // Résultat approximatif : 0.30000000000000004- Utilisez double pour la plupart des calculs scientifiques et financiers
- Utilisez float seulement pour économiser la mémoire (grands tableaux)
- Pour des calculs nécessitant une précision absolue, utilisez
BigDecimal
Qu'est-ce que le mot-clé final en Java ?
Le mot-clé final en Java peut être appliqué à des variables, des méthodes et des classes, avec des significations différentes :
En java, une constante est déclarée à l'aide du mot-clé final. La valeur ne peut être affectée qu'une seule fois et après l'affectation, la valeur d'une constante ne peut pas être modifiée.
final int MAX_VALUE = 100;
final List<String> liste = new ArrayList<>();
liste.add("Bonjour"); // OK : la référence ne change pas, mais le contenu peut changer
// liste = new ArrayList<>(); // ERREUR : ne peut pas réaffecter la référenceLorsqu'une méthode est déclarée comme finale, elle ne peut pas être remplacée (overridden) par les sous-classes. Ces méthodes sont plus rapides que toute autre méthode car elles sont résolues au moment de la compilation.
class Parent {
public final void afficher() {
System.out.println("Méthode finale");
}
}
class Child extends Parent {
// public void afficher() { } // ERREUR : ne peut pas surcharger une méthode finale
}Lorsqu'une classe est déclarée comme finale, elle ne peut pas être sous-classée. Exemple : String, Integer et autres classes enveloppes sont finales.
final class MaClasse {
// ...
}
// class SousClasse extends MaClasse { } // ERREUR : ne peut pas étendre une classe finaleQue sont les packages Java ?
En Java, un package est une collection de classes et d'interfaces qui sont regroupées car elles sont liées les unes aux autres.
L'utilisation de packages aide les développeurs à modulariser le code et à regrouper le code pour une réutilisation appropriée. Une fois que le code a été empaqueté dans des packages, il peut être importé et utilisé dans d'autres classes.
// Déclaration d'un package (en haut du fichier)
package com.monentreprise.monprojet.modeles;
// Import de classes d'autres packages
import java.util.List;
import java.util.ArrayList;
import com.monentreprise.monprojet.utils.MonUtil;
public class MaClasse {
// ...
}- Organisation : Structure logique du code
- Espace de noms : Évite les conflits de noms
- Contrôle d'accès : Niveaux de visibilité (public, protected, private, package-private)
- Réutilisabilité : Facilite le partage de code
Peut-on déclarer une classe comme abstraite sans avoir de méthode abstraite ?
Oui, nous pouvons créer une classe abstraite en utilisant le mot-clé abstract avant le nom de la classe même si elle n'a pas de méthode abstraite.
Cependant, si une classe a même une méthode abstraite, elle doit être déclarée comme abstraite sinon elle donnera une erreur de compilation.
// Classe abstraite sans méthode abstraite
public abstract class BaseDeDonnees {
protected String url;
protected String utilisateur;
public void connecter() {
System.out.println("Connexion à " + url);
}
public void deconnecter() {
System.out.println("Déconnexion");
}
}
// Sous-classe concrète
public class MySQLDatabase extends BaseDeDonnees {
public void executerRequete(String sql) {
System.out.println("Exécution MySQL : " + sql);
}
}- Empêcher l'instanciation directe de la classe
- Fournir une base commune avec des méthodes concrètes
- Définir un modèle que les sous-classes doivent suivre
Quelle est la différence entre une classe abstraite et une interface en Java ?
| Caractéristique | Classe abstraite | Interface |
|---|---|---|
| Méthodes | Peut avoir des méthodes abstraites et concrètes | Avant Java 8 : seulement des méthodes abstraites Java 8+ : méthodes par défaut et statiques |
| Variables | Peut avoir des variables d'instance (non finales) | Seulement des constantes (static final) |
| Constructeurs | Peut avoir des constructeurs | Pas de constructeurs |
| Héritage multiple | Une classe ne peut étendre qu'une seule classe abstraite | Une classe peut implémenter plusieurs interfaces |
| Modificateurs d'accès | Peut utiliser tous les modificateurs (public, private, protected) | Méthodes implicitement public |
Une classe qui implémente une interface doit implémenter toutes les méthodes de l'interface, tandis qu'une classe qui hérite d'une classe abstraite ne nécessite pas l'implémentation de toutes les méthodes de sa superclasse.
// Interface
interface Volant {
void voler();
default void atterrir() {
System.out.println("Atterrissage par défaut");
}
}
// Classe abstraite
abstract class Animal {
protected String nom;
public Animal(String nom) {
this.nom = nom;
}
public abstract void manger();
public void dormir() {
System.out.println(nom + " dort");
}
}
// Classe concrète qui hérite et implémente
class Oiseau extends Animal implements Volant {
public Oiseau(String nom) {
super(nom);
}
@Override
public void manger() {
System.out.println(nom + " mange des graines");
}
@Override
public void voler() {
System.out.println(nom + " vole dans le ciel");
}
}Quelles sont les implications en termes de performances des interfaces sur les classes abstraites ?
Les interfaces sont plus lentes en termes de performances que les classes abstraites, car des indirections supplémentaires sont nécessaires pour les interfaces.
L'utilisation d'interfaces impose également une charge supplémentaire aux développeurs car chaque fois qu'une interface est implémentée dans une classe, le développeur est obligé d'implémenter chaque méthode de l'interface.
Dans les applications modernes, la différence de performance est souvent négligeable. Le choix entre interface et classe abstraite devrait être basé sur la conception (besoin d'héritage multiple, de méthodes concrètes, etc.) plutôt que sur des considérations de performance mineures.
Importer un package importe-t-il également ses sous-packages en Java ?
En Java, lorsqu'un package est importé, ses sous-packages ne sont pas importés et le développeur doit les importer séparément si nécessaire.
Par exemple, si un développeur importe un package mvc.*, toutes les classes du package nommé mvc sont chargées mais aucune classe du sous-package n'est chargée.
// Importe les classes du package mvc, mais pas des sous-packages
import mvc.*;
// Pour charger les classes du sous-package models, il faut l'importer explicitement
import mvc.models.*;Cette conception permet d'éviter les conflits de noms et de mieux contrôler la visibilité des classes.
Pouvons-nous déclarer la méthode principale (main) de notre classe comme privée ?
En Java, la méthode main doit être public static afin d'exécuter correctement n'importe quelle application.
Si la méthode main est déclarée comme privée, le développeur n'obtiendra aucune erreur de compilation, cependant, elle ne sera pas exécutée et donnera une erreur d'exécution.
public class Test {
// Signature correcte
public static void main(String[] args) {
System.out.println("Exécution réussie");
}
// Signature incorrecte - compile mais ne s'exécute pas
private static void mainPrive(String[] args) {
System.out.println("Cette méthode ne sera pas reconnue comme point d'entrée");
}
}La JVM cherche spécifiquement une méthode avec la signature exacte : public static void main(String[] args).
Comment un objet est sérialisé en Java ?
En Java, pour convertir un objet en flux d'octets par sérialisation, une interface portant le nom Serializable est implémentée par la classe.
Tous les objets d'une classe implémentant l'interface Serializable sont sérialisables et leur état est enregistré dans un flux d'octets.
import java.io.*;
public class Personne implements Serializable {
private static final long serialVersionUID = 1L;
private String nom;
private int age;
private transient String motDePasse; // transient = non sérialisé
// Constructeurs, getters, setters
}
// Sérialisation d'un objet
public static void serialiserObjet(Personne p, String fichier) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(fichier))) {
oos.writeObject(p);
}
}
// Désérialisation
public static Personne deserialiserObjet(String fichier) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(fichier))) {
return (Personne) ois.readObject();
}
}- transient : marque un champ à ne pas sérialiser
- serialVersionUID : identifiant de version pour la compatibilité
Quand devons-nous utiliser la sérialisation ?
La sérialisation est utilisée lorsque les données doivent être transmises sur le réseau. À l'aide de la sérialisation, l'état de l'objet est enregistré et converti en un flux d'octets. Le flux d'octets est transféré sur le réseau et l'objet est recréé à la destination.
- Communication réseau : Envoi d'objets via sockets (RMI, RPC)
- Persistance : Sauvegarde de l'état d'un objet dans un fichier
- Cache : Stockage d'objets en mémoire secondaire
- Sessions HTTP : Sauvegarde des objets de session dans les serveurs d'applications
- Deep copy : Création de copies profondes d'objets
Est-il obligatoire qu'un bloc Try soit suivi d'un bloc Catch en Java pour la gestion des exceptions ?
Le bloc try doit être suivi soit du bloc catch, soit du bloc finally, soit des deux.
Toute exception levée à partir du bloc try doit être soit interceptée dans le bloc catch, soit toute tâche spécifique à effectuer avant l'avortement du code est placée dans le bloc finally.
// 1. try + catch
try {
int resultat = 10 / 0;
} catch (ArithmeticException e) {
System.out.println("Division par zéro : " + e.getMessage());
}
// 2. try + finally
try {
BufferedReader reader = new BufferedReader(new FileReader("fichier.txt"));
// lecture...
} finally {
// Nettoyage exécuté même en cas d'exception
reader.close();
}
// 3. try + catch + finally
try {
// code qui peut lever une exception
} catch (IOException e) {
// gestion de l'exception
} finally {
// nettoyage
}
// 4. try-with-resources (Java 7+) - auto-fermeture
try (BufferedReader reader = new BufferedReader(new FileReader("fichier.txt"))) {
// lecture...
} catch (IOException e) {
// gestion
}Existe-t-il un moyen d'ignorer le bloc d'exception Finally même si une exception se produit dans le bloc d'exception ?
Si une exception est levée dans un bloc Try, le contrôle passe au bloc catch s'il existe, sinon au bloc Finally. Le bloc Finally est toujours exécuté lorsqu'une exception se produit.
Le seul moyen d'éviter l'exécution de toute instruction dans le bloc Finally est de forcer l'abandon du code en écrivant la ligne de code suivante à la fin d'un bloc try :
public class TestFinally {
public static void main(String[] args) {
try {
System.out.println("Dans le bloc try");
System.exit(0); // Termine la JVM immédiatement
// Le code après System.exit() n'est pas exécuté
} catch (Exception e) {
System.out.println("Dans le bloc catch");
} finally {
// Ce bloc ne sera PAS exécuté à cause de System.exit()
System.out.println("Dans le bloc finally");
}
}
}System.exit(0) termine la machine virtuelle Java, donc le programme s'arrête immédiatement sans exécuter le bloc finally. C'est le seul cas où finally n'est pas exécuté.
Peut-on surcharger les méthodes statiques d'une classe ?
Nous ne pouvons pas surcharger (override) les méthodes statiques. Les méthodes statiques appartiennent à une classe et non à des objets individuels et sont résolues au moment de la compilation (pas au moment de l'exécution).
Même si nous essayons de surcharger une méthode statique, nous n'obtiendrons pas d'erreur de compilation, ni l'impact de la substitution lors de l'exécution du code. On appelle cela masquer (hiding) plutôt que surcharge.
class Parent {
public static void afficher() {
System.out.println("Méthode statique de Parent");
}
public void afficherInstance() {
System.out.println("Méthode d'instance de Parent");
}
}
class Child extends Parent {
// Ceci n'est PAS une surcharge (override), c'est un masquage (hiding)
public static void afficher() {
System.out.println("Méthode statique de Child");
}
@Override // Surcharge valide pour méthode d'instance
public void afficherInstance() {
System.out.println("Méthode d'instance de Child");
}
}
// Utilisation
Parent p = new Child();
p.afficher(); // Appelle Parent.afficher() (résolution à la compilation)
p.afficherInstance(); // Appelle Child.afficherInstance() (polymorphisme)String est-il un type de données en Java ?
String n'est pas un type de données primitif en Java. Lorsqu'une chaîne est créée en Java, c'est en fait un objet de la classe java.lang.String qui est créé.
Après la création de cet objet string, toutes les méthodes intégrées de la classe String peuvent être utilisées sur l'objet string.
// Création de String
String s1 = "Bonjour"; // Littéral (va dans le String Pool)
String s2 = new String("Bonjour"); // Nouvel objet
String s3 = "Bonjour"; // Référence au même objet que s1
// Méthodes de la classe String
System.out.println(s1.length()); // 7
System.out.println(s1.toUpperCase()); // "BONJOUR"
System.out.println(s1.substring(0, 3)); // "Bon"
System.out.println(s1.equals(s2)); // true (comparaison de contenu)
System.out.println(s1 == s2); // false (comparaison de références)
// Types primitifs vs String
int nombre = 10; // primitif
String texte = "10"; // objet- Primitifs : byte, short, int, long, float, double, char, boolean
- Références : String, tableaux, classes, interfaces
Dans l'exemple ci-dessous, combien d'objets string sont créés ?
String s1 = "Morocco is a beautiful country";
String s2 = "We love France";
String s3 = "Morocco is a beautiful country";Dans l'exemple ci-dessus, deux objets de la classe java.lang.String sont créés.
s1 et s3 sont des références au même objet car Java utilise un String Pool (pool de chaînes) pour optimiser la mémoire.
s2 est un objet différent car la chaîne est différente.
String s1 = "Morocco is a beautiful country"; // Objet 1 créé dans le String Pool
String s2 = "We love France"; // Objet 2 créé
String s3 = "Morocco is a beautiful country"; // Référence vers l'objet 1 (réutilisé)
// Vérification
System.out.println(s1 == s2); // false (objets différents)
System.out.println(s1 == s3); // true (même objet dans le pool)
// Avec new, un nouvel objet est toujours créé
String s4 = new String("Morocco is a beautiful country"); // Objet 3 créé (hors pool)
System.out.println(s1 == s4); // falsePourquoi les String en Java sont appelées immuables ?
En Java, les objets string sont appelés immuables car une fois que la valeur a été attribuée à une string, elle ne peut pas être modifiée et si elle est modifiée, un nouvel objet est créé.
String str = "Morocco";
// str référence un objet String avec la valeur "Morocco"
str = "France";
// Un nouvel objet String est créé avec la valeur "France"
// La référence str est déplacée vers ce nouvel objet
// L'ancien objet "Morocco" est éligible pour le garbage collector- String Pool : Permet le partage sécurisé des chaînes
- Sécurité : Évite les modifications des paramètres sensibles (nom de classe, URL, etc.)
- Thread-safe : Peut être partagé entre plusieurs threads sans synchronisation
- Performance : Optimisations possibles (caching du hashcode)
String s = "Hello";
s.toUpperCase(); // Crée un nouvel objet "HELLO", mais s pointe toujours vers "Hello"
System.out.println(s); // Affiche "Hello"
s = s.toUpperCase(); // Maintenant s pointe vers le nouvel objet
System.out.println(s); // Affiche "HELLO"
// StringBuilder/StringBuffer pour des chaînes modifiables
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // Modifie l'objet existant
System.out.println(sb); // "Hello World"- Comprenez les concepts, pas seulement les réponses par cœur
- Préparez des exemples concrets de vos projets
- Soyez prêt à écrire du code sur un tableau ou un éditeur
- Posez des questions sur l'entreprise et l'équipe
- Montrez votre passion pour la programmation et l'apprentissage continu