Java的反射与注解

反射

理论

在面向对象的编程语言如Java中,反射允许在编译期间不知道接口的名称,字段(fields,即成员变量)、方法的情况下在运行时检查类、接口、字段和方法。它还允许根据判断结果进行实例化新对象和不同方法的调用。
反射还可以使给定的程序动态地适应不同的运行情况。例如,考虑一个应用程序,它使用2个不同的类X和Y互相交替执行类似的操作。没有使用面向反射编程技术,应用程序可能是硬编码的(即把代码写死,缺乏灵活性),以调用方法名称的类X和Y类。然而,使用面向反射的编程范式中,应用程序可以在设计和编写利用反射在没有硬编码方法名称情况下调用类中的方法X和Y。
Java 中,无论生成某个类的多少个对象,这些对象都会对应于同一个 Class 对象。

  • Class类:反射的核心类,可以获取类的属性,方法等信息。
  • Field类:Java.lang.reflec包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
  • Method类: Java.lang.reflec包中的类,表示类的方法,它可以用来获取类中的方法信息或者执行方法。
  • Constructor类: Java.lang.reflec包中的类,表示类的构造方法。

用处

可以动态的运行指定的类和方法而不用重新编译

获取对象和方法

被反射的类

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
public interface PersonInterface {
void myInterface();
}
.....
......
public class Preson implements PersonInterface {
private String name;
Preson() {}
private Preson(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void printName(){
System.out.println(name);
}
@Override
public void myInterface() {
// TODO Auto-generated method stub
}
}

反射操作

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
//throws Exception
Class<?> clazz2=new Preson().getClass();//通过对象获取Class对象
Class<?> clazz1=Preson.class;//调用类的class属性获取Class对象
Class<?> clazz=Class.forName("com.cczhr.reflection.Preson");//使用类的全路径获取Class对象
Method[] methods=clazz.getDeclaredMethods();
for(Method m:methods){
System.out.println("获取方法"+m);
}
Field[] field=clazz.getDeclaredFields();
for(Field f:field){
System.out.println("获取成员属性"+f);
}
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
System.out.println("获取构造方法"+c);
}
Class<?>[] clazzInter= clazz.getInterfaces();
for(Class c:clazzInter){
System.out.println("获取Class对象的接口"+c);
}
System.out.println("获取Class对象的父类"+clazz.getSuperclass());

输出结果

生成对象、执行方法、修改成员变量

生成对象

1
2
3
4
5
6
7
8
9
Class<?> clazz=Class.forName("com.cczhr.reflection.Preson");//使用类的全路径获取Class对象
Preson p=(Preson) clazz.newInstance();//生成对象
/*
生成有构造参数的对象
//获取构造方法
Constructor c=clazz.getDeclaredConstructor(String.class);
//创建对象并设置属性
Person p1=(Person) c.newInstance(小明");
*/

执行方法(需要先生成对象)

1
2
3
4
5
6
Class<?> clazz=Class.forName("com.cczhr.reflection.Preson");//使用类的全路径获取Class对象
Preson p=(Preson) clazz.newInstance();//生成对象
//getDeclaredMethod可以获取所有方法getMethod获取的是public方法
Method method=clazz.getDeclaredMethod("printName", String.class);//方法名,参数 多参数可以这样写String.class,int.class
method.setAccessible(true);//私有方法的执行,必须在调用invoke之前加上一句,使其变为可访问
method.invoke(p, "哈哈");//实例化对象,参数

被执行的方法
Preson.java

1
2
3
4
5
6
...
private void printName(String name){
this.name=name;
System.out.println(name);
}
....

