java 反射 Reflection

一.什么是反射:

Relection(反射):是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于ReflectionAPI取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

公式:

Class  c  =  Class.forName("java.lang.String");     // 取得那个类或对象

反射好像一面镜子,我们可以通过镜子看到这个类的完整结构,所以叫反射

正常方式:

引入需要的包名    ----》  通过new关键字实例化    ----》  取得实例化对象

反射方式:

实例化对象   ----》getClass()方法  ----》得到完整的“包类”名称

Java反射的优点和缺点

优点:可以实现动态创建对象和编译,体现出很大的灵活性

缺点:对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且满足我们的要求,这类操作总是慢于直接执行相同操作,比如new对象方面

代码实现:

 

结果:

 

 

结论:一个类在内存中只有一个class对象

          一个类被加载后,类的整个结构都会被封装在class对象中

实质上我们也可以将反射理解为通过对象求解我们的类名 

 

二.那些对象可以拥有Class对象

 

class:外部类,成员类,局部内部类,匿名内部类

interface:接口

[  ]:数组

enum:枚举

annotation:注解@interface

primitive  type:基本数据类型

void


public static void main(String[] args) {
Class c1=Object.class; // 类
Class c2=Comparable.class; // 接口
Class c3= String[].class; //一维数组
Class c4=int[][].class; //二维数组
Class c5=Override.class; // 注解
Class c6= ElementType.class;// 枚举
Class c7=Integer.class; //基本数据类型
Class c8=void.class; //void
Class c9=Class.class; //class
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
}

 

输出结果

 

 

三.类加载与ClassLoader

将class文件内容加载到内存中,并将这些静态数据转换为方法区运行的数据结构,然后生成一个代表这个类的java.lang.Class对象

链接:将Java类的二进制代码合并到JVM运行时的状态之中的过程。

  1. 验证:确保加载的类信息符合JVM规范,没有安全方面问题
  2. 准备:正式为类变量(static)分配内存并设置默认值,这些内存都在方法区进行分配
  3. 解析:虚拟机常量池内的符号引用替换为直接引用地址的过程

初始化:

  • 执行类构造器<clinit>()方法的过程,该方法的作用是,自动将所有的类变量赋值动作,和静态代码块语句合并产生。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要触发其父类进行赋值
  • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步

 

 结果:

四.类的初始化

什么时候会发生类的初始化呢

类的主动引用就一定会发生初始化

  1. 当虚拟机启动时,先初始化main方法所在类
  2. new 一个类的对象
  3. 调用类的静态成员和静态方法
  4. 使用java.lang.reflect包的方法对类进行反射调用
  5. 当初始化一个类的时候,如果其父类没有被初始化,会优先初始化它的父类

类的被动引用(不会发生类的初始化):

  1. 当访问一个静态域的时候,只有真正声明这个域的类才会被初始化。例如,通过子类去拿父类的静态变量的时候,不会导致子类的初始化
  2. 通过数组定义的类的引用,不会触发此类的初始化
  3. 引用常量不会触发此类的初始化(常量在链接阶段都已经存入调用类的常量池了)

 

 

 

 

 

 第二种情况:

 

 

 

 

 

 五.类加载器的作用

类加载器的作用:将class文件字节码内容加载到内存中,并且将这些静态数据转化为运行时的数据结构,然后再堆中生成一个代表这个类的java.lang.class对象,作为方法区中类的数据访问入口。

类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过JVM垃圾回收机制可以回收这些Class对象

Java源文件(.java) ----》 Java编译器  ----》  字节码  (.class) -----》类加载器  ----》字节码校验器  ----》解释器  -----》操作系统平台

 

 

 

public class Test05 {
    public static void main(String[] args) throws ClassNotFoundException {
        //读取系统类的加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2
        
        //获取系统类的父类加载器---》扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@1540e19d
        
        //获取扩展器的父类加载器--》根加载器(c/c+++)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);//null   根加载器是不允许直接被获取的
        
        //获取当前测试类是那个加载器
        Class<?> aClass = Class.forName("annotation.Test05");
        System.out.println(aClass);//class annotation.Test05
        
        //测试JDK内置类是谁加载的
        Class<?> aClass1 = Class.forName("java.lang.Object");
        System.out.println(aClass1);//class java.lang.Object
        
        //如何获取系统类加载器的路径
        System.out.println(System.getProperty("java.class.path"));
        /*
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\charsets.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\deploy.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\access-bridge-64.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\cldrdata.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\dnsns.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jaccess.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\jfxrt.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\localedata.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\nashorn.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunec.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunjce_provider.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunmscapi.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\sunpkcs11.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext\zipfs.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\javaws.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\jce.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfr.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\jfxswt.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\jsse.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\management-agent.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\plugin.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\resources.jar;
        C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar;
        D:\其它\Markdown\demo02\out\production\demo02;
        D:\其它1\Idea\IntelliJ IDEA 2017.3.1\lib\idea_rt.jar

         */
    }
}

 

