Dlaczego języki zarządzane przez pamięć, takie jak Java, JavaScript i C #, zachowały słowo kluczowe `new`?

138

Słowo newkluczowe w językach takich jak Java, JavaScript i C # tworzy nowe wystąpienie klasy.

Wydaje się, że ta składnia została odziedziczona z C ++, gdzie newjest używana specjalnie do przydzielenia nowej instancji klasy na stercie i zwrócenia wskaźnika do nowej instancji. W C ++ nie jest to jedyny sposób na zbudowanie obiektu. Możesz także zbudować obiekt na stosie, bez użycia new- i w rzeczywistości ten sposób konstruowania obiektów jest znacznie bardziej powszechny w C ++.

Tak więc, pochodzące z języka C ++, newsłowo kluczowe w językach takich jak Java, JavaScript i C # wydawało mi się naturalne i oczywiste. Potem zacząłem uczyć się języka Python, który nie ma newsłowa kluczowego. W Pythonie instancja jest budowana po prostu przez wywołanie konstruktora, na przykład:

f = Foo()

Na początku wydawało mi się to trochę dziwne, dopóki nie przyszło mi do głowy, że Python nie ma powodu new, ponieważ wszystko jest przedmiotem, więc nie trzeba rozróżniać różnych składni konstruktorów.

Ale potem pomyślałem - jaki jest sens newJava? Dlaczego powinniśmy powiedzieć Object o = new Object();? Dlaczego nie tylko Object o = Object();? W C ++ zdecydowanie jest taka potrzeba new, ponieważ musimy rozróżnić między alokacją na stosie a alokacją na stosie, ale w Javie wszystkie obiekty są konstruowane na stercie, więc po co w ogóle newsłowo kluczowe? To samo pytanie można zadać dla Javascript. W języku C #, który jestem znacznie mniej obeznany, myślę, że newmoże mieć jakiś cel pod względem rozróżniania typów obiektów i typów wartości, ale nie jestem pewien.

Niezależnie od tego wydaje mi się, że wiele języków, które pojawiły się po C ++, po prostu „odziedziczyło” newsłowo kluczowe - tak naprawdę go nie potrzebując. To prawie jak szczątkowe słowo kluczowe . Wydaje się, że nie potrzebujemy go z żadnego powodu, a jednak już istnieje.

Pytanie: Czy mam rację? A może jest jakiś ważny powód, dla którego newnależy używać języków zarządzanych przez pamięć inspirowanych C ++, takich jak Java, JavaScript i C #, ale nie Python?

Channel72
źródło
13
Pytanie brzmi raczej, dlaczego każdy język ma newsłowo kluczowe. Oczywiście chcę stworzyć nową zmienną, głupi kompilator! Dobrym językiem moim zdaniem mają składnię jak f = heap Foo(), f = auto Foo().
7
Bardzo ważnym punktem do rozważenia jest znajomość języka dla nowych programistów. Java została zaprojektowana specjalnie dla programistów C / C ++.
1
@Lundin: To naprawdę wynik technologii kompilatora. Nowoczesny kompilator nie potrzebowałby nawet podpowiedzi; ustaliłby, gdzie umieścić obiekt na podstawie faktycznego użycia tej zmiennej. To fznaczy, kiedy nie ucieka z funkcji, przydziel miejsce na stosie.
MSalters
3
@Lundin: Może i faktycznie robi to współczesna maszyna JVM. Chodzi tylko o udowodnienie, że GC może zbierać obiekty fpo powrocie funkcji zamykającej.
MSalters
1
Pytanie, dlaczego język jest zaprojektowany w taki sposób, w jaki jest, nie jest konstruktywne, szczególnie jeśli ten projekt wymaga od nas wpisania czterech dodatkowych znaków bez wyraźnych korzyści? czego mi brakuje?
MatrixFrog,

Odpowiedzi:

93

Twoje obserwacje są poprawne. C ++ jest skomplikowaną bestią, a newsłowo kluczowe zostało użyte do odróżnienia czegoś, co było potrzebne deletepóźniej, od czegoś, co zostanie automatycznie odzyskane. W Javie i C # upuściły deletesłowo kluczowe, ponieważ śmieciarz zaopiekuje się tobą.

