Чому мови, керовані пам'яттю, такі як Java, Javascript та C # зберегли ключове слово `new`


138

newКлючові слова в мовах , як Java, JavaScript і C # створюють новий екземпляр класу.

Цей синтаксис, здається, успадкований від C ++, де newвикористовується спеціально для виділення нового екземпляра класу на купі та повернення вказівника на новий екземпляр. У C ++ це не єдиний спосіб побудови об'єкта. Ви також можете сконструювати об’єкт на стеці, не використовуючи new- і насправді такий спосіб побудови об’єктів набагато частіше зустрічається в C ++.

Тож, виходячи з фону C ++, newключове слово на таких мовах, як Java, Javascript та C #, здавалося мені природним та очевидним. Потім я почав вивчати Python, який не має newключового слова. У Python екземпляр будується просто викликом конструктора, наприклад:

f = Foo()

Спочатку це здавалося мені трохи відключеним, поки мені не прийшло в голову, що в Python немає причин new, тому що все є об'єктом, тому немає необхідності розмежовувати різні синтаксиси конструктора.

Але тоді я подумав - у чому насправді сенс newу Java? Чому ми повинні говорити Object o = new Object();? Чому б не просто Object o = Object();? У C ++, безумовно, є потреба new, оскільки нам потрібно розрізняти розподіл на купі та розподілення на стеці, але в Java всі об’єкти побудовані на купі, то чому ж навіть у них є newключове слово? Це ж питання можна задати і для Javascript. У C #, з яким я набагато менше знайомий, я думаю, що newможе мати певну мету з точки зору розмежування типів об’єктів та типів значень, але я не впевнений.

Незважаючи на це, мені здається, що багато мов, які з'явилися після C ++, просто "успадкували" newключове слово - без того, щоб справді цього потребувати. Це майже як ключове ключове слово . Нам, здається, він не потрібен з будь-якої причини, і все ж він є.

Запитання: Чи правильно я ставлюся до цього? Або є якась переконлива причина, яка newповинна бути в C ++ - натхненних мовах, керованих пам'яттю, таких як Java, Javascript та C #, але не Python?


13
Питання, скоріше, чому в будь-якій мові є newключове слово. Звичайно, я хочу створити нову змінну, тупу компілятор! Хороший мова буде по - моєму є синтаксис , як f = heap Foo(), f = auto Foo().

7
Дуже важливим моментом слід вважати те, наскільки звична мова виглядає для нових програмістів. Java була явно розроблена так, щоб виглядати знайомим програмістам на C / C ++.

1
@Lundin: Це дійсно результат технології компілятора. Сучасний компілятор навіть підказки не потребує; то з'ясувалося б, куди поставити об’єкт, виходячи з фактичного використання цієї змінної. Тобто, коли fне виходить з функції, виділіть простір стеку.
MSalters

3
@Lundin: Це може, і насправді сучасні JVM. Це лише питання доведення, що GC може збирати об’єкт, fколи повертається функція, що додає.
MSalters

1
Не конструктивно запитати, чому мова створена таким, яким вона є, особливо якщо ця конструкція вимагає від нас набрати чотири зайвих символи без очевидної користі? Що я пропускаю?
MatrixFrog

Відповіді:


93

Ваші спостереження правильні. C ++ - це складний звір, і newключове слово було використано для розрізнення того, що потрібно deleteпізніше, і того, що буде автоматично відшкодовано. У Java та C # вони скинули deleteключове слово, оскільки збирач сміття піклується про нього за вас.

Тоді проблема полягає в тому, чому вони зберегли newключове слово? Без розмови з людьми, які написали мову, важко відповісти. Мої найкращі здогадки перелічені нижче:

  • Це було семантично правильно. Якщо ви були знайомі з C ++, ви знали, що newключове слово створює об’єкт у купі. Отже, навіщо змінювати очікувану поведінку?
  • Він звертає увагу на те, що ви створюєте інстанцію об'єкта, а не викликаєте метод. У рекомендаціях стилю коду Microsoft назви методів починаються з великої літери, щоб не було плутанини.

Ruby знаходиться десь між Python та Java / C # в його використанні new. В основному ти створюєш такий об'єкт:

