黑马程序员java高新技术篇----类加载与注解

来源:互联网 发布:python正态分布随机数 编辑:程序博客网 时间:2024/05/16 01:50

android培训java培训期待与您交流!

一、类的加载

1、当程序主动使用某个类时,如果该类还未被加载到内存中,系统就会通过加载、连接和初始化三个步骤对类进行初始化,如果不出意外地话,JVM会连续完成这三个步骤,所以有时候会把这三个步骤痛称为类的加载或者类的初始化。

(1)、类的加载

是指将类的class文件读入内存并创建一个对应的java.lang.Class实例对象。类的加载由类加载器完成,类加载器通常有JVM提供,JVM提供的这些加类载器通常称为系统类加载器,不过我们也可以自定义类加载器通过使用不同的类加载器,可以从不同的来源加载类的二进制数据:
●从本地文件系统加载class文件
●从jar包中加载class文件
●通过网络加载class文件
●把一个java源文件动态编译并执行加载
●某些类是可以预加载的

(2)、类的连接

通过加载生成Class对象以后就进入了连接阶段,此阶段负责将类的二进制数据合并到jre中去,这一过程分为三个阶段:
●验证:检验被加载的类是否有正确的内部结构并与其它类协调
●准备:为类的静态属性分配内存,并设置默认值
●解析:将二进制数据中的符号引用替换成直接引用

(3)、类的初始化

主要就是对静态属性进行初始化,初始化时机有:
●创建类的实例。
●调用类的静态方法。
●访问某个类或接口的静态属性或者为该属性赋值。
●使用反射方式来强制创建某个类或接口的java.lang.Class实例。
●初始化某个类的子类时引起的父类初始化。
●直接使用java.exe命令来运行某个主类时,程序会先初始化该主类。
例外情况有:
●对于一个final修饰的静态属性,如果该属性在编译时期就可以得到属性值的话,则被认为是编译时常量,使用该属性不会引起类的初始化,反之,如果此final型属性不能再编译时获得初始值,只有在运行时才可以确定其值,那么当访问该属性时将会导致类的初始化。如下:

static final String currentTime = System.currentTimeMills()+""

●当使用ClassLoader类的loadClass方法加载类时,只加载类,不会初始化类,使用Class类的forName方法是会强制初始化类。

二、类加载器

1、当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构:

(1)、Bootstrap ClassLoader:根类加载器(又称引导类加载器),负责加载java的核心类,它多负责的查找路径为系统属性sun.boot.class.path指定的值
(2)、Extension ClassLoader:扩展类加载器,负责加载jre的扩展目录或者由系统属性java.ext.dirs指定的目录中的jar包中的类
(3)、System ClassLoader:系统类加载器,负责在JVM启动时加载系统属性java.class.path或者环境变量classpath指定的路径下的类或者jar包

通过下面的小程序可以得到各个类加载器的默认加载路径信息:

public class DeepClassLoader {public static void main(String[] args){System.out.println("Bootstrap ClassLoader的默认加载路径:");String[] bootPath = System.getProperty("sun.boot.class.path").split(";");for(String path : bootPath){System.out.println("\t"+path+";");}System.out.println();System.out.println("Extension ClassLoader的默认加载路径:");String[] extPath = System.getProperty("java.ext.dirs").split(";");for(String path : extPath){System.out.println("\t"+path+";");}System.out.println();System.out.println("System ClassLoader的默认加载路径:");System.out.println("\t"+System.getProperty("java.class.path"));}}

执行结果为:

Bootstrap ClassLoader的默认加载路径:D:\Java\Jdk\jre\lib\resources.jar;D:\Java\Jdk\jre\lib\rt.jar;D:\Java\Jdk\jre\lib\sunrsasign.jar;D:\Java\Jdk\jre\lib\jsse.jar;D:\Java\Jdk\jre\lib\jce.jar;D:\Java\Jdk\jre\lib\charsets.jar;D:\Java\Jdk\jre\lib\modules\jdk.boot.jar;D:\Java\Jdk\jre\classes;Extension ClassLoader的默认加载路径:D:\Java\Jdk\jre\lib\ext;C:\Windows\Sun\Java\lib\ext;System ClassLoader的默认加载路径:D:\Eclipse\workspace\CustomizeClassLoader\bin

2、了加载机制
(1)、父类委托机制:先让父类加载器尝试加载该类,只有父类加载失败是才尝试从自己的类路径中加载该类,其委托关系看下面层次图:

 

(2)、缓存机制:此机制会保证多有加载过的类被缓存,当程序需要用到某个类时,类加载器先从缓存中查找,当缓存中没有时才会重读此类的二进制数据,并将其装换为Class实例并缓存,这就是为什么修给了Class以后要重新启动JVM,程序所做的修改才生效。

