反射是java学习中的一个很重要的思想,在正向思维不能解决问题的时候,尝试使用反射的思想去解决问题。本文将简单介绍下反射的思想。
反射
首先我们需要了解下反射是什么,能够做什么。我们知道,java文件在程序员开发好之后,首先需要经过jvm的编译,生成字节码文件(.class)才能被机器执行。那如果我们取到字节码文件后,如何得知原有类内部的信息呢?这就需要用到反射了。使用反射,我们可以获取类的构成元素,类的成员变量,成员方法等,并能通过反射的方法对其进行执行操作。
理解Class类
学习反射,我们需要先理解两个概念。
- 万物皆是对象
先说这个概念。java是一门面向对象的语言,在java的视野里,万事万物皆是对象。这个概念大多学过java的朋友都能理解。那我这里想补充两点:
(1) java中,类也是对象。首先请看下面代码:
Class c=Teacher.class;
我们知道,c是Class的一个对象,那等价可得出,Teacher.class即是Class的对象。
(2)java中,基本数据类型和静态块不是面向对象的
基本数据类型不是面向对象的,但他们的包装类是面向对象的。
而静态块,我们知道类中的静态块和静态变量等数据在加载类的时候就已经读入内存了,而不是在创建对象的时候才加载,所以静态块和静态的变量是面向类的,当然你也可以理解为另一种形式的面向对象。
2. 静态加载和动态加载
关于静态加载和动态加载,我们也首先看一下面的代码:
public class Test {
public static void main(String[] args) {
if(args[0]=="Teacher"){
Teacher t=new Teacher();
t.say();
}
if(args[0]=="Student"){
Student s=new Student();
s.say();
}
}
}
如果没有Teacher类和Student类的时候,这里是编译失败的。倘若我们创建并引入了Teacher类,这时Teacher的部分可以编译通过,但整个类还是不能编译通过。也就是说,虽然关于Teacher的部分都已经正常,但因为缺少了Student的部分,导致整个类文件编译失败,Teacher的相关功能也不能使用。如果我们换个方式:
public class Test {
public static void main(String[] args) {
if(args[0]=="Teacher"){
Class c=Class.forName("com.ugrow.model.Teacher");
Teacher t=(Teacher) c.newInstance();
t.say();
}
if(args[0]=="Student"){
Class c=Class.forName("com.ugrow.model.Student");
Student s=(Student) c.newInstance();
s.say();
}
}
}
这样我们发现,虽然Student类没有,但是类在编译的时候是不会报错的,Teacher类的方法也可以正常使用。
这就是java加载的两种方式:
静态加载:在编译期就加载类。如使用new关键字创建对象。
动态加载:在运行期加载类。如使用类的newInstance方法创建对象。
java的反射机制是针对与编译过的文件的。
使用反射获取类的成员变量/成员方法/构造器
java的反射机制,提供了许多的方法(在java.lang.reflect中),这里介绍比较常用的几种方法:
使用反射获取成员变量
可以通过getFields()和getDeclaredFields()方法来获取对应类的成员变量。不同的getDeclaredFields()获取的是本类声明的所有成员变量,不受访问修饰符的限制。而getFields()则是获取所有的public修饰的成员变量,包括继承自父类的成员变量。
/**
* 获取成员变量
* @param obj
*/
public void getMembers(Object obj){ //obj为传入的类的对象
Class c=obj.getClass();
// Field[] fs=c.getFields();//获取所有public的成员变量
Field[] fs=c.getDeclaredFields();//获取本类声明的所有成员变量
for(Field f:fs){
Class type=f.getType();
String name=f.getName();
System.out.println(type+" "+name);
}
}
使用反射获取成员方法
java反射通过getMethods()和c.getDeclaredMethods()来获取成员方法,其中c.getDeclaredMethods()是获取本类的所有方法,不关心修饰符,getMethods()是获取类中所有的public的方法。包括继承自父类的方法。
/**
* 获取成员方法信息
* @param obj
*/
public void getMemberMethods(Object obj){
Class c=obj.getClass();
System.out.println("类的名称为:"+c.getName());
Method []mt=c.getMethods(); //获取类中所有的public的方法。包括继承自父类的方法
// Method []mt=c.getDeclaredMethods();//获取本类的所有方法,不关心修饰符
for(int i=0;i<mt.length;i++){
Class rt=mt[i].getReturnType(); //获取方法的返回值类型
System.out.print(rt.getName()+" ");
System.out.print(mt[i].getName()+"("); //获取方法名
Class []ca=mt[i].getParameterTypes(); //获取方法的参数类型列表
for(Class cls:ca){
System.out.print(cls.getName()+" ,");
}
System.out.println(")");
}
}
使用反射获取构造器
java反射通过getConstructors()和getDeclaredConstructors()来获取类的构造器防范,其中getDeclaredConstructors()获取类的所有构造器 ,getConstructors()是获取类的public的构造器。
/**
* 获取类的构造器方法
* @param obj
*/
public void getConstructors(Object obj){
Class c=obj.getClass();
Constructor [] cm=c.getConstructors();//获取类的public的构造器
// Constructor [] cm=c.getDeclaredConstructors(); //获取类的所有构造器 构造器函数一般都是public的,所以一般用上面的方法即可
for(Constructor ct:cm){
String name=ct.getName(); //获取方法名 即类名
System.out.print(name+"( ");
Class[] csm=ct.getParameterTypes();//获取构造器参数类型
for(Class cls:csm){
System.out.print(cls.getName() +" ,");
}
System.out.println(")");
}
}
使用反射来执行方法
在获取了类的方法后,我们可以通过反射机制的invoke方法来执行方法。以达到和直接调用类的方法相同的效果。
在很多时候,尤其是在做网络编程或访问的时候,有时候并不能获取到类的信息,可以通过反射的方式来直接执行需要的方法,以获得想要的信息。
示例代码如下:
public class ReflectDemo02 {
public static void main(String[] args) {
Test t=new Test();
//根据对象获取到类的类型
Class c=t.getClass();
try {
//获取方法
Method m=c.getMethod("print",int.class,int.class);
// Method m2 =c.getMethod("print", new Class[]{int.class,int.class});
//执行方法
m.invoke(t, 10,20); //等价于t.print(10,20)
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Test{
public void print(){
System.out.println("正在打印。。。");
}
public void print(int a,int b){
System.out.println("result="+(a+b));
}
public void print(String a,String b){
System.out.println("result="+a+" "+b);
}
}