反射机制

反射

反射机制可以将一个类的实例化操作,方法调用,属性的调用等操作由编码期决定改为在运行期决定,这样大大的提高了代码的灵活性;
适度的使用反射机制可以提高代码复用及灵活性.由于反射会有额外的性能开销,所以过度的使用会降低系统的性能;
1.反射是Java提供的API,是Java的一部分
2.反射提供了动态解析对象其类型和结构的功能
3.反射API提供了很多动态功能
- 动态加载类
- 动态创建对象
- 动态调用方法
- 动态解析注解
4.利用反射可以解决程序直接的耦合性问题

利用反射检查对象内部结构

Java 在Object类上定义了 getClass方法, 用于检查当前对象的具体类型

Class cls = obj.getClass();
System.out.println(cls);

再工作中可以用于检查对象的具体类型,比如检查ArrayList迭代器接口的实现类型是啥类型的:

ArrayList list=new ArrayList();
Iterator i = list.iterator();
Class cls = i.getClass();
System.out.println(cls);

反射API提供了检查类内部的结构信息:

//检查类的成员变量的方法
        /*
         * getDeclaredFields 返回当前类中声明的全部成员变量,类可能有多个成员变量,所以返回数组
         */
        Field[] fields = cls.getDeclaredFields();//不含继承的方法
        cls.getMethods();//获取包括父类可继承及本身申明的方法
        for (Field field : fields) {
            System.out.println(field);
            //检查类中声明的全部方法信息
            Method[] methods = cls.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println(method);
                //类中声明的构造器
                Constructor[] con = cls.getConstructors();
                for (Constructor constructor : con) {
                    System.out.println(constructor);

反射提供了动态加载类功能

动态加载类型:
Class类:
Class的每个实例称为类的类对象
他是反射机制的最重要的一个类
当我们需要使用一个类时,JVM首先会加载该类的class文件,这时JVM就会实例化一个Class的实例,用来加载该类的信息.
由于JVM加载一个类只进行一次,所以每个被JVM加载的类都有且只有一个Class实例用于表示他.
通过Class的实例可以获取其表示类的一系列信息,比如类的名字,有哪些属性那些构造方法等.并其提供了一个方法newInstance可以
调用其表示该类的无参构造方法将其实例化(需要有无参构造方法,否则抛出异常)
想获取一个类的对象通常可以通过下面方法得到:
1.每个类都有一个静态属性class,可以直接返回其类的对象,例如想获取表示String累的对象可以:Class cls = String.class
2.通过Class的静态方法forName加载该类并得到对象:Class cls = Class.forName(“java.lang.String”)
3.还可以通过加载器ClassLoader加载(更灵活)

Class cls = Class.forName("类名");
Class cls = obj.getClass;

这两种方法返回的Class 对象有差别吗? 没有差别!都类型信息。这个两种方式的区别在于前提条件不同!
- obj.getClass() 的前提条件是有对象 obj,查找对象的类型。
- Class.forName(类名) 的前提条件是知道“类名”,没有对象!

举个栗子:

public static void main(String[] args) {
        Scenner cin = new Scanner(System.in);
        String className = cin.nextLine();
        try {
            //通过这个cls我们可以获取Person的相关信息,并且可以实例化该类,注:除了Person类之外,还可以加载Java原生类
            Class cls = Class.forName(className);//注意:如果类名错误则会抛出ClassNotFoundException
            System.out.println(cls);
            System.out.println(cls.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

Person类:
public class Person {
    public void sayHolle() {
        System.out.println("Holle!");
    }
    public void sayHi() {
        System.out.println("Hi!");
    }
    public void sayName(String name) {
        System.out.println("大家好,我叫"+name);
    }
    public void sayName(String name,int age){
        System.out.println("大家好,我叫"+name+",今年"+age+"岁.");
    }
    private void sayBye() {
        System.out.println("Bye-Bye");
    }
}

反射API提供了动态创建对象功能

反射API可以”动态”创建对象
- 静态创建,事先知道类名,根据类名创建对象
- 动态创建,事先不知道类名,在用的时候才知道用哪个

        //动态创建对象:类型中必须包含无参构造器
       //newInstance()方法底层调用无参构造器
       /*
         * 如果没有无参数构造器,则会抛出异常InstantiationException
         * 如果调用私有构造器,则会抛出IllegalAccessException    
         */
         Class cls = Class.forName(类名);
         //通过Class实例化其表示的类
         Object obj = cls.newInstance();

反射API可以调用有参构造器来创建对象

反射API可以动态找到有参构造器,利用反射API动态创建对象
静态调用有参构造器

Person person = new Person(参数列表);

动态调用有参构造器

Class cls = Class.forName(类名);
//找到一个有参构造器
Constructor c = cls.getConstructor(int.class);
Object o = con.newInstance(n);

看一个小的Dome:

//这里需要处理很多异常:   ClassNotFoundException, NoSuchMethodException, 
                        //SecurityException, 
                        //InstantiationException, IllegalAccessException, 
                        //IllegalArgumentException, InvocationTargetException
    public static void main(String[] args) throws Exception {
        Scanner cin = new Scanner(System.in);
        System.out.println("请输入类名:");
        String className = cin.nextLine();
        System.out.println("请输入要传递的参数:");
        int n = cin.nextInt(); 
        Class cls = Class.forName(className);
        //在cls对应的类上查找一个int参数的构造器,找到以后赋值给Constructor的对象
        //如果没有找到构造器则抛出异常
        Constructor con = cls.getConstructor(int.class);// int.class表示int类型
        //newInstance()执行当前有参构造器创建对象,这里一定要传递对应,类型数据,否则将抛出异常
        Object o = con.newInstance(n);
        //检查创建对象
        System.out.println(o);//需要对toString()进行重写
    }

Class.newInstance()与Constructor.newInstance()
- Class.newInstance()只能调用类中无参构造器,如果没有无参构造器则抛出异常.
- Constructor.newInstance()可以调用任何一个构造器
- 在实际工作中,大部分类都是无参构造器!所以再利用反射创建对象时Class.newInstance()更加方便常用.

利用反射调用方法

步骤:
- 1实例化
* 1.1加载Person类
* 1.2通过Class实例化Person
* 2调用方法
* 2.1通过Class获取方法
* 2.2通过Method调用其表示的方法

上面讲的是调用可见方法
看一个简单的小Dome:

public static void main(String[] args) {
        Person p = new Person();
        p.sayHolle();
        try {
            //1.实例化      
            //1.1加载Person类
            Class cls = Class.forName("reflet.Person");
            //1.2实例化Person
            Object o = cls.newInstance();
            //2.调用方法
            //2.1通过Class.getDeclaredMethod()获取方法(Method实例)
            Method method = cls.getDeclaredMethod("sayHolle");
            //通过Method.invoke()调用并启动方法      
            method.invoke(o, null);
/*
   *通过invoke方法调用其表示的方法.该方法要求传入两个参数,第一个为当前方法所属的对象,第二个为调用该方法时要传入的   *实参(如果没有直接传null)
*/
        } catch (Exception e) {
            e.printStackTrace();
        }
}

Method类

Method类的每一个实例,用于表示一个类中的一个方法通常通过Class获取通过它可以得知其表示的方法更多的详细信息:访问修饰符,返回值类型,参数,名字等.并且可以通过invoke方法调用其表示的方法.该方法要求传入两个参数,第一个为当前方法所属的对象,第二个为调用该方法时要传入的实参(如果没有直接传null)

利用反射调用私有方法

步骤:
- 1实例化
* 1.1加载Person类
* 1.2通过Class实例化Person
* 2调用方法
* 2.1通过Class获取方法
* 2.2调用method.setAccessible(true);强制调用私有方法
* 2.3通过Method调用其表示的方法
Dome:

public static void main(String[] args) {
try {
//1.实例化
//1.1加载Person类
Class cls = Class.forName("reflet.Person");
//1.2通过Class实例化Person
Object o = cls.newInstance();
//2.调用方法
//2.1通过Class.getDeclaredMethod()获取方法(Method实例)
Method method = cls.getDeclaredMethod("sayBye");
//2.2调用method.setAccessible(true);强制调用私有方法
/*
*反射API在调用方法时,Object对象必须包含method方法,参数必须和方法实际参数一致
*否则执行API将抛出异常!
*/
method.setAccessible(true);//设置为true时可以强制调用sayBye方法(可以理解为打开访问权限)
/*注意,一定要在invoke之前调用setAccessible(true);否则会抛出异常*/
//2.3通过Method.invoke()调用并启动其表示的方法
Object obj = method.invoke(o, null);
//当方法没有返回值时,则返回null.
System.out.println(val);
} catch (Exception e) {
e.printStackTrace();
}
}

方法的变长参数(JDK1.5+的特性)

变长参数本质上就是一个数组参数!
- 使用类型… 声明的参数称为变长参数,在调用时可以传递任意多个参数
- 在方法中按照数组对变长参数进行处理
- 本质上变长参数就是数组参数,可以直接接受同类型数组参数.
- 注意事项:一个方法只能有一个变长参数,且只能作为最后一个参数
- 技巧:如果祥接收多态参数可以使用Object…
Dome:

    public static void main(String[] args]){
              test();
              test(1);
              test(2,3);
              Test(0);
              Test("你好","double");
    }
  public static void test(int... a){
             //a实际上是一个数组
             int sum = 0;
             for(int i:a){
                   sum+=i;
             }
           System.out.println(sum);
  }
  public static void Test(Object... o){
             for(Object obj:o){
                     System.out.println(obj);
             }
 }

反射的实际用处

经常用于解耦:降低程序的耦合度!!

案例:

需求:

实现一段程序:执行一个类中声明的全部以test开头的无参数构造方法.
分析:
被调用的类名—不知道,是未来的某个类
被调用的方法名—-不知道,是某些以test开头的方法
说明:当前程序将于未来的某个类的某些方法进行耦合,需要使用反射
实现:
1.有用自己输入未来的类名
2.利用反射将类加载到内存中
3.再利用反射解析类的结果,查找全部方法哪些是以test开头的方法
4.利用反射创建对象
5.执行对象中全部以test开头的方法

这里留给各位看官自己去思考吧 嘻嘻

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章