Pourquoi Java n'autorise pas la présence de définitions de fonction en dehors de la classe?


22

Contrairement à C ++, en Java, nous ne pouvons pas avoir uniquement des déclarations de fonctions 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?



1
Par définitions , entendez-vous des propriétés ou des signatures de méthode en tant que fichiers d'en-tête?
Déco

Une bonne réponse satirique à votre question: steve-yegge.blogspot.com/2006/03/…
Brandon

Réponses:


33

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 l'assemblage, 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é _fooqui sera résolu en une adresse telle que0xdeadbeeflors 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 de lier les fichiers objets compilés à partir de plusieurs langues (y compris l'assemblage) 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)pourrait être altérée _ZN4Foo4barEilorsqu'elle est placée dans un fichier objet et une adresse comme 0xBADCAFElors 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 extern "C"bloc pour désactiver la manipulation, ce qui permet de rendre le code C ++ facilement accessible dans 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 .classfichier. Les fichiers de classe contiennent une multitude d'informations sur leur contenu 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 leclass. Les informations disponibles permettent au code compilé de se décrire sans avoir besoin de fichiers séparés contenant une description dans le 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 à l'aide de 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 son 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.


lien cassé, obtenez le lien de l'archive Web
noɥʇʎԀʎzɐɹƆ

14

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 les motivations de conception du langage Java.


6
L'existence de types primitifs n'exclut-elle pas que Java soit "pure POO"?
Radu Murzea

5
@SoboLAN: Oui, et l'ajout de fonctions le rendrait encore moins "pure POO".
Giorgio

8
@SoboLAN qui dépend de votre définition de "pure POO". À un moment donné, tout est une combinaison de bits dans la mémoire de l'ordinateur après tout ...
jwenting

3
@jwenting: Eh bien, le but de l'abstraction dans les langages de programmation est de cacher autant que possible les bits sous-jacents. Plus vous pouvez voir ces bits dans votre programme, plus vous devriez commencer à penser que votre langage de programmation offre des abstractions qui fuient (à moins qu'il ne soit construit à dessein près du métal). Donc, oui, tout se résume à la manipulation de bits, mais différents langages offrent différents niveaux d'abstraction, sinon vous ne seriez pas en mesure de distinguer l'assemblage d'un langage de niveau supérieur.
Giorgio

2
Dépend de votre définition de "POO pur": chaque valeur de données est un objet et chaque opération de données est l'invocation de la méthode de résultat obtenue par le passage de messages. Pour autant que je sache, il n'y a rien de plus.
Giorgio

11

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. Cela 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 réellement bons?


1
+1, même si j'ai entendu des gens dire qu'ils aiment avoir la séparation entre l'interface publique et l'implémentation privée fournie par les fichiers d'en-tête. Ces gens ont tort, bien sûr. ;)
vaughandroid

6
@Baqueta En Java, vous pouvez y parvenir avec interfaceet class:) Pas besoin d'en-têtes!
Andres F.

1
Je ne comprendrai jamais l'opportunité d'avoir à regarder 3+ fichiers pour comprendre comment fonctionne une instance de classe simple.
Erik Reppen

@ErikReppen généralement, l'interface (ou fichier d'en-tête en C) est la chose que les clients obtiennent sous forme lisible par l'utilisateur pour écrire leurs solutions, le reste étant fourni uniquement sous forme binaire (bien sûr en Java, il n'est pas nécessaire de fournir les sources des interfaces, fichiers de classe et javadoc feront l'affaire).
jwenting

@jwenting Je n'avais pas pensé à ce cas. Je suis plus habitué à penser au prochain pauvre bâtard qui doit maintenir cette base de code stupide, parce que trop souvent c'est moi au prochain travail me poignardant les yeux après avoir passé des heures à regarder une sorte d'interface bizarre et de sous / superclasse joyeux -Architecture ronde.
Erik Reppen

3

Un fichier Java représente une classe. Si vous aviez une procédure en dehors de la classe, quelle serait la portée? Serait-ce mondial? Ou appartiendrait-il à la classe que représente le fichier Java?

Vraisemblablement, vous l'avez placé 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 la forcer à entrer dans cette classe où elle 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 limitation 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 a besoin de cette restriction pour fonctionner. Avec toutes les API disponibles, le compilateur java doit savoir exactement quoi compiler et contre quoi compiler, d'où les instructions d'importation explicites en haut du fichier. Sans avoir à regrouper vos globaux dans une classe artificielle, comment diriez-vous au compilateur Java de compiler vos globaux et non tous les 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 ()? Ça ne marcherait pas. Vous obliger à spécifier MyClass.doStuff () et YourClass.doStuff () résout ces problèmes. Forcer vos procédures à entrer dans MyClass plutôt qu'à l'extérieur ne fait que clarifier cette restriction et n'impose pas de restrictions supplémentaires à votre code.