拓展(掌握):双亲委派机制

这个机制是用来维护Java运行的安全性的,当我们定义一个类之后,并不是直接加载这个类的,他会优先寻找它的父类,如果父类存在,就会继续找到根加载器,如果存在,就会加载这个类

反之,父类不存在,根加载器也不存在,才会加载自己的;这样就可以防止我们的普通代码类和系统代码类冲突

例子:比如我们定义了一个Java.lang.String  类,那么我们的编译器永远不会执行它,因为再根加载器也有一个相同的类

这个自定义类在启动加载时:

加载器的操作:

不会直接加载,先去寻找拓展类加载器,有没有Java.lang.String  很显然,它有这个类

继续寻找,在根加载器中寻找,很显然他也有这个类,所以这个Java.lang.String类在这个时候就被加载了,根本轮不到系统加载器去加你写的类

相反的操作:我们定义一个系统没有的 top.lostyou.test  到了根加载器,发现没有,

向下一级,查看扩展类加载器,结果是也没有

就会在系统加载器进行加载你写的类了

总而言之:除了系统加载器加载以外,其它的加载器都不加载我们自己写的类

六.获得类运行时的结构

  • Fieid                类的属性
  • Method            类的方法
  • Constructor     类的构造器
  • Superclass     此类的父类
  • Inteerface      类的接口
  • Annotation     类的注解
public class Test06 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<?> c1 = Class.forName("annotation.User");
        //获得类的名字   包名+ 类名        类名
        System.out.println(c1.getName());
        System.out.println(c1.getSimpleName());

        //获得类的属性
        Field[] fields = c1.getFields();//获得 public 属性
        for (Field field : fields) {
            System.out.println("public:"+field);
            /*
            public:public int annotation.User.classRoomNumber
             */
        }
        fields=c1.getDeclaredFields(); //所有的属性
        for (Field field : fields) {
            System.out.println("所有的属性:"+field);
            /*
            所有的属性:private int annotation.User.id
            所有的属性:private java.lang.String annotation.User.name
            所有的属性:private java.lang.String annotation.User.pwd
            所有的属性:public int annotation.User.classRoomNumber
             */
        }
        System.out.println("=====================================================");
        //获得类的方法
        Method[] methods = c1.getMethods();//获取自己和父类的所有public方法
        for (Method method : methods) {
            System.out.println("自己的方法,包括父类的public:"+method);
            /*
            自己的方法,包括父类的public:public java.lang.String annotation.User.toString()
            自己的方法,包括父类的public:public java.lang.String annotation.User.getName()
            自己的方法,包括父类的public:public int annotation.User.getId()
            自己的方法,包括父类的public:public void annotation.User.setName(java.lang.String)
            自己的方法,包括父类的public:public void annotation.User.setId(int)
            自己的方法,包括父类的public:public java.lang.String annotation.User.getPwd()
            自己的方法,包括父类的public:public void annotation.User.setPwd(java.lang.String)
            自己的方法,包括父类的public:public final void java.lang.Object.wait() throws java.lang.InterruptedException
            自己的方法,包括父类的public:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
            自己的方法,包括父类的public:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
            自己的方法,包括父类的public:public boolean java.lang.Object.equals(java.lang.Object)
            自己的方法,包括父类的public:public native int java.lang.Object.hashCode()
            自己的方法,包括父类的public:public final native java.lang.Class java.lang.Object.getClass()
            自己的方法,包括父类的public:public final native void java.lang.Object.notify()
            自己的方法,包括父类的public:public final native void java.lang.Object.notifyAll()
             */
        }
        methods=c1.getDeclaredMethods();//获取自己所有的方法
        for (Method method : methods) {
            System.out.println("自己的方法所有的方法"+method);
            /*
            自己的方法所有的方法public java.lang.String annotation.User.toString()
            自己的方法所有的方法public java.lang.String annotation.User.getName()
            自己的方法所有的方法public int annotation.User.getId()
            自己的方法所有的方法public void annotation.User.setName(java.lang.String)
            自己的方法所有的方法public void annotation.User.setId(int)
            自己的方法所有的方法public java.lang.String annotation.User.getPwd()
            自己的方法所有的方法public void annotation.User.setPwd(java.lang.String)
            自己的方法所有的方法private void annotation.User.getClassRoomNumber()
             */
        }
        //获得构造器
        Constructor<?>[] constructors = c1.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
            /*
            public annotation.User()
            public annotation.User(int,java.lang.String,java.lang.String)
             */
        }
        constructors=c1.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
            /*
            public annotation.User()
            public annotation.User(int,java.lang.String,java.lang.String)
             */
        }
    }
}

 

