Why I don't have a clashing name conflict when two classes with same names are available?

290 Views Asked by At

Let's say I have a:

package org.something.a;

public class String {
   ...
}

and

package org.something.a;

public class Main {
    public static void main(String[] args) {
        String a = new String(); //IDE shows that this is a org.something.a.String type and not java.lang.String
        System.out.println(a.getClass().getName());
    }
}

I wonder why there's no name clash for String, as java.lang.String and org.something.a.String are both available in a org.something.a package, on a compile-time class-path, and why custom-defined class (org.something.b) has a preference, as IDE (IntelliJ) resolves String to org.something.a.String?

Besides, if I'll compile both files with javac *.java and then invoke java Main, I get this:

Error: Main method not found in class Main, please define the main method as: public static void main(String[] args)

but if I change the main method signature to:

public static void main(java.lang.String[] args) {...}

then it compiles and prints String. Which means, it then compiles entire file having imported java.lang.String and it failed before because, main method didn't have a java.lang.String array as a parameter.

I am well aware of this: JLS §7:

Code in a compilation unit automatically has access to all classes and interfaces declared in its package and also automatically imports all of the public classes and interfaces declared in the predefined package java.lang.

and as far as I remember, this must be a name clash. But even if it's not, the behaviour above is really unclear - why custom class has a preference and why everything changes if I just append java.lang to the main method's parameter.

Cannot find any appropriate spec page in JLS.

1

There are 1 best solutions below

1
On

6.3 Scope of a Declaration states:

The scope of a top level class or interface (§7.6) is all class and interface declarations in the package in which the top level class or interface is declared.

Then why does org.something.a.String not clash with java.lang.String, since java.lang.* is imported into every compilation unit?

7.3 Compilation Units states:

Every compilation unit implicitly imports every public class or interface declared in the predefined package java.lang, as if the declaration import java.lang.*; appeared at the beginning of each compilation unit immediately after any package declaration. As a result, the names of all those classes and interfaces are available as simple names in every compilation unit.

Note that it explicitly states the import statement as import java.lang.*;, which is called an "import on demand":

7.5.2 Type-Import-on-Demand Declarations

A type-import-on-demand declaration allows all accessible classes and interfaces of a named package, class, or interface to be imported as needed.

TypeImportOnDemandDeclaration:
    import PackageOrTypeName . * ;

And 6.4.1 Shadowing says about import-on-demand:

A type-import-on-demand declaration never causes any other declaration to be shadowed.

Therefore java.lang.String can never shadow a class named String (and similar for other classes from java.lang)


With the two classes

package org.something.a;

class String {}

public class Main {
    public static void main(String[] args) {
        String a = new String(); //IDE shows that this is a org.something.a.String type and not java.lang.String
        System.out.println(a.getClass().getName());
    }
}

you cannot run org.something.a.Main because that main method has a signature of public static void main(org.something.a.String[] args), but to be able to run that class the signature must be public static void main(java.lang.String[] args). Note the most of the times you can just write the method as public static void main(String[] args) because there is no class String in the current package that shadows java.lang.String.

If you change your code to

package org.something.a;

class String {}

public class Main {
    public static void main(java.lang.String[] args) {
        String a = new String(); //IDE shows that this is a org.something.a.String type and not java.lang.String
        System.out.println(a.getClass().getName());
    }
}

then you will be able to run that class because now the signature of the main method is correct.

Running this code will output

org.something.a.String

To see the difference between java.lang.String and org.something.a.String you should expand your main method to

package org.something.a;

class String {}

public class Main {
    public static void main(java.lang.String[] args) {
        String a = new String(); //IDE shows that this is a org.something.a.String type and not java.lang.String
        System.out.println("a has class " + a.getClass().getName());
        System.out.println("args has class " + args.getClass().getName());
        System.out.println("args has component type " + args.getClass().componentType().getName());
        java.lang.String b = new java.lang.String();
        System.out.println("b has class " + b.getClass().getName());
    }
}

which will output

a has class org.something.a.String
args has class [Ljava.lang.String;
args has component type java.lang.String
b has class java.lang.String

How does the mapping of String to java.lang.String disappear?

It doesn't disappear, it doesn't manifest itself in the first place.

It is important to understand that all those import statements are a mere convenience feature for us lazy programmers.

A single-type-import statement of the form import java.util.List; lets us tell the compiler:

Look, anytime I write List as a simple, unqualified class name I want you to use java.util.List as the qualified class name.

This import statement doesn't prevent you from using the class java.awt.List - it just means that you always have to use the fully qualified class name java.awt.List if you want to use that specific class. And it doesn't change a bit for the compilation process - the compilation process internally always only ever uses the fully qualified class names.

An import-on-demand statement of the form import java.lang.*; which is implicitly the first import statement of every compilation unit is even more lazy. It tells the compiler:

Look, anytime I write String as a simple, unqualified class name and you cannot find the definition of the String class neither in the current package nor in a single-type-import statement, look in all those packages that are imported on demand whether you can find such a class in any of those packages. And if you can find exactly one such class there use that's class fully qualified class name.

For completeness: the reason why I wrote if you can find exactly one such class is this: nobody prevents you from writing

import java.awt.*;
import java.util.*;

And the compiler will still happily compile your code - until the moment where you try to use the simple class name List. As soon as you try to use both import statements and the simple class name List the compiler will abort with an error because with those two import statements it cannot know whether you want the class name List to refer to java.awt.List or to java.util.List.

In your case (where the class String is in the same package as the class Main) this reason doesn't apply. There is no implicit import org.something.a.*.


** if java.lang.String never shadows, then where does it go..**

java.lang.String is always there. It is only the simple name String that no longer means java.lang.String but refers to the class String from the current package.