Java a eu un certain nombre de problèmes - 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

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 le fonctionnement de Java. C'est une question purement syntaxique. Je pense que c'est une question valable.

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

La première est la voie Java. Mettez 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 changeest 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.

En 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.


et cela montre que vous ne comprenez pas les gens ni Java. Vous pouvez utiliser le même nom pour l'interface et l'implémentation, tant qu'ils ne sont pas dans le même package.
jwenting le

Je considère que le package (ou l'espace de noms ou la classe externe) fait partie du nom.
Erik van Velzen

2

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.


1
Je ne vois pas pourquoi l'organisation du code source dans des unités distinctes empêcherait de conserver le format de fichier de classe tel quel.
Mat

il existe une correspondance 1: 1 entre les fichiers de classe et les fichiers source, ce qui est l'une des meilleures décisions de conception dans le système global.
ddyer

@ddyer Vous n'avez jamais vu Foo $ 2 $ 1.class alors? (voir Noms des fichiers de classe de classe interne Java )

Est-ce que cela en ferait une cartographie "sur"? dans tous les cas, chaque classe est générée en compilant exactement un fichier source, ce qui est une bonne décision de conception.
ddyer

0

C #, qui est très similaire à Java, possède ce type 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 pourrait pas faire de même, mais cela se résume probablement à 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

C ++ nécessite 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 les temps de compilation sains d'esprit 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 ne 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 en Java est inversé: le fichier que les clients utilisent n'est pas un fichier source utilisé dans la compilation de la code de classe, mais est plutôt produit par le compilateur en utilisant les informations du fichier de code de classe. Parce qu'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.


-1

Je pense que cela tient en partie au fait que Java est un langage protectionniste qui se préoccupe de son utilisation dans de grandes équipes. Les classes ne peuvent pas être écrasées ou redéfinies. Vous disposez de 4 niveaux de modificateurs d'accès qui définissent très précisément comment les méthodes peuvent et ne peuvent pas être utilisées. Tout est fort / typé statiquement pour protéger les développeurs des hijinx d'inadéquation de type causés par d'autres ou eux-mêmes. Le fait d'avoir des classes et des fonctions comme vos plus petites unités permet de réinventer beaucoup plus facilement le paradigme de la façon d'architecturer une application.

Comparé à JavaScript où les fonctions de première classe de nom / verbe à double usage tombent et se transmettent comme des bonbons d'une pinata ouverte, le constructeur de fonctions équivalent à la classe peut voir son prototype modifié en ajoutant de nouvelles propriétés ou en changeant les anciennes pour les instances qui ont déjà été créés à tout moment, absolument rien ne vous empêche de remplacer une construction par votre propre version, il y a comme 5 types et ils se convertissent et s'évaluent automatiquement dans différentes circonstances et vous pouvez attacher de nouvelles propriétés à presque n'importe quoi:

function nounAndVerb(){
}
nounAndVerb.newProperty = 'egads!';

En fin de compte, il s'agit de niches et de marchés. JavaScript est à peu près aussi approprié et désastreux entre les mains de 100 développeurs (dont beaucoup seront probablement médiocres) que Java était autrefois entre les mains de petits groupes de développeurs essayant d'écrire l'interface utilisateur Web avec. Lorsque vous travaillez avec 100 développeurs, la dernière chose que vous voulez, c'est un gars qui réinvente le foutu paradigme. Lorsque vous travaillez avec l'interface utilisateur ou que le développement rapide est une préoccupation plus critique, la dernière chose que vous voulez est de vous empêcher de faire des choses relativement simples rapidement simplement parce qu'il serait facile de les faire très bêtement si vous ne faites pas attention.

À la fin de la journée, cependant, ce sont deux langues à usage général populaires, il y a donc un peu d'argument philosophique. Mon plus gros bœuf personnel avec Java et C # est que je n'ai jamais vu ou travaillé avec une base de code héritée où la majorité des développeurs semblaient avoir compris la valeur de la POO de base. Lorsque vous entrez dans le jeu devant tout envelopper dans une classe, il est peut-être plus facile de supposer que vous faites de la POO plutôt que de faire des spaghettis déguisés en chaînes massives de 3 à 5 classes de ligne.

Cela dit, il n'y a rien de plus horrible que JavaScript écrit par quelqu'un qui en sait assez pour être dangereux et n'a pas peur de le montrer. Et je pense que c'est l'idée générale. Ce gars-là est censé jouer à peu près les mêmes règles en Java. Je dirais que le salaud trouvera toujours un moyen.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.