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

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

因此,来自C ++背景,在Java,Javascript和C#之类的语言对我来说似乎很自然并且显而易见。然后,我开始学习没有new关键字的Python。在Python中,只需调用构造函数即可构造实例,例如: Python没有理由拥有new,因为所有内容都是一个对象,因此不需要在各种构造函数语法之间进行歧义。

但是我当时想-Java中new的真正意义是什么?我们为什么要说new?为什么不只是Object o = new Object();?在C ++中,绝对需要Object o = Object();,因为我们需要区分在堆上分配和在栈上分配之间的区别,但是在Java中,所有对象都在堆上构造,那么为什么还要使用new关键字呢?对于Javascript,可能会问同样的问题。在我不太熟悉的C#中,我认为new可能在区分对象类型和值类型方面有一些用途,但我不确定。

无论如何,似乎在我看来,C ++之后出现的许多语言只是“继承”了new关键字-并不需要它。它几乎像一个残余关键字。我们似乎出于任何原因都不需要它,但是它在那里。

问题:我对此是否正确?还是有一些令人信服的理由要求new必须是受C ++启发的内存管理语言(例如Java,Javascript和C#),而不是Python?

评论

问题在于,为什么任何语言都具有new关键字。当然,我想创建一个新的变量,愚蠢的编译器!在我看来,一种好的语言应具有如下语法:f =堆Foo(),f =自动Foo()。

需要考虑的非常重要的一点是,语言对新程序员的熟悉程度。 Java的显式设计旨在使C / C ++程序员熟悉。

@Lundin:这实际上是编译器技术的结果。现代的编译器甚至不需要提示。它会根据您实际使用该变量来确定将对象放置在何处。即当f无法转义该函数时,分配堆栈空间。

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

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

#1 楼

您的观察是正确的。 C ++是一个复杂的野兽,并且使用new关键字来区分以后需要delete的内容和可以自动回收的内容。在Java和C#中,他们删除了delete关键字,因为垃圾收集器会为您处理它。如果不与编写该语言的人交谈,这很难回答。我的最佳猜测如下:


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

Ruby使用new介于Python和Java / C#之间。基本上,您实例化这样的对象:

f = Foo.new()


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

评论


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

–史蒂文·杰里斯(Steven Jeuris)
2011-2-14在16:28

是。不过,我们不会谈论泛型。 Java和C#的泛型版本都非常钝。不说C ++更容易理解。泛型实际上仅对静态类型的语言有用。像Python,Ruby和Smalltalk这样的动态类型语言就不需要它们。

–贝琳·洛里奇(Berin Loritsch)
2011年2月14日在17:30

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

–格里
2011-2-14在20:16

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

– mipadi
2011-2-15在18:02

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

–加布里埃尔Ščerbák
11年2月16日在11:10

#2 楼

简而言之,您是对的。关键字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添加新功能时遇到的问题,这种情况不会发生。也不应该。)


评论


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

–WernerCD
2011-2-15在0:25

“已经讨论了类型推断,但我将不大可能发生。”,:)好吧,Java的开发仍在继续。有趣的是,这是Java 10的唯一旗舰。在C ++中,var关键字远没有auto更好,但是我希望它会有所改善。

–帕特里克
18年6月20日在22:26

#3 楼

我可以想到两个原因:



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是一种非常简单的语言,但我怀疑这在该领域有帮助。

评论


这与其他无限递归有何不同?

– DeadMG
2011-10-28 12:37

这很好地说明了可能发生的坏事,尽管这样做的开发人员会遇到一些严重的心理问题。

– Tjaart
2012年10月30日12:32

#4 楼

在JavaScript中,因为构造函数看起来像普通函数,所以您需要它,因此JS应该如何知道要创建新对象(如果不是new关键字的话)?

使用与没有new运算符的函数调用相比,new运算符的行为不同。

例如:

Date()       //Returns a string

new Date()   //Returns a Date object





function foo() {};

foo()        //Returns `undefined`

new foo()    //Returns an empty object


#5 楼

Java仍然具有C ++的二分法:并非所有事物都是对象。 Java具有不需要动态分配的内置类型(例如,charint)。不需要动态分配的内容是固定的并且是已知的。定义对象时,编译器可以知道(实际上,的确知道)是new的简单值还是必须动态分配的对象。
再次)指出这对Java设计质量(或视情况而定缺乏质量)意味着什么。

评论


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

–PaŭloEbermann
19-09-23在22:26

#6 楼

有趣的是,VB确实需要它-区别而没有声明变量的

Dim f As Foo


,它与C#中的Foo f;等价,并且>
Dim f As New Foo()


,它声明了变量并分配了该类的新实例,这与C#中的var f = new Foo();等效,是一种实质区别。在.NET VB之前的版本中,甚至可以使用Dim f As FooDim f As New Foo-因为在构造函数上没有重载。

