为了面试而战-反射

今天来学习新的内容,反射。

反射

什么是反射?官方的解释是反射允许对封装类的字段、方法和构造函数的信息进行编程访问。这里的字段就是成员变量,方法就是成员方法,构造函数就是构造方法。

一个类的里面,常用的就是成员变量、成员方法、构造方法,反射就可以想成是一个人,他可以把这三个东西都获取到,并对他们进行操作。那获取出来有什么用呢?很有用的,比如IDEA中的自动提示功能就是反射,我们创建了一个对象,IDEA就可以把这个对象能调用的成员方法都获取出来并进行展示。还有我们在创建对象或者调用方法时,形参给忘了,按一下ctrl+p就可以获取提示,这也是反射,获取所有的形参并展示出来。说白了,反射就是可以从类里面拿东西,拿什么,就是常用的成员方法、成员变量、构造方法。

利用反射我们可以把成员变量获取出来,就可以得到这个变量的所有信息,比如修饰符、名字、数据类型、赋值或者已经记录的值。利用反射还可以获取构造方法,也能得到所有信息,扒得干干净净, 比如名字、修饰符、形参,甚至可以用获取出来的方法创建对象。也可以获取成员方法,同样干干净净,可以获取到修饰符、名字、形参、返回值,甚至方法抛出的异常,方法上的注解,运行获取的方法。在反射面前,一切都是赤裸裸的。

以上操作可以分成两类,一是获取,而是解剖。获取的时候不是从java文件中获取,而是从字节码文件.class中获取。我们要先学习如果获取.class字节码文件对象。再去从字节码文件对象中获取字段、方法以及构造方法。

获取class对象的三种方式

  1. Class.forName("全类名"); Class是类名,java定义好了这个类,用来描述字节码文件。forName()是静态方法。
  2. 类名.class;
  3. 对象.getClass(); getClass()是定义在Object中,所有对象都可以调用。

那这三种方式什么时候用呢?这三种方式就对应了java里面的三种不同的阶段,或者说我想创建一个对象,是要经历三个阶段的。

第一个阶段:先编写java文件,把他编译成class文件,这个阶段没有把代码加载到内存中,是在硬盘中操作,这个阶段称为源代码阶段。在这个阶段会通过第一种方式获取字节码文件对象。

第二个阶段:运行代码,会把.class文件加载到内存中,这个阶段称为加载阶段,用第二种方式。

第三个阶段:我要创建这个类的对象,此时称为运行阶段,用第三种方式。

看一下代码实现

public class MyReflectDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
		//第一种方式
	  	//全类名:包名+类名
	  	//最为常用
	  	Class clazz1 = Class.forName("com.itheima.myreflect1.Student");
	  	
	  	//第二种
	  	//一般更多的是当做参数,之前学多线程的时候,同步代码块synchronized(对象.class)
	  	Class clazz2 = Student.class;
	  
	  	//第三种
	  	//当我们已经有了这个类的对象时,才可以使用
	  	Student s = new Student();
	  	Class clazz3 = s.getClass();
	}
}
	  

Java中万物皆对象,class对象就是Class类。构造方法也可以看成是对象,是Constructor类。成员变量是Field类对象,成员方法是Method类的对象。

利用反射获取构造方法

public class MyReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    /*
        Class类中用于获取构造方法的方法
            Constructor<?>[] getConstructors()   返回所有公共构造方法对象的数组
            Constructor<?>[] getDeclaredConstructors()   返回所有构造方法对象的数组
            Constructor<T> getConstructor(Class<?>... parameterTypes)  返回单个公共构造方法对象
            Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回单个构造方法对象
			
        Constructor类中用于创建对象的方法
            T newInstance(Object... initargs)   根据指定的构造方法创建对象
            setAccessible(boolean flag)   设置为true,表示取消访问检查
    */
	 	//1.获取class字节码文件对象
	  	Class clazz = Class.forName("com.itheima.myreflect1.Student");
	  
	  	//2.获取构造方法
	  	/*
		获取所有公共构造方法
	  	Constructor[] cons = clazz.getConstructors();
	  	for(Constructor con : cons){
			System.out.println(con);
		}
		*/
	  	//获取所有构造方法
	  	//Constructor[] cons = clazz.getDeclaredConstructors();
	  	
	  	//获取单个
	  	Constructor con1 = clazz.getDeclaredConstructor();//空参构造
	  	
	  	//获取有参构造函数,形参里的参数类型要和类中的构造方法形参类型一致
	  	Constructor con2 = clazz.getDeclaredConstructor(String.class);
	  	Constructor con3 = clazz.getDeclaredConstructor(String.class,Integer.class);
	  	
	  	//获取权限修饰符
	  	int modifiers = con3.getModifiers();//private - 2 public - 1 在帮助文段中能查到,搜索常量字段值,就有权限修饰符所对应的数字。修饰权限符的应用场景:IDEA底层会通过反射获取到权限修饰符,如果是私有的就不会给你提示,不让调用
	  	Parameter[] parameters = con3.getParameters();
	  	
	  
	  	//拿到构造方法了,可以创建对象。con3获取的有参构造,所以创建对象的时候也得带参数
	  //表示临时取消权限校验,不做这一步会报错。因为这个构造是private修饰的,之前con3.getDeclaredConstructor()只是能让我们看见这个构造,并不能创建对象。
	  //这就叫暴力反射,你已经藏起来变成私有的,但我还能给你拿出来。
	  con4.setAccessible(true);
	  	Student stu = (Student)con3.newInstance("张三",23);

