Pourquoi des langages gérés par la mémoire, tels que Java, Javascript et C #, ont-ils conservé le mot clé `new`?

139

Le newmot clé dans des langages tels que Java, Javascript et C # crée une nouvelle instance d'une classe.

Cette syntaxe semble avoir été héritée de C ++, où elle newest utilisée spécifiquement pour allouer une nouvelle instance d'une classe sur le tas et renvoyer un pointeur sur la nouvelle instance. En C ++, ce n'est pas le seul moyen de construire un objet. Vous pouvez également construire un objet sur la pile, sans utiliser new- et en fait, cette manière de construire des objets est beaucoup plus courante en C ++.

Alors, venant d’un fond C ++, le newmot clé dans des langages tels que Java, Javascript et C # me paraissait naturel et évident. Ensuite, j'ai commencé à apprendre Python, qui n'a pas le newmot clé. En Python, une instance est construite simplement en appelant le constructeur, comme suit:

f = Foo()

Au début, cela me paraissait un peu déroutant, jusqu'à ce que je réalise qu'il n'y avait aucune raison pour que Python en ait new, car tout est un objet, il n'est donc pas nécessaire de lever les ambiguïtés entre les différentes syntaxes de constructeur.

Mais ensuite, j'ai pensé: à quoi sert vraiment newJava? Pourquoi devrions-nous dire Object o = new Object();? Pourquoi pas juste Object o = Object();? En C ++, il est absolument nécessaire new, car nous devons distinguer entre l’allocation sur le tas et l’allocation sur la pile, mais en Java, tous les objets sont construits sur le tas, pourquoi même le newmot-clé? La même question pourrait être posée pour Javascript. En C #, que je connais beaucoup moins bien, je pense qu’il newpeut être utile de distinguer les types d’objet des types de valeur, mais je ne suis pas sûr.

Quoi qu'il en soit, il me semble que de nombreux langages ayant succédé à C ++ ont simplement "hérité" du newmot clé - sans en avoir réellement besoin. C'est presque comme un mot-clé vestigial . Nous ne semblons en avoir besoin pour aucune raison, et pourtant, il est là.

Question: Ai-je raison à ce sujet? Ou y a-t-il une raison impérieuse qui newdoit être dans des langages à gestion de mémoire inspirés du C ++ tels que Java, Javascript et C #, mais pas Python?

canal72
la source
13
La question est plutôt de savoir pourquoi toutes les langues ont le newmot clé. Bien sûr, je veux créer une nouvelle variable, un compilateur stupide! Une bonne langue serait à mon avis une syntaxe comme f = heap Foo(), f = auto Foo().
7
Un point très important à prendre en compte est la familiarité d’un langage avec les nouveaux programmeurs. Java a été explicitement conçu pour paraître familier aux programmeurs C / C ++.
1
@Lundin: C'est vraiment le résultat de la technologie du compilateur. Un compilateur moderne n'aurait même pas besoin d'indices; cela déterminerait où placer l'objet en fonction de votre utilisation réelle de cette variable. C'est à dire quand fn'échappe pas à la fonction, allouez de l'espace de pile.
MSalters
3
@Lundin: C'est possible, et en fait les machines virtuelles modernes. Il s'agit simplement de prouver que le GC peut collecter des objets florsque la fonction englobante revient.
MSalters
1
Ce n’est pas constructif de demander pourquoi une langue est conçue telle quelle, en particulier si cette conception nous demande de taper quatre caractères supplémentaires sans aucun avantage évident? Qu'est-ce que je rate?
MatrixFrog

Réponses:

94

Vos observations sont correctes. C ++ est une bête compliquée, et le newmot clé a été utilisé pour faire la distinction entre quelque chose dont on aurait besoin deleteplus tard et quelque chose qui serait automatiquement récupéré. En Java et en C #, ils ont abandonné le deletemot - clé car le ramasse-miettes s'en charge pour vous.

