本文内容是根据毕晓东老师的视频教程总结而得。主要讲解java反射机制方面的基础知识。
1.java反射机制——概述及应用场景
- java反射机制是在运行状态中,对于任意一个类(class文件),都能知道这个类的所有属性和方法;
- 对于任意一个对象,都能调用它的任意一个方法和属性;
- 这种动态获取的信息以及动态调用对象的方法的功能称为java的反射机制;
- 动态获取类中信息,就是java反射,可以理解为对类的解剖。
应用场景:
- 应用程序已经可以独立运行了,为了功能扩展需对外提供接口,后期创建的类实现该接口,使用时通过接口new子类对象(早期)(源代码存在时);
- 但是后期源代码不存在时,不能通过new去创建子类对象,因此,在应用程序对外暴露接口的同时,对外提供读取配置文件功能,只需要知道子类名即可,而根据子类名称去找该类文件然后进行加载并获取该文件中所有的内容,最后对其进行调用。
如果想要对指定名称的字节码文件进行加载并获取其中的内容并调用,怎么实现?这是就使用到了反射机制。而在设计应用程序时就需要实现反射技术。
好处:大大提高应用程序的扩展性(之前通过多态但需要new子类对象并传入,现在使用java反射机制后只需要配置包+类名即可)。
如Tomcat提供了处理请求和应答的方式,因为具体的处理动作不同,所以对外提供了接口,由开发者来实现具体的请求和应答处理,该接口即Servlet。
2.反射机制——细节&Class对象
运用已有的应用程序去读取配置文件中配置好的文件名称,然后根据名称去找到对应的class文件,再将文件加载到程序中,解析出文件中的所有信息。配置文件配置变化的参数信息。如名字、运行的类名等等。
怎么反射:
编译器将java文件编译后生成二进制的字节码文件,用类来体现字节码文件的描述。用于描述字节码文件的文件名就叫Class,然后提供一些获取字节码文件中的内容的方法,如名称、字段、构造函数、一般函数,该类就可以获取字节码文件中的所有内容,反射就是依靠该类来完成的。所以想要对一个类文件进行解剖,只要获取到该类的字节码文件对象即可。
3.反射机制——获取Class对象的三种方式
获取字节码对象的方式:
- 方式一:Object类的getClass()方法(想要用这种方式,必须要明确具体的类,并创建对象,所以比较麻烦);
- 方式二:任何数据类型都具备一个静态的属性.class来获取其对应的class对象(相对简单,但是还是要明确用到类中的静态成员,还是不够扩展);
- 方式三:只要通过给定的类的字符串名称就可以获取该类,更为扩展。可以使用Class类中的方法完成。这种方式只要有名称即可,更为方便,扩展性更强。
package com.vnb.javabase.base.reflect;
public class Person {
private int age;
private String name;
public Person(int age, String name) {
super();
this.age = age;
this.name = name;
System.out.println("Person param run");
}
public Person() {
super();
System.out.println("Person run");
}
public void show(){
System.out.println(name+" show rum "+age);
}
public void paramMethod(String str,int num){
System.out.println(" paramMethod rum "+str+"::"+num);
}
public static void staticMethod(){
System.out.println("staticMethod run");
}
}
package com.vnb.javabase.base.reflect;
public class ReflectDemo {
public static void main(String[] args) {
getClassObject_3();
}
//方式一:Object类的getClass()方法
public static void getClassObject_1(){
Person p1 = new Person();
Class c1 = p1.getClass();
Person p2 = new Person();
Class c2 = p2.getClass();
//结果为true,因为对象的创建都是根据同一个class文件完成
System.out.println(c1==c2);
}
//方式二:静态的属性.class来获取其对应的class对象
public static void getClassObject_2(){
Class c1 = Person.class;
Class c2 = Person.class;
System.out.println(c1==c2);
}
//方式三:使用Class类中的方法
public static void getClassObject_3(){
String className = "com.vnb.javabase.base.reflect.Person";
try {
//需要在指定路径下找对应的Person文件,所以必须加包名
Class c1 = Class.forName(className);
System.out.println(c1);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
4.反射机制——获取Class中的构造函数
问题:通过反射获取到的对象和直接new的对象有什么区别?
- 早期创建对象:Person p = new Person();new 完后需要在classpath所在路径内找到该文件的字节码文件并加载到内存,然后在堆内存中开辟空间;
- 现在创建:只知道类名+包名,也想进行创建对象的动作。找寻该名称的类文件,直接加载进内存并产生class对象(该方式扩展性极强);
如何产生Class对象呢?
newInstance():创建一个Class对象所表示的一个新实例,如同用一个带有空参数列表的new一个实例。即Object obj = c1.newInstance();和new Person()创建的实例相同。一般被反射的类都带空参。
当获取指定名称对应类中所体现的对象时,而该对象初始化不使用空参构造时该怎么办?既然是通过指定的构造函数进行对象的初始化,所以应该先获取到该构造函数。通过字节码文件对象即可完成,该方法是Constructor<?> [] getConstructors(Class<?>… paraneterTypes)获取到类中所有的公有构造函数,getDeclaredClasses()可以获取类中的私有构造函数。paraneterTypes参数类型,都是数据类型,都有自己字节码对应的信息。
package com.vnb.javabase.base.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class ReflectDemo2 {
public static void main(String[] args) {
createNewObject();
}
public static void createNewObject(){
String className = "com.vnb.javabase.base.reflect.Person";
try {
Class c1 = Class.forName(className);
try {
Object obj = c1.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public static void createNewObject_2(){
//以下代码同此句
//Person p = new Person("小明",23);
String className = "com.vnb.javabase.base.reflect.Person";
try {
Class c1 = Class.forName(className);
try {
Constructor con = c1.getConstructor(String.class,int.class);
//通过该构造器的newInstance方法进行对象的初始化
try {
Object obj = con.newInstance("小明",38);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e1) {
e1.printStackTrace();
} catch (SecurityException e1) {
e1.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
5.反射机制——获取Class中的字段
- getFields()获取所有字段
- Field getField()返回一个Field对象
- getDeclareField()只获取本类中所有的字段但包括公有私有
package com.vnb.javabase.base.reflect;
import java.lang.reflect.Field;
public class ReflectDemo3 {
public static void main(String[] args) {
getField();
}
public static void getField(){
String className = "com.vnb.javabase.base.reflect.Person";
try {
Class c1 = Class.forName(className);
try {
//Field field = c1.getDField("age");//只获取公有字段
Field field1 = c1.getDeclaredField("age");//只能获取本类,但是包括公有和私有
//如果要调用Field的方法,就必须要先有本类对象
try {
Object obj = c1.newInstance();
//field1.setAccessible(true);
Object o = field1.get(obj);
System.out.println(o);
//发现age字段是私有的不能访问
//找到Field类的基类AccessibleObject类,是Consturctor、Field、Method的基类,提供了将反射对象的访问权限取消的功能setAccessible()
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
此时会报错:
Person run
java.lang.IllegalAccessException: Class com.vnb.javabase.base.reflect.ReflectDemo3 can not access a member of class com.vnb.javabase.base.reflect.Person with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Field.get(Field.java:390)
at com.vnb.javabase.base.reflect.ReflectDemo3.getField(ReflectDemo3.java:21)
at com.vnb.javabase.base.reflect.ReflectDemo3.main(ReflectDemo3.java:8)
如果字段是私有的,找到Field类的基类AccessibleObject类,是Consturctor、Field、Method的基类,提供了将反射对象的访问权限取消的功能setAccessible()。不建议使用,因为私有的不建议被暴露。
加上field1.setAccessible(true);后获取到正确值:
Person run
0
可以给字段设置值:
field1.set(obj, 89);
Object o1 = field1.get(obj);
System.out.println(o1);
结果:
Person run
0
89
6.反射机制——获取Class中的方法
getMethods()获取的都是公有的方法
package com.vnb.javabase.base.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectDemo4 {
public static void main(String[] args) {
getClassMethod_3();
}
//获取所有的公共方法(私有获取加上暴力访问即可)
public static void getClassMethod_1(){
String className = "com.vnb.javabase.base.reflect.Person";
try {
Class c1 = Class.forName(className);
Method[] methods = c1.;
for(Method method : methods){getMethods()
System.out.println(method);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//获取一个方法(以show方法为例)
public static void getClassMethod_2(){
String className = "com.vnb.javabase.base.reflect.Person";
try {
Class c1 = Class.forName(className);
//获取方法必须指定方法名和参数
try {
Method method = c1.getMethod("show", null);//获取空参数的show方法
//正常创建Person对象,new Person,调用方法时需要传入参数
Object obj;
try {
obj = c1.newInstance();
try {
method.invoke(obj, null);//null show rum 0
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}//即需要对象,又需要参数
//此时还是空参数,如果想对其进行赋值
Constructor con = c1.getConstructor(String.class,int.class);
Object obj1;
try {
obj1 = con.newInstance("小明",23);
method.invoke(obj1, null);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
//有参数的方法
public static void getClassMethod_3(){
String className = "com.vnb.javabase.base.reflect.Person";
try {
Class c1 = Class.forName(className);
try {
Method method = c1.getMethod("paramMethod", String.class,int.class);
try {
Object obj = c1.newInstance();
try {
method.invoke(obj, "paramMethod",23);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
方法一getMethods():获取到的都是公有方法
public void com.vnb.javabase.base.reflect.Person.show()
public void com.vnb.javabase.base.reflect.Person.paramMethod(java.lang.String,int)
public static void com.vnb.javabase.base.reflect.Person.staticMethod()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
方法二getMethod("show", null);空参:
Person run
null show rum 0
Person param run
小明 show rum 23
方法三getMethod("paramMethod", String.class,int.class)有参数:
Person run
paramMethod rum paramMethod::23
7.反射机制——反射练习
电脑运行,让主板运行
对于主板的运行,早期:
package com.vnb.javabase.base.reflect;
public class MainBoard {
public void run(){
System.out.println("MainBoard run.....");
}
}
package com.vnb.javabase.base.reflect;
public class ReflectTest {
public static void main(String[] args) {
MainBoard mainBoard = new MainBoard();
mainBoard.run();
}
}
但是当发现需要功能扩展时,需要添加声卡,于是多加一个声卡的类:
package com.vnb.javabase.base.reflect;
public class SoundCard {
public void open(){
System.out.println("open...");
}
public void close(){
System.out.println("close...");
}
}
当主板要使用声卡时:
package com.vnb.javabase.base.reflect;
public class SoundCard {
public void open(){
System.out.println("open...");
}
public void close(){
System.out.println("close...");
}
}
package com.vnb.javabase.base.reflect;
public class MainBoard {
public void run(){
System.out.println("MainBoard run.....");
}
public void useSoundCard(SoundCard c){
c.open();
c.close();
}
}
package com.vnb.javabase.base.reflect;
public class ReflectTest {
public static void main(String[] args) {
MainBoard mainBoard = new MainBoard();
mainBoard.run();
mainBoard.useSoundCard(new SoundCard());
}
}
但是这种方式扩展性特别不好,当后期需要有其他设备加入时,不好扩展,因此引入接口,从里面抽象出各种设备的功能:
package com.vnb.javabase.base.reflect;
public interface PCI {
public void open();
public void close();
}
package com.vnb.javabase.base.reflect;
public class SoundCard implements PCI{
@Override
public void open() {
System.out.println("open....");
}
@Override
public void close() {
System.out.println("close....");
}
}
package com.vnb.javabase.base.reflect;
public class MainBoard {
public void run(){
System.out.println("MainBoard run.....");
}
public void usePCI(PCI p){
if(p!=null){
p.open();
p.close();
}
}
}
package com.vnb.javabase.base.reflect;
public class ReflectTest {
public static void main(String[] args) {
MainBoard mainBoard = new MainBoard();
//mainBoard.useSoundCard(new SoundCard());
//早期没有任何功能时
mainBoard.usePCI(new SoundCard());
}
}
但是,发现每次修改声卡的时候都必须要修改程序(mainBoard.usePCI(new SoundCard());每添加一个设备就需要修改代码传递一个新创建的对象),如何做到不修改ReflectTest的代码,就可以使用到新的设备的功能:
即不用new来完成,而是只是获取其class文件,在内部实现创建对象的动作。
创建一个.properties文件存储所有的设备,然后在ReflectTest里获取到该设备后通过反射获取到设备里的信息。
pci.properties文件:
pci1=com.vnb.javabase.base.reflect.SoundCard
pci2=com.vnb.javabase.base.reflect.NetCard
package com.vnb.javabase.base.reflect;
public class NetCard implements PCI{
@Override
public void open() {
System.out.println("NetCard open....");
}
@Override
public void close() {
System.out.println("NetCard close....");
}
}
package com.vnb.javabase.base.reflect;
public class SoundCard implements PCI{
@Override
public void open() {
System.out.println("SoundCard open....");
}
@Override
public void close() {
System.out.println("SoundCard close....");
}
}
package com.vnb.javabase.base.reflect;
public interface PCI {
public void open();
public void close();
}
package com.vnb.javabase.base.reflect;
public class MainBoard {
public void run(){
System.out.println("MainBoard run.....");
}
public void usePCI(PCI p){
if(p!=null){
p.open();
p.close();
}
}
}
package com.vnb.javabase.base.reflect;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Properties;
public class ReflectTest {
public static void main(String[] args) {
MainBoard mainBoard = new MainBoard();
//mainBoard.useSoundCard(new SoundCard());
//早期没有任何功能时
//mainBoard.usePCI(new SoundCard());
//后期
File configFile = new File("pci.properties");
Properties pro = new Properties();
FileInputStream fis;
try {
fis = new FileInputStream(configFile);
try {
pro.load(fis);
for (int i = 0; i < pro.size(); i++) {
String picName = pro.getProperty("pci"+(i+1));
try {
Class clazz = Class.forName(picName);
try {
//得到实例化对象,将其强转为PCI对象
PCI pci = (PCI)clazz.newInstance();
pci.open();
pci.close();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
结果:
SoundCard open....
SoundCard close....
NetCard open....
NetCard close....