利用反射获取成员变量

public class MyReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    /*
       Class类中用于获取成员变量的方法
            Field[] getFields():                返回所有公共成员变量对象的数组
            Field[] getDeclaredFields():        返回所有成员变量对象的数组
            Field getField(String name):        返回单个公共成员变量对象
            Field getDeclaredField(String name):返回单个成员变量对象

       Field类中用于创建对象的方法
            void set(Object obj, Object value):赋值
            Object get(Object obj)              获取值

    */
	  //1.还是要先获取字节码文件的对象,因为每一个要获取的东西都是在字节码文件里的
	  Class clazz = Class.forName("com.itheima.myreflect1.Student");
	  
	  //2.获取成员变量
	  //获取公共的成员变量
	  Field[] fields = clazz.getFields();
	  //获取所有的成员变量
	  Field[] fieldsD = clazz.getDeclaredFields();
	  //获取公共单个成员变量
	  Field gender = clazz.getField("gender");
	  //获取单个成员变量
	  Filed name = clazz.getField("name");
	  
	  //获取权限修饰符
      int modifiers = name.getModifiers();
	  //获取成员变量名字
	  String n = name.getName();
	  //获取成员变量数据类型
	  Class<?> type = name.getType();
	  //获取成员变量记录的值
	  //和对象有关,要先创建一个对象
	  Student s = new Student("zhangsan",23,"男");
	  name.setAccessible(true);//临时取消访问权限
	  Object value = name.get(s);
	  //修改对象里记录的值
	  name.set(s,"lisi");
	}
}

利用反射获取成员方法

public class MyReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    /*
       Class类中用于获取成员变量的方法
            Field[] getFields():                返回所有公共成员变量对象的数组
            Field[] getDeclaredFields():        返回所有成员变量对象的数组
            Field getField(String name):        返回单个公共成员变量对象
            Field getDeclaredField(String name):返回单个成员变量对象

       Field类中用于创建对象的方法
            void set(Object obj, Object value):赋值
            Object get(Object obj)              获取值

    */
		//1.获取class字节码文件的对象
        Class clazz = Class.forName("com.itheima.myreflect3.Student");
	  
	  	//2.获取所有方法对象
	  	//(包括父类中所有的公共方法)
	  	Method[] methods = clazz.getMethods();
	  	//获取不到父类中的方法
	  	Method[] methods = clazz.getDeclaredMethod();
	  
	  //获取单个方法
	  //一定要加参数类型,因为方法可能有重载的情况,不加参数类型不知道是哪个方法
	  Method m = clazz.getDeclaredMethod("eat",String.class);
	  
	  // 获取方法的修饰符
     int modifiers = m.getModifiers();
     System.out.println(modifiers);

     // 获取方法的名字
     String name = m.getName();
     System.out.println(name);

     // 获取方法的形参
     Parameter[] parameters = m.getParameters();
     for (Parameter parameter : parameters) {
         System.out.println(parameter);
     }
	 
	 //获取方法抛出的异常
	 Class[] exceptionTypes = m.getExceptionTypes(); 
	  
	  //方法运行
	  /*Method类中用于创建对象的方法
        Object invoke(Object obj, Object... args):运行方法
        参数一:用obj对象调用该方法
        参数二:调用方法的传递的参数(如果没有就不写)
        返回值:方法的返回值(如果没有就不写)*/
	  Student s = new Student();
	  //参数一s:方法的调用者
	  //参数二“汉堡包”:调用的时候传入的实际参数
	  m.setAccessible(true);
	  Onject result = m.invoke(s,"汉堡包");

反射的作用

学习完反射的API,我们来看一下反射的作用是什么

  • 获取一个类里面所有的信息,获取到之后,再执行其他的业务逻辑
  • 结合配置文件,动态的创建对象并调用方法

用两个练习来说明作用:

