java基础-java反射详细讲解

版权声明:本文为博主原创文章,转载需注明出处. https://blog.csdn.net/piaoslowly/article/details/81909735

java基础-java反射

java里的class文件加载分为两种情况,一种就是类型是编译器已知的,这种文件的.class文件在编译的时候,编译器会把.class文件打开检查,但是注意不是加载哦,第二种就是我们可能是从别的地方获取到了一个引用,然后动态的把这个未知类型的引用的对象的.class文件加载进jvm虚拟机里。前者为RTTI,即Run- Time Type Identification 运行时类型识别;后者为“反射”。

要想理解反射的原理,首先要了解什么是类型信息。Java让我们在运行时识别对象和类的信息,主要有2种方式:一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型信息;另一种是反射机制,它允许我们在运行时发现和使用类的信息。

Class加载

理解RTTI在Java中的工作原理,首先需要知道类型信息在运行时是如何表示的,这是由Class对象来完成的,它包含了与类有关的信息。Class对象就是用来创建所有“常规”对象的,Java使用Class对象来执行RTTI,即使你正在执行的是类似类型转换这样的操作。

每个类都会产生一个对应的Class对象,也就是保存在.class文件。所有类都是在对其第一次使用时,动态加载到JVM的,当程序创建一个对类的静态成员的引用时,就会加载这个类。Class对象仅在需要的时候才会加载,static初始化是在类加载时进行的。

Class类是一个非常重要的java基础类,每当装载一个新的类型的时候,java虚拟机都会在java堆中创建一个对应于新类型的Class实例,该实例就代表此类型,通过该Class实例我们就可以访问该类型的基本信息。方法区中会存储某个被装载类的类型信息,可以通过Class实例来访问这些信息。

Class加载过程请参看《jvm-类加载》

反射(Reflection)

什么是反射?

定义:主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。

反射本身并不是一个新概念,它可能会使我们联想到光学中的反射概念,尽管计算机科学赋予了反射概念新的含义,但是,从现象上来说,它们确实有某些相通之处,这些有助于我们的理解。在计算机科学领域,反射是指一类应用,它们能够自描述和自控制。也就是说,这类应用通过采用某种机制来实现对自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

JAVA反射机制

JAVA反射机制:“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”。从这个观点看,Perl,Python,Ruby是动态语言,C++,Java,C#不是动态语言。但是JAVA有着一个非常突出的动态相关机制:Reflection,用在Java身上指的是我们可以于运行时加载、探知、使用编译期间完全未知的classes。换句话说,Java程序可以加载一个运行时才得知名称的class,获悉其完整构造(但不包括methods定义),并生成其对象实体、或对其fields设值、或唤起其methods。

Java 反射机制主要提供了以下功能:

  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时调用任意一个对象的方法。
  • 在运行时判断任意一个对象所属的类。
    这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

反射机制的优点与缺点
为什么要用反射机制?直接创建对象不就可以了吗,这就涉及到了动态与静态的概念,
静态编译:在编译时确定类型,绑定对象,即通过。
动态编译:运行时确定类型,绑定对象。动态编译最大限度发挥了java的灵活性,体现了多态的应用,有以降低类之间的藕合性。

Reflectio API

Reflection是Java被视为动态(或准动态)语言的一个关键性质。
这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息。
包括其modifiers(诸如public、static等)、 superclass(例如Object)、实现了的 interfaces (例如Serializable)、也包括其fields和methods的所有信息,并可于运行时改变fields内容或调用methods。
Java Reflection API简介

在JDK中,主要由以下类来实现Java反射机制,这些类(除了第一个)都位于java.lang.reflect包中

  1. Class类:代表一个类,位于java.lang包下。(这个不属于反射类哦,下面4个才是反射)
  2. Field类:代表类的成员变量(成员变量也称为类的属性)。
  3. Method类:代表类的方法。
  4. Constructor类:代表类的构造方法。
  5. Array类:提供了动态创建数组,以及访问数组的元素的静态方法。

Class类

