it-swarm.it

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

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

Questa sintassi sembra essere stata ereditata da C++, dove new viene utilizzato 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 parola chiave new in lingue come Java, Javascript e C # mi è sembrata naturale e ovvia per me. Poi ho iniziato a imparare Python, che non ha la parola chiave new. 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 avere new, perché tutto è un oggetto, quindi non c'è bisogno di disambiguare tra varie sintassi del costruttore.

Ma poi ho pensato: qual è davvero il punto di new in Java? Perché dovremmo dire Object o = new Object();? Perché non solo Object o = Object();? In C++ c'è sicuramente bisogno di new, poiché dobbiamo distinguere tra allocazione sullo heap e allocazione sullo stack, ma in Java tutti gli oggetti sono costruiti sull'heap, quindi perché anche avere la parola chiave new? La stessa domanda potrebbe essere posta per Javascript. In C #, con cui ho molta meno familiarità, penso che new possa avere qualche scopo in termini di distinzione tra tipi di oggetto e tipi di valore, ma non sono sicuro.

Indipendentemente da ciò, mi sembra che molti linguaggi che sono venuti dopo C++ semplicemente "ereditassero" la parola chiave new - 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 new deve essere in C++ - linguaggi di gestione della memoria ispirati come Java, Javascript e C # ma non Python?

141
Channel72

Le tue osservazioni sono corrette. Il C++ è una bestia complicata e la parola chiave new è stata utilizzata per distinguere tra qualcosa che aveva bisogno di delete in seguito e qualcosa che sarebbe stato automaticamente recuperato. In Java e C #, hanno eliminato la parola chiave delete perché il garbage collector si sarebbe preso cura di te per te.

Il problema quindi è perché hanno mantenuto la parola chiave new? 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 parola chiave new crea un oggetto sull'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 di 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 sovrascrivere l'implementazione predefinita di new() per restituire la stessa istanza ogni volta. Non è necessariamente raccomandato, ma è possibile.

97
Berin Loritsch

In breve, hai ragione. La parola chiave new è superflua in lingue come Java e C #. Ecco alcune intuizioni di Bruce Eckel che era un membro del Comitato Standard C++ negli anni '90 e in seguito ha 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 nettamente la debacle dei primitivi, che ho affrontato altrove). E poiché tutti gli oggetti sono allocati sul heap, non c'è bisogno di distinguere tra allocazione di stack e heap. Avrebbero potuto facilmente dire Cat x = Cat () o Cat x = Cat ("muffole"). O ancora meglio, un'inferenza di tipo incorporata per eliminare la ripetizione (ma che - - e altre funzionalità come le chiusure - avrebbero richiesto "troppo tempo", quindi siamo bloccati con la versione mediocre di Java invece; l'inferenza del tipo è stata discussa, ma porrò delle probabilità che non lo farà e non dovrebbe, visti i problemi nell'aggiunta di nuove funzionalità a Java).

87

Ci sono due motivi a cui posso pensare:

  • new distingue tra un oggetto e una primitiva
  • La sintassi new è 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 è che new è una specie di ridondante.

Tuttavia, potrebbero esserci conflitti nello spazio dei nomi; prendere in considerazione:

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 usare new, che afferma, 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.

15
Michael K

Java, per esempio, ha ancora la dicotomia del C++: non abbastanza tutto è un oggetto. Java ha tipi predefiniti (ad esempio, char e int) che non devono essere allocati dinamicamente.

Ciò non significa che new sia veramente necessario in Java però - l'insieme di tipi che non devono essere allocati in modo dinamico è fisso e noto. un oggetto, il compilatore potrebbe sapere (e, in effetti, lo sa) se è un valore semplice che char o 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 sulla sua mancanza, a seconda dei casi).

10
Jerry Coffin

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?

Il richiamo di una funzione JavaScript con l'operatore new comporta un comportamento diverso rispetto al richiamo di una funzione senza l'operatore new.

Per esempio:

Date()       //Returns a string

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

foo()        //Returns `undefined`

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

Mi piacciono molte risposte e vorrei solo aggiungere questo:
Semplifica 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 altre lingue hanno naturalmente la parola object riservata. Ma se non lo fosse, allora Foo = object() sarebbe il risultato della chiamata del metodo dell'oggetto o sarebbe un'istanza di un nuovo oggetto. Si spera che detto linguaggio senza la parola chiave new abbia protezioni contro questa situazione, ma avendo il requisito della parola chiave new prima della chiamata di un costruttore si consente l'esistenza di un metodo con lo stesso nome di un oggetto.

4
Kavet Kerek

È interessante notare che VB fa 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 Foo O Dim f As New Foo - perché non c'era sovraccarico sui costruttori.

4
Richard Gadsden

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 è propagata l'istruzione C switch rotta.

Non conosco Javascript e C # abbastanza bene da dire, ma non vedo alcun motivo per new in Java tranne che C++ aveva.

3
David Thornley

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 new consente di chiarire che Address è il tipo Address non il membro Address. 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, come 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 vantaggio.

3
chuckj

Non sono sicuro se Java avevano in mente questo, ma quando pensi agli oggetti come record ricorsivi, allora puoi immaginare che Foo(123) non restituisce un oggetto ma una funzione il cui fixpoint è l'oggetto da creare (cioè funzione che restituirebbe l'oggetto se fosse dato come argomento l'oggetto da costruire). E lo scopo di new è "legare il nodo "e restituisce quel punto fisso. In altre parole new renderebbe" l'oggetto da essere "consapevole del suo self.

Questo tipo di approccio potrebbe aiutare ad esempio a formalizzare l'ereditarietà: potresti estendere una funzione-oggetto con un'altra funzione-oggetto e infine fornire loro self comune usando new:

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

Qui & combina due funzioni oggetto.

In questo caso new potrebbe essere definito come:

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

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

0
DeadMG