web-dev-qa-db-fra.com

Pourquoi les langages gérés en mémoire comme Java, Javascript et C # ont-ils conservé le mot-clé `new`?

Le mot clé new 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ù new est utilisé spécifiquement pour allouer une nouvelle instance d'une classe sur le tas et renvoyer un pointeur vers la nouvelle instance. En C++, ce n'est pas la seule façon de construire un objet. Vous pouvez également construire un objet sur la pile, sans utiliser new - et en fait, cette façon de construire des objets est beaucoup plus courante en C++.

Donc, venant d'un arrière-plan C++, le mot clé new dans des langages comme Java, Javascript et C # m'a semblé naturel et évident. Ensuite, j'ai commencé à apprendre Python, qui n'a pas le mot clé new. En Python, une instance est construite simplement en appelant le constructeur, comme:

f = Foo()

Au début, cela m'a semblé un peu éteint, jusqu'à ce qu'il me vienne à l'esprit qu'il n'y avait aucune raison pour que Python ait new, parce que tout est un objet donc il n'y a pas besoin de lever l'ambiguïté entre les différentes syntaxes de constructeur.

Mais alors j'ai pensé - à quoi sert vraiment new en Java? Pourquoi devrions-nous dire Object o = new Object();? Pourquoi pas simplement Object o = Object();? En C++, il y a certainement un besoin de new, car nous devons faire la distinction entre l'allocation sur le tas et l'allocation sur la pile, mais en Java tous les objets sont construits sur le tas, alors pourquoi même avoir le mot clé new? La même question pourrait se poser pour Javascript. En C #, que je connais beaucoup moins, je pense que new peut avoir un certain intérêt en termes de distinction entre les types d'objets et les types de valeurs, mais je ne suis pas sûr.

Quoi qu'il en soit, il me semble que de nombreux langages qui sont venus après C++ ont simplement "hérité" du mot clé new - sans vraiment en avoir besoin. C'est presque comme un mot-clé résiduel . Nous ne semblons pas en avoir besoin pour une raison quelconque, et pourtant il est là.

Question: Ai-je raison à ce sujet? Ou y a-t-il une raison impérieuse pour que new doive être dans des langages gérés en mémoire inspirés de C++ comme Java, Javascript et C # mais pas Python?

141
Channel72

Vos observations sont correctes. C++ est une bête compliquée, et le mot clé new a été utilisé pour distinguer entre quelque chose qui nécessiterait delete plus tard et quelque chose qui serait automatiquement récupéré. Dans Java et C #, ils ont abandonné le mot clé delete car le garbage collector s'en chargerait pour vous.

Le problème est alors pourquoi ont-ils conservé le mot-clé new? Sans parler aux personnes qui ont écrit la langue, il est difficile de répondre. Mes meilleures suppositions sont énumérées ci-dessous:

  • C'était sémantiquement correct. Si vous connaissiez C++, vous saviez que le mot clé new 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 majuscules, de sorte qu'il peut y avoir confusion.

Ruby se situe quelque part entre Python et Java/C # dans son utilisation de new. Fondamentalement, 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 nécessairement recommandé, mais c'est possible.

97
Berin Loritsch

Bref, vous avez raison. Le nouveau mot-clé est superflu dans des langages comme Java et C #. Voici quelques idées de Bruce Eckel qui était membre du comité standard C++ dans les années 1990 et a publié plus tard des livres 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 des arguments, Cat x ("mitaines") ;. Pour créer un objet 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 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 (ignorant ostensiblement la débâcle des primitives, que j'ai abordée ailleurs). Et puisque tous les objets sont alloués sur le tas, il n'est pas nécessaire de faire la distinction entre la pile et l'allocation de tas. Ils auraient pu facilement dire Cat x = Cat () ou Cat x = Cat ("mitaines"). Ou mieux encore, une inférence de type incorporée pour éliminer la répétition (mais que - - et d'autres fonctionnalités telles que les fermetures - auraient pris "trop ​​de temps", 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 ne pense pas que ce ne sera pas le cas Et ne devrait pas, étant donné les problèmes d’ajout de nouvelles fonctionnalités à Java).

87
Nemanja Trifunovic

Il y a deux raisons auxquelles je peux penser:

  • new fait la distinction entre un objet et une primitive
  • La syntaxe new est un peu plus lisible (IMO)

