为什么像Java,Javascript和C#这样的内存管理语言保留了“ new”关键字?


138

newJava,Javascript和C#等语言中的关键字会创建类的新实例。

该语法似乎是从C ++继承的,在C ++中该语法new专门用于在堆上分配类的新实例,并返回指向该新实例的指针。在C ++中,这不是构造对象的唯一方法。您还可以在不使用new-的情况下在堆栈上构造一个对象,实际上,这种构造对象的方式在C ++中更为常见。

因此,来自C ++背景,newJava,Javascript和C#等语言中的关键字对我来说似乎很自然并且很明显。然后,我开始学习没有new关键字的Python 。在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 2011年

3
@Lundin:它可以并且实际上是现代JVM可以做到的。只是证明f封闭函数返回时GC可以收集对象。
MSalters 2011年

1
询问为什么设计语言的方式不是很有建设性的,特别是如果该设计要求我们键入四个额外的字符却没有明显的好处?我想念什么?
MatrixFrog 2013年

Answers:


93

您的观察是正确的。C ++是一个复杂的野兽,该new关键字用于区分delete以后需要的内容和可以自动回收的内容。在Java和C#中,他们删除了delete关键字,因为垃圾收集器将为您处理该关键字。

然后的问题是为什么他们保留new关键字?如果不与编写该语言的人交谈,这很难回答。我的最佳猜测如下:

  • 语义上讲这是正确的。如果您熟悉C ++,则知道该new关键字在堆上创建了一个对象。那么,为什么要改变预期的行为呢?
  • 它引起您注意以下事实:您正在实例化对象而不是调用方法。根据Microsoft代码样式建议,方法名称以大写字母开头,因此可能会造成混淆。

Ruby在的使用方面介于Python和Java / C#之间new。基本上,您可以像这样实例化一个对象:

f = Foo.new()

它不是关键字,而是类的静态方法。这意味着如果您要使用单例,则可以覆盖默认的实现,new()每次都返回相同的实例。不一定推荐它,但是有可能。


5
Ruby语法看起来不错且一致!C#中的new关键字也用作具有默认构造函数的类型的通用类型约束
Steven Jeuris 2011年

3
是。不过,我们不会谈论泛型。Java和C#的泛型版本都非常钝。不说C ++更容易理解。泛型确实仅对静态类型的语言有用。诸如Python,Ruby和Smalltalk之类的动态类型语言就不需要它们。
Berin Loritsch 2011年