评论


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

– MarkJ
2012年10月29日7:56



...以及Dim f Foo()声明一个Foos数组。哦,快乐! :-)

–亨氏
2012年10月30日12:56



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

–超级猫
2014年3月13日下午4:03

#7 楼

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

评论


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

– DeadMG
2011年10月28日在12:11

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

–博士生
2012年10月30日14:39

#8 楼

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

评论


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

– MatrixFrog
2011年10月31日在16:05

+1为损坏的开关(希望您修复:D)。

– maaartinus
13-10-17在1:38

#9 楼

在C#中,它允许在上下文中可见的类型,否则它们可能会被成员遮盖。允许您拥有与其类型相同名称的属性。请考虑以下内容:注意,使用new可以清楚地看出AddressAddress类型,而不是Address成员。这使成员可以使用与其类型相同的名称。否则,您需要在类型名称前添加前缀以避免名称冲突,例如CAddress。这是非常故意的,因为Anders从不喜欢匈牙利符号或任何类似符号,并希望C#在没有它的情况下可用。它也很熟悉的事实是双重好处。

评论


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

– gbjbaanb
2012年10月31日12:12

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

– maaartinus
13年10月17日在1:36

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

–chuckj
13-10-17在22:16

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

–斯蒂芬
2015年5月20日23:50

#10 楼

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

评论


你期待什么?最终,std :: shared_ptr foo();将不得不以某种方式调用new int。

–MSalters
2011年11月1日,9:56

#11 楼

我不确定Java设计人员是否考虑到了这一点,但是当您将对象视为递归记录时,您可以想象Foo(123)不会返回对象,而是返回其固定点是正在创建的对象的函数(即,将返回对象(如果将要构造的对象作为参数给出)。 new的目的是“打结”并返回该固定点。换句话说,new会使“待对象”知道其self。最后使用self为他们提供通用的new:定义为:

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


#12 楼

允许“无”。

随着基于堆的分配,Java拥抱了nil对象的使用。不仅是工件,而且是功能。
当对象可以具有nil状态时,则必须将声明与初始化分开。因此,新的关键字。

在C ++中,像

Person me;
这样的行将立即调用默认构造函数。

评论


嗯,Person me = Nil或默认情况下让Person me为Nil并为非Nil使用Person me = Person()怎么了?我的观点是,似乎Nil并不是这个决定的一个因素...

– Andres F.
2012年10月30日13:15



你有一定道理。人我=人();会同样有效。

–克里斯·范·贝尔(Kris Van Bael)
2012年10月30日23:51

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

– maaartinus
2014年8月3日19:49

#13 楼

Python不需要它的原因是由于它的类型系统。从根本上讲,当您在Python中看到foo = bar()时,无论bar()是被调用的方法,正在实例化的类还是函子对象,都无关紧要。在Python中,函子和函数之间没有根本区别(因为方法是对象);甚至可以说,实例化的类是函子的特例。因此,没有理由在正在发生的事情上造成人为的差异(特别是因为内存管理在Python中是如此的隐蔽)。类不是对象,因此在创建对象与调用函数或函子之间可能存在更强的概念差异。因此,即使编译器/解释器不需要new关键字,也可以理解,它可以用没有打破Python的所有界限的语言来维护。

#14 楼

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

String myString = "myString";




String myString = new String("myString");


不同字符串"myString"以前从未使用过,第一个语句在字符串池(堆的特殊区域)中创建一个String对象,而第二条语句在普通堆区域中的对象中创建两个对象,在字符串池中的另一个对象中创建一个对象。很明显,第二条语句在这种特定情况下是无用的,但是在语言中是允许的。普通的对象实例化,但是如果没有对象实例化,可能还有其他实例化对象;以上只是一个示例,还有可扩展性的余地。

评论


但这不是问题。问题是“为什么不使用String myString = String(“ myString”);?” (请注意,没有新关键字)

– Andres F.
2013年1月29日在21:42



@AndresF。有点明显的不对劲...有一个名为String的方法可以在String类中返回String,这是合法的,在调用它和调用构造函数之间必须有所区别。类似于类MyClass {public MyClass(){} public MyClass MyClass(){return null;} public void doSomething {MyClass myClass = MyClass();}} //是哪个,构造函数或方法?

–随机42
13年1月29日在22:03



但这并不明显。首先,使用名为String的常规方法违反Java样式约定,因此您可以假定以大写开头的任何方法都是构造函数。其次,如果我们不受Java的约束,那么Scala具有“案例类”,其中的构造函数看起来完全像我说的那样。那么问题出在哪里呢?

– Andres F.
2013年1月29日23:11



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

– Andres F.
2013年1月30日16:17



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

– Andres F.
2013年1月30日17:51



#15 楼

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

评论


不幸的是,您将C#与C ++混淆了。

– gbjbaanb
2012年10月31日12:14