Java认为这些编译后的class文件,这种事物也是一种对象,它也给抽象成了一种类,这个类就是Class
。
获取方式如下:
代码示例:
public class ReflectDemo01 {
public static void main(String[] args) throws Exception {
Class cls1 = Class.forName("com.slxy.domain.Person");
System.out.println(cls1); //class com.slxy.domain.Person
Class cls2 = Person.class;
System.out.println(cls2); //class com.slxy.domain.Person
Person p = new Person();
Class cls3 = p.getClass();
System.out.println(cls3); //class com.slxy.domain.Person
//地址相同
System.out.println(cls1 == cls2); //true
System.out.println(cls1 == cls3); //true
}
}
结论:
- 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
Field 代表类的成员变量(成员变量也称为类的属性)。
Field[] getFields()
:获取所有public修饰的成员变量。Field getField(String name)
:获取指定名称的 public修饰的成员变量。Field[] getDeclaredFields()
:获取所有的成员变量,不考虑修饰符。Field getDeclaredField(String name)
:获取所有的成员变量,不考虑修饰符。Person类(准备反射的类):
public class Person {
//Person类有不同修饰符的属性
private String name;
private int age;
public int a;
int b;
protected int c;
public void eat() {
System.out.println("eat...");
}
public void eat(String food) {
System.out.println("eat..." + food);
}
//get,set方法已省略...
}
实现类:
public class ReflectDemo02 {
public static void main(String[] args) throws Exception {
//获取Person的Class对象 Person类
Class personClass = Person.class;
//获取Person类的所有的以public修饰的成员变量
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field); //public int com.slxy.domain.Person.a
}
//获取Person类的成员变量a
Field a = personClass.getField("a");
Person p = new Person();
//获取a属性的值
Object value = a.get(p);
System.out.println(value); //0
//更改a属性的值
a.set(p,18);
System.out.println(p); //p=18
//获取所有的成员变量,不考虑修饰符。
Field[] all = personClass.getDeclaredFields();
for (Field field : all) {
System.out.println(field);
//private java.lang.String com.slxy.domain.Person.name
//private int com.slxy.domain.Person.age
//public int com.slxy.domain.Person.a
//int com.slxy.domain.Person.b
//protected int com.slxy.domain.Person.c
}
//获取成员变量,不考虑修饰符。
Field name = personClass.getDeclaredField("name");
//忽略权限修饰符的安全检查(也称暴力反射)
//因为Person属性是私有的,如果不忽略,会非法访问异常
name.setAccessible(true);
name.set(p,"张三");
System.out.println(name.get(p)); //张三
}
}
小贴士
- 带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。
Constructor代表类的构造方法,newInstance()
会根据传递的参数创建类的对象。
Constructor>[] getConstructors()
Constructor getConstructor(类>... parameterTypes)
Constructor getDeclaredConstructor(类>... parameterTypes)
Constructor>[] getDeclaredConstructors()
代码示例:
public class ReflectDemo03 {
public static void main(String[] args) throws Exception {
//获取Person的Class对象
Class<Person> personClass = Person.class;
//创建有参对象
Constructor con1 = personClass.getConstructor(String.class, int.class);
System.out.println(con1);
Object person1 = con1.newInstance("张三", 23);
System.out.println(person1);
//创建空参对象
Constructor con2 = personClass.getConstructor();
Object person2 = con2.newInstance();
System.out.println(person2);
//简化写法
Person person3 = personClass.newInstance();
System.out.println(person3);
}
}
Method代表类的方法,invoke()
传递object对象及参数调用该对象对应的方法。
Method[] getMethods()
Method getMethod(String name, 类>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类>... parameterTypes)
代码示例:
public class ReflectDemo04 {
public static void main(String[] args) throws Exception {
//获取Person的Class对象
Class<Person> personClass = Person.class;
//根据指定名称获取指定方法
Method eat1 = personClass.getMethod("eat");
Person p = new Person();
eat1.invoke(p); //eat...
Method eat2 = personClass.getMethod("eat", String.class);
eat2.invoke(p, "food"); //eat...food
//获取所有pubilc修饰的方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method);
//获取方法名称
System.out.println(method.getName());
}
//获取类名
String name = personClass.getName();
System.out.println(name); //com.slxy.domain.Person
}
}
配置文件pro.properties
ClassName=com.slxy.domain.Student
MethodName=sayHello
对象类Student
public class Student {
public void sayHello() {
System.out.println("Hello");
}
}
实现类
public class ReflectTest {
public static void main(String[] args) throws Exception {
//加载读取配置文件
Properties pro = new Properties();
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);
String ClassName = pro.getProperty("ClassName");
String MethodName = pro.getProperty("MethodName");
//使用反射技术来加载类文件进内存
Class cls = Class.forName(ClassName);
//创建对象
Constructor constructor = cls.getConstructor();
Object stu = constructor.newInstance();
//执行方法
Method method = cls.getMethod(MethodName);
method.invoke(stu);
}
}
注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。
作用分类
@Override
:检测被该注解标注的方法是否是继承自父类(接口)的@Deprecated
:该注解标注的内容,表示已过时@SuppressWarnings
:压制警告代码示例:
//压制所有警告
@SuppressWarnings("All")
public class AnnotationDemo02 {
@Override
public String toString() {
return super.toString();
}
@Deprecated
public void show() {
//已过时
}
public void demo() {
show();
}
}
自定义注解格式如下:
元注解
public @interface Anno01 {
属性列表;
}
通过javap
命令反编译注解的字节码文件:
Compiled from "Anno01.java"
public interface Anno01 extends java.lang.annotation.Annotation {
}
结论
注解本质上就是一个接口,该接口默认继承Annotation接口。
属性就是接口(注解)中的抽象方法,且返回值类型有限制:
可以使用的返回值类型:
定义了属性,在使用时需要给属性赋值:
default
关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值。value
,则value
可以省略,直接定义值即可。代码示例:
//注解
public @interface MyAnno01 {
int max() default 1;
String value();
//enum Person{};
MyAnno02 anno02();
String[] values();
}
实现类:
@MyAnno01(value = "All", anno02 = @MyAnno02(),values = {"a","b"})
public class Worker {
}
用于描述注解的注解。
//元注解@Target源码分析
package java.lang.annotation;
@Documented //可以被生成api文档
@Retention(RetentionPolicy.RUNTIME) //注解保留到运行时阶段
@Target({ElementType.ANNOTATION_TYPE}) //注解的作用位置为:注解类(ANNOTATION_TYPE)
public @interface Target {
ElementType[] value(); //注解的属性为enum枚举类型,且名称为value,可以省略value。
}
代码示例:
//自定义的注解
//注解的作用位置为方法,类,成员属性
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
//注解保留到运行时阶段
@Retention(RetentionPolicy.RUNTIME)
//注解可以被api文档抽取
@Documented
//注解会被子类继承
@Inherited
public @interface MyAnno03 {
//这是定义的自定义注解
}
实现类:
//因为注解的Target描述中有ElementType.TYPE,所以可以作用于类上
@MyAnno03
public class Worker {
//因为ElementType.FIELD,所以可以作用于变量上
@MyAnno03
String str;
//因为ElementType.METHOD,所以可以作用于方法上
@MyAnno03
public void show(){
}
}
小贴士
使用javadoc命令,可以将java文件生成api文档,java文件上类或方法的注释说明,也会被抽取到api文档。
注解的解析就是通过反射获取注解中定义的属性值。
Class
对象的getAnnotation()
:用来获取注解,生成了一个该注解接口的子类对象。案例:对前面反射的案例进行改进,使用注解的方式代替读取配置文件:
//注解只能作用于类上
@Target(ElementType.TYPE)
//注解保留到运行时阶段
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
String className();
String methodName();
}
实现类:
@Pro(className = "com.slxy.domain.Student",methodName = "sayHello")
public class ReflectTest {
public static void main(String[] args) throws Exception {
//获取字节码对象
Class<ReflectTest> reflectTestClass = ReflectTest.class;
//获取注解对象
Pro annotation = reflectTestClass.getAnnotation(Pro.class);
//其实就是在内存中生成了一个该注解接口的子类对象
/*
public class ProImpl implements Pro{
public String className(){
return "com.slxy.domain.Student";
}
public String methodName(){
return "sayHello";
}
}
*/
//调用注解中的属性,获取返回值
String ClassName = annotation.className();
String MethodName = annotation.methodName();
//使用反射技术来加载类文件进内存
Class aClass = Class.forName(ClassName);
//创建对象
Constructor constructor = aClass.getConstructor();
Object o = constructor.newInstance();
//执行方法
Method method = aClass.getMethod(MethodName);
method.invoke(o); //Hello
}
}
需求:当主方法执行后,会自动自行被检测的所有方法(加了Check注解的方法),判断方法是否有异常,并记录到文件bug.txt中。
注解Check
:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Check {
}
被测试的类Calculator
:
public class Calculator {
@Check
public int add() {
return 1 + 2;
}
@Check
public int sub() {
return 1 - 2;
}
@Check
public int mul() {
return 1 * 2;
}
@Check
public int div() {
return 1 / 0;
}
}
实现类:
public class TestCheck {
public static void main(String[] args) throws IOException {
Calculator c = new Calculator();
//获取字节码文件对象
Class cls = c.getClass();
//获取所有方法
Method[] methods = cls.getMethods();
//异常数量
int error = 0;
//创建字符输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
bw.write("检测的文件"+cls.getSimpleName()+".java");
bw.newLine();
for (Method method : methods) {
if (method.isAnnotationPresent(Check.class)) {
try {
//执行方法
method.invoke(c);
} catch (Exception e) {
//记录错误数
error++;
//将错误信息写入文件
bw.write("错误的方法:" + method.getName());
bw.newLine();
bw.write("错误的名称:" + e.getCause().getClass().getSimpleName());
bw.newLine();
bw.write("错误的信息:" + e.getCause().getMessage());
bw.newLine();
}
}
}
bw.write("本次测试一共的错误的数量:" + error);
bw.flush();
bw.close();
}
}
测试结果bug.txt
检测的文件Calculator.java
错误的方法:div
错误的名称:ArithmeticException
错误的信息:/ by zero
本次测试一共的错误的数量:1