假设我有一个:
package org.something.a;
public class String {
...
}
和
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());
}
}
我想知道为什么String
没有名称冲突,因为java.lang.String
和org.something.a.String
在编译时类路径上的org.something.a
包中都可用,为什么自定义类(org.something.b
)有首选项,因为IDE(IntelliJ)将String
解析为org.something.a.String
?
此外,如果我用javac *.java
编译这两个文件,然后调用java Main
,我会得到:
错误:在Main类中找不到Main方法,请将Main方法定义为:public static void main(String[]args)
但如果我将主方法签名更改为:
public static void main(java.lang.String[] args) {...}
则编译并打印CCD_ 10。这意味着,它编译了导入了java.lang.String
的整个文件,但之前失败了,因为main
方法没有java.lang.String
数组作为参数。
我很清楚这一点:JLS§7:
编译单元中的代码自动访问其包中声明的所有类和接口,还自动导入预定义包java.lang中声明的全部公共类和接口。
据我所知,这一定是名称冲突。但即使不是,上面的行为也不清楚——如果我只是将java.lang
附加到主方法的参数中,为什么自定义类有偏好,为什么一切都会改变。
在JLS中找不到任何合适的规范页面。
6.3声明的范围规定:
顶级类或接口(§7.6)的范围是声明顶级类或界面的包中的所有类和接口声明。
既然java.lang.*
被导入到每个编译单元,为什么org.something.a.String
不与java.lang.String
冲突
7.3编制单位声明:
每个编译单元都隐式导入预定义包java.lang中声明的每个公共类或接口,就好像声明
import java.lang.*;
出现在每个编译单元的开头,紧接在任何包声明之后。因此,所有这些类和接口的名称在每个编译单元中都可以作为简单名称使用。
注意,它显式地将import语句声明为import java.lang.*;
,称为";"按需进口":
7.5.2类型按需进口申报
<blockquote\类型按需导入声明允许根据需要导入命名包、类或接口的所有可访问类和接口
TypeImportOnDemandDeclaration:
import PackageOrTypeName . * ;
>和6.4.1 Shadowing谈到按需进口:
类型按需导入声明不会导致任何其他声明被隐藏。
因此,java.lang.String
永远不能遮蔽名为String
的类(对于java.lang
中的其他类也是类似的)
使用两类
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());
}
}
不能运行org.something.a.Main
,因为该main
方法的签名为public static void main(org.something.a.String[] args)
,但若要运行该类,签名必须是public static void main(java.lang.String[] args)
。请注意,大多数情况下,您可以将方法写成public static void main(String[] args)
,因为当前包中没有隐藏java.lang.String
的类String
。
如果您将代码更改为
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());
}
}
那么您将能够运行该类,因为现在主方法的签名是正确的。
运行此代码将输出
org.something.a.String
要查看java.lang.String
和org.something.a.String
之间的差异,您应该将main
方法扩展到
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());
}
}
将输出
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
String
到java.lang.String
的映射是如何消失的
它不会消失,也不会首先表现出来。
重要的是要理解,对于我们这些懒惰的程序员来说,所有这些导入语句都只是一个方便的特性。
形式为import java.util.List;
的单一类型导入语句让我们告诉编译器:
看,每当我把
List
写为一个简单的、不合格的类名时,我都希望您使用java.util.List
作为合格的类名。
此导入语句不会阻止您使用类java.awt.List
,它只是意味着如果您想使用该特定类,则必须始终使用完全限定的类名java.awt.List
。它对编译过程没有任何改变——编译过程内部总是只使用完全限定的类名。
形式为import java.lang.*;
的导入按需语句(它隐含地是每个编译单元的第一个导入语句)甚至更懒惰。它告诉编译器:
看,每当我将
String
写为一个简单的、不合格的类名,而您在当前包或单个类型导入语句中都找不到String
类的定义时,请查看所有按需导入的包,是否可以在其中任何包中找到这样的类。如果您能在那里找到一个这样的类,请使用该类的完全限定类名。
为了完整性:如果你能找到一个这样的类,我写的原因是:没有人阻止你写
import java.awt.*;
import java.util.*;
编译器仍然会愉快地编译您的代码,直到您尝试使用简单类名List
为止。一旦您尝试同时使用导入语句和简单类名List
,编译器就会因错误而中止,因为使用这两个导入语句时,它无法知道您希望类名List
是指java.awt.List
还是指java.util.List
。
在您的情况下(类String
与类Main
在同一个包中),此原因不适用。不存在隐含的import org.something.a.*
。
**如果java.lang.String
从不阴影,那么它会去哪里。**
java.lang.String
始终存在。只是简单名称String
不再意味着java.lang.String
,而是指当前包中的类String
。