概述&应用场景
java反射机制是在运行状态中,对于任意的一个类(class文件)都能够知道这个类的所有属性和方法
对于任意一个对象,都能够调用它的任意一个方法和属性
这种动态获取信息以及动态调用对象的方法的功能称为java的反射机制
动态获取类中的信息,就是java反射
可以理解为对类的解剖
对于一个已经写好的应用程序,我们想给他添加新的功能,
就计划在外边建立了一个类,然后在程序中new一个对象,直接调用方法就可以了,
但是,我们没有源代码,只有一个类(class文件),这可怎么办呢?
办法是,我们不会在应用程序里边new对象,程序都有自己对外提供的接口用来实现功能的扩展,
我们先实现了这个接口创建出来他的子类,在应用程序对外提供的一个配置文件中写入子类的名称,就可以了
程序运行会读取配置文件中的信息,当读取到写入的类名的时候,就会将该类加载进来,
如果想要对指定名称的字节码文件进行加载,并获取其中的内容,并调用,
怎么可以实现呢?这时就用到了反射技术。
比如说tomcat就是一个java应用程序,他提供了处理请求和应答的方式
因为具体的处理动作不同,所以必须对外提供了接口(Servlet),要求开发者来实现具体请求和应答处理
具体图解是这样的:
那么当应用程序读取到配置文件中给写出的类名的时候,就会去找找该名称对应的class文件,找到之后又是怎么对类文件进行解析的呢?
使用图例来说明
接下来演示获取class对象的三种方式:
第一种,Object类中有getClass方法,可以获取到class对象
先创建一个对象,用来获取
package cn.itcast.bean;
public class Person {
private String name;
private int age;
public Person(String name,int age)
{
this.name = name;
this.age = age;
System.out.println("param person run");
}
public Person()
{
super();
System.out.println("noparam person run");
}
public void show()
{
System.out.println(name+"..show run.."+age);
}
public void method()
{
System.out.println("method run");
}
public void method(String str,int age)
{
System.out.println("param method run "+str+"...."+age);
}
public static void staticMethod()
{
System.out.println("static method run");
}
}
第一种方法获取:
package cn.itcast.demo;
import cn.itcast.bean.Person;
/**
* @author Lenovo
* 获取class对象的第一种方法:通过Object类中的getClass方法
*/
public class ReflectDemo {
public static void main(String[] args) {
//创建一个对象,使用对象调用方法获取class对象,
Person p = new Person();
Class<?> clazz = p.getClass();
Person p1 = new Person();
Class<?> clazz1 = p1.getClass();
//判断两次获取到的对象是不是同一个
System.out.println(clazz==clazz1);
}
}
想要用这种方式,必须要明确具体的类,并创建对象,
麻烦
第二种方法,每个对象都有一个静态的.class属性,可以获取到所属的class对象
package cn.itcast.demo;
import cn.itcast.bean.Person;
/**
* @author Lenovo
* 获取class对象第二种方法,每个对象都有一个静态的,class属性
*/
public class ReflectDemo2 {
public static void main(String[] args) {
Class<?> clazz = Person.class;
Class<?> clazz1 = Person.class;
System.out.println(clazz==clazz1);
}
}
这种方法相对简单,但还是要明确到类中的静态成员,
还是不够扩展
第三种方法:通过字符串名称就可以获取到该类对象
package cn.itcast.demo;
import cn.itcast.bean.Person;
/**
* @author Lenovo
* 获取class对象第三种方法,直接通过字符串名称获取到该类
* 通过Class中的forName方法完成
*/
public class ReflectDemo3 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("cn.itcast.bean.Person");//这里记得要写全包名和类名
Class<?> clazz1 = Class.forName("cn.itcast.bean.Person");
System.out.println(clazz==clazz1);
}
}
这种方法有名称即可,更为方便,扩展性更强。
获取对象
那么我们获取到了class对象了,是不是就可以new对象了呢?
在早期我们创建对象是这么干的:
//早期的时候根据类名找到对应的字节码文件,加载进内存,
//然后创建该类的字节码文件对象,紧接着就创建该字节码文件对应的Person对象
cn.itcast.bean.Person p = new cn.itcast.bean.Person();
现在我们只有一个字节码文件的名字,应该怎么获取对象,并且创建对象呢?
package cn.itcast.demo;
public class GetNewObject {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//现在我们先根据字符串类型的名字,获取到他的字节码文件的对象
Class<?> clazz = Class.forName("cn.itcast.bean.Person");
//根据有了clazz就可以直接获取到对象了:
Object obj = clazz.newInstance();
}
}
就这样对象就获取成功了:
我们看到了,获取到的对象是根据Person对象的无参数的构造方法获取到的对象
但要是Person对象没有无参数构造方法,或者Person类的无参构造方法被私有了呢?
我们想要获取带参数的改造方法,也就是Person(String name,int age)这样的对象,
package cn.itcast.demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class GetNewObject {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException, IllegalArgumentException, InvocationTargetException {
//现在我们先根据字符串类型的名字,获取到他的字节码文件的对象
Class<?> clazz = Class.forName("cn.itcast.bean.Person");
//既然都获取到了字节码文件对象,那么就可以获取到所有的构造函数了
//getConstructor方法可以获取到字节码文件对象的带参数的构造器
//需要把参数类型的字节码文件传进去,获取到该种参数类型的构造器
Constructor<?> cons = clazz.getConstructor(String.class,int.class);
//然后使用获取到的构造器来创建带参数的对象
Object obj = cons.newInstance("小强",34);
}
}
可以看到,带参数的person对象执行了。
获取class对象的字段
道理也一样,都获取到字节码文件对象了,获取到他的字段还不简单?
Class中有这样的方法
所以开始获取:
package cn.itcast.demo;
import java.lang.reflect.Field;
public class GetFiled {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("cn.itcast.bean.Person");
Object obj = clazz.newInstance();//获取到对象
//想要什么字段,输进方法中
Field filed = clazz.getField("age");
//然后获取字段的信息,因为字段信息需要使用本类对象来调用,所以这里需要传进来他的本类对象
//返回指定对象上此 Field 表示的字段的值。
Object o = filed.get(obj);
//那么这个o是什么呢?我们打印一下就知道了
System.out.println(o);
}
}
运行时出现了这样的结果:
出现了异常,这是为什么呢?我获取一个age字段,他告诉我没有这个字段,,
这是因为在Person类中设置的age的访问类型是private私有的,所以他访问不到,抛出没有这个字段异常。
要想访问到私有部分的字段,可以使用Class中的方法getDeclaredField方法(可以获取到所有权限的字段)
package cn.itcast.demo;
import java.lang.reflect.Field;
public class GetFiled {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("cn.itcast.bean.Person");
Object obj = clazz.newInstance();//获取到对象
//获取所有权限类型的字段,想要什么字段,输进方法中
Field filed = clazz.getDeclaredField("age");
//然后获取字段的信息,因为字段信息需要使用本类对象来调用,所以这里需要传进来他的本类对象
//返回指定对象上此 Field 表示的字段的值。
Object o = filed.get(obj);
//那么这个o是什么呢?我们打印一下就知道了
System.out.println(o);
}
}
运行时又出现了这样的错误:
无效的访问异常,
这是因为我获取到了私有的字段,仅仅是字段,里边被私有的数据并拿不到,
我想要拿到数据怎么办呢?
Field类有一个父类AccessibleObject
他可以取消java默认的访问权限使用的是setAccessible方法
所以再修改代码如下:
package cn.itcast.demo;
import java.lang.reflect.Field;
public class GetFiled {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("cn.itcast.bean.Person");
Object obj = clazz.newInstance();//获取到对象
//获取所有权限类型的字段,包括私有,想要什么字段,输进方法中
Field field = clazz.getDeclaredField("age");
//获取到私有对象之后,想要访问里边的数据,就要对私有字段的访问取消权限检查
field.setAccessible(true);
//能访问数据了就可以给字段设置值
field.set(obj, 39);
//然后获取字段的信息,因为字段信息需要使用本类对象来调用,所以这里需要传进来他的本类对象
//返回指定对象上此 Field 表示的字段的值。
Object o = field.get(obj);
//那么这个o是什么呢?我们打印一下就知道了
System.out.println(o);
}
}
运行结果为:
这种明知道是私有成员还要访问的模式称为暴力访问。
获取class对象中的方法
直接演示:
package cn.itcast.demo;
import java.lang.reflect.Method;
public class GetMethodDemo1 {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("cn.itcast.bean.Person");
Object obj = clazz.newInstance();
//获取字节码文件兑现中的所有的方法
Method[] methods = clazz.getMethods();
for(Method method:methods)
{
System.out.println(method);
}
}
}
运行方法:
可以看到,使用getMethods方法获取到的都是公有的方法,还有他的父类Object中的方法,
想要获取本类中的方法可以使用getDeclaredMethods方法:
package cn.itcast.demo;
import java.lang.reflect.Method;
public class GetMethodDemo1 {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("cn.itcast.bean.Person");
Object obj = clazz.newInstance();
//获取字节码文件兑现中的所有的方法
Method[] methods = null;//clazz.getMethods();//获取的都是公有的方法
methods = clazz.getDeclaredMethods();//获取的都是本类中的方法,包括私有的
for(Method method:methods)
{
System.out.println(method);
}
}
}
运行结果:
结果看出,获取到了本类中的所有方法,包括私有的方法
接下来获取指定名称和空参数类型的方法:
package cn.itcast.demo;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class GetMethodDemo1 {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("cn.itcast.bean.Person");
//创建一个有参数的对象方便show方法调用
//先获取到构造器:
Constructor<?> cons = clazz.getConstructor(String.class,int.class);
//创建一个有参数的对象,
Object obj = cons.newInstance("zhagnsan",25);
//获取字节码文件对象中的指定名称指定参数类型的方法
Method method = clazz.getMethod("show", null);//获取一个空参数的方法
//拿获取到的方法进行调用是使用invoke方法,将要调用的对象和方法需要的参数传进来
method.invoke(obj, null);
}
}
运行结果:
那怎么样获取到有参数的方法呢?
package cn.itcast.demo;
import java.lang.reflect.Method;
public class GetMethodDemo1 {
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("cn.itcast.bean.Person");
//获取到参数对象,把方法的名称和参数的类型传进来
Method method = clazz.getMethod("method", String.class,int.class);
//创建一个对象用来调用方法
Object obj = clazz.newInstance();
//将对象传进来,参数也传进来使用method的invoke方法调用
method.invoke(obj,"lisi",45);
}
}
运行结果:
练习:电脑主板的例子,电脑已经安装好了,但是后期来了一个声卡,或者网卡,我们怎么样在不拆电脑的情况下让这个声卡运行起来:
先定义一个接口,后来的部件只要实现了我的这个接口,就可以被电脑使用
package cn.itcast.test;
public interface PCI {
public void open();
public void close();
}
然后定义实现了接口的声卡:
package cn.itcast.test;
public class SoundCard implements PCI {
@Override
public void open() {
// TODO Auto-generated method stub
System.out.println("声卡开启");
}
@Override
public void close() {
// TODO Auto-generated method stub
System.out.println("声卡关闭");
}
}
最后在主函数中调用:
package cn.itcast.test;
public class MainBoard {
public static void main(String[] args) {
System.out.println("主板运行......");
usePCI(new SoundCard());
}
private static void usePCI(PCI p) {
p.open();
p.close();
}
}
运行结果:
我们以前一直都是这么干的,但是
有一个缺陷,就是我每添加一个外部部件,都要回来new一个对象
能不能不改代码的前提下,来一个部件实现了PCI我就能直接用呢?
这就用到了反射,
我们需要在一个配置文件中写上实现了PCI接口的子类的名称,
然后让主程序去读取配置文件中的信息,
获取到声卡还有其他部件的类名,然后加载到内存中来,
然后开始使用class对象中的方法
然后重写程序:
建立名为pci.properties的配置文件,里边写上:
pci1=cn.itcast.test.SoundCard
键为pci加数字作为顺序,值为后来的实现了PCI接口的类的名称
然后在主函数中关联配置文件并且每次运行主程序的时候读取配置文件中的信息,加载类,创建对象,进行调用
package cn.itcast.test;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;
public class MainBoard {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
System.out.println("主板运行......");
//先创建一个配置文件的文件对象
File file = new File("pci.properties");
//既然用到了配置信息那么就创建一个配置信息的类
Properties prop = new Properties();
//创建读取配置文件的流
FileReader fr = new FileReader(file);
//将配置信息从流中读取到配置信息的类中
prop.load(fr);
//通过循环读取配置集合中的信息
for(int x = 0;x<prop.size();x++)
{
//通过键获取到对应的值,也就是要加载的类的名字
String className = prop.getProperty("pci"+(x+1));
//根据类的名字获取到类文件对象
Class<?> clazz = Class.forName(className);
//创建出来类文件对应的对象
PCI p = (PCI)clazz.newInstance();
//调用一次使用PCI的方法
usePCI(p);
}
fr.close();
}
private static void usePCI(PCI p) {
p.open();
p.close();
}
}
运行结果:
这时候,来了一个网卡,实现了PCI接口,内容是这样的:
package cn.itcast.test;
public class NetCard implements PCI {
@Override
public void open() {
// TODO Auto-generated method stub
System.out.println("网卡开启");
}
@Override
public void close() {
// TODO Auto-generated method stub
System.out.println("网卡关闭");
}
}
然后我什么都不做,只在配置文件pci.properties文件中加上一句:
然后再次运行主函数:
结果变为: