• 反射机制


    反射机制概述

    1、反射机制是干什么的?有什么用?

    答:通过反射机制可以操作字节码文件、操作代码片段(class 文件),可以使程序更加灵活(如:通过修改配置文件来创建不同对象)。

    2、反射机制的相关类在哪个包下?重要的类有哪些?

    答:在java.lang.reflect.*;包下。重要的类有java.lang.Class代表整个字节码(整个类)、一个类型;java.lang.reflect.Method代表字节码中的方法字节码;java.lang.reflect.Constructor 代表字节码中的构造方法字节码;java.lang.reflect.Field 代表字节码中的属性字节码,代表类中的成员变量(静态变量+实例变量)。

    3、获取类的三种方式

    方式一:在java.lang.Class 包中的static Class<?> forName(String className)返回带有给定字符串的类或接口相关联的 Class 对象。(作用将 XXX.class 字节码文件装载到JVM中的方法区内)

    /*Class.forName(),静态方法,参数是一个字符串,字符串需要的是一个完整类名(类名必须带有包名),使用该方法要抛异常
    */
    try{
        Class c = Class.forName("java.lang.String"); //c代表String.class文件,或者代表String类型;作用将String.class字节码文件装载到JVM中的方法区内
    }catch (ClassNotFoundException e){
        e.printStackTrace();
    }
    

    方式二:在java.lang.Object包下,Class<?> getClass()返回此 Object 的运行时类。( java 中任何一个对象都有一个 getClass() 方法)

    Class c = null;
    try{
        c = Class.forName("java.lang.String"); //c代表String.class文件,或者代表String类型;作用将String.class字节码文件装载到JVM中的方法区内
    }catch (ClassNotFoundException e){
        e.printStackTrace();
    }
    
    String s = "asd";
    Class cc = s.getClass(); //cc代表String.class字节码文件,cc代表String类型
    System.out.println(c == cc); //true (==判断对象的内存地址)
    

    c 和 cc 两个对象的内存地址相同,都指向方法区中的字节码文件。

    image-20200803103708741

    方式三:Java 中任何一种类型,包括基本数据类型都有.class属性。

    Class ccc = String.class; //ccc代表String类型
    System.out.println(c == ccc); //true (内存地址相同)
    

    通过反射机制实例化对象

    //用户类
    public class User{
        public User(){
            System.out.println("无参构造");
        }
        
    }
    
    //测试类
    public class ReflectTest {
        public static void main(String[] args) {
            try {
                Class c = Class.forName("com.reflecttest.www.User");
    
                /*newInstance()方法从JDK9之后过时,该方法会调用User类的无参构造
                 ,实例化User对象*/
                Object obj = c.newInstance();
                
                //返回:com.reflecttest.www.User@6e8dacdf
                System.out.println(obj); 
    
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                e.printStackTrace();
                }
        }
    }
    

    User类中只有无参构造方法时,结果返回:无参构造,com.reflecttest.www.User@6e8dacdf

    User类中只有有参构造方法时,报错显示没有无参构造。

    image-20200803114111906

    通过读属性文件实例化对象

    public class ReflectTest2{
        public static void main(String[] args) throws Exception{
            //通过IO流读取classinfo.properties文件(在当前工程下,与src目录同级)
            FileReader reader = new FileReader("classinfo.properties");
            //创建属性类对象Map,对应的key和value都是String
            Properties p = new Properties();
            //加载
            p.load(reader);
            //关闭流
            reader.close();
            //通过key获取value
            String className = p.getProperty("className");//com.reflecttest.www.User
            //通过反射机制实例化对象
            Class c = Class.forName(className);
            Object obj = c.newInstance();
            System.out.println(obj);
        }
    }
    

    classinfo.properties文件:

    className=com.reflecttest.www.User
    

    如果修改classinfo.properties中的类名,可以在不改变 Java 代码的基础上实例化不同的对象,非常灵活。符合OCP开闭原则:对扩展开放,对修改关闭。

    只执行静态代码块的内容时可以使用 Class.forName():

    public class M{
        //静态代码块在类加载时执行,并且只执行一次
        static{
            System.out.println("M类的静态代码块执行");
        }
    }
    
    public class Test{
        public static void main(String[] args){
            try{
                //类加载
                Class.forName("com.reflecttest.www.M");
            }catch (ClassNotFoundException e){
              e.printStackTrace();     
            }
        }
    }
    

    获取类路径下的文件的绝对路径

    为什么要用绝对路径?

    答:相对路径移植性差(在IDEA中默认的当前路径是 project 的根),如果代码换到其他位置时,这个路径就失效了。所以要使用绝对路径。

    通用的方式:适用于任何操作系统

    前提:文件必须在类路径下,即放在src下,src是类的根路径。

    注意:真正的“类的根路径”是outproductionproject工程...*.class编译产生的.class文件(程序执行的是class文件)。

    public class Path {
        public static void main(String[] args) {
    
            /**
             * Thread.currentThread():当前线程对象
             * getContextClassLoader():是当前线程对象的方法,可以获取当前线程类加载器对象
             * getResource():(获取资源)是类加载器对象的方法,当前线程的类加载器默认从类的根路径下(从src开始)加载资源
             * getPath(): 获取路径
             */
            String path = Thread.currentThread().getContextClassLoader()
                    .getResource("com/reflecttest/www/classinfo.properties").getPath();
            /*/E:/IdeaProjects/train/out/production/train/classinfo.properties*/
            System.out.println(path);
        }
    }
    

    Thread.currentThread():当前线程对象。
    getContextClassLoader():是当前线程对象的方法,可以获取当前线程类加载器对象。
    getResource():(获取资源)是类加载器对象的方法,当前线程的类加载器默认从类的根路径下(从src开始)加载资源。
    getPath():获取路径。

    为什么取绝对路径不能用以下代码?

     FileReader reader = new FileReader("E:/IdeaProjects/train/out/production/train/classinfo.properties");
    

    答:该方式只能在 windows 系统上可行,在 Linux 系统不可行(没有盘符)。

      public static void main(String[] args) throws Exception{
         /*   方式一
         //通过IO流读取classinfo.properties文件
            //FileReader reader = new FileReader("src/com/reflecttest/www/classinfo.properties");
            */
          /* 方式二
          //获取路径
           String path = Thread.currentThread().getContextClassLoader()
                    .getResource("com/reflecttest/www/classinfo.properties").getPath();
          //通过IO流读取classinfo.properties文件
          FileReader reader = new FileReader(path);
          */
          
          // 方式三:以流的形式返回
          InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/reflecttest/www/classinfo.properties");
          
            //创建属性类对象Map,对应的key和value都是String
            Properties p = new Properties();
            //加载
            p.load(reader);
            //关闭流
            reader.close();
            //通过key获取value
            String className = p.getProperty("className");
          System.out.println(className);//com.reflecttest.www.User
    

    资源绑定器

    java.util包中提供了一个资源绑定器,便于获取属性配置文件中的内容,属性配置文件xxx.properties必须放到类路径(src)下(以src为路径起点)。

    public static void main(String[] args){
        //资源绑定器,写路径的时候扩展名properties不能写
        ResourceBundle rb = ResourceBundle.getBundle("com/reflecttest/www/classinfo");
        String className = bundle.getString("className");
        System.out.println(className);
    }
    

    资源绑定器:只能绑定xxx.properties文件,且该文件必须在类路径(src)下,写路径的时候扩展名不能写。

    类加载器

    1、什么是类加载器?有哪些类加载器?

    答:专门负责加载类的命令 / 工具,ClassLoader。JDK中自带三个类加载器 启动类加载器、扩展类加载器、应用类加载器

    2、举例:

    String s = "svda";
    

    代码开始执行前,会将所需要类全部加载到JVM中,通过类加载器加载,看到以上代码类加载器会找String.class文件进行加载:

    首先通过启动类加载器加载JDK中JRE下的rt.jar包(除了String.class文件外,还有很多其他文件,都是JDK最核心的类库)例如:C:jdk11jrelib t.jar

    如果通过启动类加载器加载不到String.class文件,会通过扩展类加载器加载,注意扩展类加载器专门加载C:jdk11jrelibext文件夹。

    如果通过扩展类加载器加载不到,则会通过应用类加载器加载classpath中的类。

    3、双亲委派机制

    Java 中为了保证类加载的安全,使用了双亲委派机制,优先从启动类加载器中加载(这个称为父),再从扩展类加载器中加载(这个称为母),如果都加载不到,才会考虑从应用类加载器中加载,直到加载到为止。

    获取 Field

    类中属性private int id;是一个 Field 对象,通过 Field 可以拿到private、int、id

    class User{
        private int id;
        public String name;
        protected String adress;
        boolean sex;
        public static final double PI=3.1415926;
    }
    
    //测试类
    public class ReflectTest04{
        public static void main(String[] args){
            //获取整个类
            Class userClass = Class.forName("com.reflecttest.www.User");
            String className = userClass.getName();
            System.out.println("完整类名:"+className); //com.reflecttest.www.User
            
            String simpleName = userClass.getSimpleName();
            System.out.println("简类名:"+simpleName); //User
            
            //获取类中所有public修饰的Field
            Field[] fields = userClass.getFields();
            System.out.println(fields.length); //1,只有一个
            //取出这个Field
            Field f = fields[0];
            //获取这个Field的名字
            String fieldName = f.getName();
             System.out.println(fieldsName); 
            
            //获取所有的Field
            Field[] fs = userClass.getDeclaerdFields();
             System.out.println(fs.length); //4个
            //遍历属性
            for(Field field : fs){
                //获取属性的修饰符列表
                int i = field.getModifiers();//返回的是修饰符的代号
                System.out.println(i);
                //将代号转换成字符串
                String modifierString = Modifier.toString(i);
                System.out.println(modifierString);
                
                //获取属性类型
                Class fieldType = field.getType();
                /*String fName = fieldType.getName();*/
                //获取简易的属性类型
                String fName = fieldType.getSimpleName();
                System.out.println(fName);
                
                 //获取属性名
                  System.out.println(field.getName()); 
            }
        }
    }
    

    反编译 Field:(了解即可)

    public class ReflectTest05{
        public static void main(String[] args){
        //创建可拼接的字符串
        StringBuilder s = new StringBuilder();
        //获取类名
        Class userClass = Class.forName("com.reflecttest.www.User");
        
        s.append(Modifier.toString(userClass.getModifiers())+" class "+ userClass.getSimpleName()+"{
    ");
        
        Field[] fields = userClass.getDeclaredFields();
        for(Field field : fields){
            s.append("	");
     //获取属性修饰符    
            s.append(Modifier.toString(field.getModifiers()));
            s.append(" ");
            //获取属性类型  
            s.append(field.getType().getSimpleName());
            s.append(" ");
            //获取属性名字
            s.append(field.getName());
            s.append(";
    ");
        }
        s.append("}");
        System.out.println(s);
    }
    }
    

    访问对象属性

    使用反射机制访问对象属性(给属性赋值、获取属性的值)。

    public class ReflectTest06{
        public static void main(String[] args){
        	//获取类名
            Class userClass = Class.foeName("com.reflecttest.www.User");
            //实例化类(类似User user = new User();)
            Object obj = userClass.newInstance();//底层调用无参构造
            //根据属性名称获取Field
            Field fieldName = userClass.getDeclaredField("name");
            //给obj对象(User对象)的name属性(public修饰)赋值
            fieldName.set(obj,"zhang");//类似:user.name = "zhang";
            //读取属性的值
            System.out.println(fieldName.get(obj));
    }
    }
    

    通过配置文件可以修改类和类的属性,只需修改配置文件就可以访问不同的类属性,从而实现代码复用。

    反射机制让代码复杂了,但使得代码更灵活了。

    反射机制缺点:对于private修饰的属性要打破封装,才能访问。(在外部也可访问private修饰的存在安全隐患)

    //访问私有属性
    Field fieldName = userClass.getDeclaredField("id");
    //打破封装
    fieldName.setAccessible(true);
    //给属性赋值
    fieldName.set(obj,5);
    //获取属性值
    System.out.println(fieldName.get(obj));
    

    反射 Method

    public class User{
        public boolean login(String name,String password){
            if("admin".equals(name) && "123".equals(password)){
                return true;
            }
            return false;
        }
        
        public void logout(){
            System.out.println("系统已退出");
        }
    }
    
    
    public class ReflectTest07{
        public static void main(String[] args){
            Class userClass = Class.forName("com.reflecttest.www.User");
            //获取所有的Method(包含私有的)
            Method[] methods = userClass.getDeclaredMethods();
            //遍历Method
            for(Method method : methods){
                //获取修饰符
                System.out.println(Modifier.toString(method.getModifiers()));
                //获取方法的返回值类型
                  System.out.println(method.getReturnType().getSimpleName());
                //获取方法名
                System.out.println(method.getName());
                //方法的参数(一个方法可能有很多个参数)
                Class[] parameterTypes = method.getParameterTypes();
                for(Class parameterType : parameterTypes){
                    System.out.println(parameterType.getSimpleName());
                }
            }
        }
    }
    

    通过反射机制调用对象的方法(※)

    1、Java 中怎么区分一个方法?

    答:通过方法名和形参进行区分。

    public static void main(String[] args){
         Class userClass = Class.forName("com.reflecttest.www.User");
        //实例化对象(User user = new User();)
        Object obj = userClass.newInstance();
        //获取Method
        Method loginMethod = userClass.getDeclaredMethod("login",String.class,String.class);
        //调用方法(boolean returnValue = user.login("admin","123");)
        Object returnValue = loginMethod.invoke(obj,"admin","123");
        System.out.println(returnValue);//true
    }
    

    方法getDeclaredMethod()

    image-20200804155628049

    反编译一个类的Constructor构造方法

    class User{
        int a;
        String b;
        public User(){}
        public User(int a){
            this.a = a;
        }
        public User(int a,String b){
            this.a = a;
            this.b = b;
        }
    }
    
    public class ReflectTest10{
        public static void main(String[] args){
            StringBuilder s = new StringBuilder();
            Class userClass = Class.forName("com.reflecttest.www.User");
            s.append(Modifier.toString(userClass.getModifiers()));
            s.append(" class ");
            s.append(userClass.getSimpleName());
            s.append("{
    ");
            //拼接构造方法
            Constructor[] constructors = userClass.getDeclaredConstructors();
            for(Constructor constructor : constructors){
                s.append("	");//制表符
                s.append(Modifier.toString(constructor.getModifiers()));
                s.append(" ");
                s.append(userClass.getSimpleName());
                s.append("(");
                //拼接参数
                Class[] parameterTypes = constructor.getParameterTypes();
                for(Class parameterType : parameterTypes){
                    s.append(parameterType.getSimpleName());//获取参数类型
                    s.append(",");
                }
                //当参数个数大于0,删除最后一个逗号
                if(parameterTypes.length > 0){//不做此判断,无参构造方法会少一个括号
                    s.deleteCharAt(s.length()-1);
                }
                s.append("){}
    ");
                
            }
            s.append("}");
            System.out.println(s);
        }
    }
    

    反射机制调用构造方法:

    public class ReflectTest11{
        public static void main(String[] args){
            //不使用反射机制创建对象
            User user = new User();
            User user = new User(5,"zhang");
            
            //使用反射机制创建对象
            Class c = Class.forName("com.reflecttest.www.User");
            //调用无参构造方法创建对象
            Object obj1 = c.newInstance();
            //调用有参构造方法创建对象
            //一:获取有参构造方法
            Constructor con = c.getDeclaredConstructor(int.class,String.class);
            /*获取无参构造方法
            Constructor con = c.getDeclaredConstructor();
            Object obj3 = con.newInstance();
            System.out.println(obj3);
            */
            //二:调用构造方法new对象
            Object obj2 = con.newInstance(5,"zhang");
            System.out.println(obj2);
        }
    }
    

    获取父类和父接口

    如何获取一个类的父类,以及该类实现了哪些接口?

    public class ReflectTest11{
        public static void main(String[] args){
        //以String为例
            Class stringClass = Class.forName("java.lang.String");
            //获取String的父类
            Class superClass = stringClass.getSuperclass();
            System.out.println(superClass.getName());
            //获取String类实现的所有接口
            Class[] interfaces = stringClass.getInterfaces();
            for(Class inter : interfaces){
                System.out.println(inter.getName());
            }
    }
    }
    
  • 相关阅读:
    显示图案
    圆的面积和周长
    Python基础--list列表删除元素
    Python基础--列表添加元素
    Python基础--列表创建访问删除
    Python基础--序列简介
    一个网页通用的测试用例(转载)
    测试计划与测试方案的区别
    判断一棵树是否是二叉平衡树
    判断丑数与寻找丑数
  • 原文地址:https://www.cnblogs.com/gyunf/p/13466165.html
Copyright © 2020-2023  润新知