反射机制

反射机制概述

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是类的根路径。

注意:真正的“类的根路径”是out\production\project工程\...\*.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:\jdk11\jre\lib\rt.jar

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

如果通过扩展类加载器加载不到,则会通过应用类加载器加载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()+"{\n");
    
    Field[] fields = userClass.getDeclaredFields();
    for(Field field : fields){
        s.append("\t");
 //获取属性修饰符    
        s.append(Modifier.toString(field.getModifiers()));
        s.append(" ");
        //获取属性类型  
        s.append(field.getType().getSimpleName());
        s.append(" ");
        //获取属性名字
        s.append(field.getName());
        s.append(";\n");
    }
    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("{\n");
        //拼接构造方法
        Constructor[] constructors = userClass.getDeclaredConstructors();
        for(Constructor constructor : constructors){
            s.append("\t");//制表符
            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("){}\n");
            
        }
        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());
        }
}
}
posted @ 2020-08-09 20:23  莫哈德  阅读(97)  评论(0编辑  收藏  举报
/*地址栏logo*/