web-dev-qa-db-fra.com

Pourquoi Java ne permet pas aux définitions de fonction d'être présentes en dehors de la classe?

Contrairement à C++, en Java, nous ne pouvons pas avoir uniquement des déclarations de fonction dans la classe et des définitions en dehors de la classe. Pourquoi en est-il ainsi?

Est-ce pour souligner qu'un seul fichier en Java ne doit contenir qu'une seule classe et rien d'autre?

22
user52009

La différence entre C++ et Java réside dans ce que les langages considèrent comme leur plus petite unité de liaison.

Parce que C a été conçu pour coexister avec Assembly, cette unité est le sous-programme appelé par une adresse. (Cela est vrai pour d'autres langages qui se compilent dans des fichiers objets natifs, tels que FORTRAN.) En d'autres termes, un fichier objet contenant une fonction foo() aura un symbole appelé _foo Qui sera résolu à rien mais à une adresse telle que 0xdeadbeef lors de la liaison. C'est tout ce qu'il y a. Si la fonction doit prendre des arguments, c'est à l'appelant de s'assurer que tout ce que la fonction attend est en ordre avant d'appeler son adresse. Normalement, cela se fait en empilant des choses sur la pile, et le compilateur s'occupe du travail de grognement et s'assure que les prototypes correspondent. Il n'y a aucune vérification de ceci entre les fichiers d'objets; si vous supprimez la liaison d'appel, l'appel ne se déclenchera pas comme prévu et vous n'obtiendrez pas d'avertissement à ce sujet. Malgré le danger, cela permet aux fichiers objets compilés à partir de plusieurs langues (y compris l'assemblage) d'être liés ensemble dans un programme fonctionnel sans trop de tracas.

C++, malgré toutes ses fantaisies supplémentaires, fonctionne de la même manière. Le compilateur chausse les espaces de noms, les classes et les méthodes/membres/etc. dans cette convention en aplatissant le contenu des classes en noms uniques qui sont mutilés de manière à les rendre uniques. Par exemple, une méthode comme Foo::bar(int baz) peut être modifiée dans _ZN4Foo4barEi Lorsqu'elle est placée dans un fichier objet et une adresse comme 0xBADCAFE Lors de l'exécution. Cela dépend entièrement du compilateur, donc si vous essayez de lier deux objets qui ont des schémas de manipulation différents, vous n'aurez pas de chance. Aussi laid que cela soit, cela signifie que vous pouvez utiliser un bloc extern "C" Pour désactiver la manipulation, ce qui permet de rendre le code C++ facilement accessible à d'autres langages. C++ a hérité de la notion de fonctions flottantes de C, en grande partie parce que le format d'objet natif le permet.

Java est une bête différente qui vit dans un monde isolé avec son propre format de fichier objet, le fichier .class. Les fichiers de classe contiennent ne mine d'informations sur leur conten qui permet à l'environnement de faire des choses avec les classes au moment de l'exécution dont le mécanisme de liaison natif ne pouvait même pas rêver. Cette information doit commencer quelque part, et ce point de départ est le class. Les informations disponibles permettent au code compilé de se décrire sans avoir besoin de fichiers séparés contenant une description en code source comme vous le feriez en C, C++ ou dans d'autres langages. Cela vous donne tous les types de langages d'avantages de sécurité utilisant le manque de liaison natif, même au moment de l'exécution, et c'est ce qui vous permet de pêcher une classe arbitraire dans un fichier en utilisant la réflexion et de l'utiliser avec un échec garanti si quelque chose ne correspond pas .

Si vous ne l'avez pas déjà compris, toute cette sécurité s'accompagne d'un compromis: tout ce que vous liez à un programme Java doit être Java. (Par "lien", je veux dire à chaque fois que quelque chose dans un fichier de classe fait référence à quelque chose dans un autre.) Vous pouvez lier (au sens natif) au code natif en utilisant JNI , mais il existe un contrat implicite qui dit que si vous cassez le côté natif, vous possédez les deux pièces.

Java était grand et pas particulièrement rapide sur le matériel disponible lors de sa première introduction, un peu comme Ada l'avait été au cours de la décennie précédente. Seul Jim Gosling peut dire avec certitude quelles étaient ses motivations pour créer la plus petite unité de liaison de la classe Java, mais je devrais deviner que la complexité supplémentaire qu'aurait ajoutée l'ajout de flottants libres à l'exécution aurait pu être un tueur de deal.

33
Blrfl

Je crois que la réponse est, selon Wikipedia, que Java a été conçu pour être simple et orienté objet. Les fonctions sont censées fonctionner sur les classes dans lesquelles elles sont définies. Avec cette ligne de pensée, avoir des fonctions en dehors d'une classe n'a pas de sens. Je vais sauter à la conclusion que Java ne le permet pas car il ne cadrait pas avec la POO pure.

Une recherche rapide sur Google pour moi n'a pas donné beaucoup sur Java motivations de conception de la langue.

14
mortalapeman

La vraie question est quel serait le mérite de continuer à faire les choses en C++ et quel était le but initial du fichier d'en-tête? La réponse courte est que le style de fichier d'en-tête permettait des temps de compilation plus rapides sur les grands projets dans lesquels de nombreuses classes pouvaient potentiellement référencer le même type. Ce n'est pas nécessaire dans Java et .NET en raison de la nature des compilateurs.

Voir cette réponse ici: Les fichiers d'en-tête sont-ils vraiment bons?

11
Jonathan Henson

Un fichier Java représente une classe. Si vous aviez une procédure en dehors de la classe, quelle serait la portée? Serait-elle globale? Ou appartiendrait-elle à la classe qui Java représente?

Vraisemblablement, vous le mettez dans ce fichier Java au lieu d'un autre fichier pour une raison - car il va avec cette classe plus que toute autre classe. Si une procédure en dehors d'une classe était réellement associée à cette classe , alors pourquoi ne pas le forcer à entrer dans la classe à laquelle il appartient? Java gère cela comme une méthode statique à l'intérieur de la classe.

Si une procédure hors classe était autorisée, elle n'aurait vraisemblablement aucun accès spécial à la classe dans laquelle elle a été déclarée, la limitant ainsi à une fonction utilitaire qui ne modifie aucune donnée.

Le seul inconvénient possible de cette Java est que si vous avez vraiment des procédures globales qui ne sont associées à aucune classe, vous finissez par créer une classe MyGlobals pour les contenir et importez cette classe dans tous vos autres fichiers qui utilisent ces procédures.

En fait, le mécanisme d'importation Java Java a besoin de cette restriction pour fonctionner. Avec toutes les API disponibles, le compilateur Java doit savoir exactement quoi compiler et avec quoi compiler, donc les instructions d'importation explicites en haut du fichier. Sans avoir à regrouper vos globales dans une classe artificielle, comment diriez-vous au compilateur Java de compiler vos globaux et non tous et tous globaux sur votre chemin de classe? Qu'en est-il de la collision d'espace de noms où vous avez un doStuff () et quelqu'un d'autre a un doStuff ()? Cela ne fonctionnerait pas. Vous forçant à spécifiez MyClass.doStuff () et YourClass.doStuff () corrige ces problèmes. Forcer vos procédures à entrer dans MyClass au lieu de l'extérieur ne fait que clarifier cette restriction et n'impose pas de restrictions supplémentaires à votre code.

Java s'est trompé sur plusieurs points - la sérialisation a tellement de petites verrues qu'il est presque trop difficile d'être utile (pensez à SerialVersionUID). Il peut également être utilisé pour casser des singletons et d'autres modèles de conception courants. La méthode clone () sur Object doit être divisée en deepClone () et shallowClone () et être de type sécurisé. Toutes les classes d'API auraient pu être rendues immuables par défaut (comme elles sont dans Scala). Mais la restriction selon laquelle toutes les procédures doivent appartenir à une classe est bonne. Il sert principalement à simplifier et à clarifier la langue et votre code sans imposer de restrictions onéreuses.

3
GlenPeterson

Je pense que la plupart des personnes qui ont répondu et leurs électeurs ont mal compris la question. Cela reflète qu'ils ne connaissent pas le C++.

"Définition" et "Déclaration" sont des mots ayant une signification très spécifique en C++.

L'OP ne signifie pas changer la façon dont Java fonctionne. C'est une question purement syntaxique. Je pense que c'est une question valide.

En C++, il existe deux façons de définir une fonction membre.

La première est la méthode Java. Il suffit de mettre tout le code entre les accolades:

class Box {
public:
    // definition of member function
    void change(int newInt) { 
        this._m = newInt;
    }
private:
    int _m
}

Deuxième voie:

class Box {
public:  
    // declaration of member function
    void change(int newInt); 
private:
    int _m
}

// definition of member function
// this can be in the same file as the declaration
void Box::change(int newInt) {
    this._m = newInt;
}

Les deux programmes sont identiques. La fonction change est toujours une fonction membre: elle n'existe pas en dehors de la classe. De plus, la définition de classe DOIT inclure les noms et les types de TOUTES les fonctions et variables membres, comme en Java.

Jonathan Henson a raison de dire qu'il s'agit d'un artefact du fonctionnement des en-têtes en C++: il vous permet de placer des déclarations dans le fichier d'en-tête et des implémentations dans un fichier .cpp séparé afin que votre programme ne viole pas l'ODR (règle de définition unique). Mais il a des mérites en dehors de cela: il vous permet de voir l'interface d'une grande classe en un coup d'œil.

Dans Java vous pouvez approximer cet effet avec des classes abstraites ou des interfaces, mais celles-ci ne peuvent pas avoir le même nom que la classe d'implémentation, ce qui la rend plutôt maladroite.

3
Erik van Velzen

Je pense que c'est un artefact du mécanisme de chargement de classe. Chaque fichier de classe est un conteneur pour un objet chargeable. Il n'y a pas de place "en dehors" des fichiers de classe.

2
ddyer

C++ requiert que le texte entier de la classe soit compilé dans le cadre de chaque unité de compilation qui utilise l'un de ses membres ou produit des instances. La seule façon de garder la compilation sereine est de faire en sorte que le texte de la classe ne contienne - dans la mesure du possible - que les éléments réellement nécessaires à ses consommateurs. Le fait que les méthodes C++ soient souvent écrites en dehors des classes qui les contiennent est un vil hack vraiment vilain motivé par le fait que demander au compilateur de traiter le texte de chaque méthode de classe une fois pour chaque unité de compilation où la classe est utilisée conduirait à totalement temps de construction fous.

En Java, les fichiers de classe compilés contiennent, entre autres, des informations qui sont largement équivalentes à un fichier C++ .h. Les consommateurs de la classe peuvent extraire toutes les informations dont ils ont besoin de ce fichier, sans que le compilateur traite le fichier .Java. Contrairement à C++, où le fichier .h contient des informations mises à la disposition des implémentations et des clients des classes qu'il contient, le flux dans Java est inversé: le fichier utilisé par les clients n'est pas un fichier source utilisé dans la compilation du code de classe, mais est plutôt produit par le compilateur en utilisant les informations du fichier de code de classe. Comme il n'est pas nécessaire de diviser le code de classe entre un fichier contenant les informations dont les clients ont besoin et un fichier contenant l'implémentation, Java ne permet pas une telle division.

0
supercat

C #, qui est très similaire à Java, a ce genre de fonctionnalité grâce à l'utilisation de méthodes partielles, sauf que les méthodes partielles sont exclusivement privées.

Méthodes partielles: http://msdn.Microsoft.com/en-us/library/6b0scde8.aspx

Classes et méthodes partielles: http://msdn.Microsoft.com/en-us/library/wa80x488.aspx

Je ne vois aucune raison pour laquelle Java ne pouvait pas faire la même chose, mais il s'agit probablement simplement de savoir s'il existe un besoin perçu de la base d'utilisateurs d'ajouter cette fonctionnalité au langage.

La plupart des outils de génération de code pour C # génèrent des classes partielles afin que le développeur puisse facilement ajouter du code écrit manuellement à la classe dans un fichier séparé, si vous le souhaitez.

0