1.什么是反射
在Java中很重要的一个概念就是反射机制,什么是反射机制?举个例子,在已知一个类的情况下,肯定是可以通过new关键字来创建对象。如果现在要求通过一个对象找到一个类的名称,就需要用到反射机制。
2.Class类
如果要用反射,则必须认识Class类。
一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这些字节码都可以用对象来表示,比如String.class , int.class。在java中所有对象都是java.lang.Class类的实例,所以所有对象都可以转变为java.lang.Class类型。
获取各个字节码对应的实例对象有三种方法:
1,类名.class
2,对象.getClass()
3,Class.forName("类名")
第三种方法是最常用的获取类的方法。
2.1Class类的使用
如果想要通过Class类本身实例化其他类的对象,可以使用newInstance()方法,但是必须保证被实例化的类中含有一个无参的构造函数。代码如下:
package reflect;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void method1() throws Exception{
Class<?> c = Class.forName("reflect.Person");
Person p = (Person)c.newInstance();
p.setName("zhangsan");
System.out.println(p.getName());
}
程序运行结果为张三,程序通过Class.forName()方法实例化Class对象后,直接调用了newInstance()方法就可以根据传入的完整包.类名称的方式进行对象的实例化操作,取代了new关键字的操作方式。但是必须要保证有个无参的构造函数,否则程序运行会出现异常。
3.Constructor类
如果程序没有无参的构造函数,也是能创建对象的,只不过需要用到Constructor类。在使用constructor类创建对象时需要明确的调用对应的构造方法并把参数传递进去才可以进行实例化操作。步骤如下:
1. 通过Class类中getConstructors()方法获取该类的所有构造方法。
2. 向构造方法中传递一个对象,包含了构造方法中所需的各个参数。
3. 通过构造方法创建对象。
示例代码如下:
package reflect;
public class Person {
private String name;
private int age;
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public static void method3() throws Exception{
Class<?> c = Class.forName("reflect.Person");
Constructor<?>[] cons = c.getConstructors();
Person p = (Person) cons[1].newInstance("小明",20);
System.out.println(p.getName()+" "+p.getAge());
}
运行结果为:
小明
20
上面的程序通过Class类取得了一个类中全部的构造方法,并以数组的形式返回,Person类中只有两个构造方法,调用了第二个即数组中index为1的构造方法并创建了对象。
还可以根据构造方法内的参数获取指定的构造方法,然后在用该方法创建对象,代码如下:
public static void method3() throws Exception{
Class<?> c = Class.forName("reflect.Person");
Constructor<?> con = c.getConstructor(String.class, int.class);
Person p = (Person) cons.newInstance("小明",20);
System.out.println(p.getName()+" "+p.getAge());
}
运行结果和上面的一样,但是获取构造方法是根据,构造方法内的参数类型来获取的。
4.Method类
如果要通过反射调用类中的方法可以通过Method类来完成,步骤如下:
1. 通过Class类的getMethod(String name,Class...parameterType)方法取得一个Method对象,并设置此方法操作时所需的参数类型。
2. 通过invoke()进行调用,并向方法中传递要设置的参数。
示例如下:
package reflect;
public class SayHello {
public void sayHi(String str){
System.out.println("Hi: "+str);
}
}
package reflect;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("reflect.SayHello");
Object obj = c.newInstance();
Method m = c.getMethod("sayHi", String.class);
m.invoke(obj, "zhangsan");
}
}
运行结果为:Hi: zhangsan
以上程序通过Class类的getMethod()方法根据一个类的方法名取得方法对象,因为sayHi方法包含一个参数,所以在获取method对象的时候需要将参数类型传递进去,并通过invoke调用指定的方法。在使用invoke()方法时必须传入一个类的实例化对象,因为sayHi(String str)方法含有一个String类型的参数,所以invoke()方法被调用的时候需要传递进一个参数。
注意:如果被调用的方法是静态方法,则invoke()中可以不传递对象,即m.inoke(null,"zhangsan");
5.Field类
在反射操作中可也以取得一个类中的全部属性,但是在取得属性时有两种不同的操作
1. 得到实现的接口或父类中的公共属性:public Field[] getFields() throws SecurityException
2. 得到本来中的全部属性:public Field[] getDeclaredField() throws SecurityException
这两个方法返回的都是Field的数组,每一个Field对象表示类中的一个属性,如想操作类中的属性方法如下:
public static void main(String[] args) throws Exception {
Class<?> c = Class.forName("reflect.Person");
Object obj = c.newInstance();
Field nameField = c.getDeclaredField("name");
Field ageField = c.getDeclaredField("age");
nameField.setAccessible(true);
ageField.setAccessible(true);
nameField.set(obj, "zhangsan");
ageField.set(obj, 22);
System.out.println(nameField.get(obj) + " : " + ageField.getInt(obj));
}
运行结果为:zhangsan : 22
6.取得类的结构
6.1取得接口
要取得一个类所实现的全部接口,可以通过Class类中的getInterfaces()方法,此方法定义如下:
public Class[] getInterfaces()
该方法返回一个Class数组,然后利用Class类中的getName()方法即可获得接口的名称。
package reflect;
public interface Study {
public void studyJava();
}
package reflect;
public class Person implements Study{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public void studyJava() {
System.out.println("study java");
}
}
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("reflect.Person");
Class[] clazzs = clazz.getInterfaces();
for(Class c:clazzs){
System.out.println(c.getName());
}
}
运行结果为:reflect.Study
6.2取得父类
一个类可以实现多个接口但是只能继承一个父类,所以取得一个类的父类可以直接使用Class类中的getSuperclass()方法。此方法定义如下:
public Class<? super T> getSuperclass();
取得Person类的父类代码如下:
public static void main(String[] args) throws Exception {
Class<?> clazz = Class.forName("reflect.Person");
Class clz = clazz.getSuperclass();
System.out.println(clz.getName());
}
运行结果为:java.lang.Object
因为Person没有明确的继承一个类,所以默认继承Object类。
7.通过反射操作数组
反射除了用在类上,还可以使用反射操作数组。
public static void main(String[] args) throws Exception {
int[] a = new int[]{1,2,3};
method(a);
}
public static void method(Object obj){
Class<?> c = obj.getClass();
if(c.isArray()){
int len = Array.getLength(obj);
for(int i=0;i<len;i++){
System.out.println(Array.get(obj, i));
}
}
else{
System.out.println(obj);
}
}
程序运行结果为
1
2
3