Le problème est alors pourquoi ont-ils gardé le newmot clé? Sans parler aux auteurs de la langue, il est difficile de répondre. Mes meilleures suppositions sont énumérées ci-dessous:

  • C'était sémantiquement correct. Si vous connaissez C ++, vous savez que le newmot - clé crée un objet sur le tas. Alors, pourquoi changer le comportement attendu?
  • Il attire l'attention sur le fait que vous instanciez un objet plutôt que d'appeler une méthode. Avec les recommandations de style de code Microsoft, les noms de méthode commencent par des lettres majuscules afin d'éviter toute confusion.

Ruby se situe quelque part entre Python et Java / C # dans son utilisation de new. En gros, vous instanciez un objet comme celui-ci:

f = Foo.new()

Ce n'est pas un mot-clé, c'est une méthode statique pour la classe. Cela signifie que si vous voulez un singleton, vous pouvez remplacer l'implémentation par défaut de new()pour renvoyer la même instance à chaque fois. Ce n'est pas forcément recommandé, mais c'est possible.

Berin Loritsch
la source
5
La syntaxe Ruby est belle et cohérente! Le nouveau mot-clé en C # est également utilisé comme contrainte de type générique pour un type avec un constructeur par défaut.
Steven Jeuris
3
Oui. Nous ne parlerons pas des génériques cependant. Les versions des génériques Java et C # sont très obtuses. Ne pas dire que C ++ est beaucoup plus facile à comprendre cependant. Les génériques ne sont vraiment utiles que pour les langages statiques. Les langages typés dynamiquement comme Python, Ruby et Smalltalk n'en ont tout simplement pas besoin.
Berin Loritsch le
2
Delphi a une syntaxe similaire à Ruby, sauf que les constructeurs sont généralement (mais seulement par convention) appelés Create (`f: = TFoo.Create (param1, param2 ...) '
Gerry
En Objective-C, la allocméthode (à peu près la même chose que Ruby's new) est aussi juste une méthode de classe.
Mipadi
3
Du point de vue de la conception des langues, les mots-clés sont mauvais pour de nombreuses raisons, vous ne pouvez surtout pas les changer. D'autre part, la réutilisation du concept de méthode est plus facile, mais nécessite une certaine attention lors de l'analyse et de l'analyse. Smalltalk et ses proches utilisent des messages, C ++ et son nom, par contre, mots
Gabriel Ščerbák le
86

En bref, vous avez raison. Le mot-clé new est superflu dans des langages tels que Java et C #. Voici quelques idées de Bruce Eckel, membre du comité standard C ++ dans les années 1990 et qui a publié des ouvrages sur Java: http://www.artima.com/weblogs/viewpost.jsp?thread=260578

il devait y avoir un moyen de distinguer les objets de tas des objets de pile. Pour résoudre ce problème, le nouveau mot-clé a été approprié à partir de Smalltalk. Pour créer un objet de pile, il vous suffit de le déclarer, comme dans Cat x; ou, avec arguments, Cat x ("mitaines") ;. Pour créer un objet de tas, vous utilisez new, comme dans new Cat; ou nouveau chat ("mitaines") ;. Compte tenu des contraintes, il s’agit d’une solution élégante et cohérente.

Entrez Java, après avoir décidé que tout ce qui est en C ++ est mal fait et trop complexe. L'ironie ici est que Java pourrait et a pris la décision de jeter l'allocation de pile (en ignorant explicitement la débâcle des primitives, que j'ai déjà abordée ailleurs). Et comme tous les objets sont alloués sur le tas, il n’est pas nécessaire de faire la distinction entre l’allocation de pile et celle de tas. Ils auraient facilement pu dire Cat x = Cat () ou Cat x = Cat ("mitaines"). Ou encore mieux, l'inférence de type incorporée pour éliminer la répétition (mais cela - et d'autres fonctionnalités comme les fermetures - aurait pris "trop ​​longtemps", nous sommes donc coincés avec la version médiocre de Java à la place; l'inférence de type a été discutée mais je vais en parler. cela ne se produira pas et ne le devrait pas, étant donné les problèmes liés à l’ajout de nouvelles fonctionnalités à Java).

Nemanja Trifunovic
la source
2
Stack vs Heap ... perspicace, je n'y aurais jamais pensé.
WernerCD
1
"L'inférence de type a été discutée, mais je ne vais pas croire que cela n'arrivera pas.", :) Le développement de Java va toujours de l'avant. Il est intéressant de noter que c’est le seul produit phare de Java 10. Le varmot clé n'est nulle part aussi bon qu'en autoC ++, mais j'espère qu'il va s'améliorer.
patrik
15

Je peux penser à deux raisons:

  • new distingue entre un objet et une primitive
  • La newsyntaxe est un peu plus lisible (IMO)

Le premier est trivial. Je ne pense pas qu'il soit plus difficile de distinguer les deux. La seconde est un exemple du point de vue du PO, qui newest en quelque sorte redondant.

Il pourrait cependant y avoir des conflits d’espace de noms; considérer:

public class Foo {
   Foo() {
      // Constructor
   }
}

public class Bar {
   public static void main(String[] args) {
      Foo f = Foo();
   }

   public Foo Foo() {
      return Foo();
   }
}

Vous pourriez, sans trop étirer votre imagination, aboutir facilement à un appel infiniment récursif. Le compilateur devrait appliquer une erreur "Nom de la fonction identique à l'objet". Cela ne ferait vraiment pas de mal, mais il est beaucoup plus simple à utiliser new, ce qui Go to the object of the same name as this method and use the method that matches this signature.veut dire que Java est un langage très simple à analyser, et je suppose que cela aide dans ce domaine.

Michael K
la source
1
En quoi est-ce différent d'une autre récursion infinie?
DeadMG
C'est une assez bonne illustration d'une mauvaise chose qui pourrait arriver, bien que le développeur qui le fait aurait de graves problèmes mentaux.
Tjaart
10

Java, pour sa part, a encore la dichotomie de C ++: tout n'est pas tout objet. Java possède des types intégrés (par exemple, charet int) qui ne doivent pas être alloués de manière dynamique.

Cela ne signifie pas pour autant que cela newsoit vraiment nécessaire en Java: l'ensemble des types qui n'ont pas besoin d'être alloués de manière dynamique est fixe et connu. Lorsque vous définissez un objet, le compilateur peut savoir (et sait en fait) s'il s'agit d'une valeur simple charou d'un objet qui doit être alloué de manière dynamique.

Je m'abstiendrai (encore une fois) de donner mon avis sur ce que cela signifie pour la qualité du design de Java (ou son absence, selon le cas).

Jerry Coffin
la source
Plus précisément, les types primitifs ne nécessitent aucun appel de constructeur: ils sont écrits en tant que littéral, proviennent d'un opérateur ou d'une fonction renvoyant des primitives. En fait, vous créez également des chaînes (qui sont sur le tas) sans un new, donc ce n’est pas vraiment la raison.
Paŭlo Ebermann le
9

En JavaScript, vous en avez besoin, car le constructeur ressemble à une fonction normale. Comment JS devrait-il savoir que vous souhaitez créer un nouvel objet sans le nouveau mot clé?

utilisateur281377
la source
4

Fait intéressant, VB en a vraiment besoin - la distinction entre

Dim f As Foo

qui déclare une variable sans l'affecter, l'équivalent de Foo f;C #, et

Dim f As New Foo()

qui déclare la variable et assigne une nouvelle instance de la classe, l’équivalent de var f = new Foo();C en, est une distinction de fond. Dans les versions antérieures à .NET, vous pouviez même utiliser Dim f As Fooou Dim f As New Foo- car il n'y avait pas de surcharge sur les constructeurs.

Richard Gadsden
la source
4
VB n'a pas besoin de la version abrégée, c'est juste une abréviation. Vous pouvez également écrire la version la plus longue avec le type et le constructeur Dim f As Foo = New Foo (). Avec Option Infer On, qui est la chose la plus proche de la variable C #, vous pouvez écrire Dim f = New Foo () et le compilateur déduit le type de f.
MarkJ
2
... et Dim f As Foo()déclare un tableau de Foos. Oh joie! :-)
Heinzi
@MarkJ: Dans vb.net, le raccourci est équivalent. Dans vb6, ce n'était pas. Dans vb6, Dim Foo As New Barcréerait effectivement Fooune propriété qui construirait un Barsi elle était lue avec (ie Nothing). Ce comportement ne s'est pas limité à se produire une fois; mettre Fooà Nothinget puis le lire créerait un autre nouveau Bar.
Supercat
4

J'aime beaucoup de réponses et je voudrais juste ajouter ceci:
Cela facilite la lecture du code.
Cette situation se produirait souvent, mais considérez que vous vouliez une méthode au nom d'un verbe qui partage le même nom qu'un nom. C # et les autres langues ont naturellement le mot objectréservé. Mais si ce n'était pas le cas, cela Foo = object()résulterait de l'appel de la méthode objet ou instancierait-il un nouvel objet. Espérons que la langue sans newmot - clé dispose de protections contre cette situation, mais en newautorisant l' exigence du mot - clé avant l'appel d'un constructeur, vous autorisez l'existence d'une méthode portant le même nom qu'un objet.

Kavet Kerek
la source
4
On peut soutenir que savoir que c'est un mauvais signe.
DeadMG
1
@DeadMG: «On peut soutenir que» signifie «je ferai valoir que jusqu'à la fermeture du bar»…
Donal Fellows
3

Il y a beaucoup de traces dans de nombreux langages de programmation. Parfois, c'est là pour des raisons de conception (C ++ a été conçu pour être aussi compatible que possible avec C), parfois, c'est juste là. Pour un exemple plus flagrant, considérons dans quelle mesure l’ switchinstruction C cassée se propageait.

Je ne connais pas assez bien le langage Javascript et le langage C # pour le dire, mais je ne vois aucune raison de le faire newen Java, si ce n’est le cas du C ++.

David Thornley
la source
Je pense que JavaScript a été conçu pour "ressembler le plus possible à Java" autant que possible, même lorsqu'il fait quelque chose de particulièrement non Java. x = new Y()JavaScript n'instancier la Yclasse, parce que JavaScript ne possède classes. Mais comme il ressemble à Java, il porte le newmot - clé de Java, qui bien sûr le fait passer de C ++.
MatrixFrog
+1 pour l'interrupteur cassé (dans l'espoir que vous le répariez: D).
Maaartinus
3

En C #, cela permet aux types visibles dans des contextes où ils pourraient sinon être masqués par un membre. Le vous permet d'avoir une propriété avec le même nom que son type. Considérer ce qui suit,

class Address { 
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

class Person {
    public string Name { get; set; }
    public Address Address { get; set; }

    public void ChangeStreet(string newStreet) {
        Address = new Address { 
            Street = newStreet, 
            City = Address.City,
            State = Address.State,
            Zip = Address.Zip
        };
    }
}

Notez que l'utilisation de newpermet d'indiquer clairement que Addressle Addresstype n'est pas le Addressmembre. Cela permet à un membre d'avoir le même nom que son type. Sans cela, vous auriez besoin de préfixer le nom du type pour éviter la collision de noms, tel que CAddress. C'était très intentionnel, car Anders n'a jamais aimé la notation hongroise ou quelque chose de similaire et voulait que C # soit utilisable sans elle. Le fait que ce soit aussi familier était un double bonus.

Chuckj
la source
w00t, et Address pourraient alors dériver d’une interface appelée Address .. seulement, non. Donc, ce n’est pas un avantage de pouvoir réutiliser le même nom et de diminuer la lisibilité générale de votre code. Donc, en conservant le I pour les interfaces, mais en supprimant le C des classes, ils ont simplement créé une odeur désagréable sans aucun avantage pratique.
gbjbaanb
Je me demande s'ils n'utilisent pas "Nouveau" au lieu de "Nouveau". Quelqu'un pourrait leur dire qu'il existe également des lettres minuscules, ce qui résout en quelque sorte le problème.
Maaartinus
Tous les mots réservés dans les langages dérivés du C, tels que C #, sont en minuscule. La grammaire nécessite ici un mot réservé pour éviter toute ambiguïté, ainsi l’utilisation de "nouveau" au lieu de "nouveau".
Chuckj
@gbjbaanb Il n'y a pas d'odeur. Il est toujours très clair si vous faites référence à un type, à une propriété / à un membre ou à une fonction. La vraie odeur, c’est quand vous nommez un espace-noms de la même manière qu’une classe. MyClass.MyClass
Stephen
0

Fait intéressant, avec l'avènement de la transmission parfaite, le non-placement newn'est plus du tout requis en C ++. Donc, sans doute, ce n'est plus nécessaire dans aucune des langues mentionnées.

DeadMG
la source
4
Que voulez - vous transmettre à ? En fin de compte, std::shared_ptr<int> foo();devra appeler en new intquelque sorte.
MSalters
0

Je ne suis pas sûr que les concepteurs de Java aient pensé à cela, mais lorsque vous considérez les objets comme des enregistrements récursifs , vous pouvez imaginer que Foo(123)cela ne retourne pas un objet, mais une fonction dont le point fixe est l'objet créé (c.-à-d. Une fonction qui renverrait l'objet s'il est donné en argument, l'objet en cours de construction). Et le but de newest de "faire le noeud" et de retourner ce point de repère. En d' autres termes , newferait l'objet « à être » au courant de son self.

Ce type d’approche pourrait aider à formaliser l’héritage, par exemple: vous pourriez étendre une fonction objet avec une autre fonction objet et enfin leur fournir des éléments communs selfen utilisant new:

cp = new (Colored("Blue") & Line(0, 0, 100, 100))

Ici, &combine deux fonctions-objets.

Dans ce cas, newpourrait être défini comme:

def new(objectFunction) {
    actualObject = objectFunction(actualObject)
    return actualObject
}
Aivar
la source
-2

Autoriser 'nil'.

Parallèlement à l'allocation basée sur le tas, Java a adopté l'utilisation d'objets nil. Pas simplement comme un artefact, mais comme une fonctionnalité. Lorsque les objets peuvent avoir un état nul, vous devez alors séparer la déclaration de l'initialisation. D'où le nouveau mot clé.

En C ++ une ligne comme

Person me;

Appellerait instantanément le constructeur par défaut.

Kris Van Bael
la source
4
Euh, qu'est-ce qui ne va pas avec Person me = Nilou Person meêtre Nil par défaut et utiliser Person me = Person()pour non-Nil? Mon point est qu'il ne semble pas que Nil ait été un facteur dans cette décision ...
Andres F.
Vous avez un point. Personne moi = Personne (); fonctionnerait aussi bien.
Kris Van Bael
@AndresF. Ou encore plus simple avec Person menil et Person me()appel du constructeur par défaut. Vous aurez donc toujours besoin de parenthèses pour pouvoir invoquer quoi que ce soit et vous débarrasser ainsi d'une irrégularité C ++.
Maaartinus
-2

La raison pour laquelle Python n'en a pas besoin, c'est en raison de son système de types. Fondamentalement, quand vous voyez foo = bar()en Python, peu importe qu’il s’agisse d’ bar()une méthode invoquée, d’une classe instanciée ou d’un objet foncteur; en Python, il n'y a pas de différence fondamentale entre un foncteur et une fonction (car les méthodes sont des objets); et vous pourriez même affirmer qu'une classe en cours d'instanciation est un cas particulier de foncteur. Il n’ya donc aucune raison de créer une différence artificielle dans ce qui se passe (d’autant plus que la gestion de la mémoire est extrêmement cachée dans Python).

Dans un langage utilisant un système de typage différent ou dans lequel les méthodes et les classes ne sont pas des objets, il est possible que la différence conceptuelle entre créer un objet et appeler une fonction ou un foncteur soit plus forte . Ainsi, même si le compilateur / interprète n'a pas besoin du newmot clé, il est compréhensible qu'il puisse être géré dans des langages n'ayant pas dépassé toutes les limites de Python.

Kazark
la source
-4

En réalité, en Java, il y a une différence lors de l'utilisation de Strings:

String myString = "myString";

est différent de

String myString = new String("myString");

En supposant que la chaîne "myString"n'ait jamais été utilisée auparavant, la première instruction crée un seul objet String dans le pool String (une zone spéciale du segment de mémoire), tandis que la seconde crée deux objets, l'un dans la zone de segment normal pour les objets et l'autre dans le pool String. . Il est assez évident que la deuxième déclaration est inutile dans ce scénario particulier, mais est autorisée par le langage.

Cela signifie que new garantit que l'objet est créé dans cette zone spéciale du tas, allouée pour l'instanciation normale d'un objet, mais il peut exister d'autres moyens d'instancier des objets sans lui. celui ci-dessus est un exemple et il reste de la place pour l'extensibilité.

m3th0dman
la source
2
Mais ce n'était pas la question. La question était "pourquoi pas String myString = String("myString");?" (notez l'absence du newmot clé)
Andres F.
@AndresF. C'est un peu évident: il est légal d'avoir une méthode nommée String qui retourne une chaîne dans une classe String et il doit y avoir une différence entre l'appel et le constructeur. Quelque chose commeclass MyClass { public MyClass() {} public MyClass MyClass() {return null;} public void doSomething { MyClass myClass = MyClass();}} //which is it, constructor or method?
m3th0dman
3
Mais ce n'est pas évident. Premièrement, le fait d'avoir une méthode régulière nommée Stringva à l'encontre des conventions de style Java. Vous pouvez donc supposer que toute méthode commençant par une lettre majuscule est un constructeur. Et deuxièmement, si nous ne sommes pas limités par Java, Scala a des "classes de cas" où le constructeur ressemble exactement à ce que j'ai dit. Alors, où est le problème?
Andres F.
2
Désolé, je ne suis pas votre argument. Vous plaidez efficacement la syntaxe , la sémantique pas, et de toute façon la question portait sur newdes langues gérées . J'ai montré que ce newn'est pas du tout nécessaire (par exemple, Scala n'en a pas besoin pour les classes de cas) et qu'il n'y a pas d'ambiguïté (puisqu'il est absolument impossible de nommer une méthode MyClassen classe MyClass). Il n'y a pas d'ambiguïté pour String s = String("hello"); ce ne peut être autre chose qu'un constructeur. Enfin, je ne suis pas d’accord: les conventions de code sont là pour améliorer et clarifier la syntaxe, ce sur quoi vous vous disputez!
Andres F.
1
Donc c'est ton argument? "Les concepteurs Java avaient besoin du newmot clé car sinon la syntaxe de Java aurait été différente"? Je suis sûr que vous pouvez voir la faiblesse de cet argument;) Et oui, vous continuez à discuter de syntaxe, pas de sémantique. Aussi, la compatibilité ascendante avec quoi? Si vous concevez un nouveau langage, tel que Java, vous êtes déjà incompatible avec le C et le C ++!
Andres F.
-8

En C #, il est important de faire la distinction entre les objets créés sur la pile et le tas. Nous créons fréquemment des objets sur la pile pour de meilleures performances. Vous voyez, lorsqu'un objet de la pile sort de son champ d'application, il disparaît immédiatement lorsque la pile se défait, comme une primitive. Il n’est pas nécessaire que le ramasse-miettes en garde la trace et le gestionnaire de mémoire n’a pas besoin de temps système supplémentaire pour allouer l’espace nécessaire sur le tas.

pds
la source
5
Malheureusement, vous confondez C # avec C ++.
gbjbaanb