反射的概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
反射优缺点:
反射机制可以大大的提高代码的灵活度和可扩展性,但是随之带来的是较慢的运行效率和更多的系统开销。因此不能过度依赖反射机制。
获取Class对象的方式:
1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象 * 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
2. 类名.class:通过类名的属性class获取 * 多用于参数的传递
3. 对象.getClass():getClass()方法在Object类中定义着。 * 多用于对象的获取字节码的方式 * 结论: 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
Class类的理解(这是重点)
Java代码在计算机中 经历的阶段:三个阶段
a、首先是Source源代码阶段:
首先写好一个类的代码后,它是一个(比如说Person .java)文件,然后通过javac 编译 成 一个Person.class 文件。
然后通过这个class类的类加载器(ClassLoader)进入第二阶段,Class类对象阶段。
b、类对象阶段:
有一个Class 类对象,
里面封装了成员变量类(数组),构造方法类(数组),成员方法类(数组)
- 成员变量 Field【】fields
- 构造方法 Constructor【】cons
- 成员方法 Method【】methods
c、第三个阶段就是Runtime运行时阶段,就直接时创建对象了。
Class对象功能:
1.获取成员变量们
2.获取构造方法
3.获取成员方法们
/* Method类 Method类的每一个实例称为"方法对象"。 该类的每个实例表示某个类中的一个方法。通过方法对象我们可以得知 其表示的方法的相关信息,如:方法名,返回值类型,参数个数,参数类型 等等。 */ /* Class中的方法: Method[] getMethods() 获取当前类对象所表示的类的所有公开方法 */ Method[] methods = cls.getMethods();
* 使用类对象实例化
反射机制进行实例化的步骤: 1:加载要实例化对象的类的类对象 2:直接通过类对象的方法newInstance()实例化
Person p = new Person(); System.out.println(p); Class cls = Class.forName(className); //newInstance()方法会调用类对象所表示的类的【公开的无参构造器】实例化 Object obj = cls.newInstance();//new Person();
* 使用有参构造器实例化对象
public static void main(String[] args) throws Exception { Person p = new Person("李四",22); System.out.println(p); //加载类对象 Class cls = Class.forName("reflect.Person"); /* Constructor 构造器对象 该类的每一个实例用于表示一个类中的某个构造器 */ //通过类对象获取无参构造器 // Constructor constructor = cls.getConstructor();//Person() //Person(String,int) Constructor con = cls.getConstructor(String.class,int.class); //new Person("王五",33) Object obj = con.newInstance("王五",33); System.out.println(obj); }
* 使用反射机制调用方法
//实例化 // Class cls = Class.forName("reflect.Person"); // Object o = cls.newInstance();//Person p = new Person(); // //调用方法 // Method method = cls.getMethod("eat");//获取名为"eat"的无参方法 // method.invoke(o);//执行eat方法 p.eat()
* 调用有参方法
public static void main(String[] args) throws Exception { Person p = new Person(); p.say("你好!"); //实例化 Class cls = Class.forName("reflect.Person"); Object o = cls.newInstance(); //调用方法 Method m = cls.getMethod("say",String.class);//say(String) //invoke方法第二个参数开始为调用方法时传递的实际参数 m.invoke(o,"大家好!"); Method m2 = cls.getMethod("say",String.class,int.class); m2.invoke(o,"嘿嘿",5); }
* 反射机制访问私有成员(暴力反射)
m.setAccessible(true);//强行打开访问权限
public static void main(String[] args) throws Exception { // Person p = new Person(); // p.hehe();//编译不通过,私有方法不可以被类的外部调用 Class cls = Class.forName("reflect.Person"); Object o = cls.newInstance(); /* Class的方法: Method getMethod(String name,Class... cls) Method[] getMethods() 上述两个方法仅能获取该类的公开方法(包括从超类继承的方法) getDeclaredMethod() getDeclaredMethods() 上述两个方法可以获取本类定义的方法(含有私有的,不含有继承的) */ // Method m = cls.getMethod("hehe"); // Method[] methods = cls.getDeclaredMethods(); // for(Method method:methods){ // System.out.println(method.getName()); // } Method m = cls.getDeclaredMethod("hehe"); m.setAccessible(true);//强行打开访问权限 m.invoke(o); m.setAccessible(false);//对于私有成员,访问后关闭(必须) } }
* 反射机制可破坏单例模式
public static void main(String[] args) throws Exception { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1); System.out.println(s2); Class cls = Class.forName("reflect.Singleton"); //获取私有的无参构造器 Constructor c = cls.getDeclaredConstructor(); c.setAccessible(true); Object o = c.newInstance(); System.out.println(o); }
* 反射机制访问注解
常用的反射对象: Class,Method,Constructor,Field,Annotation 都提供了一个方法: boolean isAnnotationPresent(Class cls) 判断当前反射对象表示的内容是否被cls所表示的注解标注了
Class cls = Class.forName("reflect.Person"); //判断当前cls所表示的类Person是否被注解AutoRunClass标注了 if(cls.isAnnotationPresent(AutoRunClass.class)){ System.out.println("被标注了!"); }else{ System.out.println("没有被标注!"); }
* 访问注解参数
Class cls = Class.forName("reflect.Person"); if(cls.isAnnotationPresent(AutoRunClass.class)){ Method method = cls.getDeclaredMethod("watchTV"); if(method.isAnnotationPresent(AutoRunMethod.class)){ /* 所有反射对象都支持方法: Annotation getAnnotation(Class cls) 获取cls类对象表示的注解 */ //返回当前method对象表示的方法"watchTV"上的注解@AutoRunMethod AutoRunMethod arm = method.getAnnotation(AutoRunMethod.class); int value = arm.value(); System.out.println("参数值:"+value); } }
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AutoRunMethod { /* 注解中可以定义参数 格式为: 类型 参数名() [default 默认值] 默认值是可选的,不是必须指定的。如果指定了默认值,那么使用注解时 可以不传入该参数,此时参数使用这个默认值。 如果参数没有指定默认值,那么使用注解时必须为这个参数赋值。 如果注解中只有一个参数时,参数名建议选取"value"。这样外界在使用 该注解时,传递参数则不需要指定参数名,可以直接写作: @AutoRunMethod(5) 此时value的值就是5 如果该参数名不叫value,比如叫: int num() default 1; 则使用注解时为该参数传递值时必须写作: @AutoRunMethod(num=5) */ int value() default 1; /* 一个注解中可以定义多个参数,当使用该注解时传参格式为:name=value @AutoRunMethod(num=1,name="张三") 或 @AutoRunMethod(name="张三",num=1) (传参顺序不所谓) */ // int num() default 1; // String name(); /* 并且两个以上参数(含两个)时,其中某一个参数名指定为value,传参时也 必须指定该参数名 例如: @AutoRunMethod(value=1,name="张三") 或 @AutoRunMethod(name="张三",value=1) */ // int value() default 1; // String name(); }
/** * 在注解上我们可以使用java预定义的注解来规范某些操作,常用的有: * @Target 用于指定当前注解可以被应用的位置。 * 不指定该注解时,我们定义的注解可以被应用于任何可以使用注解的位置, * 比如:类上,方法上,构造器上,属性上,参数上等 * 可以通过为Target注解传入参数来指定可以使用的特定位置,这些位置 * 都被ElementType规定。 * @Target(ElementType.TYPE) * 只能在类上使用该注解 * * @Target({ElementType.TYPE,ElementType.FIELD}) * 可以在类上或属性上使用该注解(多个位置时要以数组形式表达) * * @Retention 用于指定当前注解的保留级别,有三个可选值 * RetentionPolicy.SOURCE 注解仅保留在源代码中 * RetentionPolicy.CLASS 注解保留在字节码中,但是不可被反射机制访问(默认保留级别) * RetentionPolicy.RUNTIME 注解保留在字节码中,可被反射机制访问 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface AutoRunClass { }