反射

反射

反射的概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射就是把java类中的各种成分映射成一个个的Java对象

我们知道java当中的类加载机制是:Class对象的由来是将..class文件读入内存,并为之创建唯一一个Class对象。

image-20200626233543710.png

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。

反射涉及到四个核心类:

  • java.lang.Class:类对象;
  • java.lang.reflect.Constructor:类的构造器对象;
  • java.lang.reflect.Method:类的方法对象;
  • java.lang.reflect.Field:类的属性对象;

反射的简单使用

获取Class对象的四种方式:

  1. 通过Object.getClass()调用
  2. 通过调用自己的.class属性调用
  3. 通过Class中的静态方法forName
  4. 通过ClassLoadloadClass
//3种使用反射的方式
public class Demo {  
    public static void main(String[] args) {  
        //第一种方式获取Class对象    
        //这一new 产生一个Student对象,一个Class对象。
        Student stu1 = new Student();  
        Class stuClass = stu1.getClass();//获取Class对象  
        System.out.println(stuClass.getName());  
          
        //第二种方式获取Class对象  
        Class stuClass2 = Student.class;  
        System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个  
          
        //第三种方式获取Class对象  
        try {  
            Class stuClass3 = Class.forName("Student");//真实路径,就是带包名的类路径,包名.类名  
            System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
        }        
    }  
}
//测试类
class Student {
    String name="123";
    //(默认的构造方法)
    Student(String str){
        System.out.println("(默认)的构造方法 s = " + str);
    }
    public Student(){
        System.out.println("调用了无参构造方法执行了");
    }
    public Student(char name){
        System.out.println("姓名:" + name);
    }
    public Student(String name ,int age){
        System.out.println("姓名:"+name+"年龄:"+ age);
    }
    protected Student(boolean n){
        System.out.println("受保护的构造方法 n = " + n);
    }
    //私有构造方法
    private Student(int age){
        System.out.println("私有的构造方法   年龄:"+ age);
    }

    static {
        System.out.println("静态代码块执行了");
    }
}
//结果:
//调用了无参构造方法执行了
//Student
//true
//true

.class的方式,并不会初始化该Class对象,需要注意的是,类名.class只是获取类的Class对象的一种方式。编译器一开始就知道有这一个类存在。

Class.forName会强制类进行初始化这个Class对象,

调用ClassLoader加载,没有对类进行初始化,如果你不显示调用类的静态成员属性或者成员方法,是不会触发类的初始化

这两种方式jvm一开始并不知道有这个类,运行的时候就会去加载。

//测试代码
public static void main(String[] args) throws Exception {  
        System.out.println("下面是测试Classloader的效果");
        Class c = Class.forName("one").getClassLoader().loadClass("Student");
        System.out.println("----------------------------------");
        Class c=Student.class;
        System.out.println("----------------------------------");
        System.out.println("下面是测试Class.forName的效果");
        Class.forName("Student");
} 
执行结果:
下面是测试Classloader的效果
----------------------------------
下面是测试Class.forName的效果
静态代码块执行了
    
//测试代码2
class one {
        public static void main(String[] args)  throws Exception{  
            System.out.println("下面是测试Classloader的效果");
            System.out.println("----------------------------------");   
           Class c = Class.forName("one").getClassLoader().loadClass("Student");
            Student.class.getClassLoader();
             System.out.println(c.getName());
            System.out.println(c.getField("a").getInt(null));
        }
}
class Student {
    public static int a ;
    static {
        a = 1;
        System.out.println("静态代码块执行了");
    }
}

执行结果:
下面是测试Classloader的效果
----------------------------------
Student
静态代码块执行了
1

ClassLoader.loadClass()Class.forName()的区别

ClassLoader类作用是根据一个指定的类的全限定名,找到对应的Class字节码文件,然后加载它转化成一个java.lang.Class类的一个实例

简单的复习一下类加载器的过程

  • 装载:查找和导入类或接口的二进制数据;
  • 链接:执行下面的校验、准备和解析步骤,其中解析步骤是可以选择的;
    • 校验:检查导入类或接口的二进制数据的正确性;
    • 准备:给类的静态变量分配并初始化存储空间;
    • 解析:将符号引用转成直接引用;
  • 初始化:激活类的静态变量的初始化Java代码和静态Java代码块。

Class.forName(className)方法,其实调用的方法是Class.forName(className,true,classloader);boolean参数表示的意思,loadClass后必须初始化。比较下我们前面准备jvm加载类的知识,我们可以清晰的看到在执行过此方法后,目标对象的 static块代码已经被执行,static参数也已经被初始化。

ClassLoader.loadClass(className)方法,其实他调用的方法是ClassLoader.loadClass(className,false);第2个boolean参数数表示目标对象被装载后不进行链接,这就意味这不会去执行该类静态块中间的内容。

他们两个的初始化类的时机不同

反射的常见操作

通过反射演示常见操作:

/*** 获取当前类及其父类的所有public方法 ,获取反射对象后使用getMethods()
    只获取当前类的所有方法:getDeclaredMethods()

*/
Method[] m = new Student().getClass().getMethods();
for(Method m1:m){
              System.out.println(m1);
}

执行结果:
静态代码块执行了
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
/*** 得到当前类中的所有属性getDeclaredFields() */
Field[] f =Class.forName("Student").getDeclaredFields();
for(Field m1:f){
    System.out.println(m1);
}
/**
获取接口getInterfaces()
获取父类getSuperclass()
*/
 Class superClass = Student.class.getSuperclass();
 System.out.println(superClass);
 Class[] interfaceses = Class.forName("Student").getInterfaces();
 System.out.println(superClass);
 for( Class c:interfaceses){
 System.out.println(c);
 }
 
 运行结果:
class java.lang.Object
静态代码块执行了
class java.lang.Object
/*
    获取相关的构造方法 获取public修饰的构造方法getConstructors()
*/
 Constructor[] c = Class.forName("Student").getConstructors();
 for(Constructor c1:c){
 System.out.println(c1);
 }
 
 运行结果:
 静态代码块执行了
public Student(java.lang.String,int)
public Student(char)
public Student()

反射的优缺点

优点:

  • 灵活、自由度高:不受类的访问权限限制,想对类做啥就做啥

缺点:

  • 性能问题:通过反射访问、修改类的属性和方法时会远慢于直接操作,但性能问题的严重程度取决于在程序中是如何使用反射的。如果使用得很少,不是很频繁,性能将不会是什么问题;
  • 安全性问题:反射可以随意访问和修改类的所有状态和行为,破坏了类的封装性,如果不熟悉被反射类的实现原理,随意修改可能导致潜在的逻辑问题;
  • 兼容性问题:因为反射会涉及到直接访问类的方法名和实例名,不同版本的API如果有变动,反射时找不到对应的属性和方法时会报异常;

你可能感兴趣的:(反射)