保存信息
对于任意一个对象,都可以把对象所有的字段名和值,报错到文件中去。
public class Student {
    private String name;
    private int age;
    private char gender;
    private double height;
    private String hobby;

    public Student() {
    }

    public Student(String name, int age, char gender, double height, String hobby) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.height = height;
        this.hobby = hobby;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 获取
     * @return gender
     */
    public char getGender() {
        return gender;
    }

    /**
     * 设置
     * @param gender
     */
    public void setGender(char gender) {
        this.gender = gender;
    }

    /**
     * 获取
     * @return height
     */
    public double getHeight() {
        return height;
    }

    /**
     * 设置
     * @param height
     */
    public void setHeight(double height) {
        this.height = height;
    }

    /**
     * 获取
     * @return hobby
     */
    public String getHobby() {
        return hobby;
    }

    /**
     * 设置
     * @param hobby
     */
    public void setHobby(String hobby) {
        this.hobby = hobby;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}";
    }
}

public class MyReflectDemo {
    public static void main(String[] args) throws IllegalAccessException, IOException {
    /*
        对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去
    */
       Student s = new Student("小A",23,'女',167.5,"睡觉");
       Teacher t = new Teacher("播妞",10000 );
       saveObject(s);
    }

    //把对象里面所有的成员变量名和值保存到本地文件中
    public static void saveObject(Object obj) throws IllegalAccessException, IOException {
		//1.获取字节码文件的对象
		Class clazz = obj.getClass();
		
		//创建IO流
		BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\a.txt"));
		//2.获取所有的成员变量
		Field[] fields = clazz.getDeclaredField();
		for(Field field : fields){
			//不知道哪个是private
			field.setAccessible(true);
			//获取成员变量的名字
			String name = field.getName();
			//获取成员变量的值
			Object value = field.get(obj);
			bw.write(name + "=" + value);
			bw.newLine();
		}
		
		bw.close();
	}
}

练习二

跟配置文件结合动态创建

反射可以跟配置文件结合的方式,动态的创建对象,并调用方法

classname=com.itheima.myreflect6.Teacher //全类名
method=teach	//方法名

  //表示程序在运行的过程中我要去创建上面这个类的对象,并调用下面这个方法。在以后我们要想调用其他类里的方法,测试类里的代码是不需要改的,只需要修改配置文件信息即可。动态的获取全类名,动态的获取方法,获取到哪个类就创建哪个类的对象,获取到哪个方法就运行哪个方法
public class MyReflectDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
	  //1.读取配置文件中的信息
	  Properties prop = new Properties();
	  //要把配置文件中的信息加载到集合中
	  //先定义IO流
	  FileInputStream fis = new FileInputStream("myreflect\prop.properties");
	  prop.load(fis);
	  fis.close();
	  
	  //2.获取全类名和方法名
	  String className = (String)prop.get("classname");
	  String methodName = (String) prop.get("method");
	  
	  //3.利用反射创建对象并运行方法
	  Class clazz  = Class.forName(className);
	  
	  //获取构造方法
	  Constructor con = clazz.getDeclaredConstructor();
	  Object o = con.newInstance();
	  
	  //获取成员方法并运行
	  Method method = clazz.getDeclaredMethod();
	  method.setAccessible(true);
	  method.invoke(o);
	}
}

总结

学完反射,我们要知道以下内

  1. 反射的作用
  2. 获取任意一个类中的所有信息
  3. 结合配置文件动态创建对象
  4. 获得class字节码文件对象的三种方式
  5. Class.forName("全类名");
  6. 类名.class
  7. 对象.getClass();
  8. 如何获取构造方法、成员方法、成员变量
  9. get:获取
  10. set:设置
  11. Constructor:构造方法
  12. Parameter:参数
  13. Field:成员变量
  14. Modifiers:修饰符
  15. Method:方法
  16. Declared:私有的

这些就是我学到的反射的基础知识,希望能够帮助大家。

#你觉得今年春招回暖了吗##牛客解忧铺##牛客在线求职答疑中心##23届找工作求助阵地#
java基础知识 文章被收录于专栏

我是一个转码的小白,平时会在牛客中做选择题,在做题中遇到不会的内容就会去找视频或者文章学习,以此不断积累知识。这个专栏主要是记录一些我通过做题所学到的基础知识,希望能对大家有帮助

全部评论
进来狠狠学习
点赞 回复 分享
发布于 2023-03-14 13:30 湖北
又是学到了的一天
点赞 回复 分享
发布于 2023-03-14 13:39 黑龙江

相关推荐

德科信息 华为OD岗位 20K+ 统招本科
点赞 评论 收藏
分享
评论
31
16
分享
牛客网
牛客企业服务