今天学习的内容是Java反射机制
一、反射的概述及应用场景
Java反射机制属于Java高级特性之一,其实简单来说就是程序在运行时可以动态地获取任意类的所有信息。
那么为什么要在程序运行时才获取类的信息呢?为什么不直接在程序中new一个该类对象呢?这就要说到反射机制的应用场景(重点理解):某些应用程序只能被使用而不能被修改(比如Tomcat、框架等等),如果想要在程序中使用自定义类的内容,就必须遵从程序提供的规范来书写类,也就是实现程序对外提供的接口!!并将实现类配置在配置文件中!!程序在运行时会读取配置文件,找到配置文件中的所配置的类的字节码文件,并将其加载进内存,并通过反射获得其信息。
举个例子,开发者只能使用Tomcat,而不能修改其程序(更别提在里面new对象了),而Tomcat处理不同HTTP请求的方式并不相同,于是程序就对外提供了Servlet接口,开发者可以通过实现Servlet接口自定义Servlet(假设实现类是MyServlet类),实现对不同HTTP请求的具体处理。而想要令Tomcat获取MyServlet类的内容,还要将MyServlet类配置在配置文件(web.xml)中,这样Tomcat运行时就会将MyServlet.class加载进内存,并通过反射获取该类的信息。
这种接口+配置文件的开发模式非常多见,比如Tomcat和框架!
二、反射的原理
道理我都懂,那么程序到底是如何通过反射获取类的信息呢?俗话说“一切皆对象”,所以加载进内存的字节码文件也会被描述为类并在堆上创建该类对象!描述字节码文件的类就是Class类,字节码文件对象就是Class对象。Class类可以获得其描述的字节码文件的所有内容,反射就是依靠Class对象完成的。获取Class对象的方法:
- Object类的getClass()方法,这种方式必须明确具体类,并创建该类对象
- 类名.class,这种方式必须明确具体类
- Class.forName("类的完全限定类名"),这种方式不需要明确具体类,有很强的扩展性(可以从配置文件读取并将类加载进内存,所以Tomcat和框架的配置文件要求指定类的完全限定类名)
获取到某类的Class对象之后,这个类就如同砧板上上猪--想拿啥就拿啥(拿了私有的不能访问,除非用setAccessible(true)方法强制访问)!比如可以获取该类的构造函数,方法,属性等等,注意这些内容也被描述成了类,比如Constructor、Method和Field等等。注意:getXXX()方法可以获取包括从父类继承到的内容,但是仅限于public成员;getDeclaredXXX()方法只能获取本类中定义的内容,但是不限制权限。
示例程序:
public class Tests101 {
public static void main(String[] args) {
getClass_1();
getClass_2();
getClass_3();
getConstroctorAndCreatNewObject_1();
getConstroctorAndCreatNewObject_2();
getField();
getMethod();
}
private static void getMethod() {
String className = "com.grc.Person";
Class<?> clazz = null;
try {
clazz = Class.forName(className);
Method[] method_1 = clazz.getMethods();
for (int i = 0; i < method_1.length; i++) {
System.out.println(method_1[i]);// 获取了包括父类方法在内的所有public方法
}
Method[] method_2 = clazz.getDeclaredMethods();
for (int i = 0; i < method_2.length; i++) {
System.out.println(method_2[i]);// 获取本类定义的所有方法
}
Method method = clazz.getMethod("showName", String.class);
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("小明", 20);
method.invoke(obj, "aaa");// name:aaa
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private static void getField() {
String className = "com.grc.Person";
Class<?> clazz = null;
try {
clazz = Class.forName(className);
Field field = clazz.getDeclaredField("name");
System.out.println(field);// private java.lang.String com.grc.Person.name
field.setAccessible(true);// 对私有属性取消权限检查
Object obj = clazz.newInstance();
field.set(obj, "kkk");
Object o = field.get(obj);
System.out.println(o);// kkk
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
private static void getConstroctorAndCreatNewObject_2() {
// 创建Person对象,使用有参构造函数
String className = "com.grc.Person";
Class<?> clazz = null;
try {
clazz = Class.forName(className);
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object obj = constructor.newInstance("小明", 20);// 实际上调用了Person("小明",20)
System.out.println(obj);// Person [name=小明, age=20]
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {// 如果该类没有该构造函数,出现此异常
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
private static void getConstroctorAndCreatNewObject_1() {
// 创建Person对象,使用无参构造函数
String className = "com.grc.Person";
Class<?> clazz = null;
try {
clazz = Class.forName(className);
Object obj = clazz.newInstance();// 实际上调用了Person()
System.out.println(obj);// Person [name=null, age=0]
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {// 如果该类没有无参构造函数会出现此异常
e.printStackTrace();
} catch (IllegalAccessException e) {// 如果该类的无参构造函数无法访问,出现此异常
e.printStackTrace();
}
}
// 获取Class对象的方法1:Object类的getClass()方法,这种方式必须要明确具体的类,并创建该类对象
private static void getClass_1() {
Person p = new Person();
Class<? extends Person> clazz = p.getClass();
System.out.println(clazz);// class com.grc.Person
}
// 获取Class对象的方法2:类名.class,这种方式必须要明确具体的类
private static void getClass_2() {
Class<Person> clazz = Person.class;
System.out.println(clazz);// class com.grc.Person
}
// 获取Class对象的方法3:Class.forName("类的完全限定类名"),这种方式扩展性很强,由于需要类的完全限定类型名
// 所以配置文件中配置的也是完全限定类名
private static void getClass_3() {
String className = "com.grc.Person";
Class<?> clazz = null;
try {
clazz = Class.forName(className);// class com.grc.Person
System.out.println(clazz);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Person {
private String name;
private int age;
public Person() {
super();
System.out.println("无参构造函数");
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
System.out.println("有参构造函数");
}
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 void showName(String name) {
System.out.println("name:" + name);
}
public void showAge() {
System.out.println("age:" + age);
}
public static void staticMethod() {
System.out.println("静态方法");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
面试题:
1.获取Class对象的方法?
答:- 方法1:类型.class,例如:String.class
- 方法2:对象.getClass(),例如:"hello".getClass()
- 方法3:Class.forName(),例如:Class.forName("java.lang.String")
- 方法2:对象.getClass(),例如:"hello".getClass()
- 方法3:Class.forName(),例如:Class.forName("java.lang.String")
2.如何通过反射创建对象?
- 方法1:通过类对象调用newInstance()方法,例如:String.class.newInstance()
- 方法2:通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance("Hello");
- 方法2:通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance("Hello");
3.
如何通过反射获取和设置对象私有字段的值?
答:可以通过类对象的getDeclaredField()方法字段(Field)对象,然后再通过字段对象的setAccessible(true)将其设置为可以访问,接下来就可以通过get/set方法来获取/设置字段的值了。