¿Por qué los lenguajes gestionados por memoria como Java, Javascript y C # retienen la palabra clave `new`?

139

La newpalabra clave en lenguajes como Java, Javascript y C # crea una nueva instancia de una clase.

Esta sintaxis parece haber sido heredada de C ++, donde newse usa específicamente para asignar una nueva instancia de una clase en el montón y devolver un puntero a la nueva instancia. En C ++, esta no es la única forma de construir un objeto. También puede construir un objeto en la pila, sin usar new, y de hecho, esta forma de construir objetos es mucho más común en C ++.

Entonces, viniendo de un fondo C ++, la newpalabra clave en lenguajes como Java, Javascript y C # me pareció natural y obvia. Entonces comencé a aprender Python, que no tiene la newpalabra clave. En Python, una instancia se construye simplemente llamando al constructor, como:

f = Foo()

Al principio, esto me pareció un poco extraño, hasta que se me ocurrió que no hay razón para que Python lo tenga new, porque todo es un objeto, por lo que no hay necesidad de desambiguar entre varias sintaxis de constructor.

Pero luego pensé: ¿de qué sirve realmente newJava? ¿Por qué deberíamos decir Object o = new Object();? ¿Por qué no solo Object o = Object();? En C ++ definitivamente hay una necesidad new, ya que necesitamos distinguir entre la asignación en el montón y la asignación en la pila, pero en Java todos los objetos se construyen en el montón, entonces, ¿por qué incluso tener la newpalabra clave? La misma pregunta podría hacerse para Javascript. En C #, con el que estoy mucho menos familiarizado, creo que newpuede tener algún propósito en términos de distinguir entre tipos de objetos y tipos de valores, pero no estoy seguro.

De todos modos, me parece que muchos lenguajes que vinieron después de C ++ simplemente "heredaron" la newpalabra clave, sin realmente necesitarla. Es casi como una palabra clave vestigial . No parece que lo necesitemos por ningún motivo y, sin embargo, está ahí.

Pregunta: ¿Estoy en lo correcto sobre esto? ¿O hay alguna razón convincente que newdeba estar en lenguajes administrados en memoria inspirados en C ++ como Java, Javascript y C # pero no Python?

Channel72
fuente
13
La pregunta es más bien por qué cualquier idioma tiene la newpalabra clave. ¡Por supuesto que quiero crear un nuevo compilador variable y estúpido! Un buen lenguaje sería en mi opinión, tienen una sintaxis como f = heap Foo(), f = auto Foo().
77
Un punto muy importante a considerar es cuán familiar es un lenguaje para los nuevos programadores. Java fue diseñado explícitamente para parecer familiar a los programadores de C / C ++.
1
@Lundin: Es realmente el resultado de la tecnología del compilador. Un compilador moderno ni siquiera necesitaría pistas; determinaría dónde colocar el objeto en función de su uso real de esa variable. Es decir, cuando fno escapa de la función, asigne espacio de pila.
MSalters
3
@Lundin: Puede, y de hecho, las JVM modernas lo hacen. Es simplemente una cuestión de demostrar que el GC puede recolectar objetos fcuando la función de cierre regresa.
MSalters
1
No es constructivo preguntar por qué un lenguaje está diseñado de la manera que es, particularmente si ese diseño nos pide que escriba cuatro caracteres adicionales sin ningún beneficio obvio. ¿Qué me estoy perdiendo?
MatrixFrog

Respuestas:

94

Tus observaciones son correctas. C ++ es una bestia complicada, y la newpalabra clave se usó para distinguir entre algo que necesitaba deletemás tarde y algo que sería reclamado automáticamente. En Java y C #, descartaron la deletepalabra clave porque el recolector de basura se encargaría de usted.

Entonces, el problema es por qué mantuvieron la newpalabra clave Sin hablar con las personas que escribieron el idioma es un poco difícil de responder. Mis mejores conjeturas se enumeran a continuación:

  • Fue semánticamente correcto. Si estaba familiarizado con C ++, sabía que la newpalabra clave crea un objeto en el montón. Entonces, ¿por qué cambiar el comportamiento esperado?
  • Llama la atención sobre el hecho de que está creando una instancia de un objeto en lugar de llamar a un método. Con las recomendaciones de estilo de código de Microsoft, los nombres de los métodos comienzan con letras mayúsculas, por lo que puede haber confusión.

