目录
前言
不知道你有没有想过这样一个问题,我们在使用IDEA集成开发工具编写代码的时候,idea可以提示你,你当前使用的类里面包含什么方法、什么属性, 不管是不是我们自己写的类,它都可以提示,那么这个功能它是如何做到的呢?答案就是利用了反射机制,今天就浅浅学习一下反射吧.。
一、反射的介绍
反射是Java程序开发语言的特征之一,它允许运行中的Java程序对自身进行检查。被private封装的资源只能类内部访问,外部是不行的,但反射能直接操作类私有属性。 反射可以在运行时获取一个类的所有信息,(包括成员变量,成员方法,构造器等),并且可以操纵类的字段、方法、构造器等部分。
二、使用反射的步骤
1、获取字节码对象
1.1什么是字节码对象
字节码对象即Class类对象(这里的Class是一个类,并不是我们定义类的时候使用class关键字,这是值得注意的地方),每一个类的的.class文件就是一个字节码对象, .class文件我们知道,是我们编写的代码经过编译之后生成的文件,这个文件包含了源码里面的类的所有信息,既然如此,通过字节码对象就可以获取到类的所有信息.。
1.2获取字节码对象的的三种方式
类名.class,这种方式很简单,不过需要注意,这种方式需要导包,即你要让虚拟机能找到你这个类,找不到就会报错
对象名.getClass(),这也是一种方式,但是我有一个疑问,既然有对象了,它的方法直接调用就可以了啊
Class.forName("包名加类名"),这种方式应该是最好的一种,不管在哪执行这句代码,在不在同一个包下,都是没有问题的。
这里需要注意的是,每个类的字节码文件都只有一个,所以三种方式获取的Class对象是同一个,请看代码演示:
public class MainTest {
public static void main(String[] args) throws ClassNotFoundException {
// 获取字节码对象
//类名.class获取,MainTest类和TestClass类在同一个包下面
Class<TestClass> aClass = TestClass.class;
//对象名.getClass()获取,需要先新建一个对象
TestClass testClass = new TestClass();
Class<? extends TestClass> aClass1 = testClass.getClass();
//Class.forName("包名+类名")获取
Class<?> aClass2 = Class.forName("com.fs.test823.TestClass");
//==比较的时对象的地址,如果不同的引用指向的是同一个地址处的对象,
//则说明它们引用的对象是同一个
System.out.println("aClass和aClass1是同一个对象:"+(aClass == aClass1));
System.out.println("aClass和aClass2是同一个对象:"+(aClass == aClass2));
System.out.println("aClass1和aClass2是同一个对象:"+(aClass1 == aClass2));
}
}
运行结果:
2、根据字节码文件获取构造方法、成员方法、属性
2.1获取构造方法(Constructor类类型)
public Constructor<T> getConstructor(Class<?>... parameterTypes)该方法需要传入参数类型的字节码对象,可以没有, 可以是一个,也可以是多个,根据构造函数的参数个数来即可,返回的是一个Constructor对象。
public Constructor <?>[] getConstructors() 该方法不需要参数,返回的是一个Constructor类型的数组。
public Constructor <T> getDeclaredConstructor(Class<?>... parameterTypes)该方法的需要的参数和返回值与第一个方法一致。
public Constructor <?>[] getDeclaredConstructors() 该方法和第二个方法的返回值一致 值得注意的,第一二个方法只能获取public修饰的构造方法,而第三四个方法可以获取所有的构造方法。
2.2获取成员方法(Method类类型)
与获取构造方法类似,获取成员方法也有四个方法分别为
public Method getMethod(String name, Class<?>... parameterTypes) 此方法与getConstructor方法类似,返回一个Method对象, 区别在于,该方法第一个参数是你需要获得的成员方法的方法名称(String类型)。
public Method[] getMethods() 类似于getConstructors方法,返回的是一个Method类型的数组。
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
public Method[] getDeclaredMethods() 同样的,前面两个方法可以获取到的都是public修饰的成员方法,后两个方法可以获取所有成员方法。
2.3获取属性(Field类类型)
获取属性也是一样的,四个方法,分别是:
public Field[] getField(String name)
public Field[] getFields()
public Field[] getDeclaredField(String name)
public Field[] getDeclaredFields() 和前面类似,不过获取单个属性的时候只需要传入属性名称即可。
3、使用构造方法、成员方法、属性
新建一个TestClass类,MainTest类里面编写测试代码,获取TestClass类里面内容,TestClass里的内容如下:
public class TestClass {
public String str;
private int i;
public TestClass() {
}
public TestClass(String str, int i) {
this.str = str;
this.i = i;
}
private TestClass(String str) {
this.str = str;
}
@Override
public String toString() {
return "str="+str+"\ti="+i;
}
public void test(){
System.out.println("我是public修饰test()方法");
}
private void test1(){
System.out.println("我是private修饰test1()方法");
}
public String getStr() {
return str;
}
public void setStr(String str) {
this.str = str;
}
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
3.1使用构造方法
构造方法的使用需要用到newInstance方法,看代码演示
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class MainTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// 获取字节码对象
//类名.class获取,MainTest类和TestClass类在同一个包下面
Class<TestClass> aClass = TestClass.class;
//获取无参构造方法
Constructor<TestClass> constructor1 = aClass.getConstructor();
//获取全参构造方法
Constructor<TestClass> constructor2 = aClass.getConstructor(String.class,int.class);
//获取private修饰的构造方法
Constructor<TestClass> constructor3 = aClass.getDeclaredConstructor(String.class);
//使用全参构造
TestClass testClass = constructor2.newInstance("dog", 123); //相当于TestClass testClass = new TestClass("dog", 123);
//重写了toString方法
System.out.println(testClass);
//使用私有单参构造
//在使用私有的方法或属性时要先设置暴力反射,否则会报IllegalAccessException异常
constructor3.setAccessible(true);
TestClass testClass1 = constructor3.newInstance("fish");
System.out.println(testClass1);
}
}
运行结果:
成功获取构造方法,并创建对象,同时给属性赋值,这里值得注意的地方就是暴力反射,如果没有constructor3.setAccessible(true);这句代码,则会抛出IllegalAccessException异常,在使用私有成员方法和使用属性的时候也是这样的。
3.2使用成员方法
成员方法的使用需要用到的是invoke方法,看代码演示
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class MainTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// 获取字节码对象
//类名.class获取,MainTest类和TestClass类在同一个包下面
Class<TestClass> aClass = TestClass.class;
//获取公有方法
Method test = aClass.getMethod("test");
//获取私有方法
Method test1 = aClass.getDeclaredMethod("test1");
//使用公有方法
//这里与使用构造方法不一样,invoke的第一个参数要是执行方法所在类的对象
//使用aClass.newInstance()可以创建aClass所代表的类的对象,不过该方法是依赖于无参构造的,如果类没有无参构造则会报InstantiationException异常
test.invoke(aClass.newInstance());
//使用私有方法
//同样的,使用私有方法前需要设置暴力反射
test1.setAccessible(true);
test1.invoke(aClass.newInstance());
}
}
运行结果
两个方法都成功运行。
3.3使用成员变量
属性的使用就略有区别,分了数据类型,获取属性的值的方法有:
Object get(Object obj)
int getInt(Object obj)
long getLong(Object obj)
boolean getBoolean(Object ob)
double getDouble(Object obj)
设置属性的值的方法有:
void set(Object obj, Object value)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z)
void setDouble(Object obj, double d)
这里TestClass里面我就放了一个String类型的数据和int类型的数据,所以只需要使用get和getInt方法就可以获取属性的值了,设置值同理,看代码演示:
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class MainTest {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// 获取字节码对象
//类名.class获取,MainTest类和TestClass类在同一个包下面
Class<TestClass> aClass = TestClass.class;
//创建一个testClass对象,后面的方法需要用到,因为属性str和i都是非静态的,它们依赖对象而存在
TestClass tc = new TestClass();
//获取公有属性
Field str = aClass.getField("str");
//获取私有属性
Field i = aClass.getDeclaredField("i");
//使用公有属性
System.out.println("给str赋值前str的值为:"+str.get(tc));
str.set(tc,"cat");
System.out.println("给str赋值后str的值为:"+str.get(tc));
//使用私有属性
//设置暴力反射
i.setAccessible(true);
System.out.println("给i赋值前i的值为:"+i.getInt(tc));
i.set(tc,147);
System.out.println("给i赋值后i的值为:"+i.getInt(tc));
}
}
运行结果:
总结
本篇文章浅浅介绍了一下反射,并且简单的使用了一下,其反射用的多的地方应该是在框架里面,那时应该会结合昨天的文章所讲述的属性集和配置文件来使用,那样会大大的提高程序的灵活性,让使用者也更加方便,还没接触框架,就暂且不赘述了吧。
文章到此结束,感谢您的阅读,祝您生活愉快!