2
Delphi具有与Ruby相似的语法,除了构造函数通常(但仅作为bhy约定)称为Create(`f:= TFoo.Create(param1,param2 ...)'
Gerry

在Objective-C中,该alloc方法(与Ruby大致相同new)也是一个类方法。
mipadi

3
从语言设计的角度来看,关键字之所以不好是有很多原因,主要是您无法更改它们。另一方面,重用方法的概念比较容易,但是在解析和分析时需要注意一些。Smalltalk及其同类使用消息,而C ++及其同类使用关键字
GabrielŠčerbák2011年

86

简而言之,您是对的。关键字new在Java和C#等语言中是多余的。以下是Bruce Eckel的一些见解,他是1990年代C ++ Standard Comitee的成员,后来发表了有关Java的书籍:http : //www.artima.com/weblogs/viewpost.jsp? thread=260578

需要某种方法将堆对象与堆栈对象区分开。为了解决此问题,从Smalltalk中使用了new关键字。要创建一个堆栈对象,只需声明它,就像在Cat x中一样;或者带有参数Cat x(“ mittens”);。要创建堆对象,请像新建Cat一样使用new。或新的Cat(“手套”);。考虑到这些限制,这是一个优雅而一致的解决方案。

在确定所有C ++都做得不好并且过于复杂之后,请输入Java。具有讽刺意味的是,Java可以而且确实做出了放弃堆栈分配的决定(有目的地忽略了原语的崩溃,这在我之前已经解决过)。而且由于所有对象都是在堆上分配的,因此无需区分堆栈分配和堆分配。他们很容易说出Cat x = Cat()或Cat x = Cat(“手套”)。甚至更好的是,通过合并类型推断来消除重复(但是-和其他功能(例如闭包)将花费“太长时间”,因此我们只能使用普通版本的Java;已经讨论了类型推断,但是我会考虑到向Java添加新功能时出现的问题,这种情况不会发生。


2
Stack vs Heap ...有见地,从来没有想过。
WernerCD'2

1
“已经讨论了类型推断,但我将不大可能不会发生。”,:)好吧,Java的开发仍在继续。有趣的是,这是Java 10的唯一旗舰。该var关键字是无处接近不如auto在C ++中,但我希望它会改善。
帕特里克

15

我可以想到两个原因:

  • new 区分对象和基元
  • new语法更具有可读性一个位(IMO)

首先是微不足道的。我认为将两者分开很难。第二个例子是OP观点的一个例子,这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)2012年

10

Java中,一个,还有C ++的二分法:不是一切都是对象。Java具有不需要动态分配的内置类型(例如charint)。

但这并不意味着new在Java中确实有必要-不需要动态分配的类型集是固定的并且是已知的。当您定义一个对象时,编译器可以知道(实际上,的确知道)这是一个简单的值char还是必须动态分配的对象。

我将(再次)避免就Java设计质量(或视情况而定)的质量这意味着什么。


更重要的是,基本类型根本不需要任何构造函数调用–它们可以作为文字编写,来自运算符或来自某些基本返回函数。您实际上还创建了不带的字符串(在堆上)new,所以这并不是真正的原因。
圣保罗Ebermann

9

在JavaScript中,您需要它,因为构造函数看起来像一个普通函数,因此,如果不是new关键字,那么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 FooDim f As New Foo-因为有人没有构造函数重载。


4
VB 不需要简写版本,仅是缩写。您还可以使用类型和构造函数Dim f As Foo = New Foo()编写更长的版本。使用Option Infer On(最接近C#的var),可以编写Dim f = New Foo(),然后编译器会推断f的类型。
MarkJ 2012年

2
...并Dim f As Foo()声明一个数组Foo秒。哦,快乐!:-)
Heinzi

@MarkJ:在vb.net中,速记是等效的。在vb6中不是。在vb6中,Dim Foo As New Bar将有效地创建Foo一个属性,该属性将构造一个Barif,如果它在while(即Nothing)被读取。这种行为不仅限于发生一次。设置FooNothing,然后阅读它会创建另一个新的Bar
超级猫

4

我喜欢很多答案,并且想补充一下:
它使代码的读取更加容易
并非经常会发生这种情况,但是考虑到您想要一个以动词名称与名词同名的方法。C#和其他语言自然会object保留这个词。但是,如果不是,那么它将Foo = object()是对象方法调用的结果,或者是实例化一个新对象。希望说没有new关键字的语言可以防止这种情况,但是通过new在调用构造函数之前对关键字有要求,可以允许存在与对象同名的方法。


4
可以说,必须知道这是一个不好的信号。
DeadMG

1
@DeadMG:“可以说”的意思是“我会争论,直到酒吧关闭”……
Donal Fellows 2012年

3

许多编程语言中都有很多残留的东西。有时它是有意设计的(C ++旨在与C尽可能兼容),有时,它就在那里。举一个更加极端的例子,考虑一下损坏的C switch语句传播了多远。

我不太了解Javascript和C#,但是new除了C ++拥有Java外,我没有其他理由使用Java。


我认为JavaScript被设计为尽可能“看起来像Java”,即使它正在做特别类似于Java的事情。x = new Y()在JavaScript中没有实例化Y类,因为JavaScript不具备类。但是它看起来像Java,因此它new从Java转发了关键字,当然也从C ++ 继承了关键字。
MatrixFrog 2011年

+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允许它很清楚,AddressAddress类型不是Address成员。这允许成员使用与其类型相同的名称。否则,您需要在类型名称前加上前缀,以避免名称冲突,例如CAddress。这是非常故意的,因为Anders从不喜欢匈牙利符号或任何类似符号,并希望C#在没有它的情况下可用。它也很熟悉的事实是双重好处。


w00t,然后Address可以从名为Address ..的接口派生而已,不可以。因此,能够重用相同的名称并降低代码的一般可读性不是什么好处。因此,通过保留I作为接口,但从类中删除C,它们只是产生了令人讨厌的气味,没有任何实际好处。
gbjbaanb 2012年

我想知道他们不使用“新”而不是“新”。有人可以告诉他们还有小写字母,可以解决这个问题。
maaartinus

C派生语言(例如C#)中的所有保留字均为小写。语法此处需要保留字,以免产生歧义,因此使用“新”而不是“新”。
chuckj

@gbjbaanb没有气味。始终完全清楚是要引用类型还是属性/成员或函数。真正的味道是,当您将名称空间命名为与类相同时。MyClass.MyClass
斯蒂芬,

0

有趣的是,随着完美转发的出现,new在C ++中也完全不需要放置。因此,可以说,上述任何一种语言都不再需要它。


4
你怎么转发?最终,std::shared_ptr<int> foo();将不得不以new int某种方式致电。
MSalters 2011年

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对象。不仅是工件,而且是功能。当对象可以具有nil状态时,则必须将声明与初始化分开。因此,新的关键字。

在C ++中,像

Person me;

将立即调用默认构造函数。


4
嗯,默认情况下为Nil Person me = Nil或将Person me其默认Person me = Person()为Nil有什么问题?我的观点是,似乎Nil并不是这个决定的一个因素……
Andres F.

你有一定道理。人我=人(); 会同样有效。
克里斯·范·贝尔

@AndresF。甚至更简单一些,Person me为nil并Person me()调用默认构造函数。因此,您始终需要括号才能调用任何内容,从而摆脱一种C ++不规则性。
maaartinus 2014年

-2

Python不需要它的原因是因为它的类型系统。从根本上讲,当您foo = bar()在Python中看到代码时,无论bar()是调用的方法,实例化的类还是函子对象,都无关紧要。在Python中,函子和函数之间没有根本区别(因为方法是对象);您甚至可以说,实例化的类是函子的特例。因此,没有理由在正在发生的事情上造成人为的差异(特别是因为内存管理在Python中是如此的隐蔽)。

在具有不同类型系统的语言中,或者在方法和类不是对象的语言中,创建对象与调用函数或函子之间可能存在更强的概念差异。因此,即使编译器/解释器不需要该new关键字,可以理解的是,它可以用没有打破Python的所有界限的语言进行维护。


-4

实际上,在Java中,使用字符串有区别:

String myString = "myString";

与...不同

String myString = new String("myString");

假设"myString"从未使用过该字符串,则第一条语句在字符串池(堆的特殊区域)中创建一个String对象,而第二条语句创建两个对象,一个在普通堆区中用于对象,另一个在字符串池中。很明显,第二条语句在这种特定情况下是无用的,但被语言所允许。

这意味着new会确保在分配给普通对象实例化的特殊区域中创建对象,但是如果没有对象实例化,可能还有其他实例化对象。上面的示例是一个示例,并且还有可扩展性的余地。


2
但这不是问题。问题是“为什么不String myString = String("myString");呢?” (请注意缺少new关键字)
Andres F.

@AndresF。有点明显的错误...有一个名为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
但这并不明显。首先,使用名为StringJava的常规约定的常规方法,因此您可以假定以大写开头的任何方法都是构造函数。其次,如果我们不受Java的约束,那么Scala具有“案例类”,其中的构造函数看起来完全像我说的那样。那么问题出在哪里呢?
Andres F.

2
抱歉,我不听您的说法。你实际上争论的语法,而不是语义了,反正问题是关于new托管语言。我已经展示了new它根本不需要(例如,对于案例类,Scala不需要它),并且也没有歧义(因为无论如何,您绝对不能MyClass在类中命名方法MyClass)。有没有歧义String s = String("hello"); 它只能是构造函数。最后,我不同意:那里的代码约定可以增强和阐明语法,这正是您所争论的!
Andres F.

1
那是你的论点?“ Java设计师需要new关键字,因为否则Java的语法会有所不同”?我确定您会看到该参数的弱点;)是的,您一直在讨论语法,而不是语义。还有,向后兼容什么呢?如果您正在设计一种新的语言(例如Java),则已经与C和C ++不兼容!
Andres F.

-8

在C#中,new对于区分在堆栈上创建的对象还是在堆上创建的对象很重要。通常,我们在堆栈上创建对象以获得更好的性能。看,当堆栈上的对象超出范围时,当堆栈解散时,它会立即消失,就像原始对象一样。不需要垃圾收集器对其进行跟踪,并且内存管理器也没有多余的开销来在堆上分配必要的空间。


5
不幸的是,您将C#与C ++混淆了。
gbjbaanb 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.