zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Java中的反射机制详解

JAVA反射 详解 机制
2023-09-14 09:13:18 时间

Java中的反射机制详解

1. Java中的反射

1.什么是反射?
2.如何用反射的机制?Java的框架如何使用反射的?
3.Java反射十分重要,反射的知识点对于Java底层,Java框架的学习都有帮助。

2. Class类

1.什么是Class类?
2. 万事万物皆对象。
3.但是在java中,基础类型与Java静态(static)修饰的东西并非是对象。
4.类(这里指的是平常编程里,我们定义的类)是不是对象呢?如果是一个对象,那么类是谁的对象(实例)呢

  • 类是java.lang.Class的实例对象
  • There is a class named Class.
    即:任何一个类都是Class类的实例对象,Class类的实例对象我们称之为 类(类型) 。且这个对象有三种实例化方式:
  • a:类名.class
  • b:某类对象的getClass()方法
  • c:Class.forName("类名");
    针对上述规则,有代码如下:
package shen.liu.enmonster;

public class Test {
    public Test(){
        System.out.println("类Test的实例化对象成功");
    }
    public static void main(String []args) throws ClassNotFoundException {
        //1.通过第一种方式获得类类型
        //我们直接可以通过 类名.class 的方式来获取一个类类型  -->  说明任何一个类都有一个隐藏的静态成员变量class
        Class c1 = Test.class;
        System.out.println(c1);

        //2.通过第二种方法获得类类型
        //新建一个普通类(本例中是Test类)的实例对象,再通过Test的实例对象来获取Test的类类型
        Test t = new Test();
        Class c2 = t.getClass();
        System.out.println(c2);

        //3.通过第三种表达方式获得类类型
        Class c3 = null;
        c3 = Class.forName("shen.liu.enmonster.Test");
        System.out.println(c3);
	try {
            Test t2 = (Test)c1.newInstance();//返回成Test对象
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

注意判别两种实例对象:

  • 一个是Class类的实例对象,这个实例对象就是普通的类
  • 另外一个就是普通类的实例对象,也就是我们通常new出来的实例对象

我们完全可以通过类的类类型来创建类的对象,如:c1.newInstance();

3. Class的动态加载类

1.什么是动态加载类?
编译时刻加载类:静态加载类;
运行时刻加载类:动态加载类。
需要区别编译,运行。

2.Class.forName("ClassName");
4.new 创建对象是静态加载类,不管有没有用到该对象,在编译时刻都会加载。
5.用到哪个就加载哪个,所以根据这个我们可以使用动态加载类。
7.对于功能性的类,我们最好使用动态加载类,而非是静态加载类。
比如说,如果我们需要用到一个类中的某个方法,但是该类中的其它方法存在问题,如果采用静态加载,则会导致该类无法使用。此时我们就应该使用动态加载(即在运行时来判断是否加载该无法运行的方法,如果不是加载“无法运行的方法”,则该类可以正常使用)。

4. 实例

4.1 验证动态/静态加载
package shen.liu.enmonster;

public class Office {
  public static void main(String []args){
      if(args[0].equals("Excel")){//如果该参数是Excel 则加载Excel类的实例
          Excel e = new Excel();
      }
      else if(args[0].equals("Word")){
          Word w = new Word();
      }
  }
}

如果我们仅仅将这个类放到shell窗口运行的话,则会报错。

//package shen.liu.enmonster;
//在使用DOS环境下编译.java文件时,最好不使用包
public class Office {
    public static void main(String []args){
        if(args[0].equals("Word")){
            Word w = new Word();
            w.function();
        }
        else if(args[0].equals("Excel")){
            Excel e = new Excel();
            e.function();
        }
    }
}

运行结果如下:
这里写图片描述
报错的原因是:无法找到Word/Excel两个类。但是我们可能仅仅只是编译一下,却不使用这两个类。当使用命令:javac Office.java时,仍然会把该类的所有对象都给实例化一遍。 导致在寻找Word和Excel时出错!这就是静态加载带来的毛病!
这个并非我们想要的结果,因为我们传递的参数可能既不是Word,也不是Excel。我们该怎么解决这个问题呢?我们使用动态加载即可解决问题,代码如下:

public class OfficeBetter {
    public static void main(String []args) 
    throws IllegalAccessException, InstantiationException {
        Class c = null;
        try {
            c = Class.forName(args[0]);//根据Class反射机制 和传进来的参数 来创建一个类类型

            //我们无法知道这个参数args[0]到底是什么,也就是说我们无法知道这个类型是Word还是Excel
            //所以我们使用一个接口,来让Word和Excel来继承,然后返回的实例类型是接口名即可
            OfficeAble oa = (OfficeAble) c.newInstance();
            oa.function();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

这里写图片描述
修改完成之后,编译Word.java文件,然后使用命令java OfficeBetter Word 运行成功!
这里写图片描述

2.使用Class的API进行操作

  • 利用反射获取成员方法
package shen.liu.enmonster;
import java.lang.reflect.Method;

public class Test {
    public Test(){
        System.out.println("类Test的实例化对象成功");
    }
    public static void main(String []args) {
        Class c1 = int.class;//int 的类类型
        Class c2 = String.class;//String 的类类型
        Class c3 = double.class;
        Class c4 = Double.class;
        Class c5 = void.class;
        //基本的数据类型,void关键字都存在类类型

//        System.out.println(c3);//作用同       System.out.println(c3.getName());
//        System.out.println(c4);
//        System.out.println(c4.getSimpleName());

        //Class的基本API操作
        //获取任意类的全部信息
        //打印类的信息,包括类的成员函数,成员变量       
        String str = "hello";
        printClassMessage(str);
    }
    public static void printClassMessage(Object obj) {
        //要获取类的信息,首先要获取类的类类型
        Class c = obj.getClass();//传递的是哪个子类的对象 c就是该子类的类类型

        //获取类的名称
        System.out.println("类全名为:"+c.getName());

        //万事万物皆对象,方法也是对象。方法是Method的对象
        /**
         * 1.一个成员方法,就是一个Method对象
         * 2.getMethod()方法获取的是所有的public的函数,包括父类继承而来
         * 3.getDeclaredMethods()获取的是所有该类自己声明的方法,无论访问权限是什么
         */
        Method[] ms = c.getMethods();//获取该类的所有方法
        System.out.println("返回类型   "+"方法名  " +"参数类型  ");

        for(Method m :ms){//一个方法循环一次
            //得到方法的返回值类型
            Class returnType = m.getReturnType();
            System.out.print(returnType.getName()+"   ");

            //得到方法的名称
            System.out.print(m.getName()+"(");

            //获取参数类型 --->得到的是参数列表的类型的类类型
            Class[] paramTypes = m.getParameterTypes();
            for(Class cl : paramTypes){
                System.out.print(cl.getName()+" ");
            }
            System.out.println(")");
        }
    }
}
  • 利用反射获取成员变量
package shen.liu.enmonster;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

/**
 * 1.获取成员变量的信息
 * 2.成员变量也是对象  java.lang.reflect.Field
 * Field类封装了关于成员变量的操作
 * getFields()方法获取的是所有public的成员变量的信息
 * getDeclaredFields获取的是该类自己声明的成员变量的信息
 */

public class Test {
    public Test(){
        System.out.println("类Test的实例化对象成功");
    }
    public static void main(String []args) {
        String str = "love";
        try {
            test(str);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
    public static void test(Object obj) throws NoSuchMethodException {
        Class c1 = obj.getClass();
        Field fs[] = c1.getDeclaredFields();
        for(Field f : fs){
            //得到成员变量的类型的类类型
            Class c2 = f.getType();
            String fName = c2.getName();

            System.out.println("类型:"+fName  +"   成员名称:"+f.getName());
        }

        /**
         * 构造函数也是对象
         * java.lang.Constructor中封装了构造函数的信息
         * getConstructors获取了所有的public的构造函数
         * getDeclaredConstructors得到所有的构造函数
         *
         */
    }
}
  • 利用反射,获取某个方法
package  shen.liu.enmonster;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//1.获取某个方法的反射
public class Test{
    public static void main(String []args){
    //要获取print(int,int)方法
        //要获取一个方法就是要获取类的信息,获取类的信息首先就是要
        //获取类的

        A a1 = new A();
        Class c = a1.getClass();

        //2.获取方法 名称 和参数列表来决定
        //getMethod获取的是public方法
        //getDeclaredMethod自己声明的方法
        try {
            Method m = c.getMethod("print",new Class[]{int.class,int.class});
            //Method m = c.getMethod("print",int.class,int.class);效果同上

            //方法的反射操作
            //a1.print(10,20);
            //方法的反射操作,是用m对象来进行方法调用 和a1.print(10,20)效果相同
            try {
                //方法如果没有返回值,返回null,有返回值则返回具体的返回值

                Object o = m.invoke(a1,new Object[]{10,20});
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

class A{
    public void print(int a ,int b){
        System.out.println(a+b);
    }
    public void print(String a,String b){
        System.out.println(a.toUpperCase()+","+b.toUpperCase());
    }
}
  • 利用反射,获取Method集合
package shen.liu.enmonster;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

/***
 * 1.通过class,Method来认识泛型的本质
 */
public class Test {
    public static void main(String []args){
        ArrayList list = new ArrayList();
        ArrayList<String> list1 =new ArrayList<String>();
        list1.add("hello");
        Class c1 = list.getClass();
        Class c2 = list1.getClass();
        System.out.println(c1==c2);
        //反射的操作 都是编译之后的操作

        /**
         * 1.c1 == c2结果返回true ,说明编译之后集合的泛型是去泛型化的
         * 2.Java中集合的泛型,是防止错误输入的,只在编译阶段有效
         * 绕过编译就无效了
         * 3.验证:通过方法的反射来操作,绕过编译
         */
        try {
            Method m = c1.getMethod("add", Object.class);
            try {
                m.invoke(list1,Object.class);

                //观察是否是加入到了list1中
                System.out.println(list1.size());

                //此时不能使用for遍历输出  -->类型错误
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}