Java学习之:反射机制

Posted This is bill

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java学习之:反射机制相关的知识,希望对你有一定的参考价值。

一、反射机制应用场景

知道在哪里用的情况很重要,任何东西的产生都有他的来由,知道了场景才知道为什么要发明这个东西。

一般在开发针对java语言相关的开发工具和框架时使用,比如根据某个类的函数名字,然后执行函数,实现类的动态调用!

而且这么看,所有面向对象的语言可能都会用到这个机制,西草原生并不支持这种机制,但是可以手动实现,详情请见好基友的文章,http://blog.csdn.net/k346k346/article/details/51698184

二、反射机制

言归正传,来具体说说什么是反射机制

Java反射机制主要提供了以下功能:在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。

先简单介绍下常用的运用反射时使用到的方法:

利用反射调用类方法时的注意点:

Method getMethod()只能调用共有方法,不能反射调用私有方法

Method getDeclaredMethod()能够访问本类中定义的所有方法,访问私有方法的时候需要使用method.setAccessible(true)来获得权限

利用发射获得调用类中指定的属性的注意点:

Filed getFiled() 获得类中指定的public属性(包括基类)

Filed getDeclaredFiled() 返回类中的任何可见性的属性(不包括基类)

如果要获得基类的某个非public属性的值,只能通过反射来调用方法

上代码

package test;

import java.lang.reflect.*;

/**
 * 基类Animal
 */
class Animal {

   public Animal() {

   }

   public int location;

   protected int age;

   private int height;

   int length;

   public int getAge() {
      return age;
   }

   public void setAge(int age) {
      this.age = age;
   }

   private int getHeight() {
      return height;
   }

   public void setHeight(int height) {
      this.height = height;
   }

}


/**
 * 子类 —— Dog
 */
class Dog extends Animal {

   public Dog() {

   }

   public String color;

   protected String name;

   private String type;

   String fur;

}

public class test {
    public static void main(String[] args) {
        System.out.println("Hello World!");

        testRefect();
    }

    private static void testRefect() {
        try {
            //返回类中的任何可见性的属性(不包括基类)
            System.out.println("Declared fileds: ");
            Field[] fields = Dog.class.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                System.out.println(fields[i].getName());
            }

            //获得类中指定的public属性(包括基类)
            System.out.println("public fields: ");
            fields = Dog.class.getFields();
            for (int i = 0; i < fields.length; i++) {
                System.out.println(fields[i].getName());
            }

            //getMethod()只能调用共有方法,不能反射调用私有方法
            Dog dog = new Dog();
            dog.setAge(1);
            Method method1 = dog.getClass().getMethod("getAge", null);
            Object value = method1.invoke(dog);
            System.out.println("age: " + value);

            //调用基类的private类型方法

         /**先实例化一个Animal的对象 */
         Animal animal = new Animal();
         Class a = animal.getClass();
         //Animal中getHeight方法是私有方法,只能使用getDeclaredMethod
         Method method2 = a.getDeclaredMethod("getHeight", null);
         method2.setAccessible(true);

         //java反射无法传入基本类型变量,可以通过如下形式
         int param = 12;
         Class[] argsClass = new Class[] { int.class };
         //Animal中getHeight方法是共有方法,可以使用getMethod
         Method method3 = a.getMethod("setHeight", argsClass);
         method3.invoke(animal, param);

         //Animal中height变量如果声明为static变量,这样在重新实例化一个Animal对象后调用getHeight(),还可以读到height的值
         int height = (Integer) method2.invoke(animal);
         System.out.println("height: " + height);


         /**不用先实例化一个Animal,直接通过反射来获得animal的class对象*/
         Class anotherAnimal = Class.forName("test.Animal");
            //Animal中getHeight方法是私有方法,只能使用getDeclaredMethod
            Method method4 = anotherAnimal.getDeclaredMethod("getHeight", null);
            method4.setAccessible(true);

            //java反射无法传入基本类型变量,可以通过如下形式
            int param2 = 15;
            Class[] argsClass2 = new Class[] { int.class };
            //Animal中setHeight方法是共有方法,可以使用getMethod
            Method method5 = anotherAnimal.getMethod("setHeight", argsClass2);
            method5.invoke(anotherAnimal.newInstance(), param2);

            //Animal中height变量必须声明为static变量,这样在重新实例化一个Animal对象后调用getHeight()才能读到height的值
            // 否则重新实例化一个新的Animal对象,读到的值为初始值
            int height2 = (Integer) method4.invoke(anotherAnimal.newInstance());
            System.out.println("height:" + height2);

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
         e.printStackTrace();
      } catch (InstantiationException e) {
         e.printStackTrace();
      }
   }

}