在java.lang.Object类中定义了getClass()方法,因此对于任意一个Java对象,都可以通过此方法获得对象的类型。
Class类是Reflection API 中的核心类,它有以下方法

  • getName():这个类型的全限定名 。
  • getSuperClass():这个类型的直接超类的全限定名
  • isInterface();这个类型是类类型还是接口类型
  • getTypeParamters();这个类型的访问修饰符
  • getFields():获得类的public类型的属性。
  • getDeclaredFields():获得类的所有属性。
  • getMethods():获得类的public类型的方法。
  • getDeclaredMethods():获得类的所有方法(包括私有的)。
  • getMethod(String name, Class[] parameterTypes):获得类的特定方法,name参数指定方法的名字,parameterTypes参数指定方法的参数类型。
  • getConstructors():获得类的public类型的构造方法。
  • getConstructor(Class[] parameterTypes):获得类的特定构造方法,parameterTypes参数指定构造方法的参数类型。
  • newInstance():通过类的不带参数的构造方法创建这个类的一个对象。

T.class与t.getClass的区别

在java中,无论是类(Class),枚举(enum),接口(interface),数组(array),注解(annotation),内置数据类型(Primitive type),void类型都包含一个特殊的实例对象,就是java.lang.Class类实例。
java.lang.Class类没有public的构造函数,java.lang.Class类实例只能有JVM自动产生,每次JVM加载Class的时候,JVM就会为其自动生成一个java.lang.Class类实例。

就是‘类型’.class

在java中,可以有两个方法获取类的Class实例。
1. Object类有个getClass()方法,可以获取类的java.lang.Class实例。
2. 每个类有个public的静态变量class。即使是int,long,void这些内置类型也是有class静态变量的,但数组是个比较特殊的,只能通过getClass()方法获取Class变量。如果对数组变量进行.class操作,则会有Unknown Class编译错误。

A a = new A();  

if(a.getClass()==A.class) {  

      System.out.println("equal");  

} else {  

      System.out.println("unequal");  

}  
输出结果:equal  

一个是根据对象获取getClass,一个是根据类型获取的class。
在反射中我们并不知道类型,只能根据对象来getClass,如果知道一个类的类型则可以使用‘类’.class获取。

Field类

获得对象的所有属性:

  • Field fields[]=classType.getDeclaredFields();
  • Class 类的getDeclaredFields()方法返回类的所有属性,包括public、protected、默认和private访问级别的属性

Method类

Method类的invoke(Object obj,Object args[])方法接收的参数必须为对象,如果参数为基本类型数据,必须转换为相应的包装类型的对象。invoke()方法的返回值总是对象,如果实际被调用的方法的返回类型是基本类型数据,那么invoke()方法会把它转换为相应的包装类型的对象,再将其返回

生成对象

  1. 通过默认构造方法创建一个新对象:

    • Object objectCopy = classType.getConstructor(new Class[]{}).newInstance(new Object[]{});
    • 以上代码先调用Class类的getConstructor()方法获得一个Constructor 对象,它代表默认的构造方法,然后调用Constructor对象的newInstance()方法构造一个实例。
  2. 获取某个类或某个对象所对应的Class对象的常用的3种方式:

    • 使用Class类的静态方法forName:Class.forName(“java.lang.String”);
    • 使用类的.class语法:String.class; 知道类型的情况下,使用“类”.class
    • 使用对象的getClass()方法:String s = “aa”; Class

反射实例

测试类:

package com.test.spring.reflect;
public class PersonDome {
    private Integer id;
    private String name;
    public Integer age;
    public static final String NAME_ADDRESS = "beijing";
    public static String getStatic;
    private static String getPriStatic;

    public void getAge() {
        System.out.println("getAge:80.....");
    }

    public static void getAddress() {
        System.out.println("static void getAddress bejing.....");
    }

    public static String s = getString();

    private static String getString() {
        System.out.println("给静态变量赋值的静态方法执行:loading");
        return "ss";
    }

    static{
        getAddress();
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}

主方法:

 private static void classFromLoad() throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class c = Class.forName("com.test.spring.reflect.PersonDome"); 

        if (c == null) {
            System.out.println("c is null");
            return;
        }

        /**
         *java中创建对象,最常见的方式就是使用new关键字创建。但new并不是唯一的方式。
         java.lang.Class有个newInstance方法,可以生成新的对象。但使用newInstance方法生成对象,必须有默认构造函数,或者public,protected的无参构造函数。
         如果类只有private构造函数,则用newInstance时,会抛出IllegalAccessException异常;
         如果类只有有参构造函数,则会抛出InstantiationException异常。
         */
        Object o = c.newInstance();

