Perché i linguaggi gestiti dalla memoria come Java, Javascript e C # hanno mantenuto la parola chiave `new`?


139

La newparola chiave in lingue come Java, Javascript e C # crea una nuova istanza di una classe.

Questa sintassi sembra essere stata ereditata dal C ++, dove newviene utilizzata specificamente per allocare una nuova istanza di una classe sull'heap e restituire un puntatore alla nuova istanza. In C ++, questo non è l'unico modo per costruire un oggetto. Puoi anche costruire un oggetto nello stack, senza usare new- e in effetti, questo modo di costruire oggetti è molto più comune in C ++.

Quindi, proveniente da uno sfondo C ++, la newparola chiave in lingue come Java, Javascript e C # mi è sembrata naturale e ovvia per me. Poi ho iniziato a imparare Python, che non ha la newparola chiave. In Python, un'istanza viene costruita semplicemente chiamando il costruttore, come:

f = Foo()

All'inizio, questo mi è sembrato un po 'off, fino a quando mi è venuto in mente che non c'è motivo per Python di avere new, perché tutto è un oggetto, quindi non c'è bisogno di chiarire le varie sintassi dei costruttori.

Ma poi ho pensato: qual è davvero il punto newin Java? Perché dovremmo dirlo Object o = new Object();? Perché non solo Object o = Object();? In C ++ ce n'è sicuramente bisogno new, dal momento che dobbiamo distinguere tra allocazione sull'heap e allocazione sullo stack, ma in Java tutti gli oggetti sono costruiti sull'heap, quindi perché anche avere la newparola chiave? La stessa domanda potrebbe essere posta per Javascript. In C #, che conosco molto meno, penso che newpossa avere qualche scopo in termini di distinzione tra tipi di oggetto e tipi di valore, ma non ne sono sicuro.

Indipendentemente da ciò, mi sembra che molti linguaggi che sono venuti dopo il C ++ semplicemente "ereditassero" la newparola chiave, senza realmente averne bisogno. È quasi come una parola chiave vestigiale . Sembra che non ne abbiamo bisogno per nessun motivo, eppure è lì.

Domanda: ho ragione su questo? O c'è qualche motivo convincente che newdeve essere in C ++ - ispirato ai linguaggi gestiti dalla memoria come Java, Javascript e C # ma non Python?


13
La domanda è piuttosto perché qualsiasi lingua ha la newparola chiave. Ovviamente voglio creare una nuova variabile, stupido compilatore! Un buon linguaggio sarebbe a mio parere hanno una sintassi simile f = heap Foo(), f = auto Foo().

7
Un punto molto importante da considerare è la familiarità di una lingua con i nuovi programmatori. Java è stato progettato esplicitamente per apparire familiare ai programmatori C / C ++.

1
@Lundin: è davvero il risultato della tecnologia del compilatore. Un compilatore moderno non avrebbe nemmeno bisogno di suggerimenti; capirebbe dove collocare l'oggetto in base all'utilizzo effettivo di quella variabile. Cioè quando fnon sfugge alla funzione, alloca lo spazio dello stack.
Salterio

3
@Lundin: Può, e in effetti lo fa la JVM moderna. Si tratta semplicemente di provare che il GC può raccogliere oggetti fquando ritorna la funzione che racchiude.
Salterio

1
Non è costruttivo chiedersi perché un linguaggio sia progettato così com'è, in particolare se quel disegno ci chiede di digitare quattro caratteri extra senza alcun vantaggio evidente? Cosa mi sto perdendo?
MatrixFrog,

Risposte:


94

Le tue osservazioni sono corrette. Il C ++ è una bestia complicata e la newparola chiave è stata utilizzata per distinguere tra qualcosa che doveva essere necessario in deleteseguito e qualcosa che sarebbe stato automaticamente recuperato. In Java e C #, hanno lasciato cadere la deleteparola chiave perché il garbage collector se ne sarebbe occupato.

Il problema quindi è perché hanno mantenuto la newparola chiave? Senza parlare con le persone che hanno scritto la lingua è difficile rispondere. Le mie ipotesi migliori sono elencate di seguito:

  • Era semanticamente corretto. Se conoscevi C ++, sapevi che la newparola chiave crea un oggetto nell'heap. Quindi, perché cambiare il comportamento previsto?
  • Richiama l'attenzione sul fatto che stai istanziando un oggetto anziché chiamare un metodo. Con i consigli sullo stile di codice Microsoft, i nomi dei metodi iniziano con le lettere maiuscole, quindi può esserci confusione.