修改成员变量(需要先生成对象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Class<?> clazz = Class.forName("com.cczhr.reflection.Preson");// 使用类的全路径获取Class对象
Preson p = (Preson) clazz.newInstance();// 生成对象
p.setName("哈哈");
//获取class对象的字段
Field field = clazz.getDeclaredField("name");
field.setAccessible(true);// 私有的加上这一句
// 获取字段的值并打印
Object val = field.get(p);//上面的field设了要获取的是name字段
System.out.println(val);
field.set(p, "yoyo");//给字段赋值
// 获取字段的值并打印
Object val2 = field.get(p);
System.out.println(val2);

类加载器

类的加载 连接 初始化

加载:
查找并加载类的二进制数据
连接:
验证:确保被加载的类的正确性
准备:为类的静态变量分配内存,并将其初始化为默认值
解析:把类中的符号引用转换为直接引用
初始化:
为类的静态变量赋予正确的初始值

类加载器

Java自带的加载器
根类加载器(使用 C++编写,程序员无法在 Java 代码中获得该类)
扩展加载器,使用 Java 代码实现
系统加载器(应用加载器),使用 Java 代码实现
用户自定义的类加载器都是 java.lang.ClassLoader 的子类

类的初始化条件

1.创建类的实例
2.访问某个类或接口的静态变量,或者对该静态 变量赋值
3.调用类的静态方法
4.反射(如 Class.forName(“com.shengsiyuan.Test”) )
5.初始化一个类的子类
6.Java虚拟机启动时被标明为启动类的类(Java Test)
注意:
1.只有当程序访问的静态变量或静态方法确实在当前类或当前接口中定义时,才可以 认为是对类或接口的主动使用
2.调用ClassLoader类的loadClass方法加载 一个类,并不是对类的主动使用,不会导致类的初始化
3.如果是编译时就知道的常量类不会初始化
如果是在运行时才知道的常量类会被初始化

通过类加载器获取文件



文件路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Class<?> clazz=Class.forName("com.cczhr.reflection.Preson");
InputStream in1= clazz.getClassLoader().getResourceAsStream("com/cczhr/reflection/t1.txt");
System.out.println(getStr(in1));
InputStream in2= clazz.getClassLoader().getResourceAsStream("t2.txt");
System.out.println(getStr(in2));
.....
.....
private static String getStr(InputStream is) throws IOException{
String str=null;
StringBuffer stringBuffer=new StringBuffer();
InputStreamReader read = new InputStreamReader (is,"utf-8");
BufferedReader br= new BufferedReader(read);
while(null!=(str=br.readLine())){
stringBuffer.append(str);
}
br.close();
return stringBuffer.toString();
}

输出

注解

理论

Java注解,是Java语言5.0版本开始支持加入源代码的特殊语法元数据。
Java语言中的类、方法、变量、参数和包等都可以被标注。
Java注解可以通过反射获取标注内容。
在编译器生成类文件时,注解可以被嵌入到字节码中。
Java虚拟机可以保留注解内容,在运行时可以获取到标注内容。
当然它也支持自定义Java注解

作用在代码的注解

@Override - 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
@Deprecated - 标记过时方法。如果使用该方法,会报编译警告。
@SuppressWarnings - 指示编译器去忽略注解中声明的警告。

作用在注解的注解(元注解)

Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
@Documented - 标记这些注解是否包含在用户文档中。
@Target - 标记这个注解应该是哪种 Java 成员。
@Inherited - 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)

Java 7 开始才有的注解

@SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。 @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
@Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。

自定义注解

Retention

例子

1
@Retention(RetentionPolicy.RUNTIME)

其中RetentionPolicy有三种级别
SOURCE:注解将被编译器丢弃
CLASS:注解在class文件中可用,但会被VM丢弃。
RUNTIME:VM将在运行期间也保留注解,因此可以通过反射机制读取注解的信息。

Target

Target可以限定运用的场景。
例子

1
@Retention(ElementType.METHOD)

ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
ElementType.CONSTRUCTOR 可以给构造方法进行注解
ElementType.FIELD 可以给属性进行注解
ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
ElementType.METHOD 可以给方法进行注解
ElementType.PACKAGE 可以给一个包进行注解
ElementType.PARAMETER 可以给一个方法内的参数进行注解
ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

Inherited

假如父类用了@Inherited注解 那么子类也会注解父类所用的注解
例子

1
@Inherited

自定义

定义的注解

1
2
3
4
5
6
7
8
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface MyAnnotation {
String name() default "哈哈";//default后面是默认值
int age() default 18;
}

定义一个类使用注解

1
2
3
4
5
6
7
8
9
10
11
12
@MyAnnotation(name="yoyo",age=11)
public class Person {
private String name;
private int age;
@MyAnnotation()
public void test(){
}
public void outPrint(){
System.out.println(name+age);
}
}

通过反射获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Class<?> clazz=Class.forName("com.cczhr.annotation.Person");//使用类的全路径获取Class对象
if(clazz.isAnnotationPresent(MyAnnotation.class)){//获取类的注解
MyAnnotation myClazzAnnotation= clazz.getAnnotation(MyAnnotation.class);
System.out.println(myClazzAnnotation.name()+myClazzAnnotation.age());
}
Method method=clazz.getDeclaredMethod("test");
if(method.isAnnotationPresent(MyAnnotation.class)){//获取方法的注解
MyAnnotation myAnnotation= method.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.name()+myAnnotation.age());
}

运行结果

枚举

使用

定义枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public enum MyEnum {
Hello(1,"你好"),World(2,"世界"),Java(3,"爪哇");
private MyEnum(int type, String name) {
this.type = type;
this.name = name;
}
private int type;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}

使用

1
2
3
4
5
System.out.println(MyEnum.Hello);
System.out.println(MyEnum.Hello.getName());
MyEnum.Hello.setName("修改name");
System.out.println(MyEnum.Hello.getName());
System.out.println(MyEnum.Hello.getType());

输出结果