Java--反射机制

​​​​​​一、反射

以下引自Java反射机制是什么?

Java反射机制是 Java 语言的一个重要特性。在学习 Java 反射机制前,大家应该先了解两个概念,编译期和运行期。
编译期是指把源码交给编译器编译成计算机可以执行的文件的过程。在 Java 中也就是把 Java 代码编成 class 文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成文本进行操作,比如检查错误。
运行期是把编译后的文件交给计算机执行,直到程序运行结束。所谓运行期就把在磁盘中的代码放到内存中执行起来。
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。简单来说,反射机制指的是程序在运行时能够获取自身的信息。在 Java 中,只要给定类的名字,就可以通过反射机制来获得类的所有信息

二、反射包,以及常用方法

1、反射机制的相关类所在包

 java.lang.reflect.*;

2、反射机制相关的类

java.lang.Class:代表整个字节码,代表一个类型,代表整个类

java.lang.reflect.Method:代表字节码中的方法字节码(代表类中的方法)

java.lang.reflect.Constructor:代表字节码中的构造方法字节码(代表类中的构造方法)

java.lang.reflect.Field:代表字节码中的属性字节码,代表类中的成员变量(静态变量+实例变量)

在这里插入图片描述

2.1获取Class类的三种方式

         1、Class c = Class.forName("完整带包类名"); 这个方法的执行会导致:类加载,类加载时,静态代码块执行 类的静态代码块执行,其它代码一律不执行

         2、Class c = 对象.getClass(); java中任何一个对象都有一个方法:getClass()

         3、Class c = 任何类型.class; java语言中任何一种类型,包括基本数据类型,它都有.class属性