打印结果:

Hello World!
Declared fileds:
color
name
type
fur
public fields:
color
location
age: 1
height: 12
height:0

特别注意的是,如果通过反射拿到的是一个类的所有原信息,包括注解(spring就是这样做的)

Class anotherAnimal = Class.forName("test.Animal");

如下语句中的 anotherAnimal 包含 Animal 类的所有原信息,但是获得该类的对象需要 newInstance() 方法

Method method5 = anotherAnimal.getMethod("setHeight", argsClass2);
            method5.invoke(anotherAnimal.newInstance(), param2);

至此,我们对于反射的运用有了一定了解,这样在平时的项目开发中我们可以轻松的运用反射的知识来调用封装类中的私有方法和变量,同时在单元测试中也可以对私有方法进行测试。

—————————————华丽丽的分割线———————————————

三、利用反射修改String对象

初学Java的时候, 我们会了解到String是一个不可变对象,例如下面代码:

String s = "Hello"; 
System.out.println("s = " + s); 
s = "Welcome"; 
System.out.println("s = " + s);

打印结果为:
s = Hello
s = Welcome

首先创建一个String对象s,然后设置s的值为“Hello”, 然后又让s的值为“Welcome”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。

也就是说,s只是一个引用,它指向了一个具体的对象,当s=“Welcome”; 这句代码执行过之后,又创建了一个新的对象, 而引用s重新指向了这个新的对象“Welcome”,原来的对象“Hello”还在内存中存在

那为什么String对象是不可变的?

要理解String的不可变性,首先看一下String类中都有哪些成员变量。 在JDK1.6中,String的成员变量有以下几个:

public final class String implements java.io.Serializable, Comparable, CharSequence{
    /** The value is used for character storage. */
    private final char value[];

    /** The offset is the first index of the storage that is used. */
    private final int offset;

    /** The count is the number of characters in the String. */
    private final int count;

    /** Cache the hash code for the string */
    private int hash; // Default to 0

在java中,数组也是一个对象,value是一个引用,它指向一个真正的数组对象,value,offset和count这三个变量都是private的,而且并没有提供setValue, setOffset和setCount等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。此外,value,offset和count这三个变量都是final的, 也就是说在String类内部,一旦这三个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。

String对象真的不可变吗?

从上文可知String的成员变量是 private final 的,也就是初始化之后不可改变。那么在这几个成员中,value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。

那么用什么方式可以访问私有成员呢? 没错,用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:

/**
 * String 对象是不可改变的对象,但可以利用反射方法修改String对象
 */
public class testReflection {

    public static void main(String[] args) throws Exception {
        test();
    }

    public static void test() throws Exception {

        String s = "Hello World";

        System.out.println("s = " + s);

        //获取String的value对象
        Field valueFieldofString = String.class.getDeclaredField("value");

        //改变value的访问属性
        valueFieldofString.setAccessible(true);

        //获得s对象上value属性的值
        char[] value = (char[]) valueFieldofString.get(s);

        //改变value所引用的数组的第5个字符
        value[5] = '_';

        System.out.println("s = " + s);

    }

} 

打印结果为:

s = Hello World
s = Hello_World

在这个过程中,s始终引用的同一个String对象,但是在反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。

四、某大牛写的blog补充

这里写图片描述]

以上是关于Java学习之:反射机制的主要内容,如果未能解决你的问题,请参考以下文章

Java学习之反射

JAVA学习之反射

JavaSE学习之反射

java学习之反射

Java语言特性学习之二反射

代码审计学习之反射型XSS