JAVA通幽(六)注解、反射和类加载机制
前言
本章学习注解、反射以及类的加载机制
范例
1.注解
注解,jdk1.5提出的概念,简单的理解就是给元素或方法或类添加额外的辅助信息,相当于一个标签一样,如我们后面学习hibernate时,有个注解叫@Service,该注解标注在某个Service的实现类上面,代表其是一个service,可以被程序识别。
语法:@注释名(参数=值)
还比如,刚刚的注解你可能还不太知道,但是下面的注解你总知道的吧。
大家是不是有点恍然大悟,这个@Override就代表该方法是重写的方法,我们通过查看其源码可以清楚的知道其定义的结构:
2.自定义注解的使用
语法:public @interface 注解名{注解体}
当然了,我们自己定义的注解上也需要加一些java提供好的注解来进一步解释,如我们上面看到的@Target
首先介绍一下元注解的使用(@Target、)
- @Target:它用来描述注解的适用范围(其值是一个常量或多个常量,常量为ElementType枚举类下的常量)
形如:@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) ,该写法需静态导入
- @Retention:它用来描述注解的生效时机(其值是一个常量或多个常量,常量为RetentionPolicy枚举类下的常量)
形如@Retention(RetentionPolicy.RUNTIME) ,表示运行程序时有效
package com.jwang.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//定义注解使用的范围
@Target(ElementType.METHOD)
//定义注解应用级别
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//定义注解参数
//语法:数据类型 属性值() [default 默认值]
String value() default "";
}
这样的话,其他类中的方法上就可以使用该注解了
package com.jwang.test;
public class AnnotationTest {
//当注解只有一个属性的时候,我们建议注解属性为value
//当注解的属性值为value的时候,可以不用写value="XX"
@MyAnnotation("注解")
//@MyAnnotation(value="注解")
public void play(){
System.out.println("玩游戏");
}
}
这样,后期我们再通过反射对注解进行一个解析就可以真正发挥注解的作用了,只是定义一个注解没有丝毫意义。
比如后期学到的spring对注解的一个扫描自动实例化就是一个典型的应用
3.反射机制
(1)Class类对象的获取方法:
- 运用Class.forName(类名完整路径);
- 运用对象.getClass()
- 运用:类名.class
首先我们看一个例子,我们通过Class类获取一个类的信息
package com.jwang.test;
@SuppressWarnings("all")
public class ReflectTest {
public static void main(String[] args) {
try {
Class clazz1 = Class.forName("com.jwang.test.Person");
Class clazz2 = new Person().getClass();
Class clazz3 = Person.class;
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
(2)运用Class对象获取类的名字、属性、方法、构造器等
设置实体类信息如下:
package com.jwang.test;
public class Person {
private int id;
private String name;
public String loc;
private Person(int id, String name) {
this.id = id;
this.name = name;
}
private void play(){
System.out.println("play game");
}
public Person() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
}
- 获得类的名字
- 获得类的属性
- 获得类的普通方法
- 获得类的构造方法
(3)利用反射操作构造方法、普通方法以及属性
- 调用类对象方法newInstance()创造对象,默认调用对象的无参构造方法,如果没有将报错。这里多说一嘴,我们后期学的很多框架,比如Spring等等,自动实例化的底层实现都是基于此方法,所以,当我们为类添加了其他构造方法的同时,请一定要记住,给指定类加入无参构造方法!!
Class clazz = Class.forName("com.jwang.test.Person");
Person person = (Person) clazz.newInstance();
System.out.println(person);
- 操作指定的构造方法创造对象
Class<Person> clazz = (Class<Person>) Class.forName("com.jwang.test.Person");
Constructor<Person> constructor = clazz.getDeclaredConstructor(int.class,String.class);
Person person = constructor.newInstance(1,"张三");
System.out.println(person);
- 利用反射操作普通方法
Class<Person> clazz = (Class<Person>) Class.forName("com.jwang.test.Person");
Method method = clazz.getDeclaredMethod("play", null);
method.invoke(clazz.newInstance(), null);
- 利用反射操作属性
Class<Person> clazz = (Class<Person>) Class.forName("com.jwang.test.Person");
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);
Person person = clazz.newInstance();
field.set(person, "王老五");
System.out.println(field.get(person));
后面将有反射操作泛型,以及反射操作注解。敬请期待。。。
4.类加载机制
首先来看一下类的加载过程:
- Loading:加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,这里既可以从ZIP包中读取(比如从jar包和war包中读取),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)
- Verification:这一阶段的主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
- Preparation:准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。
- Resolution:解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程。
- Initialization:初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由JVM主导。到了初始阶段,才开始真正执行类中定义的Java程序代码。