到底什么是反射

502 阅读6分钟

这是我参与8月更文挑战的第11天,活动详情查看: 8月更文挑战

1. 反射

1.1  为什么需要反射?

首先我们先明确两个概念,静态编译和动态编译。

静态编译:在编译时用new来创建对象,即运行前已确定了对象的类型。 动态编译:运行时确定类型,动态创建对象。动态编译最大限度发挥了java的灵活性,体现了多     态的应用,可避免硬编码(将代码写死)。我们可以明确的看出动态编译的好处,而反射就是运用了动态编译创建对象。 我们来看看一个案例,已知接口Fruit,两个实现类Apple和Orange

interface Fruit{  
    void eat();  
}    
class Apple implements Fruit{  
    public void eat(){  
        System.out.println("eat...Apple");  
    }  

}     

class Orange implements Fruit{  
    public void eat(){  
        System.out.println("eat...Orange");  
    }  
}

创建工厂类,用于返回一个水果对象,测试类中调用水果对象的eat方法

class FruitFactory{  
    public static Fruit getInstance(String fruitName){  
        Fruit f=null;  
        if("Apple".equals(fruitName)){  
            f=new Apple();  
        }  
        if("Orange".equals(fruitName)){  
            f=new Orange();  
        }  
        return f;  
    }  
}  
class FruitTest{
public static void main(String[] a){  
        Fruit f=FruitFactory.getInstance("Orange");  
        f.eat();  
    }     
}

可以发现,每当我们要添加一种新水果的时候,我们将不得不改变工厂类中的源码,而且随着水果种类的增加,你会发现你的工厂类会越来越臃肿,这是一种十分没有技术含量的做法。学习反射后就

可以解决这个问题了。

1.1  什么是反射

JAVA反射机制是在运行期间,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。

1.3 反射的源头

在JVM加载完类之后,在堆内存中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。 正常情况下,我们对类实例化的过程是:导入类所在的包---》通过new实例化--》得到类的实例 反射方式:得到类的实例--》getClass()方法得到Class对象--》得到”包,类”完整名称 在Object类中定义了一个方法 public final Class getClass(),此方法被所有子类继承,其返回值类型就是Class类,Class类是java反射的源头,通过Class类的对象可以得到类的结构.

补充:每个Java类都会编译成一个class字节码文件,Class类的对象就是描述字节码文件的.

image.png

1.1 Class类及获取Class的实例

1.3.1 Class类概述

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接

口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含

了特定某个结构(class/ interface/ enum/ annotation)的有关信息。注意以下几点:

Ø1).Class本身也是一个类

Ø2). Class对象只能由系统建立对象

Ø3). 一个加载的类在 JVM 中只会有一个Class实例

Ø4). 一个Class对象对应的是一个加载到JVM中的一个.class文件

Ø5). 每个类的实例都会记得自己是由哪个 Class实例所产生的

Ø6). 通过Class可以完整地得到一个类中的所有被加载的结构

Ø Class类是Reflection(反射)的根源,针对任何你想动态加载、运行的类,唯有先获得相应的

Class对象

1.3.2 哪些类型有Class对象

(1)class:

外部类,内部类

(2)interface:接口

(3)[]:数组

(4)enum:枚举

(5)annotation:注解

(6)primitive type:基本数据类型

1.3.3 四种方法获取Class类的实例

1)前提:已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高

示例:Class clazz = String.class;

2)前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象

示例:Class clazz = String对象.getClass();

3)前提:已知一个类的全类名(包名.类名),且该类在类路径下,可通过Class类的静态方

法forName()获取,可能抛出ClassNotFoundException。

示例:Class clazz = Class.forName(“java.lang.String”);

补充:全类名又叫类的完全限定名,就是“包名.类名”

4)其他方式:通过类加载器(不做要求)

示例:ClassLoader cl = this.getClass().getClassLoader();

Class clazz4 = cl.loadClass(“类的全类名”);

1.3.4 类的加载与ClassLoader的理解

image.png 类加载器的作用:

l 1)类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方

法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为

方法区中类数据的访问入口。

l2) 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被类加载器

加载到内存中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

1.1 Class类的常用方法

image.png

1.1 反射创建运行时类的对象

image.png 注意:Class对象的newInstance方法本质上就是在调用类的无参构造方法

示例:

//1.根据全类名获取对应的Class对象String name = “com.ydgk.test.Person";Class clazz = null;clazz = Class.forName(name);//2.调用指定参数结构的构造器,生成Constructor的实例Constructor con = clazz.getConstructor(String.class,Integer.class);//3.通过Constructor的实例创建对应类的对象,并初始化类属性Person p2 = (Person) con.newInstance("Peter",20);System.out.println(p2)

  总结:以上两种创建运行时类的对象的区别:

 第1种是直接用Class对象的newInstance(),本质是在调用类的无参构造器,此时创建的对象的属性没有初始化

  第2种是先得到有参构造器,再调用构造器的newInstance(),本质就是在调用类的有参构造器,此时创建的对象的属性已经初始化了

1.6获取运行时类的完整结构

通过反射可以获取运行时类的完整结构,如下所示:

image.png

image.png

image.png

image.png

image.png

1.7调用运行时类的指定结构

1.7.1调用指定方法

image.png

image.png Object invoke(Object obj, Object … args)的说明:

1.Object 代表方法的返回值,若方法无返回值,此时返回null

2.若方法为静态方法,此时形参Object obj可为null

3.若方法的形参列表为空,则不传递可变参数 args的实参 4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用

方法对象的setAccessible(true)方法,将可访问private的方法。

1.7.2 调用指定属性

image.png

image.png