Bootstrap

【Java反射】非常重要

反射的概念

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.获取成员变量们

getFields()

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 {
}

;