        try {
            //返回o的构造方法
            System.out.println("---返回o的构造方法---");
            Constructor constructor = o.getClass().getConstructor();
            System.out.println(constructor.getDeclaringClass());

            //返回A类的所有为public 声明的构造方法
            System.out.println("------返回A类的所有为public 声明的构造方法-----");
            Constructor[] cons = o.getClass().getConstructors();
            System.out.println(Arrays.asList(cons));

            //返回A类所有的构造方法,包括private
            System.out.println("-----返回A类所有的构造方法,包括private--------");
            Constructor[] cons2 = o.getClass().getDeclaredConstructors();
            System.out.println(Arrays.asList(cons2));

            //返回类的getAge 方法
            System.out.println("------返回类的getAge 方法-----invoke");
            Method m = o.getClass().getMethod("getAge");
            //执行
            m.invoke(o.getClass().newInstance(), null);

            //返回A类所有的public 方法
            System.out.println("---------返回A类所有的public 方法-------");
            Method[] ms = o.getClass().getMethods();
            System.out.println(Arrays.asList(ms));


            //返回A类所有的方法,包括private
            System.out.println("-------返回A类所有的方法,包括private--------");
            Method[] allMs = o.getClass().getDeclaredMethods();
            System.out.println(Arrays.asList(allMs));


            //返回A类的public字段
            System.out.println("------返回A类的public字段---------");
            Field field = o.getClass().getField("age");
            System.out.println(field + "," + field.get(o.getClass().newInstance()));

            Field[] fields = o.getClass().getDeclaredFields();
            System.out.println(Arrays.asList(fields));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

解说:假设com.test.spring.reflect.PersonDome这个类,并没有在我们的项目中,而是在数据库中,或者是我们网络请求过来的,然后我们根据他来生成类的字节码文件class。
这个时候我们拿到了class字节码文件,但是我们并不知道这个class里面有些什么,接下来使用c.newInstance()生成一个对象,根据生成的对象获取这个类的所有信息。这也就是反射的原由了。

反射的第一种理解:
重温一下类的加载过程:在《jvm-类加载》文章中已经写类的加载过程了。
程序启动时:
加载-》验证-》准备-》解析-》初始化-》使用-》卸载。

网上看到的是:
RTTI和反射之间的真正区别只在于:
RTTI,编译器在编译时打开和检查.class文件
反射,运行时打开和检查.class文件

但是现在好了,程序已经启动了,在网络中的某个类,我们还没有开始进行类加载呢,当我们在程序运行时,这个时候才开始进行类加载-》… 调用了反射包里面的api,检查这个类的信息,这个是不是就反过来了。

本来是加载-》使用-》运行;现在是运行-》加载-》使用。
类的加载使用过程反过来了,先使用后加载,这个只是从字面意思上说明好想是反过来了,我们叫它反射,暂时可以这么理解,但是要知道,反射机制的作用是”可以访问、检测和修改它本身状态或行为的一种能力“。

反射的第二种理解:
先通过forName获取到类的字节码,然后通过Object o = c.newInstance()创建一个对象出来。
通过o这个对象,我们可以获取到类的所有信息,获取这些信息后你可以做任何修改,或者监控。
对象来自于类的实例化,现在通过对象反过来可以获取类的所有信息,就好像鸡生了蛋,蛋又孵化出了鸡一样。这个过程称为反射。恩,这个我感觉比较理解更准确一点,符合反射的定义与反射的机制。

Array实例

private static void testArray(){
        String[] aa = {"aa","bb"};
        Class c1 = aa.getClass();
        System.out.println("是否是数值:"+c1.isArray());
        System.out.println("数组组件类型的 Class:"+c1.getComponentType());
        //初始化数值
        String[] arr = (String[]) Array.newInstance(c1.getComponentType(), 2);
        //数组设值
        Array.set(arr, 0, "111");
        Array.set(arr, 1, "222");
        //访问
        System.out.println(arr[0]);
        System.out.println(Array.get(arr, 1));
    }

class.forName与ClassLoad的区别

比较对比

1.使用class.forName加载

private static void testClassFrom() {
        try {
            Class<?> c = Class.forName("com.test.spring.reflect.PersonDome");
            System.out.println("forName=" + c.getName());
            System.out.println("----------start-forName----newInstance");
            c.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

给静态变量赋值的静态方法执行:loading
static void getAddress bejing.....
forName=com.test.spring.reflect.PersonDome
----------start-forName----newInstance    

解说:com.test.spring.reflect.PersonDome类请参看反射里面的例子。
使用Class.forName时,在加载类的时候会执行类的static区域。

可以使用 Class.forName(“com.test.spring.reflect.PersonDome”, false, Thread.currentThread().getContextClassLoader());
通过第二个参数设置为false,则可以控制类static区域不执行。

从源码中可以看出,Class.forName是通过ClassLoad来实现的,所以严格来说他们没啥区别,只不过Class.forName封装了一下ClassLoad,做了一些自己的事情而已。

注意:**java中创建对象,最常见的方式就是使用new关键字创建。但new并不是唯一的方式。
java.lang.Class有个newInstance方法,可以生成新的对象。但使用newInstance方法生成对象,必须有默认构造函数,或者public,protected的无参构造函数。如果类只有private构造函数,则用newInstance时,会抛出IllegalAccessException异常;如果类只有有参构造函数,则会抛出InstantiationException异常。**

2.使用ClassLoad加载

private static void testCLassLoad() {
        ClassLoader loader = ClassLoader.getSystemClassLoader();
        try {
            Class<?> c = loader.loadClass("com.test.spring.reflect.PersonDome");
            System.out.println("loadClass=" + c.getName());
            System.out.println("----------start-loadClass----newInstance");
            c.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
loadClass=com.test.spring.reflect.PersonDome
----------start-loadClass----newInstance
给静态变量赋值的静态方法执行:loading
static void getAddress bejing.....    

可以看到,使用ClassLoader加载类的时候,并不会执行类的static区域,而是在实例化时才会执行static区域。

源码分析

1.Class.forName源码

public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }
private static native Class<?> forName0(String name, boolean initialize,
                                            ClassLoader loader,
                                            Class<?> caller)
        throws ClassNotFoundException;

可以看到forName最后还是调用的ClassLoad去加载类,只不过forName0的第二个参数为true,就是加载类的时候执行static区域。

2.ClassLoad源码

 public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    protected final void resolveClass(Class<?> c) {
        resolveClass0(c);
    }    

当第二个参数为true时会执行resolveClass方法。

resolveClass可以不完全地(不带解析)装入类,也可以完全地(带解析)装入类。当编写我们自己的loadClass时,可以调用resolveClass,这取决于loadClass的resolve参数的值.

注意:我在测试的过程中犯的错

public static void main(String args[]){
        testClassFrom();

        System.out.println();
        System.out.println("--------load------");
        testCLassLoad();
}

testClassFrom;testCLassLoad一起执行会发现,PersonDome这个类的静态方法区只会有一个能打印出来.
因为:他们两个都是使用同一个类加载,而静态方法,变量属于类变量,类方法,只会被加载一次。不管new多少个对象,他们都是一个块公共区。

http://blog.csdn.net/xyang81/article/details/7292380ClassLoad加载原理。
http://blog.csdn.net/qq_27093465/article/details/52262340参考
http://blog.csdn.net/shuanghujushi/article/details/51635990参考,还行

加载数据库驱动

Class.forNmae(“com.mysql.jdbc.Driver”);

public class Driver extends NonRegisteringDriver implements java.sql.Driver {  
    public Driver() throws SQLException {  
    }  

    static {  
        try {  
            DriverManager.registerDriver(new Driver());  
        } catch (SQLException var1) {  
            throw new RuntimeException("Can\'t register driver!");  
        }  
    }  
}  

可以看出,使用Class.forName时,就会执行了static区域,然后就实例化了Driver了。

参考文档

https://baike.baidu.com/item/JAVA%E5%8F%8D%E5%B0%84%E6%9C%BA%E5%88%B6/6015990?fr=aladdin
http://blog.csdn.net/BuddyUU/article/details/52458241
http://blog.csdn.net/qianzhiyong111/article/details/7321097
https://zhidao.baidu.com/question/87612466.html反射是什么(参看下面的其他答案,最佳答案是瞎扯)
http://blog.csdn.net/zhu_tianwei/article/details/43269441反射原理
http://china-jianchen.iteye.com/blog/728774
http://www.cnblogs.com/luoxn28/p/5686794.html

猜你喜欢

转载自blog.csdn.net/piaoslowly/article/details/81909735