Entretien Java : questions POO
1. Quelles sont les fonctionnalités de Java ?
Répondre:-
Concepts de POO :
- orientation de l'objet ;
- héritage;
- encapsulation ;
- polymorphisme;
- abstraction.
-
Multiplateforme : un programme Java peut être exécuté sur n’importe quelle plateforme sans aucune modification. La seule chose dont vous avez besoin est une JVM installée (machine virtuelle Java).
-
Hautes performances : JIT (compilateur Just In Time) permet des performances élevées. JIT convertit le bytecode en code machine, puis la JVM démarre l'exécution.
- Multithreading : un thread d'exécution appelé
Thread
. La JVM crée un thread appelémain thread
. Un programmeur peut créer plusieurs threads en héritant de la classe Thread ou en implémentant une interfaceRunnable
.
2. Qu'est-ce que l'héritage ?
L'héritage signifie qu'une classe peut hériter (« étend ») d'une autre classe. De cette façon, vous pouvez réutiliser le code de la classe dont vous héritez. La classe existante est connue sous le nom desuperclass
, et celle en cours de création est connue sous le nom de subclass
. Ils disent aussi parent
et child
.
public class Animal {
private int age;
}
public class Dog extends Animal {
}
où Animal
est parent
, et Dog
- child
.
3. Qu’est-ce que l’encapsulation ?
Cette question revient souvent lors des entretiens avec les développeurs Java. L'encapsulation masque l'implémentation à l'aide de modificateurs d'accès, à l'aide de getters et de setters. Ceci est fait afin de fermer l'accès pour un usage externe dans les endroits où les développeurs le jugent nécessaire. Un exemple accessible de la vie est une voiture. Nous n'avons pas d'accès direct au fonctionnement du moteur. Pour nous, le travail consiste à mettre la clé dans le contact et à démarrer le moteur. Et quels processus se dérouleront sous le capot ne nous regardent pas. De plus, notre ingérence dans cette activité peut conduire à une situation imprévisible, à cause de laquelle nous pouvons casser la voiture et nous blesser. Exactement la même chose se produit en programmation. Bien décrit sur Wikipédia . Il existe également un article sur l'encapsulation sur JavaRush .4. Qu'est-ce que le polymorphisme ?
Le polymorphisme est la capacité d'un programme à utiliser de manière identique des objets avec la même interface sans informations sur le type spécifique de cet objet. Comme on dit, une interface - plusieurs implémentations. Avec le polymorphisme, vous pouvez combiner et utiliser différents types d'objets en fonction de leur comportement commun. Par exemple, nous avons une classe Animal, qui a deux descendants : Chien et Chat. La classe générique Animal a un comportement commun à tous : émettre un son. Dans le cas où nous devons rassembler tous les descendants de la classe Animal et exécuter la méthode « faire un son », nous utilisons les possibilités du polymorphisme. Voici à quoi cela ressemblera :List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
Le polymorphisme nous aide donc. De plus, cela s'applique également aux méthodes polymorphes (surchargées). Pratique de l'utilisation du polymorphisme
Questions d'entretien - Syntaxe Java
5. Qu'est-ce qu'un constructeur en Java ?
Les caractéristiques suivantes sont valables :- Lorsqu'un nouvel objet est créé, le programme utilise le constructeur approprié pour le faire.
- Un constructeur est comme une méthode. Sa particularité est qu'il n'y a pas d'élément de retour (y compris void), et son nom est le même que le nom de la classe.
- Si aucun constructeur n'est écrit explicitement, un constructeur vide sera créé automatiquement.
- Le constructeur peut être remplacé.
- Si un constructeur avec paramètres a été créé, mais que vous en avez également besoin sans paramètres, vous devez l'écrire séparément, car il n'est pas créé automatiquement.
6. Quelles sont les deux classes qui n'héritent pas d'Object ?
Ne vous laissez pas berner par les provocations, de telles classes n'existent pas : toutes les classes directement ou via leurs ancêtres sont héritées de la classe Object !7. Qu'est-ce qu'une variable locale ?
Une autre question populaire lors d'un entretien avec un développeur Java. Une variable locale est une variable définie dans une méthode et qui existe jusqu'au moment où la méthode est exécutée. Une fois l'exécution terminée, la variable locale cessera d'exister. Voici un programme qui utilise la variable locale helloMessage dans la méthode main() :public static void main(String[] args) {
String helloMessage;
helloMessage = "Hello, World!";
System.out.println(helloMessage);
}
8. Qu'est-ce qu'une variable d'instance ?
Une variable d'instance est une variable définie à l'intérieur d'une classe et elle existe jusqu'au moment où l'objet existe. Un exemple est la classe Bee, qui a deux variables nectarCapacity et maxNectarCapacity :public class Bee {
/**
* Current nectar capacity
*/
private double nectarCapacity;
/**
* Maximal nectar that can take bee.
*/
private double maxNectarCapacity = 20.0;
...
}
9. Que sont les modificateurs d'accès ?
Les modificateurs d'accès sont un outil qui vous permet de personnaliser l'accès aux classes, méthodes et variables. Il existe les modificateurs suivants, classés par ordre d'accès croissant :private
- utilisé pour les méthodes, les champs et les constructeurs. Le niveau d'accès est uniquement la classe dans laquelle il est déclaré.package-private(default)
- peut être utilisé pour les cours. Accès uniquement dans un package spécifique dans lequel une classe, une méthode, une variable, un constructeur est déclaré.protected
— le même accès quepackage-private
+ pour les classes qui héritent d'une classe avec le modificateurprotected
.public
- également utilisé pour les cours. Accès complet à toute l’application.
10. Qu'est-ce que les méthodes prioritaires ?
Le remplacement de méthode se produit lorsque l'enfant souhaite modifier le comportement de la classe parent. Si vous souhaitez que ce qui se trouve dans la méthode parent soit exécuté, vous pouvez utiliser une construction comme super.methodName() dans l'enfant, qui fera le travail de la méthode parent, et ensuite seulement ajouter de la logique. Conditions à remplir :- la signature de la méthode doit être la même ;
- la valeur de retour doit être la même.
11. Qu'est-ce qu'une signature de méthode ?
Une signature de méthode est un ensemble du nom de la méthode et des arguments qu'elle accepte. Une signature de méthode est un identifiant unique pour une méthode lors de la surcharge des méthodes.12. Qu'est-ce que la surcharge de méthode ?
La surcharge de méthode est une propriété du polymorphisme dans laquelle, en modifiant la signature de la méthode, vous pouvez créer différentes méthodes pour les mêmes actions :- même nom de méthode ;
- des arguments différents ;
- il peut y avoir un type de retour différent.
add()
of ArrayList
peut être surchargé comme suit et effectuera l'addition d'une manière différente, en fonction des arguments entrants :
add(Object o)
- ajoute simplement un objet ;add(int index, Object o)
— ajoute un objet à un index spécifique ;add(Collection<Object> c)
— ajoute une liste d'objets ;add(int index, Collection<Object> c)
— ajoute une liste d'objets, à partir d'un certain index.
13. Qu'est-ce qu'Interface ?
L'héritage multiple n'est pas implémenté en Java, donc pour pallier ce problème, des interfaces telles que nous les connaissons ont été ajoutées ;) Pendant longtemps, les interfaces n'avaient que des méthodes sans les implémenter. Dans le cadre de cette réponse, nous en parlerons. Par exemple:
public interface Animal {
void makeSound();
void eat();
void sleep();
}
Certaines nuances en découlent :
- toutes les méthodes de l'interface sont publiques et abstraites ;
- toutes les variables sont publiques statiques finales ;
- les classes n'en héritent pas (étend), mais les implémentent (implémente). De plus, vous pouvez implémenter autant d’interfaces que vous le souhaitez.
- les classes qui implémentent une interface doivent fournir des implémentations de toutes les méthodes dont dispose l'interface.
public class Cat implements Animal {
public void makeSound() {
// method implementation
}
public void eat() {
// implementation
}
public void sleep() {
// implementation
}
}
14. Quelle est la méthode par défaut dans l'interface ?
Parlons maintenant des méthodes par défaut. Pour quoi, pour qui ? Ces méthodes ont été ajoutées pour que tout soit « à la fois le vôtre et le nôtre ». De quoi je parle ? Oui, d'une part, il fallait ajouter de nouvelles fonctionnalités : lambdas, Stream API, d'autre part, il fallait laisser ce qui fait la renommée de Java : la rétrocompatibilité. Pour ce faire, il a fallu introduire des solutions toutes faites dans les interfaces. C'est ainsi que les méthodes par défaut nous sont parvenues. Autrement dit, la méthode par défaut est une méthode implémentée dans l'interface qui possède le mot-clédefault
. Par exemple, la méthode bien connue stream()
du Collection
. Regardez-le, cette interface n'est pas aussi simple qu'il y paraît ;). Ou encore une méthode tout aussi connue forEach()
du Iterable
. Il n’existait pas non plus jusqu’à l’ajout des méthodes par défaut. À propos, vous pouvez également en savoir plus sur JavaRush .
15. Comment alors hériter de deux méthodes par défaut identiques ?
Sur la base de la réponse précédente concernant la méthode par défaut, vous pouvez poser une autre question. Si vous pouvez implémenter des méthodes dans des interfaces, alors théoriquement vous pouvez implémenter deux interfaces avec la même méthode, et comment faire cela ? Il existe deux interfaces différentes avec la même méthode :interface A {
default void foo() {
System.out.println("Foo A");
}
}
interface B {
default void foo() {
System.out.println("Foo B");
}
}
Et il existe une classe qui implémente ces deux interfaces. Pour éviter toute incertitude et compiler le code, nous devons remplacer la méthode foo()
dans la classe C
et nous pouvons simplement appeler une méthode foo()
de l'une des interfaces qu'elle contient - A
ou B
. Mais comment choisir une méthode d'interface spécifique А
ou В
? Il existe une structure comme celle-ci pour celaA.super.foo()
:
public class C implements A, B {
@Override
public void foo() {
A.super.foo();
}
}
ou:
public class C implements A, B {
@Override
public void foo() {
B.super.foo();
}
}
Ainsi, une méthode foo()
de classe C
utilisera soit la méthode par défaut foo()
de l'interface A
, soit une méthode foo()
de l'interface B
.
16. Que sont les méthodes et classes abstraites ?
Java a un mot réservéabstract
utilisé pour désigner les classes et méthodes abstraites. Tout d’abord, quelques définitions. Une méthode abstraite est une méthode créée sans implémentation avec un mot-clé abstract
dans une classe abstraite. C'est-à-dire qu'il s'agit d'une méthode comme dans l'interface, uniquement avec l'ajout d'un mot-clé, par exemple :
public abstract void foo();
Une classe abstraite est une classe qui contient également abstract
le mot :
public abstract class A {
}
Une classe abstraite a plusieurs fonctionnalités :
- un objet ne peut pas être créé sur cette base ;
- il peut avoir des méthodes abstraites ;
- il ne peut pas avoir de méthodes abstraites.
17. Quelle est la différence entre String, String Builder et String Buffer ?
Les valeursString
sont stockées dans un pool de chaînes constantes. Une fois qu’une ligne est créée, elle apparaîtra dans ce pool. Et il ne sera pas possible de le supprimer. Par exemple:
String name = "book";
...la variable fera référence au pool de chaînes Pool de chaînes constantes Si vous définissez le nom de la variable sur une valeur différente, vous obtiendrez ce qui suit :
name = "pen";
Pool de chaînes constant Ces deux valeurs resteront donc là. Tampon de chaîne :
- les valeurs
String
sont stockées sur la pile. Si la valeur est modifiée, la nouvelle valeur sera remplacée par l'ancienne ; String Buffer
synchronisé et donc thread-safe ;- En raison de la sécurité du filetage, la vitesse de fonctionnement laisse beaucoup à désirer.
StringBuffer name = "book";
Dès que la valeur de name change, la valeur sur la pile change : StringBuilder Exactement la même chose que StringBuffer
, sauf qu'il n'est pas thread-safe. Par conséquent, sa vitesse est nettement supérieure à celle de StringBuffer
.
18. Quelle est la différence entre une classe abstraite et une interface ?
Classe abstraite :- les classes abstraites ont un constructeur par défaut ; il est appelé chaque fois qu'un enfant de cette classe abstraite est créé ;
- contient à la fois des méthodes abstraites et non abstraites. Dans l'ensemble, il se peut qu'elle ne contienne pas de méthodes abstraites, mais qu'elle reste néanmoins une classe abstraite ;
- une classe qui hérite d'une classe abstraite doit implémenter uniquement des méthodes abstraites ;
- une classe abstraite peut contenir une variable d'instance (voir question n°5).
- n'a pas de constructeur et ne peut pas être initialisé ;
- seules les méthodes abstraites doivent être ajoutées (sans compter les méthodes par défaut) ;
- les classes qui implémentent une interface doivent implémenter toutes les méthodes (sans compter les méthodes par défaut) ;
- les interfaces ne peuvent contenir que des constantes.
19. Pourquoi l'accès à un élément d'un tableau nécessite-t-il O(1) ?
Cette question vient littéralement de la dernière interview. Comme je l'ai appris plus tard, cette question est posée afin de voir comment une personne pense. Il est clair que cette connaissance a peu de signification pratique : il suffit de connaître ce fait. Tout d’abord, nous devons clarifier que O(1) est une désignation de la complexité temporelle d’un algorithme lorsque l’opération se déroule en temps constant. C'est-à-dire que cette désignation est l'exécution la plus rapide. Pour répondre à cette question, nous devons comprendre ce que nous savons sur les tableaux ? Pour créer un tableauint
, il faut écrire ce qui suit :
int[] intArray = new int[100];
Plusieurs conclusions peuvent être tirées de cet enregistrement :
- Lors de la création d'un tableau, son type est connu. Si le type est connu, alors il est clair quelle sera la taille de chaque cellule du tableau.
- On sait quelle sera la taille du tableau.
Comment obtenir O(1) en accédant aux objets dans une ArrayList ?
Cette question suit immédiatement la précédente. C’est vrai que lorsqu’on travaille avec un tableau et qu’il y a des primitives là-bas, on sait à l’avance quelle est la taille de ce type lors de sa création. Mais que se passe-t-il si nous avons un schéma comme celui de l'image : et que nous voulons créer une collection avec des éléments de type A, et ajouter différentes implémentations - B, C, D :List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
Dans cette situation, comment pouvez-vous comprendre quelle sera la taille de chaque cellule, car chaque objet sera différent et peut avoir des champs supplémentaires différents (ou être complètement différent). Ce qu'il faut faire? Ici, la question est posée de manière à prêter à confusion et à confondre. On sait qu'en effet, la collection ne stocke pas d'objets, mais seulement des liens vers ces objets. Et tous les liens ont la même taille, et c’est connu. Donc compter l’espace ici fonctionne de la même manière que dans la question précédente.
21. Autoboxing et unboxing
Contexte historique : l'autoboxing et l'autounboxing sont l'une des principales innovations du JDK 5. L'autoboxing est le processus de conversion automatique d'un type primitif vers la classe wrapper appropriée. Le déballage automatique - fait exactement le contraire de l'auto-boxing - convertit la classe wrapper en primitive. Mais s'il existe une valeur wrappernull
, alors une exception sera levée lors du déballage NullPointerException
.
Primitive correspondante - wrapper
Primitif | Classe d'emballage |
---|---|
booléen | Booléen |
int | Entier |
octet | Octet |
carboniser | Personnage |
flotter | Flotter |
long | Long |
court | Court |
double | Double |
L'emballage automatique se produit :
-
lors de l'attribution à une primitive d'une référence à la classe wrapper :
AVANT Java 5 :
//manual packaging or how it was BEFORE Java 5. public void boxingBeforeJava5() { Boolean booleanBox = new Boolean(true); Integer intBox = new Integer(3); // and so on to other types } после Java 5: //automatic packaging or how it became in Java 5. public void boxingJava5() { Boolean booleanBox = true; Integer intBox = 3; // and so on to other types }
-
lors du passage d'une primitive comme argument à une méthode qui attend un wrapper :
public void exampleOfAutoboxing() { long age = 3; setAge(age); } public void setAge(Long age) { this.age = age; }
Le déballage automatique se produit :
-
lorsque nous attribuons une variable primitive à la classe wrapper :
//before Java 5: int intValue = new Integer(4).intValue(); double doubleValue = new Double(2.3).doubleValue(); char c = new Character((char) 3).charValue(); boolean b = Boolean.TRUE.booleanValue(); //and after JDK 5: int intValue = new Integer(4); double doubleValue = new Double(2.3); char c = new Character((char) 3); boolean b = Boolean.TRUE;
-
Dans les cas d'opérations arithmétiques. Ils s'appliquent uniquement aux types primitifs ; pour cela, vous devez effectuer un déballage sur la primitive.
// Before Java 5 Integer integerBox1 = new Integer(1); Integer integerBox2 = new Integer(2); // for comparison it was necessary to do this: integerBox1.intValue() > integerBox2.intValue() //в Java 5 integerBox1 > integerBox2
-
lorsqu'il est transmis à un wrapper dans une méthode qui accepte la primitive correspondante :
public void exampleOfAutoboxing() { Long age = new Long(3); setAge(age); } public void setAge(long age) { this.age = age; }
22. Quel est le mot-clé final et où l'utiliser ?
Le mot-cléfinal
peut être utilisé pour les variables, les méthodes et les classes.
- Une variable finale ne peut pas être réaffectée à un autre objet.
- la classe finale est stérile)) elle ne peut pas avoir d'héritiers.
- La méthode final ne peut pas être remplacée sur un ancêtre.
variables finales
;Java nous offre deux façons de créer une variable et de lui attribuer une valeur :- Vous pouvez déclarer une variable et l'initialiser plus tard.
- Vous pouvez déclarer une variable et l'affecter immédiatement.
public class FinalExample {
//final static variable, which is immediately initialized:
final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
//final is a variable that is not initialized, but will only work if
//initialize this in the constructor:
final long creationTime;
public FinalExample() {
this.creationTime = System.currentTimeMillis();
}
public static void main(String[] args) {
FinalExample finalExample = new FinalExample();
System.out.println(finalExample.creationTime);
// final field FinalExample.FINAL_EXAMPLE_NAME cannot be assigned
// FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";
// final field Config.creationTime cannot be assigned
// finalExample.creationTime = 1L;
}
}
La variable Final peut-elle être considérée comme une constante ?
Comme nous ne pourrons pas attribuer une nouvelle valeur à une variable finale, il semble que ce soient des variables constantes. Mais ce n’est qu’un premier coup d’œil. Si le type de données auquel la variable fait référence estimmutable
, alors oui, c'est une constante. Mais si le type de données mutable
est mutable, à l'aide de méthodes et de variables, il sera possible de modifier la valeur de l'objet auquel final
la variable fait référence, et dans ce cas, elle ne peut pas être appelée constante. Ainsi, l’exemple montre que certaines des variables finales sont en réalité des constantes, mais d’autres ne le sont pas et peuvent être modifiées.
public class FinalExample {
//immutable final variables:
final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
final static Integer FINAL_EXAMPLE_COUNT = 10;
// mutable filter variables
final List<String> addresses = new ArrayList();
final StringBuilder finalStringBuilder = new StringBuilder("constant?");
}
Variables finales locales
Lorsqu'unefinal
variable est créée dans une méthode, elle est appelée local final
variable :
public class FinalExample {
public static void main(String[] args) {
// This is how you can
final int minAgeForDriveCar = 18;
// or you can do it this way, in the foreach loop:
for (final String arg : args) {
System.out.println(arg);
}
}
}
Nous pouvons utiliser le mot-clé final
en boucle étendue for
car une fois l'itération de la boucle terminée, for
une nouvelle variable est créée à chaque fois. Mais cela ne s'applique pas à une boucle for normale, donc le code ci-dessous générera une erreur de compilation.
// final local changed j cannot be assigned
for (final int i = 0; i < args.length; i ++) {
System.out.println(args[i]);
}
Cours final
Vous ne pouvez pas étendre une classe déclarée commefinal
. En termes simples, aucune classe ne peut hériter de celle-ci. Un bon exemple final
de classe dans le JDK est String
. La première étape pour créer une classe immuable consiste à la marquer comme final
, afin qu'elle ne puisse pas être étendue :
public final class FinalExample {
}
// Compilation error here
class WantsToInheritFinalClass extends FinalExample {
}
Méthodes finales
Lorsqu’une méthode est marquée comme finale, on l’appelle une méthode finale (logique, non ?). La méthode Final ne peut pas être substituée dans une classe descendante. À propos, les méthodes de la classe Object - wait() et notify() - sont définitives, nous n'avons donc pas la possibilité de les remplacer.public class FinalExample {
public final String generateAddress() {
return "Some address";
}
}
class ChildOfFinalExample extends FinalExample {
// compile error here
@Override
public String generateAddress() {
return "My OWN Address";
}
}
Comment et où utiliser final en Java
- utilisez le mot-clé final pour définir certaines constantes au niveau de la classe ;
- créez des variables finales pour les objets lorsque vous ne souhaitez pas qu'ils soient modifiés. Par exemple, des propriétés spécifiques à un objet que nous pouvons utiliser à des fins de journalisation ;
- si vous ne souhaitez pas que le cours soit étendu, marquez-le comme final ;
- si vous devez créer une classe immuable<, vous devez la rendre finale ;
- si vous souhaitez que l'implémentation d'une méthode ne change pas dans ses descendants, désignez la méthode comme
final
. Ceci est très important pour garantir que la mise en œuvre ne change pas.
23. Qu'est-ce qui est mutable et immuable ?
Mutable
Les mutables sont des objets dont les états et les variables peuvent être modifiés après la création. Par exemple, des classes telles que StringBuilder, StringBuffer. Exemple:public class MutableExample {
private String address;
public MutableExample(String address) {
this.address = address;
}
public String getAddress() {
return address;
}
// this setter can change the name field
public void setAddress(String address) {
this.address = address;
}
public static void main(String[] args) {
MutableExample obj = new MutableExample("first address");
System.out.println(obj.getAddress());
// update the name field, so this is a mutable object
obj.setAddress("Updated address");
System.out.println(obj.getAddress());
}
}
Immuable
Les objets immuables sont des objets dont les états et les variables ne peuvent pas être modifiés après la création de l'objet. Pourquoi pas une excellente clé pour un HashMap, n'est-ce pas ?) Par exemple, String, Integer, Double, etc. Exemple:// make this class final so no one can change it
public final class ImmutableExample {
private String address;
ImmutableExample (String address) {
this.address = address;
}
public String getAddress() {
return address;
}
//remove the setter
public static void main(String[] args) {
ImmutableExample obj = new ImmutableExample("old address");
System.out.println(obj.getAddress());
// Therefore, do not change this field in any way, so this is an immutable object
// obj.setName("new address");
// System.out.println(obj.getName());
}
}
24. Comment écrire une classe immuable ?
Après avoir compris ce que sont les objets mutables et immuables, la question suivante est naturelle : comment l'écrire ? Pour écrire une classe immuable immuable, vous devez suivre des étapes simples :- rendre le cours final.
- rendez tous les champs privés et créez uniquement des getters pour eux. Bien entendu, les setters ne sont pas nécessaires.
- Rendre tous les champs mutables définitifs afin que la valeur ne puisse être définie qu'une seule fois.
- initialiser tous les champs via le constructeur, en effectuant une copie complète (c'est-à-dire en copiant l'objet lui-même, ses variables, les variables des variables, etc.)
- clonez des objets variables mutables dans les getters pour renvoyer uniquement des copies de valeurs et non des références à des objets réels.
/**
* An example of creating an immutable object.
*/
public final class FinalClassExample {
private final int age;
private final String name;
private final HashMap<String, String> addresses;
public int getAge() {
return age;
}
public String getName() {
return name;
}
/**
* Clone the object before returning it.
*/
public HashMap<String, String> getAddresses() {
return (HashMap<String, String>) addresses.clone();
}
/**
* In the constructor, deep copy the mutable objects.
*/
public FinalClassExample(int age, String name, HashMap<String, String> addresses) {
System.out.println("Performing a deep copy in the constructor");
this.age = age;
this.name = name;
HashMap<String, String> temporaryMap = new HashMap<>();
String key;
Iterator<String> iterator = addresses.keySet().iterator();
while (iterator.hasNext()) {
key = iterator.next();
temporaryMap.put(key, addresses.get(key));
}
this.addresses = temporaryMap;
}
}
GO TO FULL VERSION