Ruby si trova tra Python e Java / C # nel suo uso new. Fondamentalmente si crea un'istanza di un oggetto come questo:

f = Foo.new()

Non è una parola chiave, è un metodo statico per la classe. Ciò significa che se si desidera un singleton, è possibile ignorare l'implementazione predefinita di new()restituire sempre la stessa istanza. Non è necessariamente raccomandato, ma è possibile.


5
La sintassi di Ruby sembra bella e coerente! La nuova parola chiave in C # viene anche utilizzata come vincolo di tipo generico per un tipo con un costruttore predefinito.
Steven Jeuris,

3
Sì. Non parleremo di generici però. Sia la versione generica di Java che quella di C # sono molto ottuse. Non dire C ++ è molto più facile da capire però. I generici sono davvero utili solo per le lingue tipizzate staticamente. Linguaggi tipicamente dinamici come Python, Ruby e Smalltalk non ne hanno bisogno.
Berin Loritsch,

2
Delphi ha una sintassi simile a Ruby, tranne per il fatto che i costruttori sono di solito (ma solo una convenzione) chiamata Create (`f: = TFoo.Create (param1, param2 ...) '
Gerry

In Objective-C, il allocmetodo (all'incirca la stessa cosa di Ruby new) è anche solo un metodo di classe.
mipadi,

3
Dal punto di vista della progettazione linguistica, le parole chiave sono errate per molte ragioni, principalmente non è possibile modificarle. D'altro canto, riutilizzare il concetto di metodo è più semplice, ma necessita di attenzione durante l'analisi e l'analisi. Smalltalk e i suoi parenti usano messaggi, C ++ e parole chiave d'altro canto
Gabriel Ščerbák,

86

In breve, hai ragione. La parola chiave new è superflua in lingue come Java e C #. Ecco alcuni approfondimenti di Bruce Eckel, membro del Comitato Standard C ++ negli anni '90 e in seguito pubblicato libri su Java: http://www.artima.com/weblogs/viewpost.jsp?thread=260578

doveva esserci un modo per distinguere gli oggetti heap dagli oggetti stack. Per risolvere questo problema, la nuova parola chiave è stata appropriata da Smalltalk. Per creare un oggetto stack, è sufficiente dichiararlo, come in Cat x; o, con argomenti, Cat x ("muffole") ;. Per creare un oggetto heap, usi new, come in new Cat; o nuovo gatto ("muffole") ;. Dati i vincoli, questa è una soluzione elegante e coerente.

Inserisci Java, dopo aver deciso che tutto C ++ è fatto male e troppo complesso. L'ironia qui è che Java potrebbe e ha preso la decisione di buttare via l'allocazione dello stack (ignorando in modo evidente la debacle dei primitivi, che ho affrontato altrove). E poiché tutti gli oggetti sono allocati sull'heap, non è necessario distinguere tra allocazione di stack e heap. Avrebbero potuto facilmente dire Cat x = Cat () o Cat x = Cat ("muffole"). O meglio, l'inferenza di tipo incorporata per eliminare la ripetizione (ma che - e altre caratteristiche come le chiusure - avrebbe richiesto "troppo tempo", quindi siamo bloccati con la versione mediocre di Java; l'inferenza di tipo è stata discussa ma lo farò laici non accadrà e non dovrebbe, visti i problemi nell'aggiunta di nuove funzionalità a Java).


2
Stack vs Heap ... perspicace, non ci avrei mai pensato.
WernerCD,

1
"L'inferenza del tipo è stata discussa, ma mi pronuncerò probabilità che non accadrà.", :) Bene, lo sviluppo di Java continua ancora. Interessante che questa sia l'unica ammiraglia per Java 10 però. La varparola chiave non è così vicina come autoin C ++, ma spero che migliorerà.
patrik,

15

Ci sono due motivi a cui posso pensare:

  • new distingue tra un oggetto e una primitiva
  • La newsintassi è un po 'più leggibile (IMO)

Il primo è banale. Non credo sia più difficile distinguere i due in entrambi i modi. Il secondo è un esempio del punto del PO, che newè una specie di ridondante.

Tuttavia, potrebbero esserci conflitti nello spazio dei nomi; tener conto di:

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

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

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

Potresti, senza allungare troppo l'immaginazione, finire facilmente con una chiamata infinitamente ricorsiva. Il compilatore dovrebbe imporre un errore "Nome funzione uguale all'oggetto". Questo non farebbe davvero male, ma è molto più semplice da usare new, il che afferma che Go to the object of the same name as this method and use the method that matches this signature.Java è un linguaggio molto semplice da analizzare, e sospetto che ciò aiuti in quella zona.


1
In che modo questo è diverso da qualsiasi altra ricorsione infinita?
DeadMG

Questa è un'illustrazione abbastanza buona di una cosa brutta che potrebbe accadere, anche se lo sviluppatore che lo fa avrebbe alcuni seri problemi mentali.
Tjaart,

10

Java, per esempio, ha ancora la dicotomia di C ++: Non del tutto ogni cosa è un oggetto. Java ha tipi predefiniti (ad esempio, chare int) che non devono essere allocati in modo dinamico.

Ciò non significa che newsia davvero necessario in Java: l'insieme di tipi che non devono essere allocati in modo dinamico è fisso e noto. Quando si definisce un oggetto, il compilatore potrebbe sapere (e, di fatto, sa) se è un valore semplice quello charo un oggetto che deve essere allocato in modo dinamico.

Mi asterrò (ancora una volta) dal discutere su ciò che ciò significhi sulla qualità del design di Java (o la sua mancanza, a seconda dei casi).


Più precisamente, i tipi primitivi non hanno bisogno di alcuna chiamata del costruttore - o sono scritti come letterali, provengono da un operatore o da una funzione di ritorno primitivo. In realtà crei anche stringhe (che si trovano nell'heap) senza un new, quindi questo non è proprio il motivo.
Paŭlo Ebermann,

9

In JavaScript ne hai bisogno perché il costruttore sembra una normale funzione, quindi come dovrebbe JS sapere che vuoi creare un nuovo oggetto se non fosse per la nuova parola chiave?


4

È interessante notare che VB ne ha bisogno - la distinzione tra

Dim f As Foo

che dichiara una variabile senza assegnarla, l'equivalente di Foo f;in C # e

Dim f As New Foo()

che dichiara la variabile e assegna una nuova istanza della classe, l'equivalente di var f = new Foo();in C #, è una distinzione sostanziale. In pre.NET VB, potresti persino usare Dim f As Fooo Dim f As New Foo- perché non c'era sovraccarico sui costruttori.


4
VB non ha bisogno della versione abbreviata, è solo un'abbreviazione. Puoi anche scrivere la versione più lunga con tipo e costruttore Dim f As Foo = New Foo (). Con Option Infer On, che è la cosa più vicina alla variante di C #, puoi scrivere Dim f = New Foo () e il compilatore determina il tipo di f.
MarkJ,

2
... e Dim f As Foo()dichiara una matrice di Foos. Oh gioia! :-)
Heinzi,

@MarkJ: in vb.net, la scorciatoia è equivalente. In vb6, non lo era. In vb6, Dim Foo As New Barrenderebbe effettivamente Foouna proprietà che costruirà a Barse fosse letta while (ie Nothing). Questo comportamento non si è limitato ad accadere una volta; l'impostazione Fooper Nothinge poi la lettura si creerebbe un altro nuovo Bar.
supercat

4

Mi piacciono molte risposte e vorrei solo aggiungere questo:
Rende più facile la lettura del codice
Non che questa situazione si verifichi spesso, ma considera che volevi un metodo nel nome di un verbo che condividesse lo stesso nome di un sostantivo. C # e altra lingua hanno naturalmente la parola objectriservata. Ma se non lo Foo = object()fosse, sarebbe il risultato della chiamata del metodo dell'oggetto o creerebbe un'istanza di un nuovo oggetto. Si spera che detto linguaggio senza la newparola chiave abbia protezioni contro questa situazione, ma avendo il requisito della newparola chiave prima della chiamata di un costruttore si consente l'esistenza di un metodo con lo stesso nome di un oggetto.


4
Probabilmente, doverlo sapere è un brutto segno.
DeadMG

1
@DeadMG: "Probabilmente" significa "Sosterrò che fino alla chiusura del bar" ...
Donal Fellows

3

Ci sono molte cose rudimentali in molti linguaggi di programmazione. A volte è lì per progettazione (C ++ è stato progettato per essere il più compatibile possibile con C), a volte è proprio lì. Per un esempio più eclatante, considera fino a che punto si switchpropagò l'istruzione C rotta .

Non conosco Javascript e C # abbastanza bene da dire, ma non vedo alcun motivo per newin Java se non che C ++ lo aveva.


Penso che JavaScript sia stato progettato per "sembrare il più possibile simile a Java", anche quando sta facendo qualcosa di particolarmente non simile a Java. x = new Y()in JavaScript non crea un'istanza della Yclasse, poiché JavaScript non ha classi. Ma sembra Java, quindi porta newavanti la parola chiave da Java, che ovviamente l'ha portata avanti da C ++.
MatrixFrog,

+1 per l'interruttore rotto (nella speranza di ripararlo: D).
maaartinus,

3

In C # consente tipi visibili in contesti in cui potrebbero altrimenti essere oscurati da un membro. Ciò consente di avere una proprietà con lo stesso nome di un tipo. Considera quanto segue,

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

Si noti che l'uso di newconsente di chiarire che Addressil Addresstipo non è il Addressmembro. Ciò consente a un membro di avere lo stesso nome del suo tipo. Senza questo sarebbe necessario aggiungere il prefisso al nome del tipo per evitare la collisione del nome, ad esempio CAddress. Questo è stato molto intenzionale in quanto ad Anders non è mai piaciuta la notazione ungherese o qualcosa di simile e voleva che C # fosse utilizzabile senza di essa. Il fatto che fosse anche familiare era un doppio bonus.


w00t e Address potrebbe quindi derivare da un'interfaccia chiamata Address .. solo, no, non è possibile. Quindi non è un grande vantaggio, essere in grado di riutilizzare lo stesso nome e diminuire dalla leggibilità generale del codice. Quindi, mantenendo l'I per le interfacce, ma rimuovendo la C dalle classi, hanno appena creato un odore sgradevole senza alcun vantaggio pratico.
gbjbaanb,

Mi chiedo che non usino "Nuovo" anziché "Nuovo". Qualcuno potrebbe dire loro che ci sono anche lettere minuscole, che in qualche modo risolvono questo problema.
maaartinus,

Tutte le parole riservate nei linguaggi derivati ​​C, come C #, sono in minuscolo. La grammatica richiede una parola riservata qui per evitare ambiguità, quindi l'uso di "nuovo" anziché "Nuovo".
Chuckj,

@gbjbaanb Non c'è odore. È sempre assolutamente chiaro se ti riferisci a un tipo, a una proprietà / a un membro oa una funzione. Il vero odore è quando si nomina uno spazio dei nomi uguale a una classe. MyClass.MyClass
Stephen

0

È interessante notare che, con l'avvento di un perfetto inoltro, newin C ++ non è nemmeno necessario il posizionamento . Quindi, probabilmente, non è più necessario in nessuna delle lingue menzionate.


4
Cosa inoltri a ? Alla fine, std::shared_ptr<int> foo();dovrà chiamare in new intqualche modo.
Salterio

0

Non sono sicuro che i progettisti Java avessero in mente questo, ma quando si considerano gli oggetti come record ricorsivi , si può immaginare che Foo(123)non restituisca un oggetto ma una funzione il cui punto di fissaggio è l'oggetto da creare (cioè la funzione che restituirebbe l'oggetto se viene fornito come argomento l'oggetto in costruzione). E lo scopo di new"legare il nodo" e restituire quel punto fisso. In altre parole new, renderebbe "il futuro oggetto" consapevole del suo self.

Questo tipo di approccio potrebbe aiutare, ad esempio, a formalizzare l'ereditarietà: è possibile estendere una funzione-oggetto con un'altra funzione-oggetto e infine fornirle in comune selfusando new:

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

Qui &combina due funzioni oggetto.

In questo caso newpotrebbe essere definito come:

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

-2

Per consentire "zero".

Insieme all'allocazione basata su heap, Java ha abbracciato l'uso di oggetti nulli. Non solo come artefatto, ma come funzionalità. Quando gli oggetti possono avere uno stato nullo, è necessario separare la dichiarazione dall'inizializzazione. Da qui la nuova parola chiave.

In C ++ una linea simile

Person me;

Chiamerebbe immediatamente il costruttore predefinito.


4
Uhm, cosa c'è che non va Person me = Nilo che Person meè Nil per impostazione predefinita e che utilizza Person me = Person()per non-Nil? Il mio punto è che non sembra che Nil sia stato un fattore in questa decisione ...
Andres F.

Tu hai un punto. Person me = Person (); funzionerebbe ugualmente bene.
Kris Van Bael,

@AndresF. O ancora più semplice con l' Person meessere zero e Person me()invocare il costruttore predefinito. Quindi avresti sempre bisogno di parentesi per invocare qualsiasi cosa e quindi sbarazzarti di un'irregolarità C ++.
maaartinus,

-2

Il motivo per cui Python non ne ha bisogno è a causa del suo sistema di tipi. Fondamentalmente, quando vedi foo = bar()in Python, non importa che cosa sia bar()un metodo che viene invocato, una classe che viene istanziata o un oggetto functor; in Python non esiste alcuna differenza fondamentale tra un funzione e una funzione (poiché i metodi sono oggetti); e potresti anche sostenere che una classe che viene istanziata è un caso speciale di un funzione. Pertanto, non vi è alcun motivo per creare una differenza artificiale in ciò che sta accadendo (soprattutto perché la gestione della memoria è così estremamente nascosta in Python).

In un linguaggio con un diverso sistema di battitura o in cui metodi e classi non sono oggetti, esiste la possibilità di una differenza concettuale più forte tra la creazione di un oggetto e la chiamata di una funzione o di un funzione. Pertanto, anche se il compilatore / interprete non ha bisogno della newparola chiave, è comprensibile che possa essere mantenuto in lingue che non hanno infranto tutti i confini di Python.


-4

In realtà in Java c'è una differenza quando si usano le stringhe:

String myString = "myString";

è diverso da

String myString = new String("myString");

Supponendo che la stringa "myString"non sia mai stata utilizzata prima, la prima istruzione crea un singolo oggetto String nel pool String (un'area speciale dell'heap) mentre la seconda istruzione crea due oggetti, uno nell'area normale dell'heap per gli oggetti e l'altro nel pool String . È abbastanza ovvio che la seconda affermazione è inutile in questo particolare scenario, ma è consentita dalla lingua.

Ciò significa che nuovo assicura che l'oggetto viene creato in quell'area speciale dell'heap, allocata per l'istanza normale dell'oggetto, ma potrebbero esserci altri modi per creare un'istanza senza di essa; quello sopra è un esempio e c'è spazio per l'estensibilità.


2
Ma questa non era la domanda. La domanda era "perché no String myString = String("myString");?" (nota l'assenza della newparola chiave)
Andres F.

@AndresF. Tipo di ovvio imho ... è legale avere un metodo chiamato String che restituisce una stringa in una classe String e ci deve essere una differenza tra chiamarla e chiamare il costruttore. Qualcosa di simileclass MyClass { public MyClass() {} public MyClass MyClass() {return null;} public void doSomething { MyClass myClass = MyClass();}} //which is it, constructor or method?
m3th0dman,

3
Ma non è ovvio. Innanzitutto, avere un metodo regolare chiamato Stringva contro le convenzioni di stile Java, quindi puoi semplicemente supporre che qualsiasi metodo che inizia con maiuscole sia un costruttore. E in secondo luogo, se non siamo vincolati da Java, Scala ha "case class" in cui il costruttore appare esattamente come ho detto. Quindi dov'è il problema?
Andres F.

2
Mi dispiace, non seguo il tuo argomento. Si sta effettivamente discutendo la sintassi , non la semantica, e in ogni caso la domanda era su newper lingue gestite . Ho dimostrato che newnon è affatto necessario (ad esempio Scala non ne ha bisogno per le classi di casi) e anche che non c'è ambiguità (dal momento che non si può assolutamente avere un metodo chiamato MyClassin classe MyClasscomunque). Non c'è ambiguità per String s = String("hello"); non può essere nient'altro che un costruttore. E infine, non sono d'accordo: le convenzioni sul codice sono lì per migliorare e chiarire la sintassi, che è ciò di cui stai discutendo!
Andres F.

1
Quindi questa è la tua discussione? "I progettisti Java avevano bisogno della newparola chiave perché altrimenti la sintassi di Java sarebbe stata diversa"? Sono sicuro che puoi vedere la debolezza di tale argomento;) E sì, continui a discutere di sintassi, non di semantica. Inoltre, retrocompatibilità con cosa? Se stai progettando un nuovo linguaggio, come Java, sei già incompatibile con C e C ++!
Andres F.

-8

In C # new è importante distinguere tra oggetti creati in pila e in pila. Spesso creiamo oggetti in pila per prestazioni migliori. Vedi, quando un oggetto nella pila esce dal campo di applicazione, scompare immediatamente quando la pila si dipana, proprio come una primitiva. Non è necessario che Garbage Collector tenga traccia di esso e non vi è alcun sovraccarico aggiuntivo del gestore della memoria per allocare lo spazio necessario sull'heap.


5
sfortunatamente stai confondendo C # con C ++.
gbjbaanb,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.