这是我参与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类的对象就是描述字节码文件的.
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的理解
类加载器的作用:
l 1)类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方
法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为
方法区中类数据的访问入口。
l2) 类缓存:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被类加载器
加载到内存中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。
1.1 Class类的常用方法
1.1 反射创建运行时类的对象
注意: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获取运行时类的完整结构
通过反射可以获取运行时类的完整结构,如下所示:
1.7调用运行时类的指定结构
1.7.1调用指定方法
Object invoke(Object obj, Object … args)的说明:
1.Object 代表方法的返回值,若方法无返回值,此时返回null
2.若方法为静态方法,此时形参Object obj可为null
3.若方法的形参列表为空,则不传递可变参数 args的实参 4.若原方法声明为private,则需要在调用此invoke()方法前,显式调用
方法对象的setAccessible(true)方法,将可访问private的方法。