/** 1、Class c = Class.forName("完整带包类名") */
        Class strClass = null;
        Class dateClass = null;
        try {
            /**
            Class.forName()
            1、静态方法
            2、方法的参数是一个字符串。
            3、字符串需要的是一个完整类名。
            4、完整类名必须带有包名。java.lang包也不能省略。
            */
            strClass = Class.forName("java.lang.String");// strClass代表String.class文件,strClass代表String类型
            System.out.println("strClass = " + strClass);//class java.lang.String

            dateClass = Class.forName("java.util.Date");
            System.out.println("dateClass = " + dateClass);//class java.util.Date

            Class integerClass = Class.forName("java.lang.Integer");
            System.out.println("integerClass = " + integerClass);//class java.lang.Integer

            Class systemClass = Class.forName("java.lang.System");
            System.out.println("systemClass = " + systemClass);//class java.lang.System

            /**
             Class.forName("完整带包类名")
             这个方法的执行会导致:类加载,类加载时,静态代码块执行
             类的静态代码块执行,其它代码一律不执行
             */
            Class.forName("reflect.bean.User");//User类的静态代码块执行了!
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        /** 2、Class c = 对象.getClass() */
        String str = "abc";
        Class strClass2 = str.getClass();
        System.out.println("strClass2 = " + strClass2);//class java.lang.String
        System.out.println(strClass == strClass2);// true 未重写的 == 比较内存地址;strClass和strClass2两个变量中保存的内存地址都是一样的,都指向方法区中的字节码文件

        Date date = new Date();
        Class dateClass2 = date.getClass();
        System.out.println("dateClass2 = " + dateClass2);
        System.out.println(dateClass == dateClass2);//class java.util.Date

        /** 3、Class c = 任何类型.class */
        Class strClass3 = String.class;
        System.out.println("strClass3 = " + strClass3);//class java.lang.String
        System.out.println(strClass == strClass3);// true

        Class dateClass3 = date.getClass();
        System.out.println("dateClass3 = " + dateClass3);//class java.util.Date
        System.out.println(dateClass == dateClass3);// true

        Class doubleClass = Double.class;
        Class doubleClass1 = double.class;
newInstance()方法
/**
 获取Class作用
    通过Class的newInstance()方法来实例化对象
    注:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造存在
 */
try {
            // 通过反射机制,获取Class,通过Class来实例化对象
            Class className = Class.forName("reflect.bean.User");

            // newInstance()方法会调用User类的无参数构造方法,完成对象的创建(必须保证无参构造方法是存在)
            Object obj = className.newInstance();//User对象无参构造

            System.out.println(obj);//reflect.bean.User@1b6d3586
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

 获取类的父类,以及实现的接口

try {
            // 以String举例
            Class stringClass = Class.forName("java.lang.String");

            System.out.println("=====父类=====");
            // 获取String的父类
            Class superClass = stringClass.getSuperclass();
            System.out.println(superClass.getName());//java.lang.Object

            System.out.println("=====接口=====");

            // 获取String类实现的所有接口(一个类可以实现多个接口)
            Class[] interfaces = stringClass.getInterfaces();
            for (Class interfaceStr:interfaces){
                System.out.println(interfaceStr.getName());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

(2)获取路径

classinfo.properties文件

className=reflect.bean.User
classNames=reflect.bean.User,reflect.bean.People
#建议key和value之间使用=的方式
#=左边是key,=右边是value
#尽量不要有空格
#name  =  zhangsan
#尽量不使用冒号:
#usernamex:admin
#属性配置文件的key重复的话,value会自动覆盖!
#password=admin123
// 这种方式写死了类对象;只能创建一个User类型的对象
        //User user = new User();

        FileReader fr = null;
        try {
            // 通过IO流读取classinfo.properties文件
            fr = new FileReader("src\\reflect\\classinfo.properties");

            // 创建属性类对象Map
            Properties ps = new Properties();
            // 加载
            ps.load(fr);

            /** 创建单个对象 */
            // 通过key获取value
            String className = ps.getProperty("className");
            // 通过反射机制实例化对象
            Class cls = Class.forName(className);
            Object obj = cls.newInstance();
            System.out.println(obj);

            /** 创建批量对象 */
            String classNames = ps.getProperty("classNames");
            String[] clsNames = classNames.split(",");
            for (String clsName:clsNames) {
                Class cls1 = Class.forName(clsName);
                Object obj1 = cls1.newInstance();
                System.out.println(obj1);
                /*
                User对象无参构造
                reflect.bean.User@4554617c
                People对象无参构造
                reflect.bean.People@74a14482
                */
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            //快捷键,fr.notnull
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

【1】以流的形式返回文件路径

/**
 以流的形式返回文件路径
    Thread.currentThread() 当前线程对象
    getContextClassLoader() 线程对象的方法,获取到当前线程的类加载器对象
    getResourceAsStream() 类加载器对象的方法,当前线程类加载器默认从类的根路径下以流的形式加载资源
 * */
public class IOProperties {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            String filePath = Thread.currentThread().getContextClassLoader()
                    .getResource("reflect/classinfo.properties").getPath();
            fr = new FileReader(filePath);

            Properties ps = new Properties();
            ps.load(fr);
            String clsName = (String) ps.get("className");
            Class cls = Class.forName(clsName);
            System.out.println(clsName);//reflect.bean.User
            System.out.println(cls);//class reflect.bean.User

            //以流的形式返回
            InputStream is = Thread.currentThread().getContextClassLoader()
                    .getResourceAsStream("reflect/classinfo.properties");

            Properties ps1 = new Properties();
            ps1.load(is);
            String clsName1 = (String) ps.get("className");
            Class cls1 = Class.forName(clsName);
            System.out.println(clsName1);//reflect.bean.User
            System.out.println(cls1);//class reflect.bean.User
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            //ifn 快捷键
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

【2】获取文件绝对路径

/**
 获取文件绝对路径
    文件需要在类路径(即src目录)下
 */
public class PropertiesPath {
    public static void main(String[] args) {
        // 这种获取文件路径方式缺点:移植性差,在IDEA中默认的当前路径是project的根
        // 源代码不在IDEA,在其他系统,开发工具打开,当前路径就可能不是project的根,这时路径就无效
        //FileReader reader = new FileReader("src/reflect/classinfo.properties");

        /**
         获取文件通用路径
            Thread.currentThread() 当前线程对象
            getContextClassLoader() 线程对象的方法,获取到当前线程的类加载器对象
            getResource() 类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
         注:这个文件必须在类路径下;src(类的根路径)下的都是类路径下
            找不到会报异常:java.lang.NullPointerException
         */
        String filePath1 = Thread.currentThread().getContextClassLoader()
                .getResource("classinfo1.properties").getPath();
        ///D:/Java/JavaProject/JavaSE/out/production/JavaSE/classinfo1.properties
        System.out.println(filePath1);

        //获取文件的绝对路径(从类的根路径src下作为起点开始)
        String filePath = Thread.currentThread().getContextClassLoader()
                .getResource("reflect/classinfo.properties").getPath();
        ///D:/Java/JavaProject/JavaSE/out/production/JavaSE/reflect/classinfo.properties
        System.out.println(filePath);
    }
}

【3】资源绑定器

/**
 资源绑定器
    java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
    注:
      1、资源绑定器,只能绑定xx.properties文件
      2、xx.properties文件必须在类(src目录)路径下
      3、写路径的时候,路径后面的扩展名不能写
 */
public class ResourceBundleTest {
    public static void main(String[] args) {
        ResourceBundle rb = ResourceBundle.getBundle("reflect/classinfo");

        String clsName = rb.getString("className");
        Class cls = clsName.getClass();
        System.out.println(clsName);
        System.out.println(cls);
    }
}

2.2、获取字段Field

反射字段

/** 获取类名 */
            Class studentCls = Class.forName("reflect.bean.Student");

            //getName() 获取完整类名
            System.out.println("完整类名:" + studentCls.getName());//完整类名:reflect.bean.Student
            //getSimpleName() 获取简类名
            System.out.println("简类名:" + studentCls.getSimpleName());//简类名:Student
            System.out.println("==================================");

            /** 获取字段Field */
            // getFields() 获取类中所有的public修饰的Field
            Field[] fields = studentCls.getFields();
            System.out.println(fields.length);//2
            for (Field field : fields){
                // getName() 获取Field的键,名字
                System.out.println(field.getName());
                //no
                //MATH_PI
            }
            System.out.println("==================================");

            Field[] fieldArray = studentCls.getDeclaredFields();
            System.out.println(fieldArray.length);//5
            for (Field field:fieldArray){
                // 获取属性的修饰符列表
                // 返回的修饰符是一个数字,每个数字是修饰符的代号
                int modify = field.getModifiers();
                System.out.println(modify);
                // 将属性修饰符数字转换成属性字符串
                String modefyStr = Modifier.toString(modify);

                // 获取属性的类型
                Class fieldType = field.getType();//class java.lang.String
                String fieldTypeStr = fieldType.getSimpleName();//String

                // 获取属性的名字
                String fieldName = field.getName();

                System.out.println("修饰符:" + modefyStr
                                    + " 字段类型:" + fieldTypeStr
                                    + " 字段名:" + fieldName);

                /*
                2
                修饰符:private 字段类型:String 字段名:name
                4
                修饰符:protected 字段类型:int 字段名:age
                0
                修饰符: 字段类型:boolean 字段名:sex
                1
                修饰符:public 字段类型:int 字段名:no
                25
                修饰符:public static final 字段类型:double 字段名:MATH_PI
                */
            }

 反编译类的字段

StringBuilder sb = new StringBuilder();

        try {
            Class studetClass = Class.forName("reflect.bean.Student");

            //获取类的修饰符字符串
            int modifyInt = studetClass.getModifiers();
            String modifyStr = Modifier.toString(modifyInt);
            sb.append(modifyStr + " class " + studetClass.getSimpleName() + "{\n");

            //获取所有字段
            Field[] fields = studetClass.getDeclaredFields();
            for (Field field:fields){
                sb.append("\t");
                sb.append(Modifier.toString(field.getModifiers()));
                sb.append(" ");
                sb.append(field.getType().getSimpleName());
                sb.append(" ");
                sb.append(field.getName());
                sb.append(";\n");
            }
            sb.append("}");

            System.out.println(sb);
            /*
            public class Student{
                private String name;
                protected int age;
                 boolean sex;
                public int no;
                public static final double MATH_PI;
            }
            */
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

 字段set、get取赋值

Class studentClass = Class.forName("reflect.bean.Student");
            // obj就是Student对象(底层调用无参数构造方法)
            Object obj = studentClass.newInstance();

            Field noField = studentClass.getDeclaredField("no");
            // 给obj对象(Student)的no属性赋值
            noField.set(obj,18);
            // 读取属性的值
            System.out.println(noField.get(obj));


            Field nameField = studentClass.getDeclaredField("name");
            // 打破封装(反射机制的缺点:打破封装!)
            // 设置完成后,在外部也可以访问private
            nameField.setAccessible(true);
            nameField.set(obj,"lisi");
            //java.lang.IllegalAccessException: Class reflect.getfield.FieldGetAndSet can not access a member of class reflect.bean.Student with modifiers "private"
            System.out.println(nameField.get(obj));

2.3、获取方法Method

可变长度参数

/**
 可变长度参数
    int... args 这就是可变长度参数
    语法是:类型...  (注:一定是3个点...)

    1、可变长度参数要求的参数个数是:0~N个
    2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个
    3、可变长度参数可以当做一个数组来看待
 */
public class VariableLengthParameter {
    public static void main(String[] args) {
        m();
        m(1);
        m(1,2);

        m2(100);
        m2(200, "abc");
        m2(200, "abc", "def");

        String[] strs = {"a","b","c"};
        // 也可以传1个数组
        m3(strs);

        // 直接传1个数组
        m3(new String[]{"我","是","中","国", "人"});

        m3("我","是","中","国", "人");
    }

    public static void m(int... args){
        System.out.println("m方法执行了!");
    }

    /*
     可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个
        Move 'args' to the end of the list
        public static void m2(int... args,String... args1){
        }
     */
    public static void m2(int args,String... args1){
    }

    public static void m3(String... args){
        for (int i = 0;i < args.length;i++){
            System.out.print(args[i]);
        }
    }
}

 反射Method

try {
            Class loginClass = Class.forName("reflect.getmethod.LoginService");

            // 获取所有的Method(包括私有的!)
            Method[] methods = loginClass.getDeclaredMethods();

            for (Method method:methods){
                System.out.println("==========");
                //获取方法的修饰符
                String methodModify = Modifier.toString(method.getModifiers());
                System.out.println("方法的修饰符:" + methodModify);

                //获取方法的返回值类型
                Class returnType = method.getReturnType();
                String methodReturnTypeStr = returnType.getSimpleName();
                System.out.println("返回值类型:" + methodReturnTypeStr);

                //获取方法名
                String methodName = method.getName();
                System.out.println("方法名:" + methodName);

                //获取参数(形参)列表
                Class[] parameterTypes = method.getParameterTypes();
                for (Class parameter:parameterTypes){
                    System.out.println("参数(形参)列表:" + parameter.getSimpleName());
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

反编译类的方法

try {
            StringBuilder sb = new StringBuilder();

            Class loginClass = Class.forName("reflect.getmethod.LoginService");
            sb.append(Modifier.toString(loginClass.getModifiers()) + " class " + loginClass.getSimpleName() + "{\n");

            Method[] methods = loginClass.getDeclaredMethods();
            for (Method method:methods){
                sb.append("\t");
                sb.append(Modifier.toString(method.getModifiers()));
                sb.append(" ");
                sb.append(method.getReturnType().getSimpleName());
                sb.append(" ");
                sb.append(method.getName());
                sb.append("(");
                Class[] parameterTypes = method.getParameterTypes();
                for (Class parameter:parameterTypes){
                    sb.append(parameter.getSimpleName());
                    sb.append(",");
                }
                if (parameterTypes.length >= 1){
                    // 删除指定下标位置上的字符
                    sb.deleteCharAt(sb.length() - 1);
                }
                sb.append("){}\n");
            }

            sb.append("}");
            System.out.println(sb);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

方法调用

public class LoginService {
    /**
     * 登录方法
     * @param account 用户名
     * @param password 密码
     * @return true表示登录成功,false表示登录失败!
     */
    public String login(String account,String password){
        if ("admin".equals(account) && "123".equals(password)){
            return "用户名密码正确,登陆成功!";
        }
        return "用户名密码错误,登陆失败!";
    }

    // java中区分一个方法,依靠方法名和参数列表
    private void login(int cunt){

    }

    /**
     * 退出系统的方法
     */
    public void logout(){
        System.out.println("系统已经安全退出!");
    }
}
/**
 反射机制调用方法
 * */
public class InvokeMethod {
    public static void main(String[] args) {
        try {
            Class loginService = Class.forName("reflect.getmethod.LoginService");
            Object obj = loginService.newInstance();

            Method method = loginService.getDeclaredMethod("login",String.class,String.class);
            String message = (String) method.invoke(obj,"admin","123");
            System.out.println(message);//用户名密码正确,登陆成功!

            Method method1 = loginService.getDeclaredMethod("login",int.class);
            method.invoke(obj,1);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

2.4 、获取构造方法Constructor

反编译类的Constructor构造方法

try {
            StringBuilder sb = new StringBuilder();

            Class studentClass = Class.forName("reflect.bean.Student");
            sb.append(Modifier.toString(studentClass.getModifiers()));
            sb.append(" class ");
            sb.append(studentClass.getSimpleName());
            sb.append("{\n");

            //获取构造方法列表
            Constructor[] constructors = studentClass.getDeclaredConstructors();
            for (Constructor constructor:constructors){
                sb.append("\t");
                sb.append(Modifier.toString(constructor.getModifiers()));
                sb.append(" ");
                sb.append(studentClass.getSimpleName());
                sb.append("(");

                //获取参数列表
                Class[] parameteTypes = constructor.getParameterTypes();
                for (Class parameter:parameteTypes){
                    sb.append(parameter.getSimpleName());
                    sb.append(",");
                }
                if (parameteTypes.length > 1){
                    sb.deleteCharAt(sb.length() - 1);
                }
                sb.append("){}\n");
            }

            sb.append("}");

            System.out.println(sb);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

通过反射机制调用构造方法实例化java对象 

try {
            Class studentClass = Class.forName("reflect.bean.Student");
            Object obj = studentClass.newInstance();

            //Student{name='null', age=0, sex=false, no=0}
            Constructor constructor = studentClass.getConstructor();
            Object obj1 = constructor.newInstance();
            System.out.println(obj1);

            //Student{name='zhansgan', age=18, sex=true, no=1}
            Constructor constructor1 = studentClass.getConstructor(String.class,int.class,boolean.class,int.class);
            Object obj2 = constructor1.newInstance("zhansgan",18,true,1);
            System.out.println(obj2);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

3、类加载器

(1)专门负责加载类的命令/工具;ClassLoader

(2)JDK中自带了3个类加载器
        启动类加载器:rt.jar
        扩展类加载器:ext/*.jar
        应用类加载器:classpath

(3)代码在开始执行之前,会将所需要类全部加载到JVM当中
        通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载

        加载过程

        首先通过“启动类加载器”加载。
                注:启动类加载器专门加载:XX\Java\jdk1.8.0_101\jre\lib\rt.jar
                rt.jar中都是JDK最核心的类库
        如果通过“启动类加载器”加载不到的时候,会通过"扩展类加载器"加载。
                注:扩展类加载器专门加载:XX\Java\jdk1.8.0_101\jre\lib\ext\*.jar

        如果“扩展类加载器”没有加载到,那么会通过“应用类加载器”加载。
                注:应用类加载器专门加载:classpath中的类

三、反射的优缺点:

1、优点:在运行时获得类的各种内容(如类名,构造方法,字段,方法等),进行反编译,对于Java这种先编译再运行的语言,能够很方便的创建灵活的代码

2、缺点:

(1)反射会消耗一定的系统资源(如果不需要动态创建对象,尽量不用反射)

(2)反射调用方法时可以跳过权限检查,可能会破坏封装性而导致安全问题

四、反射的用途:

1、反编译:.class---->.java

2、通过反射机制访问java对象的属性,方法,构造方法等

3、当我们在使用IDE,比如Ecplise时,当我们输入一个对象或者类,并想调用他的属性和方法是,一按点号,编译器就会自动列出他的属性或者方法,这里就是用到反射。

4、反射最重要的用途就是开发各种通用框架。比如很多框架(Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,他们可能需要根据配置文件加载不同的类或者对象,调用不同的方法,这个时候就必须使用到反射了,运行时动态加载需要的加载的对象。

如,加载数据库驱动的,用到的也是反射

Class.forName("com.mysql.jdbc.Driver"); // 动态加载mysql驱动

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值