当 Java 理解构造函数创建一个新实例时,为什么需要“new”

use*_*235 4 java new-operator

我在网上搜索了Java中“new”的用法,它是用于创建一个全新的类实例。为什么需要通过“new”关键字明确指示?难道不明白构造函数调用会实例化一个新对象吗?例如,如果没有new,很明显会MyClass AnInstance = MyClass(AnArgument)创建一个新对象。

从实际角度来看,我提出这个问题的原因是因为我认为“新”是为了防范危险而设计的,并且我想了解这种危险。

jav*_*dba 9

冒着一个答案被认为是“意见”的风险,我会抛弃java紧随 C 和 C++ 之后的内容,并本质上继承了后者的语法 - 尽管destroy考虑到垃圾收集器,类似的东西是不必要的。

因此,对于它没有严格的“要求”,new而是遵循它最强烈派生的语言的模式。


rzw*_*oot 8

这是命名空间的问题。

在java中,方法和字段可以具有相同的名称,并且它们完全不相关。事实上,类型也可以,包也可以。所有 4 个都是完全正交的。这 4 者永远不会相遇

package foo;

public class foo {
  int foo;

  public foo() { /* constructor */ }

  public void foo() { /* method */ }
}
Run Code Online (Sandbox Code Playgroud)

这是完全合法的。这里没有任何内容会覆盖任何内容 - 这是一个foo在 package 中命名的类foo,有 3 个成员:一个名为 的字段foo、一个采用零参数的构造函数,以及一个foo不采用任何参数且不返回任何内容的方法。你可以编译它。它会。没有任何警告。您的 IDE 可能会抱怨以小写字母开头的类型是非常规的。但这只是惯例。lang 规范允许这样做。javac将编译上面的就好了。

那么,如果你可以将所有这些东西都命名为同一个东西,那么它是如何工作的呢?

从上下文中java总是知道你在做什么。

在某些语言中,调用方法时括号是可选的,或者至少所有内容都位于同一名称空间中。

例如,在 JavaScript 中:

function test() {
}

var x = test;
Run Code Online (Sandbox Code Playgroud)

是合法的;x现在是对该函数的引用test

事实上,这也意味着你不能在 javascript 中拥有同名的方法和变量:

function test() {
}

test = 5;
test();
Run Code Online (Sandbox Code Playgroud)

不起作用。最后一条语句尝试将“5”作为函数执行,但事实并非如此。

在java中,情况并非如此(这些括号不是可选的,没有括号,它就与该方法无关,句号),因为java在具有4个完全不相关的名称空间方面有些独特。

因此,为什么new 必须存在。因为如果它不存在,java 将不知道要挖掘 2 个相关命名空间(类型和方法)中的哪一个。换句话说,这个:

public class Test {
  public Test() {}
  public static void Test() {}
}
Run Code Online (Sandbox Code Playgroud)

是合法的java。它编译得很好。那么,如果导入这个 Test 类,然后写:Test();,我在谈论哪一个?

在java中,很清楚:如果你写new Test(),它就是构造函数,如果你写Test(),它就是静态方法。

我们可以详细讨论拥有正交命名空间是否是一个明智的想法。然而,java 不喜欢破坏向后兼容性,在这一点上,改变这一切显然不值得所带来的痛苦,所以你的问题将归结为:“为什么 java 的设计者决定使用正交命名空间30 年前”,这……在这样的问题中很难回答,我需要一根鞭子和一顶棕色帽子,还有一些约翰·威廉姆斯的音乐。

在类级别,这些构造函数被编译为特殊方法,其行为大多类似于静态方法(因为它们不进行虚拟查找,并且不需要接收器 - 它与静态方法共享的属性,但不与实例方法共享),命名为<init>. 不Test,或者不管你的班级叫什么名字。

所以,最好考虑一下它们(因为这实际上是它的工作原理):

  • 构造函数就是构造函数。它们与类型相关联。他们没有名字
  • 要编写构造函数,您需要重复类型名称并且根本不添加任何返回类型(甚至不添加void- 或者您正在声明一个方法!) - 但这只是为了让编译器知道您在做什么。将类型名称的重复视为 java 避免引入关键字的方法(它也可能代替public constructor(String arg1, int arg2)public TypeNameGoesHere(String arg1, int arg2)但是,事实并非如此。再说一遍,30 年,如果我们想深入了解原因,请避开一些蛇)。

注意:命名空间实际上确实满足。它位于 select AST 链中:a.b.c.d.e不明确:是 package a.b,然后是 type c,具有名为 的静态内部类型d,并且具有名为 的静态字段吗e?或者它是a包含类b、内部类c、静态字段的包d,其中的类型z具有名为 的实例字段e

Javac 在编译过程中在黑暗中进行了最猛烈的尝试,并将其“锁定”。在运行时(类文件)级别,命名空间确实无法满足。每条指令都清楚地表明标识符所指的是 4 个命名空间中的哪一个。


rua*_*akh 7

例如,如果没有new,很明显会MyClass AnInstance = MyClass(AnArgument)创建一个新对象。

是吗?

考虑这个类(它是真正有效的 Java):

class ClientOfMyClass {
    // define a method named 'MyClass' that is not a constructor:
    static SubclassOfMyClass MyClass(final String arg) {
        return null;
    }

    String AnArgument = "hello";
    MyClass AnInstance = MyClass(AnArgument);
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,MyClass(AnArgument)调用此类中命名的方法MyClass,并且从未实际创建MyClass

我认为您的错误在于认为这new MyClass(AnArgument)是调用名为 的构造函数的语法MyClass。我认为事实并非如此。MyClass相反,它是通过调用适当的构造函数来创建 的实例的语法。事实上,定义构造函数的语法涉及重复类名;但我认为我们不应该将其视为构造函数名称。请注意,new MyClass.MyClass(AnArgument)即使构造函数不在范围内,我们也不会编写,并且请注意,它作为类的类型参数new MyGenericClass<T>(AnArgument)传递,而不是其构造函数(因为构造函数没有类型参数)。TMyGenericClass

  • @user2153235:严格来说,编译器在调用构造函数之前实例化类;但是,是的,`new MyClass(...)`需要`MyClass`有一个合适的构造函数,然后作为初始化的一部分被调用。 (2认同)