Java基础复习-反射
本文仅对所学java知识的查缺补漏
简述
Reflection(反射)
:反射机制允许程序在执行期借助于Reflection API
取得任何类的内部信息,并能直接操作任意对象的内部属性及方法;- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象地称之为:反射。
- 正常方式:
- 引入需要的包类名称
- 通过new实例化
- 取得实例化对象
- 反射方式:
- 实例化对象
getClass()
方式- 得到完整的包类名称
- 特性:动态性
- 正常方式:
反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
类的加载过程
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化:
- 加载:将
class
文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class
对象,作为方法区中类数据的访问入口(即引用地址)。所有需要访问和使用类数据只能通过这个Class对象。这个加载的过程需要类加载器参与。 - 链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以
cafe
开头,没有安全方面的问题 - 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 验证:确保加载的类信息符合JVM规范,例如:以
- 初始化:
- 执行**类构造器
<clinit>()
**方法的过程。类构造器<clinit>()
方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。 - 当初始化一个类时,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的
<clinit>()
方法在多线程环境中被正确加锁和同步。
- 执行**类构造器
反射4方式
name.setAccessible(true);
保证当前属性是可访问的,如果不设置,那么private修饰的将不可访问
/**
* 反射操作
* @Author: fxx
* @Date: 2020/12/29 15:46
*/
public class ReflectionTest {
//正常操作,通过对象操作属性,不能直接操作私有的属性和方法
@Test
public void test1(){
Person person = new Person(30, "小明");
person.age = 10;
System.out.println(person.toString());
person.show();
}
//反射操作
@Test
public void test2()throws Exception{
Class clazz = Person.class;
//得到Person的构造器
Constructor constructor = clazz.getConstructor(int.class, String.class);
//创建对象
Object instance = constructor.newInstance(25, "Tom");
Person person = (Person)instance; //强转之后person就能得到方法和属性
System.out.println(instance);
//获得属性
Field age = clazz.getDeclaredField("age");
age.set(person, 55);
System.out.println(person.toString());
//调用方法
Method show = clazz.getMethod("show");
show.invoke(person);
System.out.println("**************");
//通过反射,调用Person类的私有结构,比如:私有的构造器,方法,属性
Constructor constructor1 = clazz.getDeclaredConstructor(String.class);
constructor1.setAccessible(true);
Person person1 = (Person)constructor1.newInstance("Marry");
System.out.println(person1);
//调用私有属性
Field name = clazz.getDeclaredField("name");
//保证当前属性是可访问的,如果不设置,那么private修饰的将不可访问
name.setAccessible(true);
name.set(person1, "Jack");
System.out.println(person1);
//调用私有方法
Method showNation = clazz.getDeclaredMethod("showNation", String.class);
showNation.setAccessible(true);
//invoke:运行方法,返回值即调用方法的返回值,static方法的调用对象是运行时对象
String str = (String)showNation.invoke(person1, "China");
System.out.println(str);
}
//获取Class实例的方式:4种
@Test
public void test3() throws ClassNotFoundException {
//方式一:调用运行时类的属性:.class
Class clazz1 = Person.class;
System.out.println(clazz1);
//方式二:通过运行时类的对象,调用getClass()
Person person = new Person();
Class clazz2 = person.getClass();
System.out.println(clazz2);
//方式三:调用Class的静态方法:forName(String classPath);
Class clazz3 = Class.forName("javabasic.day28.Person");
System.out.println(clazz3);
System.out.println(clazz1 == clazz2);
System.out.println(clazz2 == clazz3);
//方式四:使用类的加载器---ClassLoader (了解)
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("javabasic.day28.Person");
System.out.println(clazz4);
System.out.println(clazz1 == clazz4);
}
}
体会反射
//newInstance()的要求
@Test
public void test() throws IllegalAccessException, InstantiationException {
Class<Person> clazz = Person.class;
/*
newInstance():调用此方法,创建对应的运行时类对象,内部调用了运行时类的空参构造器
如果没有空参构造器,将抛出异常
要想该方法正常创建运行时类的对象:
要求:1.运行时类必须提供空参的构造器
2.空参构造器的访问权限不能设置为private
在javabean中要求提供一个public的空参构造器。原因:
1.便于通过反射,创建运行时类的对象
2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
*/
Person person = clazz.newInstance();
System.out.println(person);
}
//体会反射的动态性
@Test
public void test1() throws Exception{
int i;
for (i=0;i<100;i++){
int random = new Random().nextInt(3);
switch (random){
case 0:
System.out.println(getInstance("java.lang.Object"));
break;
case 1:
System.out.println(getInstance("java.util.Date"));
break;
case 2:
System.out.println(getInstance("javabasic.day28.Person"));
}
}
}
public Object getInstance(String classPath)throws Exception{
Class clazz = Class.forName(classPath);
return clazz.newInstance();
}
反射-属性
/**
* 获取运行时类的属性结构
* @Author: fxx
* @Date: 2020/12/30 12:49
*/
public class FieldTest {
@Test
public void test()throws Exception{
Class<Person1> clazz = Person1.class;
//获取属性结构
//getFields():获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for(Field field : fields){
System.out.println(field);
}
System.out.println("**********");
//getDeclaredFields():获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f :
declaredFields) {
System.out.println(f);
}
}
//权限修饰符 数据类型 变量名
@Test
public void test1(){
Class<Person1> clazz = Person1.class;
Field[] declaredFields = clazz.getDeclaredFields();
for (Field f :
declaredFields) {
//权限修饰符
int modifiers = f.getModifiers();
System.out.print(Modifier.toString(modifiers)+" ");
//数据类型
Class type = f.getType();
System.out.print(type+" ");
//变量名
String name = f.getName();
System.out.println(name);
}
}
}
反射-方法
/**
* 获取运行时类的方法结构
* @Author: fxx
* @Date: 2020/12/30 13:25
*/
public class MethodTest {
@Test
public void test(){
Class<Person1> clazz = Person1.class;
//getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
for (Method m:
methods) {
System.out.println(m);
}
System.out.println("*********");
//getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类的)
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m:
declaredMethods) {
System.out.println(m);
}
}
/**
* 权限修饰符 返回值类型 方法名(参数类型1 形参名1,...)throws XxxException{}
*/
@Test
public void test2(){
Class<Person1> clazz = Person1.class;
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method m: declaredMethods) {
//获取方法注解
Annotation[] annotations = m.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//权限修饰符
System.out.print(Modifier.toString(m.getModifiers())+" ");
//返回值类型
System.out.print(m.getReturnType().getName() + " ");
//方法名
System.out.print(m.getName());
System.out.print("(");
//形参列表
String str = null;
Class[] parameterTypes = m.getParameterTypes();
if (!(parameterTypes == null && parameterTypes.length == 0)){
for (int i=0;i<parameterTypes.length;i++) {
System.out.print(parameterTypes[i].getName() + " args_"+i + (str = parameterTypes.length-1 == i?"":","));
}
}
System.out.println(")");
//抛出的异常也可以通过反射获取
// m.getExceptionTypes()
}
}
}
反射-其他
/**
* 其他反射
* @Author: fxx
* @Date: 2020/12/30 14:02
*/
public class OtherTest {
@Test
public void test(){
Class<Person1> clazz = Person1.class;
//getConstructors():获取当前运行时类中声明为public的构造器(不包含父类)
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("************");
//getDeclaredConstructors():获取当前运行时类中声明的所有构造器(不包含父类)
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
}
/**
* 获取运行时类的父类
*/
@Test
public void test2(){
Class<Person1> clazz = Person1.class;
//父类
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
//带泛型父类
Type genericSuperclass = clazz.getGenericSuperclass();
System.out.println(genericSuperclass);
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
//获取泛型类型
Type[] arguments = parameterizedType.getActualTypeArguments();
for (Type argument : arguments) {
System.out.println(argument.getTypeName());
}
}
/**
* 获取运行时类实现的接口
*/
@Test
public void test5(){
Class<Person1> clazz = Person1.class;
//获取运行时类实现的接口,不包括父类实现的接口
Class[] interfaces = clazz.getInterfaces();
for (Class anInterface : interfaces) {
System.out.println(anInterface);
}
}
/**
* 获取运行时类所在的包
*/
@Test
public void test6(){
Class<Person1> clazz = Person1.class;
Package aPackage = clazz.getPackage();
System.out.println(aPackage);
}
}
问题
1
封装性和反射是否矛盾:
不矛盾。封装性是指封装了实现的细节,提供了公有API供外部调用。而反射是不想用封装好的,想自己调用属性和方法做其他的实现。(即:我已经提供了API了,使用者可以直接调用我的API去用,但是如果使用者想用的API我没提供,那么使用者可以调用我的私有属性去自己造)
2
开发中建议:
- 如果在编译的时候知道用哪个类的情况下,则用new的方式;
- 如果在编译期不知道该调用哪个类,方法,必须等到运行时根据运行过程来决定调用哪个类或者方法的,则用反射。
关于java.lang.Class
类的理解
3
类的加载过程:
程序经过javac.exe
命令以后,会生成一个或多个字节码文件(.class
结尾)。(这是编译,不是类的加载)
接着我们使用java.exe
命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class
的一个实例。(类也是对象,Class
类也是对象,Class
的实例对应着一个运行时类,即:Class clazz = Person.class
);
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式(4种)来获取此运行时类。
运行时类在内存中只有一份
4
Class的实例可以是哪些结构?
类、接口、数据、注解、基本数据类型、枚举、void等
对于数组来说,只要数组的元素类型和维度一样,就是同一个Class
(即:1维和2维不是同一个Class
),与长度无关。