Qu'est-ce qui permet de créer une instance de classe à l'intérieur de la classe elle-même?
public class My_Class
{
My_Class new_class= new My_Class();
}
Je sais que c'est possible et je l'ai fait moi-même, mais je ne peux toujours pas me faire croire que ce n'est pas quelque chose comme "qui était le premier - Poulet ou œuf?" type de problème. Je serais heureux de recevoir une réponse qui clarifiera cela du point de vue de la programmation ainsi que du point de vue JVM/compilateur. Je pense que comprendre cela m'aidera à clarifier certains concepts très importants de goulot d'étranglement de la programmation OO.
J'ai reçu quelques réponses mais aucune n'est claire dans la mesure où je m'y attendais.
Il n'y a absolument aucun problème à créer des instances d'une classe dans la classe elle-même. Le problème apparent de poulet ou d'oeuf est résolu de différentes manières pendant la compilation du programme et son exécution.
Temps de compilation
Lorsqu'une classe qui crée une instance d'elle-même est en cours de compilation, le compilateur constate que la classe a une dépendance circulaire sur elle-même. Cette dépendance est facile à résoudre: le compilateur sait que la classe est déjà en cours de compilation, il n'essaiera donc pas de la recompiler. Au lieu de cela, il prétend que la classe existe déjà génère du code en conséquence.
Exécution
Le plus gros problème de poulet ou d'oeuf avec une classe créant un objet en soi, c'est quand la classe n'existe même pas encore; c'est-à-dire lorsque la classe est en cours de chargement. Ce problème est résolu en divisant le chargement de classe en deux étapes: d'abord la classe est définie puis elle est initialisée.
Définir signifie enregistrer la classe auprès du système d'exécution (JVM ou CLR), afin qu'il connaisse la structure des objets de la classe et le code à exécuter lors de l'appel de ses constructeurs et méthodes.
Une fois la classe définie, elle est initialisée. Cela se fait en initialisant les membres statiques et en exécutant les blocs d'initialisation statiques et d'autres choses définies dans le langage particulier. Rappelez-vous que la classe est déjà définie à ce stade, donc le runtime sait à quoi ressemblent les objets de la classe et quel code doit être exécuté pour les créer. Cela signifie qu'il n'y a aucun problème à créer des objets de la classe lors de son initialisation.
Voici un exemple qui illustre comment l'initialisation et l'instanciation de classe interagissent en Java:
class Test {
static Test instance = new Test();
static int x = 1;
public Test() {
System.out.printf("x=%d\n", x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
Voyons comment la JVM exécuterait ce programme. La JVM charge d'abord la classe Test
. Cela signifie que la classe est d'abord définie, pour que la JVM sache que
Test
existe et qu'elle a une méthode main
et un constructeur, et queTest
a deux variables statiques, l'une appelée x
et l'autre appelée instance
, etTest
. En d'autres termes: à quoi ressemble un objet; quels attributs il a. Dans ce cas, Test
n'a pas d'attributs d'instance.Maintenant que la classe est définie, c'est initialisé. Tout d'abord, la valeur par défaut 0
Ou null
est affectée à chaque attribut statique. Cela définit x
sur 0
. Ensuite, la JVM exécute les initialiseurs de champ statique dans l'ordre du code source. Il y en a deux:
Test
et affectez-la à instance
. La création d'une instance comporte deux étapes: Test()
est appelé pour initialiser l'objet. La JVM peut le faire car elle possède déjà le code du constructeur de la phase de définition de classe. Le constructeur affiche la valeur actuelle de x
, qui est 0
.x
sur 1
.Ce n'est que maintenant que la classe a terminé le chargement. Notez que la JVM a créé une instance de la classe, même si elle n'était pas encore complètement chargée. Vous en avez la preuve car le constructeur a imprimé la valeur par défaut initiale 0
Pour x
.
Maintenant que la JVM a chargé cette classe, elle appelle la méthode main
pour exécuter le programme. La méthode main
crée un autre objet de la classe Test
- le deuxième dans l'exécution du programme. Encore une fois, le constructeur affiche la valeur actuelle de x
, qui est maintenant 1
. Le résultat complet du programme est:
x=0
x=1
Comme vous pouvez le voir, il n'y a pas de problème de poulet ou d'oeuf: la séparation du chargement de classe dans les phases de définition et d'initialisation évite complètement le problème.
Qu'en est-il lorsqu'une instance de l'objet souhaite créer une autre instance, comme dans le code ci-dessous?
class Test {
Test buggy = new Test();
}
Lorsque vous créez un objet de cette classe, là encore, il n'y a pas de problème inhérent. La machine virtuelle Java sait comment l'objet doit être disposé en mémoire afin de pouvoir lui allouer de la mémoire. Il définit tous les attributs à leurs valeurs par défaut, donc buggy
est défini sur null
. Ensuite, la JVM commence à initialiser l'objet. Pour ce faire, il doit créer un autre objet de classe Test
. Comme précédemment, la JVM sait déjà comment faire: elle alloue la mémoire, définit l'attribut sur null
, et commence à initialiser le nouvel objet ... ce qui signifie qu'elle doit créer un troisième objet de la même classe, puis un quatrième, un cinquième, et ainsi de suite, jusqu'à ce qu'il soit à court d'espace de pile ou de mémoire de tas.
Il n'y a pas de problème conceptuel à l'esprit: ce n'est qu'un cas courant d'une récursion infinie dans un programme mal écrit. La récursivité peut être contrôlée par exemple à l'aide d'un compteur; le constructeur de cette classe utilise la récursivité pour faire une chaîne d'objets:
class Chain {
Chain link = null;
public Chain(int length) {
if (length > 1) link = new Chain(length-1);
}
}
La principale chose que je me vois toujours créer une instance à partir de la classe, c'est quand j'essaie de référencer un élément non statique dans un contexte statique, comme quand je crée un cadre pour un jeu ou autre, j'utilise le principal pour configurer réellement le cadre. Vous pouvez également l'utiliser pour quand il y a quelque chose dans un constructeur que vous souhaitez définir (comme dans ce qui suit, je rend mon JFrame différent de null):
public class Main {
private JFrame frame;
public Main() {
frame = new JFrame("Test");
}
public static void main(String[] args) {
Main m = new Main();
m.frame.setResizable(false);
m.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
m.frame.setLocationRelativeTo(null);
m.frame.setVisible(true);
}
}
D'autres réponses ont principalement couvert la question. Si cela aide à envelopper un cerveau, qu'en est-il d'un exemple?
Le problème de la poule et de l'oeuf est résolu comme tout problème récursif: le cas de base qui ne continue pas à produire plus de travail/instances/autre.
Imaginez que vous avez créé une classe pour gérer automatiquement l'invocation d'événements inter-threads si nécessaire. Très pertinent pour les WinForms filetés. Ensuite, vous souhaitez que la classe expose un événement qui se produit chaque fois que quelque chose s'inscrit ou se désenregistre auprès du gestionnaire, et bien sûr, elle devrait également gérer l'invocation entre les threads.
Vous pouvez écrire le code qui le gère deux fois, une fois pour l'événement lui-même et une fois pour l'événement d'état, ou écrire une fois et réutiliser.
La majorité de la classe a été supprimée car elle n'est pas vraiment pertinente pour la discussion.
public sealed class AutoInvokingEvent
{
private AutoInvokingEvent _statuschanged;
public event EventHandler StatusChanged
{
add
{
_statuschanged.Register(value);
}
remove
{
_statuschanged.Unregister(value);
}
}
private void OnStatusChanged()
{
if (_statuschanged == null) return;
_statuschanged.OnEvent(this, EventArgs.Empty);
}
private AutoInvokingEvent()
{
//basis case what doesn't allocate the event
}
/// <summary>
/// Creates a new instance of the AutoInvokingEvent.
/// </summary>
/// <param name="statusevent">If true, the AutoInvokingEvent will generate events which can be used to inform components of its status.</param>
public AutoInvokingEvent(bool statusevent)
{
if (statusevent) _statuschanged = new AutoInvokingEvent();
}
public void Register(Delegate value)
{
//mess what registers event
OnStatusChanged();
}
public void Unregister(Delegate value)
{
//mess what unregisters event
OnStatusChanged();
}
public void OnEvent(params object[] args)
{
//mess what calls event handlers
}
}
L'attribut pour contenir l'auto-instance doit être statique
public class MyClass {
private static MyClass instance;
static {
instance = new MyClass();
}
// some methods
}