黑马程序员——JAVA学习笔记十三(高新技术二)
8, 注解
Annotation(注解)是JDK5.0及以后版本引入的。
注解是以 @注解名 的形式标识
注解不会影响程序语义,只作为标识
注解是新的类型(与接口很相似),它与类、接口、枚举是在同一个层次,它们都称作为java的一个类型(TYPE)。
它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
它的作用非常的多,例如:进行编译检查、生成说明文档、代码分析等
注释类型 是一种特殊的接口
用 @interface 声明如
public@interface MyAnnotation
{
属性
方法
}
注解可以有属性和方法,但只能定义 public abstract 的方法和 static final 的属性
所有定义的方法被编译器默认都是 public abstract
方法的返回类型可以是基本数据类型,String ,Class , 枚举 , 注解及这些类的数组
可以在方法声明中定义默认返回值 ,如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,例如:@MyAnnotation("lhm")。
public@interface MyAnnotation
{
static final 属性名;
public abstract 返回类型方法名() default 默认值;
}
1
2
3
4
5
6
7
8
9
10
11
12
|
//元注解,元数据,元信息。注解的注解。 //生命周期 SOURECE CLASS RUNTIME: @Retention (RetentionPolicy.RUNTIME) @Target ({ElementType.METHOD,ElementType.TYPE}) //什么地方被标记 public @interface AnnotationDemo { String color() default "blue" ; int value() default 0 ; Class<?> clazz() default String. class ; int [] arrayAttr() default { 1 , 2 }; //数组类型 EnumType.TrafficLamp lamp () default EnumType.TrafficLamp.GREEN; MetaAnnotation metaannotation () default @MetaAnnotation ( "metaannotation" ); } |
a ), @Override
Override 的源码
@Target(ElementType.METHOD) //只能用来注解方法
@Retention(RetentionPolicy.SOURCE) //只保留在源文件 , 编译后去掉
public @interface Override {
}
用在方法定义之前表示在重写父类某个方法 ,如果方法利用此注释类型进行注解但没有重写超类方法,
则编译器会生成一条错误消息
b ), @Deprecated其作用是标记某个过时的类或方法 , 不赞成使用
c), @SuppressWarnings
SuppressWarnings 的源码
//可以注解接口,类,枚举,注解类型,域,方法,参数,构造方法,局部变量
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE) //保留到源文件 , 编译后去掉
public @interface SuppressWarnings {
String[] value();
}
其作用是告诉编译器 不要发出被注解元素(以及包含在该元素中的所有程序元素)的某些警告信息
参数有
deprecation,使用了过时的类或方法时的警告
unchecked,执行了未检查的转换时的警告
fallthrough,当 Switch 程序块直接通往下一种情况而没有 Break 时的警告
path,在类路径、源文件路径等中有不存在的路径时的警告
serial,当在可序列化的类上缺少serialVersionUID 定义时的警告
finally ,任何 finally 子句不能正常完成时的警告
all,关于以上所有情况的警告
(2)java.lang.annotation包下的注解类 , 用来注解注解类
a), @Target表示该注解用于什么地方,Target 注解类源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
ElementType 是枚举
public enum ElementType {
TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR,
LOCAL_VARIABLE, ANNOTATION_TYPE,PACKAGE
}
可能的 ElementType 参数包括:
ElementType.PACKAGE 包声明
ElementType.TYPE 类Class,接口Interface,枚举Enum, 注解类型Annotation 声明
ElementType.FIELD 域声明(包括 enum 实例)
ElementType.CONSTRUCTOR 构造器声明
ElementType.METHOD 方法声明
ElementType.PARAMETER 参数声明
ElementType.LOCAL_VARIABLE 局部变量声明
ElementType.ANNOTATION_TYPE 注解类型声明
b),@Retention 表示在什么级别保存该注解信息。Retention注解类源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
返回类型是枚举
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
可选的 RetentionPolicy 参数包括:
RetentionPolicy.SOURCE 注解将被编译器丢弃
RetentionPolicy.CLASS 注解在class文件中可用,但会被JVM丢弃
RetentionPolicy.RUNTIME JVM将在运行期也保留注释,因此可以通过反射机制读取注解的信息。
c), @Documented 声明注解类型信息可以显示到 java doc文档中
d), @Inherited 允许子类继承父类中的注解
注解的生命期
注解的生命期有三个 , 默认只保留到class文件 , 在运行时就会消失
可以通过在一个注解类前定义了@Retetion 明确保留到什么时期
源码(.java) ,
通过 Retetion(RetentionPolicy.SOURCE) 注解的注解类,注解只保留在一个源文件当中,
当编译器将源文件编译成class文件时,编译器将其去掉
编译后文件(.class) ,
通过 Retetion(RetentionPolicy.CLASS)注解的注解类,注解在源码、编译好的.class文件中保留,
当加载class文件到内存时,虚拟机将其去掉
运行时
通过 Retetion(RetentionPolicy.RUNTIME)注解的注解类,该注解在源码、编译好的.class文件中保留
在程序运行期间都会存在内存当中。此时,我们可以通过反射来获得定义在某个类上的所有注解
注解的使用 : 定义注解-->声明注解-->使用注解-->注解信息的提取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
@AnnotationDemo ( 3 ) public class AnnotationTest { @SuppressWarnings ( "unused" ) //忽略警告 private int x; @SuppressWarnings ( "unused" ) //忽略警告 private int y; public AnnotationTest( int x, int y) { super (); this .x = x; this .y = y; } @Override //表示已经覆盖 public int hashCode() { // TODO Auto-generated method stub return super .hashCode(); } @Override //表示已经覆盖 public boolean equals(Object obj) { // TODO Auto-generated method stub return super .equals(obj); } @SuppressWarnings ( "deprecation" ) //忽略警告 @AnnotationDemo (color= "red" , arrayAttr={ 4 , 5 , 6 }, value= 9 , lamp=EnumType.TrafficLamp.RED, clazz = System. class , metaannotation= @MetaAnnotation ( "hello" )) public static void main(String[] args) { // TODO Auto-generated method stub System.runFinalizersOnExit( true ); if (AnnotationTest. class .isAnnotationPresent(AnnotationDemo. class )){ AnnotationDemo anno = AnnotationTest. class .getAnnotation(AnnotationDemo. class ); System.out.println(anno.color()+ ".........." + anno.value()+ "......." +anno.lamp()+ "......." + anno.clazz().getName()+ "......" +anno.metaannotation().value()); for ( int i:anno.arrayAttr()) { System.out.print(i+ "," ); } } Method[] methods = AnnotationTest. class .getMethods(); for (Method method: methods) { if (method.getName().equals( "main" )){ AnnotationDemo anno = method.getAnnotation(AnnotationDemo. class ); System.out.println( "\r\n" +anno.color()+ ".........." +anno.value()+ "......." +anno.lamp() + "......." +anno.clazz().getName()+ "......" +anno.metaannotation().value()); for ( int i:anno.arrayAttr()) { System.out.print(i+ "," ); } } } } @Deprecated //过时了 public static void sayHello(){ System.out.println( "hi,wufei" ); } } |
通过反射获得注解
对于生命周期为运行期间的注解,都可以通过反射获得该元素上的注解实例。
1、声明在一个类中的注解可以通过该类Class对象的getAnnotation或getAnnotations方法获得。
2、声明在一个字段中的注解通过Field对象的getAnnotation或getAnnotations方法获得
3、声明在一个方法中的注解通过Method对象的getAnnotation或getAnnotations方法获得
9, 类加载器
顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。下面详细介绍这个 Java 类。
java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。不过本文只讨论其加载类的功能。为了完成加载类的这个职责,ClassLoader提供了一系列的方法,比较重要的方法如 表 1所示。关于这些方法的细节会在下面进行介绍。
ClassLoader 中与加载类相关的方法
方法 说明
getParent() 返回该类加载器的父类加载器。
loadClass(String name) 加载名称为 name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name) 查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name) 查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
resolveClass(Class<?> c) 链接指定的 Java 类。
类加载器的树状组织结构
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,不是用JAVA类,并不继承自 java.lang.ClassLoader。
扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
1
2
3
4
5
6
7
8
9
10
11
|
public class ClassLoaderTree { public static void main(String[] args) { ClassLoader loader = ClassLoaderTree. class .getClassLoader(); while (loader != null ) { System.out.println(loader.toString()); loader = loader.getParent(); } } } |
类加载器的代理模式:
类加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。
加载类的过程
在前面介绍类加载器的代理模式的时候,提到过类加载器会首先代理给其它类加载器来尝试加载某个类。这就意味着真正完成类的加载工作的类加载器和启动这个加载过程的类加载器,有可能不是同一个。真正完成类的加载工作是通过调用 defineClass来实现的;而启动类的加载过程是通过调用 loadClass来实现的。前者称为一个类的定义加载器(defining loader),后者称为初始加载器(initiating loader)。在 Java 虚拟机判断两个类是否相同的时候,使用的是类的定义加载器。也就是说,哪个类加载器启动类的加载过程并不重要,重要的是最终定义这个类的加载器。两种类加载器的关联之处在于:一个类的定义加载器是它引用的其它类的初始加载器。如类 com.example.Outer引用了类 com.example.Inner,则由类 com.example.Outer的定义加载器负责启动类 com.example.Inner的加载过程。
方法 loadClass()抛出的是 java.lang.ClassNotFoundException异常;方法 defineClass()抛出的是 java.lang.NoClassDefFoundError异常。
类加载器在成功加载某个类之后,会把得到的 java.lang.Class类的实例缓存起来。下次再请求加载该类的时候,类加载器会直接使用缓存的类的实例,而不会尝试再次加载。也就是说,对于一个类加载器实例来说,相同全名的类只加载一次,即 loadClass方法不会被重复调用。
虽然在绝大多数情况下,系统默认提供的类加载器实现已经可以满足需求。但是在某些情况下,您还是需要为应用开发出自己的类加载器。比如您的应用通过网络来传输 Java 类的字节代码,为了保证安全性,这些字节代码经过了加密处理。这个时候您就需要自己的类加载器来从某个网络地址上读取加密后的字节代码,接着进行解密和验证,最后定义出要在 Java 虚拟机中运行的类来一般来说,自己开发的类加载器只需要覆写 findClass(String name)方法即可。java.lang.ClassLoader类的方法 loadClass()封装了前面提到的代理模式的实现。该方法会首先调用 findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,会调用父类加载器的 loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,就调用 findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,最好不要覆写 loadClass()方法,而是覆写 findClass()方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
private String classDir ; @Override protected Class<?> findClass (String name) throws ClassNotFoundException { String classPath = classDir + "\\" +name+ ".class" ; FileInputStream fis = null ; try { fis = new FileInputStream(classPath); ByteArrayOutputStream buf = new ByteArrayOutputStream(); cyher(fis, buf); System.out.println( "heh" ); byte [] bufr = buf.toByteArray(); return defineClass( null ,bufr, 0 , bufr.length); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { } finally { if (fis != null ) try { fis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return null ; } |
线程上下文类加载器
线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。