Problem polega na tym, dlaczego zachowali newsłowo kluczowe? Bez rozmowy z ludźmi, którzy napisali język, trudno jest odpowiedzieć. Moje najlepsze domysły są wymienione poniżej:

  • To było semantycznie poprawne. Jeśli znasz C ++, wiesz, że newsłowo kluczowe tworzy obiekt na stercie. Po co więc zmieniać oczekiwane zachowanie?
  • Zwraca uwagę na fakt, że tworzysz obiekt, a nie metodę. W przypadku zaleceń dotyczących stylu kodu Microsoft nazwy metod zaczynają się od wielkich liter, co może powodować zamieszanie.

Ruby jest gdzieś pomiędzy Pythonem a Java / C # w użyciu new. Zasadniczo tworzysz taki obiekt:

f = Foo.new()

To nie jest słowo kluczowe, to statyczna metoda dla klasy. Oznacza to, że jeśli chcesz singletonu, możesz zastąpić domyślną implementację new()zwracania tej samej instancji za każdym razem. Niekoniecznie jest to zalecane, ale jest możliwe.

Berin Loritsch
źródło
5
Składnia Ruby wygląda ładnie i spójnie! Nowe słowo kluczowe w C # jest również używany jako rodzajowy typu ograniczeń dla typu z domyślnego konstruktora.
Steven Jeuris
3
Tak. Nie będziemy jednak rozmawiać o rodzajach. Zarówno generyczne wersje Java i C # są bardzo tępe. Nie można jednak powiedzieć, że C ++ jest znacznie łatwiejsze do zrozumienia. Generyczne naprawdę są przydatne tylko dla języków o typie statycznym. Dynamicznie pisane języki, takie jak Python, Ruby i Smalltalk, po prostu ich nie potrzebują.
Berin Loritsch
2
Delphi ma podobną składnię jak Ruby, z tym wyjątkiem, że konstruktory są zwykle (ale tylko konwencją bhy) nazywane Create (`f: = TFoo.Create (param1, param2 ...) '
Gerry
W Objective-C allocmetoda (mniej więcej taka sama jak Ruby new) jest również metodą klasową.
mipadi
3
Z perspektywy projektowania języka słowa kluczowe są złe z wielu powodów, głównie nie można ich zmienić. Z drugiej strony ponowne użycie koncepcji metody jest łatwiejsze, ale wymaga pewnej uwagi podczas analizy i analizy. Smalltalk i jego krewni używają wiadomości, C ++ i itskin, z drugiej strony słowa kluczowe
Gabriel Ščerbák
86

Krótko mówiąc, masz rację. Słowo kluczowe new jest zbędne w językach takich jak Java i C #. Oto niektóre spostrzeżenia Bruce'a Eckela, który był członkiem C ++ Standard Comitee w latach 90., a później opublikował książki o Javie: http://www.artima.com/weblogs/viewpost.jsp?thread=260578

musiał istnieć sposób na odróżnienie obiektów stosu od obiektów stosu. Aby rozwiązać ten problem, nowe słowo kluczowe zostało przejęte z Smalltalk. Aby utworzyć obiekt stosu, wystarczy go zadeklarować, jak w Cat x; lub, z argumentami, Cat x („mitenki”); Aby utworzyć obiekt sterty, użyj nowego, jak w nowym Cat; lub nowy kot („mitenki”) ;. Biorąc pod uwagę ograniczenia, jest to eleganckie i spójne rozwiązanie.

Wejdź do Javy po stwierdzeniu, że wszystko C ++ jest źle zrobione i nadmiernie złożone. Ironią tutaj jest to, że Java mogła i podjęła decyzję o odrzuceniu przydziału stosów (wyraźnie ignorując klęskę prymitywów, o której mówiłem gdzie indziej). A ponieważ wszystkie obiekty są przydzielane na stosie, nie ma potrzeby rozróżniania między stosem a stosem. Mogliby z łatwością powiedzieć Cat x = Cat () lub Cat x = Cat („mitenki”). Lub jeszcze lepiej, wprowadzono wnioskowanie o typie, aby wyeliminować powtarzanie (ale to - i inne funkcje, takie jak zamknięcia - zajęłoby „zbyt długo”, więc zamiast tego utknęliśmy w przeciętnej wersji Javy; wnioskowanie o tym zostało omówione, ale omówię to prawdopodobieństwo, że tak się nie stanie, i nie powinno, biorąc pod uwagę problemy z dodawaniem nowych funkcji do Java).

Nemanja Trifunovic
źródło
2
Stack vs Heap ... wnikliwe, nigdy bym o tym nie pomyślał.
WernerCD
1
„wnioskowanie o typach zostało już omówione, ale będę się starał, że tak się nie stanie.”, :) Cóż, rozwój Java nadal trwa. Ciekawe, że jest to jedyny sztandarowy produkt dla Java 10. Słowo varkluczowe nie jest tak dobre jak autow C ++, ale mam nadzieję, że się poprawi.
patrik
15

Są dwa powody, dla których mogę wymyślić:

  • new rozróżnia obiekt od prymitywu
  • newSkładnia jest nieco bardziej czytelne (IMO)

Pierwszy jest trywialny. Nie wydaje mi się, żeby trudniej było odróżnić te dwie strony. Drugi jest przykładem punktu PO, który newjest w pewnym sensie zbędny.

Mogą jednak wystąpić konflikty przestrzeni nazw; rozważać:

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

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

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

Możesz bez nadmiernego rozciągania wyobraźni łatwo skończyć z nieskończenie rekurencyjnym wezwaniem. Kompilator musiałby wymusić błąd „Nazwa funkcji taka sama jak obiekt”. To naprawdę nie zaszkodzi, ale jest o wiele prostsze w użyciu new, co stanowi, że Go to the object of the same name as this method and use the method that matches this signature.Java jest bardzo prostym językiem do analizy i podejrzewam, że pomaga w tym obszarze.

Michael K.
źródło
1
Czym różni się to od jakiejkolwiek innej nieskończonej rekurencji?
DeadMG
To dość dobra ilustracja złej rzeczy, która może się zdarzyć, chociaż programista, który to robi, miałby poważne problemy psychiczne.
Tjaart
10

Java, na przykład, wciąż ma dychotomię C ++: nie całkiem wszystko jest przedmiotem. Java ma wbudowane typy (np. charI int), które nie muszą być przydzielane dynamicznie.

Nie oznacza to jednak, że newjest to naprawdę konieczne w Javie - zestaw typów, które nie muszą być przydzielane dynamicznie, jest stały i znany. Podczas definiowania obiektu kompilator może wiedzieć (a tak naprawdę wie), czy jest to prosta wartość tego charobiektu, czy obiekt, który należy przypisać dynamicznie.

Powstrzymam się (jeszcze raz) od wyrażania opinii na temat tego, co to znaczy na temat jakości projektu Java (lub jego braku, w zależności od przypadku).

Jerry Coffin
źródło
Co więcej, typy prymitywne wcale nie potrzebują żadnego wywołania konstruktora - albo są pisane dosłownie, pochodzą od operatora, albo z jakiejś funkcji zwracającej prymityw. W rzeczywistości tworzysz również łańcuchy (które są na stercie) bez a new, więc to nie jest tak naprawdę powód.
Paŭlo Ebermann
9

W JavaScript potrzebujesz go, ponieważ konstruktor wygląda jak normalna funkcja, więc skąd JS powinien wiedzieć, że chcesz utworzyć nowy obiekt, gdyby nie nowe słowo kluczowe?

użytkownik 281377
źródło
4

Co ciekawe, VB tego potrzebuje - rozróżnienia między

Dim f As Foo

który deklaruje zmienną bez przypisywania jej, odpowiednik Foo f;w C # i

Dim f As New Foo()

która deklaruje zmienną i przypisuje nową instancję klasy, odpowiednik var f = new Foo();w C #, jest istotnym rozróżnieniem. W wersjach pre-.NET VB można nawet użyć Dim f As Foolub Dim f As New Foo- ponieważ nie wystąpiło przeciążenie konstruktorów.

Richard Gadsden
źródło
4
VB nie potrzebuje wersji skróconej, to tylko skrót. Możesz także napisać dłuższą wersję z typem i konstruktorem Dim f As Foo = New Foo (). Dzięki opcji Infer On, która jest najbliższa wariacji C #, możesz napisać Dim f = New Foo (), a kompilator określi typ f.
MarkJ
2
... i Dim f As Foo()deklaruje tablicę z Foos. O radości! :-)
Heinzi
@MarkJ: W vb.net skrót jest równoważny. W wersji vb6 tak nie było. W vb6 Dim Foo As New Barskutecznie tworzy Foowłaściwość, która konstruowałaby a, Bargdyby była czytana podczas (tj Nothing.). To zachowanie nie ograniczało się do jednorazowego zdarzenia; ustawienie Foona Nothingczym czytanie byłoby utworzyć kolejne Bar.
supercat
4

Lubię wiele odpowiedzi i chciałbym po prostu dodać:
Ułatwia to czytanie kodu.
Nie żeby taka sytuacja zdarzała się często, ale pomyśl, że chciałeś metody w nazwie czasownika o tej samej nazwie co rzeczownik. C # i inny język mają oczywiście słowo objectzastrzeżone. Ale gdyby tak nie było, byłby Foo = object()to wynik wywołania metody object lub instancja nowego obiektu. Mam nadzieję, że język bez newsłowa kluczowego ma zabezpieczenia przed tą sytuacją, ale wymagając tego newsłowa kluczowego przed wywołaniem konstruktora, zezwalasz na istnienie metody o tej samej nazwie co obiekt.

Kavet Kerek
źródło
4
Prawdopodobnie znajomość tego jest złym znakiem.
DeadMG
1
@DeadMG: „Prawdopodobnie” oznacza „Będę się kłócił, że dopóki bar się nie zamknie”…
Donal Fellows
3

W wielu językach programowania jest wiele szczątkowych rzeczy. Czasami jest tam z założenia (C ++ został zaprojektowany tak, aby był jak najbardziej kompatybilny z C), czasem po prostu tam jest. Aby uzyskać bardziej skandaliczny przykład, zastanów się, jak daleko switchrozprzestrzeniła się uszkodzona instrukcja C.

Nie znam JavaScriptu i języka C # wystarczająco dobrze, aby to powiedzieć, ale nie widzę powodu neww Javie, z wyjątkiem tego, że C ++ miał go.

David Thornley
źródło
Myślę, że JavaScript został zaprojektowany tak, aby „wyglądał jak Java” w jak największym stopniu, nawet jeśli robi coś szczególnie nie podobnego do Java. x = new Y()w JavaScript nie wystąpienia z Yklasy, ponieważ JavaScript nie mają zajęcia. Ale wygląda jak Java, więc przenosi newsłowo kluczowe do przodu z Java, które oczywiście przeniosło je z C ++.
MatrixFrog,
+1 za zepsuty przełącznik (w nadziei, że go naprawisz: D).
maaartinus
3

W języku C # pozwala na typy widoczne w kontekstach, w których mogłyby zostać zasłonięte przez członka. Pozwala mieć właściwość o takiej samej nazwie jak jej typ. Rozważ następujące,

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

Zauważ, że użycie newpozwala na jasne, że nie Addressjest to Addresstyp Addressczłonka. Dzięki temu członek może mieć taką samą nazwę jak jego typ. Bez tego trzeba by poprzedzić nazwę typu, aby uniknąć kolizji nazwy, np CAddress. Było to bardzo celowe, ponieważ Anders nigdy nie lubił węgierskiej notacji ani niczego podobnego i chciał, aby C # był bez niej użyteczny. Fakt, że był również znany, był podwójną premią.

chuckj
źródło
w00t, a adres może pochodzić z interfejsu o nazwie tylko adres… nie, nie może. Nie ma więc dużej korzyści, ponieważ można ponownie użyć tej samej nazwy i zmniejszyć ogólną czytelność kodu. Zachowując I dla interfejsów, ale usuwając C z zajęć, stworzyli nieprzyjemny zapach bez praktycznych korzyści.
gbjbaanb
Zastanawiam się, że nie używają słowa „Nowy” zamiast „nowy”. Ktoś może im powiedzieć, że są też małe litery, które rozwiązują ten problem.
maaartinus
Wszystkie słowa zastrzeżone w językach pochodnych C, takich jak C #, są pisane małymi literami. Gramatyka wymaga tutaj zarezerwowanego słowa, aby uniknąć dwuznaczności, dlatego też użycie „nowego” zamiast „nowego”.
chuckj
@gbjbaanb Nie ma zapachu. Zawsze jest całkowicie jasne, czy masz na myśli typ, właściwość / element członkowski, czy funkcję. Prawdziwy zapach ma miejsce, gdy nazwiesz przestrzeń nazw taką samą jak klasa. MyClass.MyClass
Stephen
0

Co ciekawe, wraz z nadejściem idealnego przekierowania neww C ++ nie jest wcale wymagane umieszczanie . Prawdopodobnie nie jest już potrzebny w żadnym z wymienionych języków.

DeadMG
źródło
4
Co masz przekazania do ? Ostatecznie std::shared_ptr<int> foo();będzie musiał new intjakoś zadzwonić .
MSalters
0

Nie jestem pewien, czy projektanci Java mieli to na uwadze, ale kiedy myślisz o obiektach jako rekursywnych rekordach , możesz sobie wyobrazić, że Foo(123)nie zwraca obiektu, ale funkcję, której punktem stałym jest tworzony obiekt (tj. Funkcja, która zwróci obiekt, jeśli jest podany jako argument budowanego obiektu). A celem newjest „zawiązanie węzła” i zwrócenie tego punktu stałego. Innymi słowy new, uświadomiłby to „obiektowi, który ma być” self.

Takie podejście może pomóc w sformalizowaniu dziedziczenia, na przykład: możesz rozszerzyć funkcję obiektu o inną funkcję obiektu i w końcu zapewnić im wspólną funkcję, selfużywając new:

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

Tutaj &łączy dwie funkcje obiektowe.

W takim przypadku newmożna zdefiniować jako:

def new(objectFunction) {
    actualObject = objectFunction(actualObject)
    return actualObject
}
Aivar
źródło
-2

Aby zezwolić na „zero”.

Wraz z alokacją opartą na stercie, Java objęła stosowanie zerowych obiektów. Nie tylko jako artefakt, ale jako funkcja. Gdy obiekty mogą mieć stan zerowy, musisz oddzielić deklarację od inicjalizacji. Stąd nowe słowo kluczowe.

W C ++ wiersz podobny

Person me;

Natychmiast wywołałby domyślnego konstruktora.

Kris Van Bael
źródło
4
Hm, co w tym złego Person me = Nillub mające Person mebyć Nil domyślnie i korzystania Person me = Person()przez nie-Nil? Chodzi mi o to, że nie wydaje się, aby Zero miał wpływ na tę decyzję ...
Andres F.
Masz rację. Person me = Person (); działałoby równie dobrze.
Kris Van Bael
@AndresF. Lub jeszcze prościej, Person meponieważ nie ma nic i Person me()wywołuje domyślnego konstruktora. Dlatego zawsze potrzebujesz nawiasów, aby wywołać cokolwiek, a tym samym pozbyć się jednej nieprawidłowości w C ++.
maaartinus
-2

Powodem, dla którego Python go nie potrzebuje, jest system typów. Zasadniczo, kiedy widzisz foo = bar()w Pythonie, nie ma znaczenia, czy bar()jest wywoływana metoda, instancja klasy, czy obiekt funktowy; w Pythonie nie ma zasadniczej różnicy między funktorem a funkcją (ponieważ metody są obiektami); i można nawet argumentować, że instancja klasy jest szczególnym przypadkiem funktora. Dlatego nie ma powodu, aby tworzyć sztuczną różnicę w tym, co się dzieje (zwłaszcza, że ​​zarządzanie pamięcią jest tak bardzo ukryte w Pythonie).

W języku z innym systemem pisania lub w którym metody i klasy nie są obiektami, istnieje możliwość silniejszej różnicy pojęciowej między tworzeniem obiektu a wywoływaniem funkcji lub funktora. Tak więc, nawet jeśli kompilator / interpreter nie potrzebuje newsłowa kluczowego, zrozumiałe jest, że może on być utrzymywany w językach, które nie przekroczyły wszystkich granic, które ma Python.

Kazark
źródło
-4

W rzeczywistości w Javie istnieje różnica w korzystaniu z ciągów:

String myString = "myString";

jest różny od

String myString = new String("myString");

Załóżmy, że ciąg "myString"nigdy nie był używany, pierwsza instrukcja tworzy pojedynczy obiekt String w puli String (specjalny obszar sterty), podczas gdy druga instrukcja tworzy dwa obiekty, jeden w normalnym obszarze sterty dla obiektów, a drugi w puli String . Jest całkiem oczywiste, że druga instrukcja jest bezużyteczna w tym konkretnym scenariuszu, ale jest dozwolona przez język.

Oznacza to, że nowy zapewnia, że ​​obiekt jest tworzony w tym specjalnym obszarze sterty, przeznaczonym do normalnego tworzenia instancji obiektu, ale mogą istnieć inne sposoby tworzenia instancji bez niego; powyższy jest przykładem i pozostało miejsce na rozszerzenie.

m3th0dman
źródło
2
Ale to nie było pytanie. Pytanie brzmiało „dlaczego nie String myString = String("myString");?” (zauważ brak newsłowa kluczowego)
Andres F.
@AndresF. To oczywiste, że imho ... legalna jest metoda o nazwie String, która zwraca String w klasie String, i musi istnieć różnica między wywołaniem go a wywołaniem konstruktora. Coś w styluclass MyClass { public MyClass() {} public MyClass MyClass() {return null;} public void doSomething { MyClass myClass = MyClass();}} //which is it, constructor or method?
m3th0dman
3
Ale to nie jest oczywiste. Po pierwsze, posiadanie zwykłej metody o nazwie Stringjest sprzeczne z konwencjami stylu Java, więc możesz założyć, że dowolna metoda zaczynająca się od wielkich liter jest konstruktorem. Po drugie, jeśli nie jesteśmy ograniczeni przez Javę, wówczas Scala ma „klasy przypadków”, w których konstruktor wygląda dokładnie tak, jak powiedziałem. Więc gdzie jest problem?
Andres F.,
2
Przepraszam, nie podążam za twoją argumentacją. Jesteś skutecznie argumentując składni , a nie semantyka, a mimo to pytanie było o newdla zarządzanych języków . Pokazałem, że newwcale nie jest potrzebny (np. Scala nie potrzebuje go do klas obserwacji), a także, że nie ma dwuznaczności (ponieważ absolutnie nie można mieć metody nazwanej MyClassw klasie MyClass). Nie ma żadnych dwuznaczności dla String s = String("hello"); nie może być niczym innym jak konstruktorem. Wreszcie nie zgadzam się: konwencje kodu mają na celu ulepszenie i wyjaśnienie składni, o co się kłócisz!
Andres F.,
1
Więc to twój argument? „Projektanci Java potrzebowali newsłowa kluczowego, ponieważ w przeciwnym razie składnia Javy byłaby inna”? Jestem pewien, że widzisz słabość tego argumentu;) I tak, wciąż dyskutujesz o składni, a nie semantyce. Również kompatybilność wsteczna z czym? Jeśli projektujesz nowy język, taki jak Java, jesteś już niezgodny z C i C ++!
Andres F.,
-8

W języku C # new jest ważne, aby odróżnić obiekty utworzone na stosie od stosu. Często tworzymy obiekty na stosie dla lepszej wydajności. Widzisz, gdy obiekt na stosie wykracza poza zakres, znika natychmiast, gdy stos się rozwija, zupełnie jak prymityw. Moduł zbierający śmieci nie musi go śledzić i nie ma dodatkowych kosztów związanych z zarządzaniem pamięcią w celu przydzielenia niezbędnej przestrzeni na stercie.

pds
źródło
5
niestety mylisz C # z C ++.
gbjbaanb