反射的基本概念
什么是反射?JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法,这种动态获取、调用对象方法的功能称为java语言的反射机制。
在我们一般创建类对象的时候,一般是通过其类中的构造方法来创建对象,也就是通过关键字new,这种通过new关键字创建的对象叫做静态加载,在程序编译期间会对其进行检查,而不是在运行期间。而反射是动态加载,它可以在运行时创建对象,获得对象,调用对象的方法,成员变量。(简单来说new关键字在编译的时候就对类的信息确定了,而反射则是在程序运行的时候才获取类信息。)
我们在运用反射机制的时候,用到了java.lang.Class,以及java.lang.reflect中的Method、Field、Constructor等等classes。
在这里Class类是反射机制中重要的一个类,因为某个类在编译的时候,该类的所有信息被保存在.class字节码文件中(Class对象)。反射机制就是在程序运行时,根据保存在.class字节码文件的Class对象来获得类的信息,然后再创建类的对象或者调用类中的方法等。
对于java.lang包中的Class类理解,可以看上一篇Class类的介绍。
反射的使用例子
这里创建了一个测试用的People类,代码如下
public class People {
//姓名
private String name;
//年龄
private Integer age;
//性别
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public People(){
System.out.println("People类被实例化");
}
public People(String name, Integer age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public void publicMethod(){
System.out.println("调用了公有方法");
}
private void privateMethod(){
System.out.println("调用了私有方法");
}
}
1.通过反射创建类对象
public class Test {
public static void main(String[] args){
try {
//这里用类的全限定名来获得该类的Class对象
Class c = Class.forName("com.example.demo.People");
//用Class对象的newInstance()方法来创建类对象
People people=(People)c.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
输出:
Class对象的newInstance()方法表示调用该类的无参构造函数来创建此 Class 对象所表示的类的一个新实例。所以People无参构造函数被调用,控制台输出内容。
2.通过反射获得类中的所有方法(Method)
public class Test {
public static void main(String[] args){
try {
//这里用类的全限定名来获得该类的Class对象
Class c = Class.forName("com.example.demo.People");
//通过Class的getDeclaredMethods()方法获得该类的所有方法
Method[] methods=c.getDeclaredMethods();
for (int i=0;i<methods.length;i++){
System.out.println("方法名称为:"+methods[i].getName());
System.out.println("方法返回值为:"+methods[i].getReturnType());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出:
3.通过反射获得类中的所有字段(Field)
public class Test {
public static void main(String[] args){
try {
//这里用类的全限定名来获得该类的Class对象
Class c = Class.forName("com.example.demo.People");
//通过Class的getDeclaredFields()方法获得该类的所有字段
Field[] fields=c.getDeclaredFields();
for (int i=0;i<fields.length;i++){
System.out.println("成员变量名称:"+fields[i].getName());
System.out.println("成员变量类型:"+fields[i].getType().getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出:
4.通过反射获得类中的所有构造方法(Constructor)
public class Test {
public static void main(String[] args){
try {
//这里用类的全限定名来获得该类的Class对象
Class c = Class.forName("com.example.demo.People");
//通过Class的getDeclaredConstructors()方法获得该类的所有构造方法
Constructor[] constructors=c.getDeclaredConstructors();
for (int i=0;i<constructors.length;i++){
System.out.println("第"+(i+1)+"个构造函数:"+constructors[i].getName());
//获得构造函数的参数列表,这里获得的是参数类型列表的类类型,也就是Class对象。如int.class
Class[] classes=constructors[i].getParameterTypes();
for (int j=0;j<classes.length;j++){
System.out.println("参数类型:"+classes[j].getName());
}
System.out.println("===========================================");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出:
更多的Class类中的方法到java.lang.Class包下查找,Method和Field还有Constructor类的更多方法到java.reflect.相应的包下查找,可以到https://tool.oschina.net/apidocs/apidoc?api=jdk-zh该网址查询
反射的作用场景
反射作用的最多的场景就是各种框架,比如在JAVA中流行的Spring框架IOC容器,我们在资源文件XML文件或者properties文件中或者使用注解在容器中配置我们的Bean信息,后面我们在程序中需要获取该Bean时,对该Bean进行依赖注入,容器会读取配置,而配置中就有该类的全限定名(如上述People类的全限定名是 com.example.demo.People),拿到这个全限定名后,spring会动态地去加载该类的Class对象,Class对象加载完成时,spring即可用该Class对象动态地创建该类的实例对象。(这里强调突出动态两个字,就是表明这都是在程序运行时所进行的,也就是用了反射进行动态加载。)
在我们加载数据库驱动的时候,使用Class.forName(“com.mysql.jdbc.Driver”)来加载驱动类,因为JDBC的规范中规定,Driver类必须向DriverManager注册自己,即任何一个JDBCDriver的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()加载完Driver类时,初始化也被完成,类的静态代码块被执行。所以不用获得Driver类的对象(即不用Class类的newInstance()方法),来注册到DriverManager中,因为在加载完成初始化时,类的静态代码块已经被执行了,Driver类也完成了向DriverManager注册。
另外个利用场景就是我们如果需要用到别人写的类,但是别人的类没有完成,那么我们想要我们的代码中运用别人的类并且通过编译的话,就需要用到反射,也就是在程序运行的时候,动态的加载该类的实例对象,这样就能通过编译了。只不过我们没拿到别人的类,在动态加载的时候会获取不到该类的Class对象,但是编译不会错误,也不会影响程序。