Le premier est trivial. Je ne pense pas qu'il soit plus difficile de distinguer les deux de toute façon. Le second est un exemple du point de l'OP, qui est que new est 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, vous retrouver facilement avec 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 d'utiliser new, qui indique, Go to the object of the same name as this method and use the method that matches this signature. Java est un langage très simple à analyser, et je soupçonne que cela aide dans ce domaine.

15
Michael K

Java, pour sa part, a toujours la dichotomie de C++: pas tout à fait tout est un objet. Java a des types intégrés (par exemple, char et int) qui ne doivent pas être alloués dynamiquement.

Cela ne signifie pas que new est vraiment nécessaire dans Java cependant - l'ensemble des types qui n'ont pas besoin d'être alloués dynamiquement est fixe et connu. Lorsque vous définissez un objet, le compilateur pourrait savoir (et, en fait, sait) si c'est une simple valeur que char ou un objet qui doit être alloué dynamiquement.

Je m'abstiendrai (encore une fois) de dire ce que cela signifie sur la qualité de la conception de Java (ou son absence, selon le cas).

10
Jerry Coffin

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

L'appel d'une fonction JavaScript avec l'opérateur new entraîne un comportement différent de celui d'une fonction sans l'opérateur new.

Par exemple:

Date()       //Returns a string

new Date()   //Returns a Date object
function foo() {};

foo()        //Returns `undefined`

new foo()    //Returns an empty object
10
user281377

J'aime beaucoup de réponses et je voudrais juste ajouter ceci:
Cela facilite la lecture du code
Non pas que cette situation se produise 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 d'autres langages ont naturellement le mot object réservé. Mais si ce n'était pas le cas, Foo = object() serait le résultat de l'appel de méthode objet ou serait-il une instanciation d'un nouvel objet. Espérons que le langage sans le mot clé new a des protections contre cette situation, mais en ayant l'exigence du mot clé new avant l'appel d'un constructeur, vous autorisez l'existence d'une méthode avec le même nom que un objet.

4
Kavet Kerek

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

Dim f As Foo

qui déclare une variable sans l'assigner, l'équivalent de Foo f; en 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(); en C #, est une distinction de fond. Dans pre-.NET VB, vous pouviez même utiliser Dim f As Foo Ou Dim f As New Foo - car il n'y avait pas de surcharge sur les constructeurs.

4
Richard Gadsden

Il y a beaucoup de choses résiduelles dans de nombreux langages de programmation. Parfois, il est là par conception (C++ a été conçu pour être aussi compatible que possible avec C), parfois, il est juste là. Pour un exemple plus flagrant, considérez dans quelle mesure l'instruction C switch cassée s'est propagée.

Je ne connais pas assez Javascript et C # pour le dire, mais je ne vois aucune raison pour new in Java sauf que C++ l'a eu.

3
David Thornley

En C #, il autorise les types visibles dans des contextes où ils pourraient autrement être masqués par un membre. Le vous permet d'avoir une propriété du 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 new permet de préciser que Address est le type Address et non le membre Address. Cela permet à un membre d'avoir le même nom que son type. Sans cela, vous devrez préfixer le nom du type pour éviter la collision de noms, comme 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 qu'il était également familier était un double bonus.

3
chuckj

Je ne sais pas si Java ont pensé à cela, mais quand vous pensez aux objets comme enregistrements récursifs, alors vous pouvez imaginer que Foo(123) ne renvoie pas un objet mais une fonction dont le point fixe est l'objet en cours de création (c'est-à-dire une fonction qui retournerait l'objet s'il était donné comme argument l'objet en cours de construction). Et le but de new est de "lier le nœud "et renvoyer ce point fixe. En d'autres termes, new rendrait" l'objet-à-être "conscient de son self.

Ce type d'approche pourrait aider à formaliser l'héritage par exemple: vous pouvez étendre une fonction objet avec une autre fonction objet et enfin leur fournir un self commun en utilisant new:

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

Ici & combine deux fonctions objet.

Dans ce cas, new pourrait être défini comme:

def new(objectFunction) {
    actualObject = objectFunction(actualObject)
    return actualObject
}
0
Aivar

Fait intéressant, avec l'avènement du transfert parfait, le non-placement new n'est plus du tout requis en C++. Donc, sans doute, il n'est plus nécessaire dans aucune des langues mentionnées.

0
DeadMG