f = Foo.new()

Це не ключове слово, це статичний метод для класу. Це означає, що якщо ви хочете сингл, ви можете переосмислити реалізацію за замовчуванням, new()щоб кожен раз повертати той самий екземпляр. Це не обов'язково рекомендується, але можливо.


5
Синтаксис Ruby виглядає приємно і послідовно! Нове ключове слово в C # також використовуються в якості універсального типу обмеження для типу з конструктором за замовчуванням.
Стівен Євріс

3
Так. Ми не будемо говорити про дженерики. І Java, і версія # дженериків C # дуже тупі. Не кажучи, що C ++ набагато простіше зрозуміти. Дженріки справді корисні лише для мов, які набираються статично. Динамічно набрані мови, такі як Python, Ruby та Smalltalk, вони їм просто не потрібні.
Берін Лорич

2
У Delphi є синтаксис, схожий на Ruby, за винятком того, що конструктори зазвичай (але тільки bhy умовно) називаються Create (`f: = TFoo.Create (param1, param2 ...) '
Геррі

У Objective-C allocметод (приблизно те саме, що і у Ruby new) - це також просто метод класу.
mipadi

3
З точки зору дизайну мови, ключові слова є поганими з багатьох причин, головним чином ви не можете їх змінити. З іншого боку, повторно використовувати поняття методу простіше, але потребує певної уваги під час розбору та аналізу. Smalltalk та його споріднені користуються повідомленнями, C ++ та itkin за іншими ключовими словами
Габріель Шчербак

86

Словом, ви праві. Ключове слово new зайве в таких мовах, як Java та C #. Ось деякі відомості Брюса Еккеля, який був членом стандартного комітету C ++ у 1990-х, а пізніше видав книги на Java: http://www.artima.com/weblogs/viewpost.jsp?thread=260578

там повинен був бути якийсь спосіб відрізнити купи об'єктів від об'єктів стеки. Щоб вирішити цю проблему, нове ключове слово було призначене від Smalltalk. Щоб створити об’єкт стека, ви просто оголосите його, як у Cat x; або, з аргументами, Cat x ("рукавиці"); Щоб створити купу об'єктів, ви використовуєте новий, як у нового Cat; або новий Кіт ("рукавиці"); Враховуючи обмеження, це елегантне і послідовне рішення.

Введіть Java, вирішивши, що все C ++ погано зроблено і надмірно складне. Іронія полягає в тому, що Java могла і все-таки прийняти рішення відкинути розподіл стеків (наголосно ігноруючи розбір примітивів, про який я звертався в іншому місці). Оскільки всі об’єкти розміщені на купі, немає необхідності розмежовувати розподіл між стеком та купою. Вони легко могли сказати Cat x = Cat () або Cat x = Cat ("рукавиці"). Або ще краще, включений висновок типу для усунення повторення (але це - та інші функції, такі як закриття - зайняло б "занадто довго", тому ми затрималися з посередньою версією Java, а висновок типу обговорювався, але я буду шанси на це не відбудуться. І не слід, враховуючи проблеми в додаванні нових функцій до Java).


2
Стек проти Хіпа ... проникливий, ніколи б не подумав про це.
WernerCD

1
"обговорення типу було обговорено, але я буду шанси, що цього не відбудеться.", :) Ну, розвиток Java все ще йде вперед. Цікаво, що це єдиний флагман для Java 10. varКлючове слово ніде близько , як добре , як autoв C ++, але я сподіваюся , що це буде поліпшуватися.
патрік

15

Я думаю про дві причини:

  • new розрізняє предмет і примітив
  • newСинтаксис є трохи більш зручним для читання (ІМО)

Перший - тривіальний. Я не думаю, що це важче сказати обом і в іншому випадку. Другий - приклад точки ОП, яка newє якоюсь зайвою.

Однак можуть виникнути конфлікти у просторі імен; врахуйте:

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

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

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

Ви могли, не надто розтягуючи уяву, легко закінчитись безмежно рекурсивним дзвінком. Компілятору доведеться застосувати помилку "Ім'я функції, яке є об'єктом". Це дійсно не зашкодить, але набагато простіше у використанні new, що стверджує, що Go to the object of the same name as this method and use the method that matches this signature.Java - це дуже проста мова для розбору, і я підозрюю, що це допомагає в цій області.


1
Чим це відрізняється від будь-якої іншої нескінченної рекурсії?
DeadMG

Це досить гарна ілюстрація поганого, що може статися, хоча у розробника, який це робить, виникли б серйозні психічні проблеми.
Тярт

10

Для Java, як і раніше, є дихотомія С ++: не зовсім все є об'єктом. У Java є вбудовані типи (наприклад, charта int), які не потрібно розподіляти динамічно.

Це не означає, що newце дійсно потрібно в Java - набір типів, які не потрібно динамічно розподіляти, є фіксованим і відомим. Коли ви визначаєте об'єкт, компілятор може знати (і насправді він знає), чи це просте значення, charчи об'єкт, який повинен бути розподілений динамічно.

Я утримаюсь (ще раз) від думки про те, що це означає про якість дизайну Java (або її відсутність, залежно від випадку).


Більш того, примітивним типам взагалі не потрібен будь-який виклик конструктора - вони або записуються як буквальний, походять від оператора, або від якоїсь примітивної функції, що повертається. Ви фактично також створюєте рядки (які знаходяться на купі) без а new, тому це насправді не причина.
Paŭlo Ebermann

9

У JavaScript вам це потрібно, оскільки конструктор виглядає як нормальна функція, тож як JS повинен знати, що ви хочете створити новий об’єкт, якщо це не було для нового ключового слова?


4

Цікаво, що VB це потребує - відмінність між

Dim f As Foo

яка оголошує змінну, не присвоюючи її, еквівалент Foo f;в C # і

Dim f As New Foo()

який оголошує змінну і призначає новий екземпляр класу, еквівалентний var f = new Foo();в C #, є істотним відмінністю. У pre-.NET VB ви могли навіть використовувати Dim f As Fooабо Dim f As New Foo- оскільки не було перевантаження на конструктори.


4
VB не потрібна скорочена версія, це лише абревіатура. Ви також можете написати більш довгу версію за допомогою типу та конструктора Dim f As Foo = New Foo (). За допомогою опції Infer On, яка є найближчою вар до C # 'var, ви можете написати Dim f = New Foo (), і компілятор визначає тип f.
MarkJ

2
... і Dim f As Foo()оголошує масив з Fooс. О радість! :-)
Heinzi

@MarkJ: У vb.net стенограма еквівалентна. У vb6 це не було. У vb6, Dim Foo As New Barфактично зробить Fooвластивість, яка побудує a, Barякби воно було прочитане while (тобто Nothing). Ця поведінка не обмежувалася тим, що траплялися один раз; установка Fooна Nothingа потім читання було б створити ще один новий Bar.
supercat

4

Мені подобається багато відповідей, і я просто хотів би додати це:
Це полегшує читання коду
Не те, що ця ситуація траплятиметься часто, але вважайте, що ви хочете методу в імені дієслова, яке поділяло те саме ім'я, що і іменник. C # та інша мова, природно, мають objectзарезервоване слово . Але якби цього не було, це було б Foo = object()результатом виклику методу об'єкта або було б створенням нового об'єкта. Сподіваємось, мова без newключового слова захищає цю ситуацію, але, маючи вимогу newключового слова перед викликом конструктора, ви дозволяєте існувати метод з тим самим ім'ям, що і об'єкт.


4
Можливо, це знати - це поганий знак.
DeadMG

1
@DeadMG: "Можливо" означає: "Я буду стверджувати, що поки бар не закриється" ...
стипендіати

3

У багатьох мовах програмування є багато руйнівної речі. Іноді це там за дизайном (C ++ був розроблений таким чином, щоб бути максимально сумісним із C), іноді - просто там. Для більш кричущого прикладу розглянемо, наскільки switchпоширюється непрацюючий твердження C.

Я не знаю Javascript і C # досить добре, щоб сказати, але я не бачу причин newу Java, окрім того, що у C ++ його було.


Я думаю, що JavaScript був розроблений так, щоб "виглядати так само, як Java", наскільки це можливо, навіть коли він робить щось особливо не схоже на Java. x = new Y()в JavaScript НЕ екземпляр в Yклас, тому що JavaScript НЕ має класів. Але це схоже на Java, тому воно переносить newключове слово вперед від Java, що, звичайно, перенесло його з C ++.
MatrixFrog

+1 за пошкоджений перемикач (сподіваюся, ви його відремонтуєте: D).
maaartinus

3

У C # це дозволяє види, видимі в контекстах, де вони в іншому випадку можуть бути затемнені членом. Дозволяє мати властивість з тим самим іменем, як і його тип. Розглянемо наступне,

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

Зверніть увагу на використання newдозволяє йому бути ясно , що Addressце Addressтип не Addressчлен. Це дозволяє члену мати те саме ім’я, що і його тип. Без цього вам потрібно буде встановити префікс імені типу, щоб уникнути зіткнення імені, наприклад CAddress. Це було дуже навмисно, оскільки Андерс ніколи не любив угорські позначення чи щось подібне і хотів, щоб C # був корисним без нього. Те, що було також знайоме, було подвійним бонусом.


w00t, і Address може виходити з інтерфейсу, який називається Address .. Тож це не велика користь, будучи в змозі повторно використовувати одне ім’я та зменшитись із загальної читабельності вашого коду. Таким чином, зберігаючи I для інтерфейсів, але видаляючи C з класів, вони просто створили неприємний запах без жодної практичної користі.
gbjbaanb

Цікаво, що вони не використовують "Нове", а не "Нове". Хтось міг би сказати їм, що є також малі літери, які вирішують цю проблему.
maaartinus

Усі зарезервовані слова на мовах, похідних від C, такі як C #, мають малі літери. Граматика вимагає тут застереженого слова, щоб уникнути двозначності, таким чином, використання "нового" замість "Нового".
chuckj

@gbjbaanb Запаху немає. Завжди абсолютно зрозуміло, ви маєте на увазі тип чи властивість / член чи функцію. Справжній запах - це коли ви називаєте простір імен таким же, як клас. MyClass.MyClass
Стівен

0

Цікаво, що з появою ідеального переадресації newвзагалі не потрібне розміщення в C ++. Тож, певно, вона більше не потрібна ні в одній із згаданих мов.


4
Що ви вперед до ? Зрештою, std::shared_ptr<int> foo();доведеться new intякось зателефонувати .
MSalters

0

Я не впевнений, чи мали на увазі дизайнери Java це, але коли ви думаєте про об'єкти як рекурсивні записи , то ви могли б уявити, що Foo(123)він не повертає об'єкт, а функцію, фіксичною точкою якої є об'єкт, що створюється (тобто функція, яка повертається об'єкт, якщо він заданий як аргумент об'єкта, який будується). І мета new- "зав'язати вузол" і повернути цю точку фіксації. Іншими словами, newце зробило б зрозуміти про це "об'єктом бути" self.

Такий підхід може допомогти формалізувати спадкування, наприклад: ви можете розширити об'єкт-функцію за допомогою іншої об'єктної функції та нарешті надати їм спільне self, використовуючи new:

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

Тут &поєднуються дві об’єкт-функції.

У цьому випадку newможна визначити як:

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

-2

Щоб дозволити 'нуль'.

Поряд з розподілом на основі купи, Java охопила використання об'єктів нуля. Не просто як артефакт, а як особливість. Коли об'єкти можуть мати нульовий стан, то вам доведеться відокремити декларацію від ініціалізації. Звідси нове ключове слово.

У C ++ такий рядок

Person me;

Негайно зателефонував би конструктор за замовчуванням.


4
Гм, що не так з тим, Person me = Nilчи Person meматимете Nil за замовчуванням та використовувати Person me = Person()для не-Nil? Моя думка, що, здається, Ніл не був фактором у цьому рішенні ...
Андрес Ф.

Ти маєш рацію. Person me = Person (); працювали б однаково добре.
Кріс Ван Баел

@AndresF. Або навіть простіше: Person meнульовий і Person me()виклик конструктора за замовчуванням. Тому вам завжди потрібні дужки, щоб викликати що-небудь, і таким чином позбутися однієї C ++ нерівності.
maaartinus

-2

Причина, чому Python цього не потребує, - це через тип його системи. Принципово, коли ви бачите foo = bar()в Python, не має значення, чи bar()це метод, який викликається, клас, який інстанціюється, або об'єкт функтора; в Python немає принципової різниці між функтором і функцією (адже методи - це об'єкти); і ви навіть можете стверджувати, що інстанціювання класу є особливим випадком функціонера. Таким чином, немає підстав створювати штучну різницю в тому, що відбувається (тим більше, що управління пам’яттю настільки надзвичайно приховано в Python).

У мові з іншою системою набору тексту, або в якій методи та класи не є об'єктами, існує можливість більш сильної концептуальної різниці між створенням об'єкта та викликом функції чи функтора. Таким чином, навіть якщо компілятору / інтерпретатору не потрібне newключове слово, зрозуміло, що воно може підтримуватися мовами, які не порушили всіх меж, якими володіє Python.


-4

Насправді в Java є різниця при використанні Strings:

String myString = "myString";

відрізняється від

String myString = new String("myString");

Припустимо, що рядок "myString"ніколи раніше не використовувався, перший оператор створює один об'єкт String в пулі String (спеціальна область купи), тоді як другий вираз створює два об'єкти, один у звичайній області купи для об'єктів та інший у String пулі . Цілком очевидно, що друге твердження є марним у цьому конкретному сценарії, але дозволене мовою.

Це означає, що нові запевняють, що об’єкт створений у тій спеціальній області купи, виділеній для нормальної інстанції об'єкта, але можуть існувати й інші способи інстанції об'єктів без нього; вище наведений приклад, і для розширення залишилось місце.


2
Але це не було питання. Питання було "чому ні String myString = String("myString");?" (зверніть увагу на відсутність newключового слова)
Андрес Ф.

@AndresF. Вигляд очевидного imho ... законно мати метод на ім'я String, який повертає String в клас String, і має бути різниця між викликом його та викликом конструктора. Щось на кшталтclass MyClass { public MyClass() {} public MyClass MyClass() {return null;} public void doSomething { MyClass myClass = MyClass();}} //which is it, constructor or method?
m3th0dman

3
Але це не очевидно. По-перше, звичайний метод з назвою Stringсуперечить умовам стилю Java, тому ви можете просто припустити, що будь-який метод, починаючи з великих літер, є конструктором. По-друге, якщо нас Java не обмежує, то у Scala є "класи випадків", де конструктор виглядає точно так, як я сказав. То де проблема?
Андрес Ф.

2
Вибачте, я не дотримуюся ваших аргументів. Ви ефективно аргументуючи синтаксис , а НЕ семантика, і в будь-якому випадку мова йшла про newдля керованих мов . Я показав, що newце зовсім не потрібно (наприклад, Scala не потрібен для класів випадків), а також, що немає неоднозначності (оскільки ви абсолютно не можете мати метод, названий MyClassу класі MyClass). Там немає ніякої двозначності для String s = String("hello"); це не може бути чим іншим, як конструктором. І нарешті, я не погоджуюсь: умовні положення коду існують для покращення та уточнення синтаксису, про що ви сперечаєтесь!
Андрес Ф.

1
Так це ваш аргумент? "Java-дизайнерам потрібне було newключове слово, тому що в іншому випадку синтаксис Java був би іншим"? Я впевнений, що ви можете бачити слабкість цього аргументу;) І так, ви продовжуєте обговорювати синтаксис, а не семантику. Також зворотна сумісність з чим? Якщо ви розробляєте нову мову, наприклад, Java, ви вже несумісні з C і C ++!
Андрес Ф.

-8

У C # new важливо розрізняти об’єкти, створені на стеку, порівняно з купою. Часто ми створюємо об’єкти на стеку для кращої продуктивності. Дивіться, коли об’єкт у стеку виходить із сфери застосування, він зникає негайно, коли стек розгадується, як і примітив. У сміттєзбірнику немає необхідності слідкувати за цим, і немає додаткових накладних витрат менеджера пам'яті, щоб виділити необхідний простір на купі.


5
на жаль, ви плутаєте C # з C ++.
gbjbaanb
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.