反射机制就是在程序“运行状态”时:对任意一个类,我们通过反射都能够知道这个类的所有属性和方法;对于任意一个对象,我们同样也都能够调用它的任意一个方法和属性。
目录
一、反射的入口
想要使用反射,我们必须获取到某一个类的Class对象,java中一共有三种方式可以获取到某个类的Class对象:
- Class.forName(类的全限定名);
- 类名.class;
- 对象.getClass();(getClass()方法是Object类中的一个native方法)
下面我们结合代码实例具体的看一下反射的用法。先创建一个接口和一个实现类,并在实现类里面赋予一些成员属性和方法。
接口1:
public interface MyInterface {
void interfaceMethod();
}
接口2:
public interface MyInterface2 {
void interfaceMethod2();
}
实现类:
public class Person implements MyInterface,MyInterface2{
private int id;
private String name;
private int age;
public String gender;
@Override
public void interfaceMethod() {
System.out.println("interface Method......");
}
@Override
public void interfaceMethod2() {
System.out.println("interface2 Method......");
}
//准备一个静态方法
public static void staticMehtod(){
System.out.println(" static method......");
}
//准备一个公有方法
public void publicMethod(){
System.out.println("public Method......");
}
//准备一个私有方法
private void privateMethod(){
System.out.println("private Method......");
}
//无参构造
public Person() {
}
//一参构造
public Person(int id) {
this.id = id;
}
//三餐构造
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
//私有构造
private Person(String name){
this.name = name;
}
//get和set方法
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
测试类:
//通过反射获取类
public class RelectDemo01 {
public static void main(String[] args) {
//1.Class.forName(类的全限定名) 一般推介使用此方式
try {
Class<?> personClass = Class.forName("com.shhxbean.test.reflect.Person");
System.out.println(personClass);
}catch (ClassNotFoundException e){
e.printStackTrace();
}
//2.类名.class
Class<Person> personClass2 = Person.class;
System.out.println(personClass2);
//3.对象.getClass()
Person person = new Person();
Class<? extends Person> personClass3 = person.getClass();
System.out.println(personClass3);
}
}
输出:
class com.shhxbean.test.reflect.Person
class com.shhxbean.test.reflect.Person
class com.shhxbean.test.reflect.Person
二、反射可以拿到什么
其实一个类或对象,只要你里面有的,通过反射都可以拿到,不管你是实现了多个接口,继承了某个类,里面有什么属性、什么方法,在反射面前一切都是虚无......下面我们具体展开看一看:
2.1获取所有实现的接口
上面的代码实例中我们我们让Person实现了两个接口,现在通过反射拿到这两个接口,关键方法就是getInterfaces();
public class RelectDemo02 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//获取Person实现的所有接口
Class<?>[] interfaces = personClass.getInterfaces();
for(Class<?> inter : interfaces){
System.out.println(inter);
}
}
}
输出:
interface com.shhxbean.test.reflect.MyInterface
interface com.shhxbean.test.reflect.MyInterface2
2.2获取所有的父类
Person类没有显式的继承某个父类,但它有默认的父类Object,我们通过反射也可以拿到,关键方法getSuperClass();
public class RelectDemo03 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//获取Person的父类
Class<?> superClass = personClass.getSuperclass();
System.out.println(superClass);
}
}
输出:
class java.lang.Object
2.3获取所有的构造方法
在Person类中定义了三个构造方法,我们通过反射来拿到他们,关键字getConstractors();这个也是获取的public修饰的构造方法,如果构造方法是private修饰的,这种方式是拿不到的,那如何拿private的,getDeclaredConstractors()即可。
public class RelectDemo04 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//获取Person的构造方法
Constructor<?>[] constructors = personClass.getConstructors();
for(Constructor<?> constructor :constructors){
System.out.println(constructor);
}
}
}
输出:
public com.shhxbean.test.reflect.Person()
public com.shhxbean.test.reflect.Person(int)
public com.shhxbean.test.reflect.Person(int,java.lang.String,int)
2.4获取方法
这里获取方法又分为两种情况:
- 获取所有的公共方法(本类及其父类、接口中所有的public修改的方法),关键字getMethods();
- 只获取当前类中所有的方法(包括public、private修饰的所有方法),关键字getDeclaredMethods();
public class RelectDemo05 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//情况一:获取所有公共的方法(包括父类、接口中所有public的方法)
System.out.println("情况一:获取所有公共的方法:");
Method[] methods = personClass.getMethods();
for(Method method : methods){
System.out.println("情况一:" + method);
}
System.out.println("==================================");
//情况二:只获取当前类中所有方法(包括public\private)
System.out.println("情况一:获取所有公共的方法:");
Method[] declaredMethods = personClass.getDeclaredMethods();
for(Method declaredMethod : declaredMethods){
System.out.println("情况二:" + declaredMethod);
}
}
}
输出:
情况一:获取所有公共的方法:
情况一:public java.lang.String com.shhxbean.test.reflect.Person.getName()
情况一:public int com.shhxbean.test.reflect.Person.getId()
情况一:public void com.shhxbean.test.reflect.Person.setName(java.lang.String)
情况一:public void com.shhxbean.test.reflect.Person.interfaceMethod()
情况一:public void com.shhxbean.test.reflect.Person.interfaceMethod2()
情况一:public void com.shhxbean.test.reflect.Person.setId(int)
情况一:public int com.shhxbean.test.reflect.Person.getAge()
情况一:public void com.shhxbean.test.reflect.Person.setAge(int)
情况一:public static void com.shhxbean.test.reflect.Person.staticMehtod()
情况一:public void com.shhxbean.test.reflect.Person.publicMethod()
情况一: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 final void java.lang.Object.wait() 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()
==================================
情况一:获取所有公共的方法:
情况二:public java.lang.String com.shhxbean.test.reflect.Person.getName()
情况二:public int com.shhxbean.test.reflect.Person.getId()
情况二:public void com.shhxbean.test.reflect.Person.setName(java.lang.String)
情况二:public void com.shhxbean.test.reflect.Person.interfaceMethod()
情况二:public void com.shhxbean.test.reflect.Person.interfaceMethod2()
情况二:public void com.shhxbean.test.reflect.Person.setId(int)
情况二:public int com.shhxbean.test.reflect.Person.getAge()
情况二:public void com.shhxbean.test.reflect.Person.setAge(int)
情况二:public static void com.shhxbean.test.reflect.Person.staticMehtod()
情况二:public void com.shhxbean.test.reflect.Person.publicMethod()
情况二:private void com.shhxbean.test.reflect.Person.privateMethod()
通过上面的输出结果我们可以看到情况一拿到了很多方法,既有类本身的public方法,也有其父类Object中的wait()、equals()等方法,还有接口中的方法。而情况二中只拿到了类本身中的方法,包括public和private修饰的方法。
2.5获取属性
属性的获取和上面方法的获取类似,也是分两种情况:
- 获取所有的公共属性,关键字getFields();
- 获取所有属性,关键字getDeclaredFields();
public class RelectDemo06 {
public static void main(String[] args) {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
情况一:获取所有公共的属性(包括父类、接口中所有public的属性)
System.out.println("情况一:获取所有公共的属性:");
Field[] fields = personClass.getFields();
for(Field field :fields){
System.out.println("情况一:" + field);
}
System.out.println("==================================");
//情况二:只获取当前类中所有属性(包括public\private)
System.out.println("情况二:获取所有属性:");
Field[] declaredfields = personClass.getDeclaredFields();
for(Field declaredfield : declaredfields){
System.out.println("情况二:" + declaredfield);
}
}
}
输出:
情况一:获取所有公共的属性:
情况一:public java.lang.String com.shhxbean.test.reflect.Person.gender
==================================
情况二:获取所有属性:
情况二:private int com.shhxbean.test.reflect.Person.id
情况二:private java.lang.String com.shhxbean.test.reflect.Person.name
情况二:private int com.shhxbean.test.reflect.Person.age
情况二:public java.lang.String com.shhxbean.test.reflect.Person.gender
2.6获取对象实例
上面列举的是通过反射拿到了一个类中的一些方法、属性等,我们也可以通过反射来创建某个类(或接口)的实例对象,关键字newInstance();
public class RelectDemo07 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Object instance = personClass.newInstance();
Person person = (Person) instance;
//通过实例对象调用方法
person.interfaceMethod();
}
}
输出:
interface Method......
三、利用反射获取对象实例,并操作对象
上面的内容只是大体的罗列了一下通过反射可以拿到的一些东西,现在我们具体的通过反射机制创建一个实例对象,并对其做一些特定的操作。
3.1操作属性
public class RelectDemo08 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//操作属性
try {
//利用反射机制获取实例对象
Person person = (Person)personClass.newInstance();
//getDeclaredField()中的参数就是属性的名字
Field idField = personClass.getDeclaredField("id");
//因为id属性是private的,所以需要通过setAccessible(true)屏蔽掉private限制
idField.setAccessible(true);
//第一个参数是对象名,第二个参数是要给此属性赋的值
idField.set(person,1);
System.out.println("id=" + person.getId());
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
输出:
id=1
这里面需要注意的就三点:
- getDeclaredField(参数)方法,我们在上面讲的getDeclaredFields()方法没有参数,它拿到的是类中的所有属性,返回的也是一个Field数组,而getDeclaredField(参数)方法中需要传递参数,这个参数就是属性名,通过这个方法可以拿到指定的属性,返回值就是一个Field,而不是数组;
- setAccessible()方法,如果类中的某个属性/方法是private修饰的,我们需要调用Field/Method.setAccessible(true)才能不受限制的给其赋值或调用;
- set()方法的两个参数,第一个是对象引用,第二个是你要赋的具体的值。
3.2操作方法
public class RelectDemo09 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//操作方法
try {
//利用反射机制获取实例对象
Person person = (Person)personClass.newInstance();
//getDeclaredMethod()中的参数:第一个是方法名,第二个是方法的参数类型
Method method = personClass.getDeclaredMethod("privateMethod",null);
//因为privateMethod方法是private的,所以需要通过setAccessible(true)屏蔽掉private限制
method.setAccessible(true);
//第一个参数是对象名,第二个是要给privateMethod方法传递的参数
method.invoke(person,null);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
输出:
private Method......
操作方法同上面的操作属性类似,这里需要注意的有以下几点:
- getDeclaredMethod(参数1,参数2)方法拿到的是某个具体的方法,第一个参数是你要获取方法的方法名,第二个是这个方法的参数类型。比如我们person类中的privateMethod()这个方法,它的方法名就是privateMethod,并且它没有参数,所以我们第二个参数就直接写null。如果它是privateMethod(String s)这种类型,那么它的参数类型是String,这个时候getDeclaredMethod方法就改写成getDeclaredMethod("privateMethod",String.class)。
- 属性的赋值是通过set方法,而方法的调用则是通过invoke()方法
3.3操作构造方法
public class RelectDemo10 {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
//反射入口
Class<?> personClass = null;
try {
personClass = Class.forName("com.shhxbean.test.reflect.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//操作构造方法
try {
//利用反射机制获取实例对象
Person person = (Person)personClass.newInstance();
//getConstructor中的参数是构造函数的参数类型
//在反射中,根据类型获取方法时:基本类型(int char...)和包装类(Integer Character...)是不同的类型
//如果是私有的构造方法通过getDeclaredConstructor即可获取,其他的不变
Constructor<?> constractor = personClass.getConstructor(int.class);
System.out.println(constractor);
//通过构造方法创建对象,因为我们在上面是通过personClass.getConstructor(int.class)
//拿到的构造方法(有参构造),所以通过它新建对象的时候也必须传递一个参数,否则会报错
Person person2 = (Person) constractor.newInstance(16);
System.out.println("person2=" + person2);
//获取私有构造
Constructor<?> constructor2 = personClass.getDeclaredConstructor(String.class);
constructor2.setAccessible(true);
//通过私有构造创建对象
Person person3 = (Person) constructor2.newInstance("周杰伦");
System.out.println("person3=" + person3);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
四、补充
4.1动态加载类
有时候在程序运行之前我们不知道要调用那个类的那个方法,只有在运行的时候才知道。这个时候反射就会发挥出他的价值。我们可以把类名、方法名等配置在一个文件中,通过读取文件来让程序取执行不同类的不同方法,进而避免修改程序文件,而只是根据实际情况修改配置文件即可。假如我们现在有两个类Student和Teacher,如下:
public class Student {
public void study(){
System.out.println("I am studying...");
}
}
public class Teacher {
public void teach(){
System.out.println("I am teaching...");
}
}
现在我们再在工程目录下创建一个专门的配置文件class.txt(名字随便起),里面专门配置类和方法名,如下:
内容如下:
classname=com.shhxbean.test.reflect.Teacher
methodname=teach
下面我们写个demo演示一下:
public class ReflectDemo11 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//动态加载类名和方法
Properties properties = new Properties();
properties.load(new FileReader("class.txt"));
String classname = properties.getProperty("classname");
String methodname = properties.getProperty("methodname");
try {
Class<?> aClass = Class.forName(classname);
//getMethod()、invoke()为可变参数类型...
Method method = aClass.getMethod(methodname);
method.invoke(aClass.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
I am teaching...
可以看到我们通过Properties读取了class.txt文件的内容,拿到方法名和类名,最后通过反射实现了对其的调用。这时如果我们想要调用Student类的study方法,只要在class.txt文件中把classname和methodname做相应的修改即可,而不需要修改程序。
4.2越过泛型检查
我们知道,假如在一个List里面有泛型限定,那么你只能向这个List里面添加指定类型的元素,其他元素是添加不进去的,但是通过反射就可以绕过泛型检查,想添加啥添加啥,如下demo:
public class ReflectDemo12 {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//因为有泛型的约束,所以下面的这行代码在编译期就会报错,它不允许你往list里面放String
//类型的元素
//list.add("a");
//通过反射绕过泛型检查
Class<? > listClass = list.getClass();
//利用反射调用add方法,绕过泛型检查,Object.class表示了可以往进添加任何类型的元素
Method method = listClass.getMethod("add", Object.class);
method.invoke(list,"aaaaa");
System.out.println(list);
}
}
输出:
[1, 2, 3, aaaaa]
不过需要提醒的是虽然可以通过反射机制访问private等访问修饰符不允许访问的属性/方法,也可以忽略掉泛型的约束,但在实际开发中不建议这样做,因为可能会造成程序的混乱。