JAVA基础反射-详解(个人觉得应该还算清楚)

反射在JAVA中充当着举足轻重的作用,反射能够帮助我们在运行时动态的创建对象.

什么叫运行时动态的创建对象?

RTTI为编译期的方法,反射为运行时的方法,所以为了更好的理解反射我们首先要理解RTTI,在此之前我们先要知道java是文件从装载到执行是怎么运行的,才不会对编译期和运行期产生混淆的概念.

1.java文件执行过程:

1.源文件:我们编写.java文件

2.编译:将java文件编译成.class二进制字节码文件(同时会生成同名class文件用于保存Class对象)

3.运行: 把生成的class文件交给jvm.

4.类加载:类加载过程比较复杂,在此粗略说一下.

(1)加载:
将字节码文件通过类加载器装载到内存(java为动态加载,类在使用的时候才会被加载).

(2)连接

  • 验证:为了保证字节码文件符合JVM规范,对其进行验证.
  • 准备:为类变量分配内存,赋初值(初值为JVM中常量定义值)
  • 解析:将符号引用替换成直接引用

(3)初始化:对静态变量进行初始化操作,执行顺序自上而下

5.开始使用

2.Class对象:

Class对象是我们反射运行的基础,是非常重要的一环,在编译的同时会生成同名class文件将Class对象保存到其中.

Class对象中包含类有关的信息,换句话说我们通过反射获取信息的渠道就是Class对象.

Class类提供的方法可以查阅相关文档.

3.什么是RTTI?

RTTI概念最早出现在C++,后来在thinking in java中被Bruce Eckel中提出java中的rtti的说法,但java文档中并没有RTTI的相关概念,众说纷纭.

RTTI的作用:在运行时识别一个对象类型,要求对象类型必须是在编译期已知.

RTTI(Run-Time Type Identification),通过运行时类型信息程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型.

--百度百科

这句话在java中简单来说就是可以查看多态中基类对象实际上指向的是哪个子类.

举个栗子:

基类为Animal,有方法showName,打印自己本类名称的方法.

子类有dog,cat,fish.

当我们通过多态调用时:

Animal a = new cat();//会触发向上转型机制,将对象都作为Animal对象.

a.showName();//此时调用的cat的showName,我们在使用的时候 需要知道现在Animal a对象实际上指向的是谁, 这时候就需要RTTI发挥作用.

RTTI的作用:在运行时识别一个对象类型.

4.什么是反射?

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts on objects, within security restrictions.

The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. Programs can suppress default reflective access control. For more information, see the Reflection documentation.

来自java官方文档

简而言之就是我们可以通过反射在运行时获取程序中的成员信息.

作用:

从上面来看好像看不出直观的区别,当在程序运行后,在网络中获取到一个字节流然后被告知是类对象,或者从磁盘中获取了一段文件,是类信息,要对其进行对象的创建和获取就只能通过反射了,因为此时程序已经过了编译阶段.

反射最重要的用途就是开发各种通用框架.

比如我们现在最常用的Spring框架中的IOC控制反转中javabean自动装载,根据XML进行不同初始化的操作等,实现就是建立在通过reflection的基础之上,在我们编译过后动态的加载需要的对象.

反射的使用:
反射可以用于判断任意对象所属的类,获得Class对象,构造任意一个对象以及调用一个对象。

第一步:获取Class对象(所有我们需要的信息都在这个对象里面,所以前面说这个对象是核心.)

方法1:通过forName

Class.forName():方法返回一个Class对象

forName(String className)//字符串为类名
 
forName(String name, boolean initialize, ClassLoader loader) //类名称,是否进行初始化,选择加载器.

实际上通过forName(String className)获取类对象时相当于调用了
Class.forName(className, true, currentLoader)  自动进行初始化,并且加载器选择的是当前类定义的加载器.

方法2:通过类直接获取

Class<?> clz= String.class//通过.class方法 获取Class对象

方法3:通过对象获取Class

TestA A =new TestA();

A.getClass();

第二步:使用Class对象

判断类实例:
可以使用 instanceof

我们现在也可以使用获取Class对象后中的isInstance();方法进行判断

创建实例:

(1)使用Class对象的newInstance()方法来创建Class对象对应类的实例。

Class<?> c = String.class;//获取Class对象
Object str = c.newInstance();//通过newInstance方法获取实例对象

(2)先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例。这种方法可以用指定的构造器构造类的实例。

//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);


上面说完构造 下边说获取:

	方法获取:
public Method[] getDeclaredMethods() throws SecurityException
//获取所有声明方法,但是不包括继承


Class<?> c = TestA.class;//获取对象
Method[] methods = c.getDeclaredMethods();//通过方法返回Method数组
for(Method method:methods){
    System.out.println(method);//通过Foreach循环打印,println在没有指定的时候会隐式调用toString方法,大家可以自己重写试一下.
}

//输出结果:
public java.lang.String TestA.toString()
public int TestA.getA()
public void TestA.setA(int)		

其他获取方法使用基本相同,在此不多说,有兴趣可以查看官方文档.


方法的调用:
	当我们在上边获取了一个方法之后,我们可以通过invoke方法对其进行调用.
public class test1 {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class<?> klass = methodClass.class;
        //创建methodClass的实例
        Object obj = klass.newInstance();
        //获取methodClass类的add方法
        Method method = klass.getMethod("add",int.class,int.class);
        //调用method对应的方法 => add(1,4)
        Object result = method.invoke(obj,1,4);
        System.out.println(result);
    }
}
class methodClass {
    public final int fuck = 3;
    public int add(int a,int b) {
        return a+b;
    }
    public int sub(int a,int b) {
        return a+b;
    }
}
	//这里代码来自sczyh30具体地址会放到下面网页参考.

利用反射创建数组:

public static void testArray() throws ClassNotFoundException {
    Class<?> cls = Class.forName("java.lang.String");//获取对象
    Object array = Array.newInstance(cls,25);//传入cls对象,数组长度.
    //往数组里添加内容
    Array.set(array,0,"hello");
    Array.set(array,1,"Java");
    Array.set(array,2,"fuck");
    Array.set(array,3,"Scala");
    Array.set(array,4,"Clojure");
    //获取某一项的内容
    System.out.println(Array.get(array,3));
}
//这里代码来自sczyh30具体地址会放到下面网页参考.

这里主要借助反射中的Array类:
public static Object newInstance(Class<?> componentType, int length)
    throws NegativeArraySizeException {
    return newArray(componentType, length);
}
//componentType是一个需要构造的类型,就是我们穿进去的cls类型,后边是指定的长度

public static void set(Object array,
                   int index,
                   Object value)
            throws IllegalArgumentException,
                   ArrayIndexOutOfBoundsException
通过Array提供的set方法进行数组设置, 传入数组对象,下标,数值.
下边get同理.

参考资料:

[1]:https://www.sczyh30.com/posts/Java/java-reflection-1/#%E4%B8%80%E3%80%81%E5%9B%9E%E9%A1%BE%EF%BC%9A%E4%BB%80%E4%B9%88%E6%98%AF%E5%8F%8D%E5%B0%84%EF%BC%9F
[2]:Thinking in java page 313-335
posted @ 2018-05-03 11:08  CurryRice  阅读(218)  评论(0编辑  收藏  举报