3、自定义类加载器

(1)、ClassLoader有三个关键方法
●loadClass(String name)该方法为ClassLoader的入口,根据指定的二进制名称来加载类,系统就是调用ClassLoader的该方法来获取指定类的Class对象。
●findClass(String name)根据二进制名称来查找类
●defineClass(String name,byte[] b,int off,int len)将指定类的字节码文件读入字节数组b,并把它转化为Class对象。

(2)、loadClass方法的执行步骤如下:
●用findLoadedClass(String name)来检查是否已经加载类,如果已经加载则直接返回。
●在父类加载器上调用loadClass方法,如果父类加载器为null,则使用根类加载器来加载。
●调用findClass方法查找类。
所以,当我们自定义类时,推荐重写findClass方法,以避免覆盖默认类加载器的父类委托、缓冲机制两种策略。

三、注解

1、注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

2、注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类。就像你要调用某个类,得先有开发好这个类。

3、三个基本Annotation
● @Override--用来指定方法覆盖,告诉编译器检查这个方法,并从父类查找是否包含一个被该方法重写的方法,它可以强制一个子类必须覆盖父类方法。
● @Deprecated--用于表示某个程序元素已过时。
● @SuppressWarnings--指示被Annotation标识的元素取消显示指定的编译器警告。

4、四个元Annotation
● @Retention--用于指定该Annotation可以保留多长时间,其中包含一个RetentionPolicy类型的value变量,所以使用该注解时一定要为value成员变量赋值,它的值只有三个:
   RetentionPolicy.CLASS--编译器将把注解记录在class文件中,当运行java程序时,JVM不再保留注释,这也是默认值。
   RetentionPolicy.RUNTIME--编译器将把注解记录在class文件中,当运行java程序时,JVM也会保留注释,可以通过反射获取该注解。 @Deprecated即为此类注解。
   RetentionPolicy.SOURCE--编译器将直接丢弃该策略的注解。 @Override、 @SuppressWarnings即为此类注解。
● @Target--用于指定被修饰的Annotation能够修饰哪些程序元素。其中包含一个ElementType[]类型的value变量,它的值可以为:
   ElementType.ANNOTATION_TYPE--注释类型声明
   ElementType.CONSTRUCTOR--构造方法声明
   ElementType.FIELD--字段声明(包括枚举常量)
   ElementType.LOCAL_VARIABLE--局部变量声明
   ElementType.METHOD--方法声明
   ElementType.PACKAGE--包声明
   ElementType.PARAMETER--参数声明 
   ElementType.TYPE--类、接口(包括注释类型)或枚举声明
● @Documented--用于指定被该元Annotation修饰的Annotation类可以被javadoc工具提取成文档,即API文档中将会包含该Annotation。
● @Inherited--被它修饰的Annotation将具有继承性。

5、反射里面与Annotation相关的方法:
(1)、getAnnotation(Class<T> annotationClass)如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null。
(2)、getDeclaredAnnotations()、getAnnotations()返回直接存在于此元素上的所有注释。
(3)、isAnnotationPresent(Class<? extends Annotation> annotationClass)如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。

6、自定义注解
注解的自定义形式与接口相似,只是在定义属性和访问属性时都是以方法的形式,如下:

@Retention(RetentionPolicy.RUNTIME)//定义该注解运行时可通过反射获取@Target(ElementType.METHOD)   //定义该注解只可作用于方法public @interface MyAnnotation {    //类似于接口定义形式String value() default "jzk";  //以方法的形式定义了一个字符类型的成员变量,且指定默认值}public class AnnotationTest {    public static void main(String[] args) throws Exception{System.out.println(MainClass.class.getMethod("test")    //判断指定方法是否有指定类型的注解.isAnnotationPresent(MyAnnotation.class));System.out.println(MainClass.class.getMethod("test")    //获取注解实例,然后获取成员值.getAnnotation(MyAnnotation.class).value());    }        //指定value值,此时指定值覆盖默认值,当自定义注解只有一个value成员变量时    //也可以使用 @MyAnnotation("jkk")这种形式    @MyAnnotation(value="jkk") // @MyAnnotation()    使用value成员的默认值    public void test(){}}

自定义注解的成员变量类型可以为:8中基本类型,枚举类型,注解类型,Class类型,或者前面任何一种类型的数组,如下

public @interface AnnoElement {String value();}public enum EunmElement {A,B,C;}@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface MyAnnotation {String strElement();EunmElement enumEemelms();int[] intArray();AnnoElement annoElement();Class<?> classElement();}//要使用就得逐个赋值@MyAnnotation(strElement="jzk",enumEemelms=EunmElement.A,annoElement= @AnnoElement("jkk"),classElement=AnnoElement.class,intArray={1,2,3})


 

 

原创粉丝点击