JAVA反射学习之——基本学习
反射通常用来做下在的事情:
1. 动态加载类,获得类的信息(属性,方法,构造器)。
2. 动态的构造对像。
3. 动态的调用类和对像的方法。
4. 动态的调用和处理属性(这于的处理是指修改)。
5. 处理注解。
首先,来看看什么是类的加载?
程序在首次使用某个类时,如果该类还未被加载到内存中,JVM就会通过加载,连接,初始化三个步骤来对该类进行初始化,初始化的结果就是把类编译后的class文件加载到内存中,并为之创建一个java.lang.class对像,没错!是为class文件创建一个对像。关于JVM的一些特性后面再研究,今天先知道怎么用吧。
一、获得Class对像的三种方式
上面说了,class文件加载到内存,并为之创建一个对像,那怎么得到这个对像呢?
方法一:通过Object超类的getClass方法获得Class对像
方法二:调用类的class属性来获取该类对应的Class对像
但上面两种方法都有它们自身的局限性,就是预先得知道了是哪个类,话说回来,你都知道了是哪个类了,为啥还要费事的得到类的class对像,然后再去操作它的属性和方法呢?
public static void main(String[] args)
{
Persion p1 = new Persion();
Persion p2 = new Persion();
//第一种方法得到类的Class文件对像
Class c1 = p1.getClass();
Class c2 = p2.getClass();
//第二种方法得到类的Class文件对像
Class c3 = Persion.class;
//通过打印结果表明,同一个类只有一个Class文件对像
//实际上是这样的,只要类被加载过一次,它就不会再被重新加载了
//所以事实上,c2和c3还是c1所指向的那个对像
System.out.println(p1 == p2); //false
System.out.println(c1 == c2); //true
System.out.println(c1 == c3); //true
}
上面的两种方法我觉得知道就行了,并没有什么实用性吧,因为用到反射的地方,通常是为了使代码具有通用性,而不是针对某一个已知的特定类。
方法三、使用Class类的forName(String clazzName)方法来动态加载类。
简单的用法测试如下:
//第三种方法
Class c4 = Class.forName("反射.Persion");
//打印信息表明c4=c3
System.out.println(c4 == c3);
值得说明的是,clazzName是一个带包名的完整的类名,不然运行会报错!
查询JDK文档的Class类,具有如下描述:
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的”。
public static void printClassName(Object obj) {
//通过反射获得类的全名(带包名)
System.out.println( obj.getClass().getName());
//获得包名
System.out.println( obj.getClass().getPackage());
//通过反射获得类的包内名称
System.out.println( obj.getClass().getSimpleName());
}
public static void main(String[] args)
{
printClassName(new Persion());
}
上面Class类的三个方法都非常有用的。从JDK文档摘录如下:
1. String getName()
以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
2. Package getPackage() 获取此类的包。
3. String getSimpleName() 返回源代码中给出的底层类的简称。
以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称。
2. Package getPackage() 获取此类的包。
3. String getSimpleName() 返回源代码中给出的底层类的简称。
二、从Class中获取信息
上面已经讲明了怎么来获得类的Class对像,那获得了Class对像用来干嘛呢?
1. 通过Class对像获取构造方法并创建对像
构造方法分有参和无参之分,又有public和private等修饰,所以就对应有好几个方法。
//1.获取类的Class对像
Class clazz = Class.forName("反射.Persion");
//2.获取类的public无参构造方法
Constructor cons = clazz.getConstructor();
//3.通过类的无参构造方法返回实例
Object obj = cons.newInstance();
最基本的方法和步骤就是上面所写,getConstructor是获得无参构造方法,而且,必须是public的!
在实际当中,用的最多的,其实是下面两个:
//获得所有的public构造方法
Constructor[] cons1 = clazz.getConstructors();
//获得所有的构造方法(不管有参无参,public还是private等 )
Constructor[] cons2 = clazz.getDeclaredConstructors();
然而,上面三个获取构造方法的声明中都是指定了变参列表的,所以我们在获取的时候,可以指定参数列表类型,从而获得得与之相匹配的构造方法来创建对像。
典型代码如下所示:
Constructor cons = clazz.getConstructor(String.class,int.class,String.class);
Object obj = cons.newInstance("刘德华",50,"香港");
需要注意的是,如果想使用私有的构造,那就要用getDecleardConstructor方法了。
Constructor类有一个方法是getName(),以字符串的形式返回获取构造方法的名称,这样的话,在一些工具类的编写中,不用知道具体的是什么类而编写个有通用性的代码,但要注意的是,还要获得构造方法的参数类型!下面的一个方法就是用于此作用的,后面学习到再进行研究。
Class<?>[] getParameterTypes()
按照声明顺序返回一组 Class 对象,这些对象表示此 Constructor 对象所表示构造方法的形参类型。
按照声明顺序返回一组 Class 对象,这些对象表示此 Constructor 对象所表示构造方法的形参类型。
2. 获得成员属性
属性也称字段,同样的,有getField获得一个成员属性,有getFields获得所有的public型成员属性,有getDeclaredFields获得所有的成员属性。而Field的getName方法是获得成员属性的类中的名子。
//获取所有的成员属性
Field[] fs = clazz.getDeclaredFields();
//打印成员属性的类中的名子
for(Field f: fs ){
System.out.println(f.getName());
}
那怎么获取和修改特定的属性呢?
//1.获取类的Class对像
Class clazz = Class.forName("反射.Persion");
//2.创建类的对像
Object obj = clazz.newInstance();
//3.获取特定的字段
Field name = clazz.getDeclaredField("name");
//4在操作字段前,首先要获得访问权限
name.setAccessible(true);
//5.使用set方法给特定的字段赋值
name.set(obj, "刘德华");
//6.同理肯定有get方法获得某个对像的字段值
System.out.println(name.get(obj));
3. 获得成员方法并使用
有了上面获取成员属性和构造器的知识,再看获取成员方法并使用的方法就so easy了。一个例子学会最基本的应用:
//1.获取类的Class对像
Class clazz = Class.forName("反射.Persion");
//2.创建类的对像
Object obj = clazz.newInstance();
//3.获取所有的方法,并打印出方法名
Method[] ms = clazz.getDeclaredMethods();
for(Method m: ms){
System.out.println(m.getName());
}
//4.获取无参方法function1,并调用它
Method function1 = clazz.getDeclaredMethod("function1");
function1.invoke(obj);
//5.获取带参方法function2
Method function2= clazz.getDeclaredMethod("function2", String.class);
function2.invoke(obj, "Hello World");
好了,上面就是反射的基本用法了,仅仅是使用而已,初步了解了反射能干什么。
仅仅提供类名,就可以获得创建类的对像,获得类的属性和方法,通过反射创建的类,可以修改它的属性,也可以调用它的成员方法。反射的功能可谓强大。
那既然JAVA语言提供了反射特性,那JVM本身是不是也用了反射呢?一个类不管有多少个对像,都只有一个Class文件,JVM本身是否也在用上面所述的那些方法去处理JAVA程序?
Persion p1 = new Persion();
Persion p2 = new Persion();
上面第一行代码就把Persion类加载到内存中了,为Persion类的class文件创建了Class对像,那其实第二句的代码在编译或执行的时候,JVM只是调用了Class类的newInstance()方法来创建另一个对像而已,而不管它是Persion还是其它什么类,我是一个通用性的方法,正是在此原理的基础上,其它的框架也才用反射搭建起来?这是否就是反射的核心和本质?