反射:很容易想到“正射”,其实对于我们通过new 类名()创建对象的方式就相当于是正射,我们在编译期就已知对象的类型的情况下创建出该类型的对象。那么“反射”就是在编译期我们不确定要创建的对象类型,还要创建出该对象的操作。反射创建对象方式的类型只有在运行期才能确定。
首先我们要了解到反射机制中的一个类Class,Class类封装了相应类的类型信息,诸如(类名、保命、父类、方法、实现的接口、成员变量等),它也是我们开启后续反射操作的一个入口。
一、Class类
Class类是由final修饰,并私有了构造方法,我们无法通过new关键字调用构造方法完成Class对象的创建。那我们如何得到一个类的Class对象,一共有3种方式:
- 类名.class(访问类中的静态成员变量class)
- 实例对象.getClass()
- Class.forName("类的全限定名")【该方式用的较多】
通过对两次获取到的String类的Class对象进行内存地址的比较结果为true,也就是我们拿的是同一个Class对象,这是因为Class对象是通过JVM创建出来的,在某个类第一次被使用时会被JVM将其字节码文件从磁盘移到内存中运行,并为该类创建出它的Class对象,后续再次使用到这个类时就无需再进行加载,因此两次拿到的Class对象是同一个。
Class类中封装了大量的类型信息,我们可以通过Class对象调用相应的方法获取信息
下面是通过Class对象获取String类型的一部分信息:
Class对象是进行反射的基础,Constructor,Field,Method这些对象的创建都以Class对为基础
我们在正常的程序中创建对象:例如Person p=new Person();
通过new关键字调用Person类的无参构造完成Person对象的创建
下面我们了解Constructor类是如何通过反射的方式创建对象
二、Constructor类
Constructor类是对构造方法进行封装,通过反射创建对象我们首先需要获取构造方法
Class cls=String.class;
1、Constructor[ ] getConstructors():获取所有public修饰的构造方法(包含父类)
Constructor[]cons=cls.getConstructors();
2、Constructor[ ] getDeclaredConstructors():获取当前类所有的构造方法(不包含继承来的)
Constructor[]cons=cls.getDeclaredConstructors();
3、Constructor getConstructor():通过传入的参数个数、参数类型对应不同的构造方法
以String为例,不传参获取的是它的无参构造方法
Constructor con=cls.getConstructor();
传入一个String.class,获取的是一个为String类型参数的构造方法
Constructor con=cls.getConstructor(String.class);
我们已经得到了Construcror对象,再调用newInstance()即可创建String类型的对象,该方法的参数是一个动态参数,该参数表示传入构造方法的值,可以是0个或多个。
只有当要用某个无参构造方法创建该类对象时,可以省略创建Constructor类对象的这个过程:
直接使用Class对象.newInstance()
public class Demo04 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class cls=Example.class;
// Constructor[]cons=cls.getConstructors();
// System.out.println(cls.getSimpleName()+"所有public修饰的构造函数:");
Constructor[]cons=cls.getDeclaredConstructors();
System.out.println(cls.getSimpleName()+"所有的构造函数:");
for(Constructor con:cons){
System.out.println(con);
}
System.out.println("----------------------------");
Example ep1=(Example) cls.newInstance();
System.out.println(ep1);
Example ep2=(Example) cls.getConstructor().newInstance();
System.out.println(ep2);
Example ep3=(Example) cls.getConstructor(int.class).newInstance(7);
System.out.println(ep3);
Example ep4=(Example) cls.getConstructor(int.class,double.class).newInstance(7,9);
System.out.println(ep4);
Constructor con1=cls.getDeclaredConstructor(float.class);
con1.setAccessible(true);
Example ep5=(Example) con1.newInstance(5.6f);
System.out.println(ep5);
Constructor con2=cls.getDeclaredConstructor(String.class);
Example ep6=(Example) con2.newInstance("basiguangnian");
System.out.println(ep6);
}
}
class Example{
private Example(float n){
System.out.printf("调用(private)Example的构造 n=%f\n",n);
}
protected Example(String str){
System.out.printf("调用(protected)Example的构造方法 str=%s\n",str);
}
public Example(){
System.out.println("调用Example的无参构造");
}
public Example(int a){
System.out.printf("调用Example的有参构造 a=%d\n",a);
}
public Example(int a,double b){
System.out.printf("调用Example的有参构造 a=%d,b=%f\n",a,b);
}
}
Constructor对象.newInstance()完成对象的创建,但创建出的对象为Object类型,若需要获取指定类型的对象,做向下转型即可。
如果构造方式是private修饰,直接调用newInstance()会抛出IllegalAccessException,因为类中定义为private的方法仅限于该类的内部使用,这体现了类的封装性,要想通过反射的方式调用私有构造方法创建对象,Constructor对象.setAccessiable(true),设置该方法为可访问的,但这样破坏了封装性。
三、Field类
Field类:对类中的成员变量进行了封装,每一个成员变量都会被封装为一个Field对象
Field类中的方法:
getModifiers():获取字段的访问权限(以整型数字展示)
为了获取访问修饰符的名称,可以使用Modifier类的静态方法toString()进行转换
getType():字段的类型
getName():字段的名称 获得所有public修饰的字段(包括父类)
Field[] fields = cls.getFields(); 获得所有定义的字段(当前类)
Field[] fields = cls.getDeclaredFields();
通过字段名获取指定名称的公共成员字段Field对象 Field field=cls.getField("bookName");
以上方法都可以获取Filed对象,我们可以对Field对象进行setXXX()或getXXX()操作
field.set(对象,值):设置对象的该字段值
filed.get(对象):获取对象的该字段值为了方便观察,以下类中只展示了成员变量
class Book extends Product{
public String bookName;//图书名称
public int stock;//库存
private float sale;//折扣
private boolean isShelf;//是否上架
}
class Product{
public double price;
}
public class Demo07 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchFieldException {
Class cls=Book.class;
// 获得所有public修饰的字段(包括父类)
Field[] fields = cls.getFields();
// 获得所有定义的字段(当前类)
// Field[] fields = cls.getDeclaredFields();
for(Field f:fields){
System.out.println(f.getModifiers());
System.out.println("成员变量的访问修饰符:"+ Modifier.toString(f.getModifiers()));
System.out.println("成员变量的类型:"+f.getType());
System.out.println("成员变量的名称:"+f.getName());
System.out.println();
}
Object obj=cls.newInstance();
Field bookName=cls.getField("bookName");
bookName.set(obj,"三毛");
System.out.println(bookName.get(obj));
System.out.println(obj);
}
}
四、Method类
Method类:对类中的方法进行了封装,也就是每一个方法都会被封装为一个Method对象。
以下方法可以获取类中的Method对象
Method []getMethods():得到所有public修饰的方法(包括从父类)
Method []getDeclaredMethods():得到本类中所有的方法(不包括继承的)
Method getMethod(方法名,方法参数字节码【无参数时null】):
按名称得到某个特定的public方法(包括从父类或接口继承的方法)
Method getDeclaredMethod(方法名,方法参数字节码):
按名称得到某个特定的方法(不包括继承的方法)
得到某个方法对应的Method对象后,需要调用invoke()来在某个对象上执行该方法
Method对象.invoke(方法作用对象,具体实际参数)
public class Demo11 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//计算指定数值的位数
int r = (int)Math.log10(16457894)+1;
System.out.println(r);
Class cls=Math.class;
Method method=cls.getMethod("log10",double.class);
double ret=(Double) method.invoke(null,16457894);
int res=(int)ret+1;
System.out.println(res);
}
}
Math.log10()+1用于判断当前数值的位数,两种方法的调用的运行结果均为8
当该Method对象表示的方法是static修饰时,作用对象为null
在执行一个私有访问权限的方法时,调用invoke前要执行setAccessible设置为true
了解了反射后,我们将正常的操作方式与反射机制进行对比,总结反射机制的优缺点:
优点 :在不知道具体类型的情况下,就可以对该类进行某些操作,使程序的灵活性更高,可扩展性更强
缺点 : 1、JVM在解释执行字节码文件时才能知道相关信息,效率较低
2、通过反射机制穿透了程序原本的封装性,能够操作私有的属性或方法,对于程序来说是不安全的
小结:
- 要进行反射操作必须先拿到这个类对应的Class对象
- 反射方式创建对象的过程:Class--->Constructor--->某个类的对象
- 对于private的数据需要使用getDeclaredXXX()获取返回相应的类型对象,要通过该对象进行某些操作,需设置当前对象的可访问性,setAccessible(true)