Почему языки с управлением памятью, такие как Java, Javascript и C #, сохранили ключевое слово `new`?


139

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

Ответы:


94

Ваши наблюдения верны. 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 ...) '
Gerry

В Objective-C allocметод (примерно такой же, как у Ruby new) также является просто методом класса.
Мипади

3
С точки зрения языкового дизайна, ключевые слова являются плохими по многим причинам, в основном вы не можете их изменить. С другой стороны, повторное использование концепции метода проще, но требует некоторого внимания при анализе и анализе. Smalltalk и его родственники используют сообщения, C ++ и itskin, с другой стороны, ключевые слова
Габриэль Шербак,

86

Короче говоря, вы правы. Ключевое слово new излишне в таких языках, как Java и C #. Вот некоторые идеи Брюса Экеля, который был членом C ++ Standard Comitee в 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

Это довольно хорошая иллюстрация того, что может случиться, хотя у разработчика, который это делает, будут серьезные психические проблемы.
Tjaart

10

Java, например, до сих пор дихотомии C ++: не совсем все является объектом. Java имеет встроенные типы (например, charи int), которые не должны быть распределены динамически.

Это не означает, что newэто действительно необходимо в Java - набор типов, которые не нужно распределять динамически, фиксирован и известен. Когда вы определяете объект, компилятор может знать (и фактически знает), является ли это простым значением charили объектом, который должен быть динамически размещен.

Я воздержусь (еще раз) от мнения о том, что это означает о качестве дизайна Java (или его отсутствии, в зависимости от обстоятельств).


Более того, примитивные типы вообще не нуждаются ни в каком вызове конструктора - они либо пишутся как литерал, поступают от оператора или из некоторой функции, возвращающей примитив. На самом деле вы также создаете строки (которые находятся в куче) без new, так что это на самом деле не причина.
Паŭло Эберманн

9

В JavaScript это нужно, потому что конструктор выглядит как обычная функция, так как же JS узнает, что вы хотите создать новый объект, если бы он не был для ключевого слова new?


4

Интересно, что это нужно VB - различие между

Dim f As Foo

который объявляет переменную без ее присвоения, эквивалент Foo f;в C #, и

Dim f As New Foo()

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


4
VB не нужна сокращенная версия, это просто аббревиатура. Вы также можете написать более длинную версию с типом и конструктором Dim f As Foo = New Foo (). С опцией Infer On, которая является ближайшей к var в C #, вы можете написать Dim f = New Foo (), и компилятор выведет тип f.
MarkJ

2
... и Dim f As Foo()объявляет массив из Fooс. О, радость! :-)
Хайнци

@MarkJ: В vb.net сокращенное обозначение эквивалентно. В vb6 этого не было. В vb6 Dim Foo As New Barбыло бы эффективно создать Fooсвойство, которое могло бы создать a, Barесли бы оно было прочитано while (то есть Nothing). Такое поведение не ограничивалось случаем один раз; установка Fooна Nothingа затем чтение было бы создать еще один новый Bar.
суперкат

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 включил использование объектов nil. Не просто как артефакт, а как особенность. Когда объекты могут иметь нулевое состояние, тогда вы должны отделить объявление от инициализации. Отсюда и новое ключевое слово.

В C ++ строка вроде

Person me;

Будет немедленно вызвать конструктор по умолчанию.


4
Хм, что не так с тем Person me = Nil, Person meчто по умолчанию используется Nil и используется Person me = Person()для не-Nil? Моя точка зрения заключается в том, что не похоже, что Нил был фактором в этом решении ...
Андрес Ф.

У вас есть точка. Персона меня = Персона (); будет работать одинаково хорошо.
Крис Ван Баел

@AndresF. Или даже проще с Person meнулем и Person me()вызовом конструктора по умолчанию. Таким образом, вам всегда нужны круглые скобки, чтобы вызывать что-либо и, таким образом, избавиться от одной ошибки C ++.
Maaartinus

-2

Причина, по которой Python не нуждается в этом, заключается в его системе типов. Фундаментально, когда вы видите foo = bar()в Python, не имеет значения, bar()является ли метод, который вызывается, класс, который создается, или объект функтор; в Python нет принципиальной разницы между функтором и функцией (потому что методы являются объектами); и вы можете даже утверждать, что экземпляр класса является частным случаем функтора. Таким образом, нет никакой причины создавать искусственную разницу в происходящем (особенно потому, что управление памятью в Python настолько скрыто).

В языке с другой системой типирования или в котором методы и классы не являются объектами, существует более сильная концептуальная разница между созданием объекта и вызовом функции или функтора. Таким образом, даже если компилятору / интерпретатору не нужно newключевое слово, понятно, что оно может поддерживаться в языках, которые не нарушают все границы, которые имеет Python.


-4

На самом деле в Java есть разница при использовании строк:

String myString = "myString";

отличается от

String myString = new String("myString");

Предположим, что строка "myString"никогда не использовалась ранее, первый оператор создает один объект String в пуле String (специальная область кучи), а второй оператор создает два объекта, один в обычной области кучи для объектов и другой в пуле String. , Совершенно очевидно, что второе утверждение бесполезно в этом конкретном сценарии, но допустимо языком.

Это означает, что new гарантирует, что объект создан в этой специальной области кучи, выделенной для обычной реализации объекта, но могут быть и другие способы создания экземпляров объекта без него; Выше приведен пример, и есть место для расширения.


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.