顺便复习一下类加载器,java中的双亲委派模型如下:
启动类加载器:负责加载存放在JDK\jre\lib下或是Xbootclasspath参数指定的路径中能被JVM识别的类(不会加载不识别的类),无法在java程序中引用该加载器。
扩展类加载器:负责加载存放在JDK\jre\lib\ext下或是由java.ext.dirs系统变量指定的路径中的所有类库,可被java程序引用。
应用程序加载器:负责加载用户指定的类路径classpath上的类,可被java代码引用(使用ClassLoader.getSystemClassLoader()),如果没有定义自定义加载器,则该加载器默认为自定义加载器。
自定义类加载器:由程序员自己实现的类加载器。
双亲委派模型的工作方式:当一个类加载器接收到加载类请求时,会先将请求上传给自己的父加载器,父加载器又上交给自己的父加载器,最终传递给启动类加载器,当父加载器在自己的搜索范围没有找到对应类时,子类加载器才会开始尝试加载。
顺带提一下,我们常用的java.lang包存储在JDK\jre\lib中的rt.jar包下,利用jd_gui反编译后如下图:
那么类加载器通过什么来找到想要加载的类呢?通过类的全限定名,即包名+类名,例如java.lang.String,在class文件中会变为java\lang\String,其实,java和lang都是一个文件名,String是一个class文件,利用全限定名来加载类有很多好处,拿java.lang.String来说,当类加载请求传递给启动类加载器时,启动类加载器首先会在搜索范围内查找名为java的文件,若找到,会接着找名lang的文件,最后查找名为String的class文件,若没找到名为java的文件,启动类加载器会将加载请求丢给扩展类加载器,减少搜索时间,和子网一个道理,另外,使用全限定名加载类,意味着不同的包可以有名称相同的类。
那么class文件中的全限定名是怎么来的呢?
编译器编译时加上的。
那么编译器如何知道类的全限定名呢?
由import提供,import的主要作用就是为编译器提供全限定名。
import有两种语法:
A、import 包名.类名;
B、import 包名.*;
两者有什么区别呢?记得java老师是这么说的,语法B会加载所有的类,语法A会加载对应类,现在看来貌似不是,JVM只在运行时动态加载所需要的类。两者的区别我觉得主要体现在编译期。
下面进行一个实验,项目结构如下:
newclass0.deal.java代码如下:
package newclass0;
public class deal
{
public void output()
{
System.out.println("这是newclass0");
}
}
newclass1.deal.java代码如下:
package newclass1;
public class deal
{
public void output()
{
System.out.println("这是newclass1");
}
}
图一:
首先,java不允许import相同类名,为什么?
我们想使用import的原因之一是为了在引用同一个项目不同包中的类时,可以直接使用类名,若图一允许的话,我们想使用deal类,还是要加上包名来指出我们想引用哪个包的deal类。import毫无作用,所以java语法直接不允许这么使用。
图二:
从上图可以看到,当我们使用语法A时,编译器首先会查找newclass1包,查找到后在查找deal4类,没有查找到,所以报错。
图三:
注意出错的位置,不在import,说明编译器在编译import语句时只会查找是否有newclass1包和newclass0包,不会具体查找里面的类,在出错位置,编译器在newclass0中发现类deal后并没有停止搜索,会在newclass1中继续搜索,运用语法B,则编译器会把所有运用语法B导入的包中的类都查一遍,以确保类的全限定名具有唯一性,若我们把
deal x=new deal();
改为:
newclass0.deal x=new newclass0.deal();
编译后通过,因为我们直接给出了类的全限定名,此时import语句无效(import就是为了提供全限定名的,但全限定名已经给出)
图四:
从上图可以看出,提供全限定名来说,优先级为语法A>语法B,若语法A满足全限定名,不会在去查找语法Bimport的包。
总结:在编译import语句时,编译器会查找项目中是否有对应的包或类,import编译通过后,下文中用到的import语句导入的类,编译器会根据import语句自动将其转换为包名\类名的全限定名,import主要是为了提供全限定名。