Ruby está en algún lugar entre Python y Java / C # en su uso new. Básicamente, crea una instancia de un objeto como este:

f = Foo.new()

No es una palabra clave, es un método estático para la clase. Lo que eso significa es que si desea un singleton, puede anular la implementación predeterminada de new()devolver la misma instancia cada vez. No es necesariamente recomendado, pero es posible.

Berin Loritsch
fuente
55
¡La sintaxis de Ruby se ve bonita y consistente! La nueva palabra clave en C # también se usa como una restricción de tipo genérico para un tipo con un constructor predeterminado.
Steven Jeuris
3
Si. Sin embargo, no hablaremos de genéricos. Tanto la versión genérica de Java como la de C # son muy obtusas. Sin embargo, no decir que C ++ es mucho más fácil de entender. Los genéricos realmente solo son útiles para lenguajes tipados estáticamente. Los lenguajes de tipo dinámico como Python, Ruby y Smalltalk simplemente no los necesitan.
Berin Loritsch
2
Delphi tiene una sintaxis similar a Ruby, excepto que los constructores son usualmente (pero solo por convención) llamados Create (`f: = TFoo.Create (param1, param2 ...) '
Gerry
En Objective-C, el allocmétodo (más o menos lo mismo que el de Ruby new) también es solo un método de clase.
mipadi
3
Desde la perspectiva del diseño del lenguaje, las palabras clave son malas por muchas razones, principalmente no puedes cambiarlas. Por otro lado, reutilizar el concepto de método es más fácil, pero necesita un poco de atención al analizar y analizar. Smalltalk y sus familiares usan mensajes, C ++ y itskin, por otro lado, palabras clave
Gabriel Ščerbák
86

En resumen, tienes razón. La palabra clave new es superflua en lenguajes como Java y C #. Aquí hay algunas ideas de Bruce Eckel, que fue miembro del Comité Estándar de C ++ en la década de 1990 y luego publicó libros sobre Java: http://www.artima.com/weblogs/viewpost.jsp?thread=260578

tenía que haber alguna forma de distinguir los objetos del montón de los objetos de la pila. Para resolver este problema, la nueva palabra clave se apropió de Smalltalk. Para crear un objeto de pila, simplemente lo declara, como en Cat x; o, con argumentos, Cat x ("mitones") ;. Para crear un objeto de montón, usa new, como en new Cat; o nuevo gato ("mitones"); Dadas las limitaciones, esta es una solución elegante y consistente.

Ingrese a Java, después de decidir que todo C ++ está mal hecho y es demasiado complejo. La ironía aquí es que Java pudo y tomó la decisión de tirar la asignación de la pila (ignorando deliberadamente la debacle de las primitivas, que he abordado en otro lugar). Y dado que todos los objetos se asignan en el montón, no hay necesidad de distinguir entre la pila y la asignación del montón. Podrían haber dicho fácilmente Cat x = Cat () o Cat x = Cat ("mitones"). O incluso mejor, incorporó la inferencia de tipos para eliminar la repetición (pero eso, y otras características como los cierres, habrían tomado "demasiado tiempo", por lo que estamos atascados con la versión mediocre de Java; se ha discutido la inferencia de tipos, pero lo haré es probable que no suceda, y no debería, dados los problemas para agregar nuevas funciones a Java).

Nemanja Trifunovic
fuente
2
Stack vs Heap ... perspicaz, nunca lo habría pensado.
WernerCD
1
"se ha discutido la inferencia de tipos, pero estableceré probabilidades de que no sucederá", :) Bueno, el desarrollo de Java aún continúa. Sin embargo, es interesante que este sea el único buque insignia de Java 10. La varpalabra clave no es tan buena como autoen C ++, pero espero que mejore.
patrik
15

Hay dos razones por las que puedo pensar:

  • new distingue entre un objeto y un primitivo
  • La newsintaxis es un poco más legible (IMO)

El primero es trivial. No creo que sea más difícil distinguir los dos de cualquier manera. El segundo es un ejemplo del punto del OP, que newes algo redundante.

Sin embargo, podría haber conflictos de espacio de nombres; considerar:

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

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

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

Podrías, sin estirar demasiado la imaginación, terminar fácilmente con una llamada infinitamente recursiva. El compilador tendría que imponer un error "Nombre de la función igual que el objeto". Esto realmente no estaría de más, pero es mucho más simple de usar new, lo que indica que Go to the object of the same name as this method and use the method that matches this signature.Java es un lenguaje muy simple de analizar, y sospecho que esto ayuda en esa área.

Michael K
fuente
1
¿Cómo es esto diferente a cualquier otra recursión infinita?
DeadMG
Esa es una buena ilustración de algo malo que podría suceder, aunque el desarrollador que lo haga tendría algunos problemas mentales graves.
Tjaart
10

Java, por su parte, todavía tiene la dicotomía de C ++: no bastante todo es un objeto. Java tiene tipos incorporados (por ejemplo, chary int) que no tienen que asignarse dinámicamente.

Sin embargo, eso no significa que newsea ​​realmente necesario en Java: el conjunto de tipos que no necesitan asignarse dinámicamente es fijo y conocido. Cuando define un objeto, el compilador podría saber (y, de hecho, sabe) si es un valor simple charo un objeto que debe asignarse dinámicamente.

Me abstendré (una vez más) de opinar sobre lo que esto significa sobre la calidad del diseño de Java (o la falta del mismo, según sea el caso).

Jerry Coffin
fuente
Más concretamente, los tipos primitivos no necesitan ninguna llamada de constructor: se escriben como literales, provienen de un operador o de alguna función primitiva de retorno. En realidad, también crea cadenas (que están en el montón) sin un new, por lo que esta no es realmente la razón.
Paŭlo Ebermann
9

En JavaScript lo necesita porque el constructor parece una función normal, entonces, ¿cómo debería saber JS que desea crear un nuevo objeto si no fuera por la nueva palabra clave?

usuario281377
fuente
4

Curiosamente, VB lo necesita: la distinción entre

Dim f As Foo

que declara una variable sin asignarla, el equivalente de Foo f;en C #, y

Dim f As New Foo()

que declara la variable y asigna una nueva instancia de la clase, el equivalente de var f = new Foo();en C #, es una distinción sustantiva. En pre.NET VB, incluso podría usar Dim f As Fooo Dim f As New Foo, porque no había sobrecarga en los constructores.

Richard Gadsden
fuente
44
VB no necesita la versión abreviada, es solo una abreviatura. También puede escribir la versión más larga con tipo y constructor Dim f As Foo = New Foo (). Con Option Infer On, que es lo más cercano a la var de C #, puede escribir Dim f = New Foo () y el compilador infiere el tipo de f.
MarkJ
2
... y Dim f As Foo()declara una matriz de Foos. ¡Oh Alegría! :-)
Heinzi
@ MarkJ: En vb.net, la taquigrafía es equivalente. En vb6, no fue así. En vb6, Dim Foo As New Barefectivamente haría Foouna propiedad que construiría un Barsi se leyera while (es decir Nothing). Este comportamiento no se limitó a suceder una vez; el establecimiento Foode Nothingy después de leer sería crear otro nuevo Bar.
supercat
4

Me gustan muchas respuestas y me gustaría agregar esto:
hace que la lectura del código sea más fácil
No es que esta situación suceda a menudo, pero considere que quería un método en el nombre de un verbo que compartiera el mismo nombre que un sustantivo. C # y otro lenguaje tienen naturalmente la palabra objectreservada. Pero si no fuera así, sería Foo = object()el resultado de la llamada al método del objeto o sería instanciar un nuevo objeto. Con suerte, dicho lenguaje sin la newpalabra clave tiene protecciones contra esta situación, pero al tener el requisito de la newpalabra clave antes de llamar a un constructor, permite la existencia de un método con el mismo nombre que un objeto.

Kavet Kerek
fuente
44
Podría decirse que tener que saber esto es una mala señal.
DeadMG
1
@DeadMG: "Podría decirse" significa "Argumentaré eso hasta que cierre el bar" ...
Donal Fellows
3

Hay muchas cosas vestigiales en muchos lenguajes de programación. A veces está ahí por diseño (C ++ fue diseñado para ser lo más compatible posible con C), a veces, está ahí. Para un ejemplo más atroz, considere hasta qué punto se switchpropagó la declaración C rota .

No sé Javascript y C # lo suficientemente bien como para decirlo, pero no veo ninguna razón newen Java, excepto que C ++ lo tenía.

David Thornley
fuente
Creo que JavaScript fue diseñado para "parecerse a Java" tanto como sea posible, incluso cuando está haciendo algo que no es similar a Java. x = new Y()en JavaScript no crea una instancia de la Yclase, porque JavaScript no tiene clases. Pero se parece a Java, por lo que lleva la newpalabra clave hacia adelante desde Java, que por supuesto la llevó hacia adelante desde C ++.
MatrixFrog
+1 para el interruptor roto (con la esperanza de que lo repares: D).
maaartinus
3

En C #, permite tipos visibles en contextos en los que de otro modo un miembro podría ocultarlos. El le permite tener una propiedad con el mismo nombre que su tipo. Considera lo siguiente,

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
        };
    }
}

Tenga en cuenta que el uso de newpermite que quede claro que ese Addresses el Addresstipo, no el Addressmiembro. Esto permite que un miembro tenga el mismo nombre que su tipo. Sin esto, deberá prefijar el nombre del tipo para evitar la colisión de nombres, como CAddress. Esto fue muy intencional ya que a Anders nunca le gustó la notación húngara ni nada similar y quería que C # fuera utilizable sin ella. El hecho de que también fuera familiar era una doble bonificación.

chuckj
fuente
w00t, y Address podría derivarse de una interfaz llamada Address ... solo, no, no puede. Por lo tanto, no es un gran beneficio, poder reutilizar el mismo nombre y disminuir la legibilidad general de su código. Entonces, al mantener la I para las interfaces, pero al eliminar la C de las clases, simplemente crearon un olor desagradable sin beneficio práctico.
gbjbaanb
Me pregunto si no usan "Nuevo" en lugar de "nuevo". Alguien podría decirles que también hay letras minúsculas, que resuelven este problema.
maaartinus
Todas las palabras reservadas en lenguajes derivados de C, como C #, son minúsculas. La gramática requiere una palabra reservada aquí para evitar la ambigüedad, por lo tanto, el uso de "nuevo" en lugar de "Nuevo".
chuckj
@gbjbaanb No hay olor. Siempre está completamente claro si se está refiriendo a un tipo o una propiedad / miembro o una función. El verdadero olor es cuando nombras un espacio de nombres igual que una clase. MyClass.MyClass
Stephen
0

Curiosamente, con el advenimiento del reenvío perfecto, la no colocación newtampoco es necesaria en C ++. Entonces, podría decirse que ya no es necesario en ninguno de los idiomas mencionados.

DeadMG
fuente
44
¿Qué te enviará a ? En definitiva, std::shared_ptr<int> foo();tendrá que llamar de new intalguna manera.
MSalters
0

No estoy seguro de si los diseñadores de Java tenían esto en mente, pero cuando piensas en los objetos como registros recursivos , podrías imaginar que Foo(123)no devuelve un objeto sino una función cuyo punto de fijación es el objeto que se está creando (es decir, la función que devolvería el objeto si se da como argumento el objeto que se está construyendo). Y el propósito de newes "atar el nudo" y devolver ese punto de fijación. En otras palabras, newharía que el "objeto a ser" sea consciente de ello self.

Este tipo de enfoque podría ayudar a formalizar la herencia, por ejemplo: podría extender una función de objeto con otra función de objeto y finalmente proporcionarles algo común selfmediante el uso de new:

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

Aquí &combina dos funciones de objeto.

En este caso newpodría definirse como:

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

Para permitir 'nulo'.

Junto con la asignación basada en el montón, Java adoptó el uso de objetos nulos. No solo como un artefacto, sino como una característica. Cuando los objetos pueden tener un estado nulo, debe separar la declaración de la inicialización. De ahí la nueva palabra clave.

En C ++ una línea como

Person me;

Llamaría instantáneamente al constructor predeterminado.

Kris Van Bael
fuente
44
Um, ¿qué tiene de malo Person me = Nilo Person meser Nil por defecto y usar Person me = Person()para no Nil? Mi punto es que no parece que Nil haya sido un factor en esta decisión ...
Andres F.
Usted tiene un punto. Persona yo = Persona (); funcionaría igualmente bien.
Kris Van Bael
@AndresF. O incluso más simple con Person meser nulo e Person me()invocar al constructor predeterminado. Por lo tanto, siempre necesitará paréntesis para invocar cualquier cosa, y así deshacerse de una irregularidad de C ++.
maaartinus
-2

La razón por la cual Python no lo necesita es por su sistema de tipos. Fundamentalmente, cuando ve foo = bar()en Python, no importa si se bar()trata de un método que se invoca, una clase que se instancia o un objeto de función; en Python, no hay una diferencia fundamental entre un functor y una función (porque los métodos son objetos); e incluso podría argumentar que una clase que se instancia es un caso especial de un functor. Por lo tanto, no hay razón para crear una diferencia artificial en lo que está sucediendo (especialmente porque la administración de memoria está extremadamente oculta en Python).

En un lenguaje con un sistema de mecanografía diferente, o en el que los métodos y las clases no son objetos, existe la posibilidad de una diferencia conceptual más fuerte entre crear un objeto y llamar a una función o functor. Por lo tanto, incluso si el compilador / intérprete no necesita la newpalabra clave, es comprensible que pueda mantenerse en lenguajes que no hayan roto todos los límites que tiene Python.

Kazark
fuente
-4

En realidad, en Java hay una diferencia al usar cadenas:

String myString = "myString";

es diferente de

String myString = new String("myString");

Suponiendo que la cadena "myString"nunca se haya usado antes, la primera instrucción crea un solo objeto String en el grupo de cadenas (un área especial del montón) mientras que la segunda instrucción crea dos objetos, uno en el área de montón normal para los objetos y otro en el grupo de cadenas . Es bastante obvio que la segunda afirmación es inútil en este escenario particular, pero el lenguaje lo permite.

Esto significa que new asegura que el objeto se crea en esa área especial del montón, asignada para la creación de instancias de objeto normal, pero puede haber otras formas de instanciar objetos sin él; el anterior es un ejemplo, y queda espacio para la extensibilidad.

m3th0dman
fuente
2
Pero esta no era la pregunta. La pregunta era "¿por qué no String myString = String("myString");?" (tenga en cuenta la ausencia de la newpalabra clave)
Andres F.
@AndresF. Es obvio que ... es legal tener un método llamado String que devuelva un String en una clase String y tiene que haber una diferencia entre llamarlo y llamar al constructor. Algo así comoclass MyClass { public MyClass() {} public MyClass MyClass() {return null;} public void doSomething { MyClass myClass = MyClass();}} //which is it, constructor or method?
m3th0dman
3
Pero no es obvio. Primero, tener un método regular llamado Stringva en contra de las convenciones de estilo Java, por lo que puede asumir que cualquier método que comience con mayúsculas es un constructor. Y segundo, si Java no nos limita, entonces Scala tiene "clases de casos" donde el constructor se ve exactamente como dije. ¿Entonces, dónde está el problema?
Andres F.
2
Lo siento, no sigo tu argumento. Usted está discutiendo con eficacia la sintaxis , la semántica no, y de todos modos la pregunta era acerca newde lenguajes administrados . He demostrado que newno es necesario en absoluto (por ejemplo, Scala no lo necesita para las clases de casos) y también que no hay ambigüedad (ya que de todos modos no puede tener un método nombrado MyClassen la clase MyClass). No hay ambigüedad para String s = String("hello"); No puede ser otra cosa que un constructor. Y finalmente, no estoy de acuerdo: las convenciones de código están ahí para mejorar y aclarar la sintaxis, ¡de eso es lo que estás discutiendo!
Andres F.
1
¿Entonces ese es tu argumento? ¿"Los diseñadores de Java necesitaban la newpalabra clave porque de lo contrario la sintaxis de Java habría sido diferente"? Estoy seguro de que puedes ver la debilidad de ese argumento;) Y sí, sigues discutiendo la sintaxis, no la semántica. Además, ¿ compatibilidad hacia atrás con qué? Si está diseñando un nuevo lenguaje, como Java, ¡ya es incompatible con C y C ++!
Andres F.
-8

En C # new es importante distinguir entre los objetos creados en la pila y el montón. Con frecuencia creamos objetos en la pila para un mejor rendimiento. Vea, cuando un objeto en la pila se sale del alcance, desaparece inmediatamente cuando la pila se desenreda, como un primitivo. No es necesario que el recolector de basura realice un seguimiento, y no hay una sobrecarga adicional del administrador de memoria para asignar el espacio necesario en el montón.

pds
fuente
55
desafortunadamente estás confundiendo C # con C ++.
gbjbaanb