当编写一个java源代码文件时,此文件通常被称为编译单元(有时也被称为转译单元)。每个编译单元都必须有一个后缀名.java,而在编译单元内则可以有一个public类,该类的名称必须与文件的名称相同(包括大小写,但不包括文件的后缀名.java)。每个编译单元只能有一个public类,否则编译器就不会接受(即只能有0-1个public类)。如果在该编译单元之中还有额外的类的话(即其他非public类),那么在包之外的世界是无法看见这些类的,这是因为它们不是public类,而且它们主要用来为主public类提供支持。
首先说一下为什么public的类名要与.java文件名一致的问题~
B.java文件
1 package com.culiu.ccj.servant.tagstatistics.test; 2 3 /** 4 * 描述: 5 * 创建人: BruceCloud 6 * 创建时间: 2016/5/18 10:10. 7 */ 8 public class B { 9 } 10 11 class C{ 12 }
Test.java文件
1 package com.culiu.ccj.servant.tagstatistics.test.aa; 2 3 import com.culiu.ccj.servant.tagstatistics.test.B; 4 5 /** 6 * 描述: 7 * 创建人: BruceCloud 8 * 创建时间: 2016/5/18 10:10. 9 */ 10 public class Test { 11 public static void main(String[] args) { 12 try { 13 B a = new B(); 14 // C c = new C(); 15 Class c = Class.forName("com.culiu.ccj.servant.tagstatistics.test.C"); 16 System.out.println(c.getName()); 17 } catch (ClassNotFoundException e) { 18 e.printStackTrace(); 19 } 20 } 21 }
上面代码B.java中, 有2个类(public B和非public的 C), 在B.java这个编译单元中, public B是该编译单元对外的接口类, C则是包内使用的类, 根据java的编程规则, 在Test.java中第14行引入C时会编译出错, 提示你找不到C这个类, 因为C没有被public修饰,
所以C只能在com.culiu.ccj.servant.tagstatistics.test包下面使用, 而第13行B却可以使用, 因为在使用B的时候在上面使用了import com.culiu.ccj.servant.tagstatistics.test.B;对B进行了导入(可以导入的前提是com.culiu.ccj.servant.tagstatistics.test.B
这个编译单元提供了对外公开的接口, 即public修饰的类), 而你使用import com.culiu.ccj.servant.tagstatistics.test.C;的时候会报错, 提示你找不到C, 因为不存在com.culiu.ccj.servant.tagstatistics.test.C这个编译单元(即使存在也不存在public入口)~~
接下来在程序运行到Test.java第13行时, jvm会去加载B这个类, 而加载的方式就是通过import后面的编译单元名称进行加载的(即通过com.culiu.ccj.servant.tagstatistics.test.B进行加载的, 加载的内容就是该编译单元的入口类, 就是public修饰的类), 所以
public修饰的的类名必须要与.java文件名一致, 就是为了方便在加载编译单元入口类的时候不用在进行名字转换了, 这样省了很多麻烦, 直接去加载com.culiu.ccj.servant.tagstatistics.test.B.class就行了, 而无需在进行名字转换~~~
试想一下, 如果B.java中的public类的名字是C, 那么在加载编译单元B的时候还需要去查找编译单元B的入口类名字, 假想步骤如下:
1.取得编译单元B的位置import com.culiu.ccj.servant.tagstatistics.test.B;
2.再在1的位置中找到要加载的入口类(此时因为public的类名跟B.java的文件名不一致, 所以无法直接通过1中的com.culiu.ccj.servant.tagstatistics.test.B.class来加载了, 需要进行
入口类的名称查找或者转换, 最终加载的也就是com.culiu.ccj.servant.tagstatistics.test.C.class)
这样会很麻烦~~莫不如直接规定public的类与.java文件名相同~~~
PS: 上面Test.java中有一点需要注意, 虽然B.java中的C类无法在其他包下使用, 但是却可以在其他包下通过反射来进行加载~~如第15行~~
到此, 为什么public类名必须和.java文件名相同的问题讲完了~~
接下来讲为什么.java文件中只能有一个public类~~~
因为在文件系统中, 一个文件只能有一个名字, 这里的情况就是一个编译单元(即.java文件)只能有一个名, 而这个名字的作用恰好还是jvm用来加载该编译单元入口类的, 而上面讲了jvm在
加载编译单元入口类的时候是通过编译单元名字来进行加载的, , 所以如果你的编译单元中有多个public类(即有多个入口), 那么jvm就无法分辨到底要去加载哪个入口类了~~
到此, 为什么.java文件中只能有一个public类的问题讲完了~~
写在最后:
以上内容都是本人自己的理解, 不敢保证java设计者们就是这么想的, 我写出的目的也方便日后自己忘了的时候还能翻出来此文再来回想一下曾经的想法~~
欢迎各路Java大神来讨论一下并留下自己的理解~~一切的一切都是以最终能搞明白问题的本质为目的~~所以还请口下留情~~