目录
前言
不知道你有没有想过这样一个问题,我们在使用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使用成员方法
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));
}
}
运行结果:
总结
本篇文章浅浅介绍了一下反射,并且简单的使用了一下,其反射用的多的地方应该是在框架里面,那时应该会结合昨天的文章所讲述的属性集和配置文件来使用,那样会大大的提高程序的灵活性,让使用者也更加方便,还没接触框架,就暂且不赘述了吧。
文章到此结束,感谢您的阅读,祝您生活愉快!