Search code examples
javanew-operator

Why does Java require "new" when it is understood that a constructor creates a new instance


I've surfed the web for the Java use of "new", and it is for creating a brand new class instance. Why does that require explicit indication via the "new" keyword? Isn't it understood that a constructor invocation instantiates a new object? Without new, for example, it is clear that MyClass AnInstance = MyClass(AnArgument) creates a new object.

From a practical perspective, my reason for asking is because I assume that "new" was designed in to guard against a hazard, and I want to be aware of the hazard.


Solution

  • It's an issue of namespaces.

    In java, methods and fields can have the same name and they are utterly unrelated. In fact, so can types, and so can packages. All 4 are completely orthogonal. Never the 4 shall meet:

    package foo;
    
    public class foo {
      int foo;
    
      public foo() { /* constructor */ }
    
      public void foo() { /* method */ }
    }
    

    That is entirely legit. Nothing overwrites anything here - that's a class named foo in package foo, with 3 members: A field named foo, a constructor that takes zero arguments, and a method named foo that takes no arguments and returns nothing. You can compile it. It will. With no warnings. Your IDE will probably complain that it's non-conventional to start a type with a lowercase letter. But that's just convention. The lang spec allows this. javac will compile the above just fine.

    So, if you can have all these things all named the same thing, how does that work then?

    From context java always knows what you are doing.

    In some languages, parentheses are optional when invoking methods, or, at least, everything is in the same namespace.

    For example, in javascript:

    function test() {
    }
    
    var x = test;
    

    Is legal; x is now a reference to the test function.

    It also means you cannot, in fact, have a method and a variable with the same name in javascript:

    function test() {
    }
    
    test = 5;
    test();
    

    Does not work. That last statement attempts to execute '5' as a function which it is not.

    In java this is not the case (those parens aren't optional, and without the parens, it just isn't about that method, period), because java is somewhat unique in having 4 completely unrelated namespaces.

    Hence, why the new MUST exist. Because if it wasn't there, java wouldn't know which of the 2 relevant namespaces (types, and methods) to dig through. Said differently, this:

    public class Test {
      public Test() {}
      public static void Test() {}
    }
    

    is legal java. It compiles fine. So, if import this Test class and then write: Test();, which one was I talking about?

    In java, it's clear: If you write new Test(), it's the constructor, if you write Test(), it's the static method.

    We can debate at length whether it was a wise idea to have orthogonal namespaces. However, java does not like to break backwards compatibility, and at this point changing it all is clearly not worth the pain that would cause, so your question would then boil down to: "Why did the designers of java decide to go with orthogonal namespaces 30 years ago" which.. is hard to answer in an SO question, I'd need a whip and a brown hat, and some John Williams music.

    At the class level, those constructors are compiled as special methods, that mostly act like static methods (in that they do not do virtual lookup and do not require a receiver - properties it shares with static methods and it does not with instance methods), named <init>. Not Test or whatever your class is named.

    So, it's best to think of them (As this is really how it works):

    • Constructors are constructors. They associate with the type. They do not have a name.
    • To write a constructor, you repeat the type name and add no return type at all (not even void - or you're declaring a method!) - but that's just to let the compiler know what you are doing. Consider the repetition of your type's name as java's way to avoid introducing a keyword (it might as well have been public constructor(String arg1, int arg2) instead of public TypeNameGoesHere(String arg1, int arg2), but, it isn't. Again, 30 years, dodge some snakes, if we want to dive into why).

    NB: The namespaces actually do meet. It's in select AST chains: a.b.c.d.e is ambiguous: Is that package a.b, then type c, with a static inner type named d, that has a static field named e? Or is it package a, with class b, inner class c, static field d, which has type z which has an instance field named e?

    Javac takes its best wild stab in the dark at it during compilation and 'locks it in'. At the runtime (class files) level, truly the namespaces cannot meet. Every instruction makes it clear in which of the 4 namespaces an identifier is referring to.