值得注意的是,不管是方法,属性,构造器,我们都可以获得指定的,但是由于重载的存在,我们获得指定的,必须要把类型丢进去

例如:Method getName = c1.getDeclaredMethod("setName",String.class);

七.动态创建对象和执行方法

思考:我们获得Class对象了,我们可以做些什么呢?
我们就可以用这个class对象创建类的对象:newInstance()方法

1.我们必须要有一个无参构造器

2.类的构造器访问权限足够

        Class c1 = Class.forName("annotation.User");
        //默认创建对象是无参构造实现的
        User user =(User) c1.newInstance();
        System.out.println(user);

 

有参构造也可以实现

1.通过class类的指定取构造器方法丢入参数结构类型

2.然后开始在此对象使用newInstance()中传入数据创建对象

3.通过Constructor

        //通过有参构造创建对象
        Constructor constructor = c1.getConstructor(int.class, String.class, String.class);  //
        User user1 = (User) constructor.newInstance(110, "msf", "mmm");
        System.out.println(user1);

 

其它操作

        //通过反射调用普通方法
        //invoke()  激活的意思
        //传参 : 对象   方法值
        Method setName = c1.getMethod("setName", String.class);
        setName.invoke(user,"i am gold");
        System.out.println(user.getName());

        //通过反射操作属性
        //私有属性不能直接操作,因为有反射有安全检测,且默认开启
        //name.setAccessible(true);  私有属性名 . setAccessible(true);  这样就完成关闭了
        Field name = c1.getDeclaredField("name");
        name.setAccessible(true);//关闭安全检测
        name.set(user,"gold");
        System.out.println(user.getName());

 

总结,我们在通过反射使用对象的方法,属性,有参构造等等,都要通过class对象先拿到,然后传入需要的类型然后固定是哪个方法

拓展:私有属性的操作

setAccessible()

对普通的属性可以直接操作,但是私有属性有安全检测,所以使用的时候必须要关闭安全检测

setAccessible(true/false)

true:关闭安全jianc

false:开启安全检测(默认开启的)

八.通过反射获取注解信息

ORM:Object  relationship  Mapping

对象关系映射

Java属性和数据库字段 一 一 对应

public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("annotation.user");
        //通过反射获得类注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        
        //获得属性注解
        Field id = c1.getDeclaredField("id");
        word annotation = id.getAnnotation(word.class);
        System.out.println(annotation.columNmae());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}
 class user{
    @word(columNmae = "db_id",type = "int",length = 10)
    private int id;
    private int age;
    private String name;

     public user() {
     }

     public user(int id, int age, String name) {
         this.id = id;
         this.age = age;
         this.name = name;
     }

     public int getId() {
         return id;
     }

     public void setId(int id) {
         this.id = id;
     }

     public int getAge() {
         return age;
     }

     public void setAge(int age) {
         this.age = age;
     }

     public String getName() {
         return name;
     }

     public void setName(String name) {
         this.name = name;
     }

     @Override
     public String toString() {
         return "user{" +
                 "id=" + id +
                 ", age=" + age +
                 ", name='" + name + '\'' +
                 '}';
     }
 }
 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
 @interface word{
    String columNmae();
    String type();
    int length();
 }

 

posted @ 2022-11-24 21:59  回忆也交给时间  阅读(122)  评论(0编辑  收藏  举报