2、Java基础-反射

1. 什么是反射?

Java反射可以在程序运行的时候获取类和对象的所有信息,并可以使用它的字段和方法,大量应用于框架中。

2. 反射的优缺点

2.1. 优点

极大的提高了程序的 灵活性扩展性,它可以允许程序创建和控制任何类的对象。

2.2. 缺点

  1. 安全问题:,由于可以使用 setAccessible(true)方法设置为可被强制访问、可以无视泛型参数的安全检查等。
  2. 性能问题:由于反射需要检查方法可见性、进行参数校验、JVM无法进行优化等原因导致反射的性能相对较差。

3. Class类

在Java中,除了基础类型(比如int)等,其他所有的类型都是 class,包括interface。

class是由JVM动态加载的,JVM会在第一次读取到class的时候,将其加载进内存。而每加载一种class,JVM就会为其创建一个Class类型的实例,并将该class类型和这个Class的实例关联起来。

注意区分 Class 和 class,class是一种类型,而Class是一个名为Class的class。

那么如何创建一个Class实例,我们看下JDK中的Class类的源码

我们发现到,Class类的构造器是私有的,只有JVM可以创建Class实例,我们是不能主动创建Class实例的。

由于JVM为每一个加载的class类型创建了对应的Class实例,并且在实例中保存了该class类型的所有信息,包括类名、包名、方法、字段等,因此如果能够获取Class实例,我们就可以通过这个Class实例获取到该实例对应的class类型的所有信息。这种通过Class实例获取class信息的方法就是反射(Reflection)。

3.1. 获取class的Class实例

  1. 直接通过class的静态变量class获取:
Class cls = String.class;
  1. 通过class类型对应的Class实例的getClass()方法获取:
String str = "GuoziGe";
Class cls = str.getClass();
  1. 通过全限定名(完整包名)获取:
Class cls = Class.forName("java.lang.String");

3.2. 动态加载

JVM在执行Java程序的时候,并不是一次性将所有用到的class全部加载到内存,而是在第一次用到class的时候加载。

4. 访问字段

对于任何一个对象实例,只要我们获取到了它的Class,就可以获取到它的一切信息。

4.1. 获取字段信息

Class类提供了4个方法用于获取字段信息:

  1. Field getField(name):根据字段名获取某个public的field(包括父类)
  2. Field[] getFields():获取所有public的field(包括父类)
  3. Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  4. Field[] getDeclaredFields():获取当前类的所有field(不包括父类)

注意:不能通过子类的Class直接获取父类的私有属性,当然可以通过子类的Class获取到父类的Class后间接获取。

我们定义两个类

分别测试一下上面的四个方法

输出为:

4.2. 获取字段值

上面我们已经可以使用反射获取一个对象实例的字段Field实例,使用该实例可以获取该字段的值等。

我们看下Field的源码

发现包含了很多字段的信息,比如字段名、字段类型、字段修饰符、修饰的注解等等。我们可以使用字段实例的field.get(对象实例)方法获取字段值。

来,还是上代码看看

输出为:

哎?我们发现了问题,grade是可以正常获取到值的。但是score(私有字段)无法正常取到值,触发了异常(IllegalAccessException)非法访问,不能直接访问学生类的私有字段。

那怎么解决呢?当然我们可以直接将private改成public(手动滑稽),获取再获取值之前,使用setAccessible(true)方法将其强制可访问。来看看效果:

正常输出。

那么为啥可以的,如果可以通过反射获取private的值的话,那么封装还有什么意义呢?

其实,我们在日常的实际开发过程中,一般都是通过student.name来获取值,编译器会根据public、private等来决定是否可以允许访问字段,以达到封装的目的。

而反射是一种非常规的用法,它更多给框架使用。

注意:setAccessible(true)方法可能失败,JVM运行时如果有安全检查则会阻止,比如不允许java、javax包下的类调用,保证JVM核心库的安全性。

4.3. 设置字段值

同上,我们使用了field.get(对象实例)来获取字段值,我们可以使用field.set(对象实例, 需要设置的数据)来设置字段值。

5. 调用方法

5.1. 获取方法信息

同样的,Class中也提供了四个方法用于获取方法信息:

  1. Method[] getMethods():获取所有public的Method(包括父类)
  2. Method getMethod(name, Class…):获取某个public的Method(包括父类)
  3. Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
  4. Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)

我们重载方法,参数分别为int类型和String类型。

5.2. 调用方法

同字段信息,方法信息中也包含了很多,比如方法名、方法的访问控制权限等等。

我们可以通过method.invoke(对象实例, 方法参数)调用方法。

同字段的使用,针对private出现非法访问的情况,我们可以在方法调用之前使用setAccessible(true)方法设置为可访问。

6. 调用构造方法

我们之前创建对象都是使用new关键词

Student s = new Student();

使用反射创建新的实例,可以通过Class中提供的newInstance()方法。

Student s = Student.class.newInstance();

不过使用上述方法只能调用该类的public无参构造方法。

为了调用任意的构造方法,Java反射机制提供了Constructor对象,包含了构造方法的所有信息,和Method类似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值