小言_互联网的博客

紫薇星上的Java——反射(上)

298人阅读  评论(0)

我在学Java的时候就是在反射这里懵逼的,所以这篇文章来整理一下反射的知识点。


认识反射机制


反射机制简介

在Java语言里之所以有如此众多的开源技术支撑,很大的一部分来自Java的最大特征——反射机制,如果不能灵活的使用反射机制进行项目的开发与设计,那么就可以说并未接触到Java的精髓。

所有的技术实现的目标只有一点:重用性。

关于反射技术首先要考虑“反”与“正”的操作,所谓的正操作指的是当我们要使用一个类的时候,要先导入程序所在的包,然后根据类进行对象的实例化,并且依靠对象调用类中的方法,这是一个正常的过程。但是“反”的过程就是说,有一个对象,根据实例化对象反推出其类型。

传统的正向操作是这样子的:


  
  1. import java.util.Date; //导入程序所在的包,类,相当于知道对象的出处了
  2. public class TestDemo {
  3. public static void main(String[] args) throws Exception {
  4. Date date = new Date(); //通过类产生实例化对象
  5. System.out.println(date.getTime()); //根据对象调用类中的方法
  6. }
  7. }

如果要想实现“反”的处理操作,那么首先要采用的就是Object类中的提供的操作方法:

  • 获取Class对象信息:public final native Class<?> getClass();

  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Date date = new Date(); //通过类产生实例化对象
  4. System.out.println(date.getClass()); //根据实例化对象找到对象所属类型
  5. }
  6. }
class java.util.Date

getClass()可以帮助使用者找到对象的根源。


Class类对象的三种实例化操作

反射之中的所有核心操作都是通过Class类对象来展开的,可以说Class类是反射操作的根源所在,但这个类如果要想获取它的实例化对象,可以通过三种方式完成,首先来观察Class类的定义:


  
  1. public final class Class<T> implements java.io.Serializable,
  2. GenericDeclaration,
  3. Type,
  4. AnnotatedElement

从JDK1.5开始,Class在定义的时候可以使用泛型进行标记,这样的用法主要是希望可以避免所谓的向下转型。接下来通过三种实例化操作解释三种实例化形式:

  • Object类支持

Object类可以根据实例化对象获取Class对象:public final native Class<?> getClass();


  
  1. class Person {} //自定义的程序类
  2. public class TestDemo {
  3. public static void main(String[] args) throws Exception {
  4. Person person = new Person(); //已经存在有指定类的实例化对象
  5. Class<? extends Person> cls = person.getClass();
  6. System.out.println(cls);
  7. System.out.println(cls.getName()); //获取类的完整名称
  8. }
  9. }

  
  1. class com .test .Person
  2. com .test .Person

如果现在只是想获得Class对象,就必须产生指定类对象后才可以获得,这会造成一个无用的实例化对象。

  • JVM直接支持

采用“类.class”的形式实例化:


  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Class<? extends Person> cls = Person.class;
  4. System.out.println(cls);
  5. System.out.println(cls.getName()); //获取类的完整名称
  6. }
  7. }

  
  1. class com .test .Person
  2. com .test .Person

如果要采用此种形式,必须要导入程序的开发包。 

  • Class类支持

在Class类中提供有一个操作的static方法——加载类:public static Class<?> forName(String className) throws ClassNotFoundException;

我们首先在另一个包中创建一个类:


  
  1. package com.zijun.vo;
  2. public class Person {
  3. }

然后回到这个测试类中,现在如果要使用Person类实例化对象就要导包,但我们使用Class.foeName()就可以不用导包:


  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> cls = Class.forName( "com.zijun.vo.Person");
  4. System.out.println(cls);
  5. System.out.println(cls.getName()); //获取类的完整名称
  6. }
  7. }

  
  1. class com .zijun .vo .Person
  2. com .zijun .vo .Person

同时它也没有extends上下限的限制,这种模式最大的特点就是可以使用字符串的形式定义要使用的类型,并且程序中不需要编写任何的import语句。但如果此时要使用的程序类不存在,就会抛出java.lang.ClassNotFoundException的异常。

这三种实例化实现方式我们必须全部掌握。


反射应用案例

经过一系列的分析之后虽然获得了Class类的实例化对象,但依然觉得这个对象的获取意义不是很大,所以我们通过几个案例进行程序的说明,这些案例都是在实际开发中会使用到的。


反射实例化对象

获取Class对象后最大的意义并不是在于一个对象的实例化操作形式,更重要的是Class类中存在有一个对象的反射实例化方法,这种方法代替了关键字new:

  • 在JDK1.9之前的实例化:public T newInstance() throws InstantiationException, IllegalAccessException;
  • 在JDK1.9之后:clazz.getDeclaredConstructor().newInstance(); 

  
  1. public class Person {
  2. //任何情况下如果要实例化类对象一定要调用类中的构造方法
  3. public Person() { //无参构造方法
  4. System.out.println( "*****Person类构造方法*****");
  5. }
  6. @Override
  7. public String toString() {
  8. return "我是一个人!";
  9. }
  10. }

  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> cls = Class.forName( "com.zijun.vo.Person");
  4. Object obj = cls.newInstance(); //实例化对象,JDK1.9之后被废除了
  5. System.out.println(obj); //输出对象会调用toString()方法
  6. }
  7. }

  
  1. *****Person类构造方法*****
  2. 我是一个人!

在这个程序中没有一个关键字new,但仍然会有*****Person类构造方法*****输出,主要是因为cls.newInstance()方法,而“我是一个人!”这个输出是因为调用了System.out.println(obj)方法。

现在我们发现通过反射实现的对象实例化处理,依然要调用类中的无参构造方法,其本质等价于“类  对象  =  new  类();”,也就是说相当于隐含了关键字new,而直接使用字符串进行替代,所以newInstance()方法就等价于关键字new。

但刚才说到从JDK1.9之后newInstance()方法就被替代了,因为默认的Class类中的newInstance()方法只能够调用无参构造,所以来讲很多开发者会认为其描述的不准确,于是将其变换了形式。


反射与工厂设计模式

如果想要进行对象的实例化处理除了可以使用关键字new之外,还可以使用反射机制来完成,这是一定会思考一个问题:为什么要提供一个反射的实例化?到底是使用关键字new还是使用反射呢?

如果想要理解此类问题,最好的解决方案就是通过工厂设计模式来解决,工厂类在我之前的一篇文章中简单整理过,这里我们再来说一下。

工厂设计模式的最大特点:客户端的程序类不直接牵扯到对象的实例化管理,只与接口发生关联,通过工厂类获取指定接口的实例化对象。

  • 传统的工厂设计模式

  
  1. interface IMessage {
  2. public void send(); //发送消息
  3. }
  4. class NetMessage implements IMessage{
  5. public void send() {
  6. System.out.println( "发送消息!");
  7. }
  8. }
  9. public class TestDemo {
  10. public static void main(String[] args) throws Exception {
  11. IMessage msg = new NetMessage(); //如果直接实例化一定会有耦合问题
  12. }
  13. }

在实际的开发过程中,接口的主要作用是为不同的层提供偶遇一个操作的标准。但如果此时如果直接将一个子类设置为接口实例化操作,就一定会有耦合问题,所以使用工厂类来来解决此类问题:


  
  1. interface IMessage {
  2. public void send(); //发送消息
  3. }
  4. class NetMessage implements IMessage{
  5. public void send() {
  6. System.out.println( "发送消息!");
  7. }
  8. }
  9. class Factory{
  10. private Factory() {} // 没有产生实例化对象的意义,所以构造方法私有化
  11. public static IMessage getInstance(String className) {
  12. if( "NetMessage".equalsIgnoreCase(className)) {
  13. return new NetMessage();
  14. }
  15. return null;
  16. }
  17. }
  18. public class TestDemo {
  19. public static void main(String[] args) throws Exception {
  20. IMessage msg = Factory.getInstance( "NetMessage");
  21. msg.send();
  22. }
  23. }

 现在这个工厂设计模式是一个很简单的设计模式,我们将工厂设计模式的图示拿过来:

但这个工厂设计模式最主要的缺点在于:这种工厂设计模式属于静态设计模式,如果现在要追加一个子类,就意味着原来的工厂类一定要作出修改,因为不追加这种判断就无法获取指定的接口对象的:


  
  1. interface IMessage {
  2. public void send(); //发送消息
  3. }
  4. class NetMessage implements IMessage{
  5. @Override
  6. public void send() {
  7. System.out.println( "发送消息!");
  8. }
  9. }
  10. class CloudMessage implements IMessage{
  11. @Override
  12. public void send() {
  13. System.out.println( "云消息发送!");
  14. }
  15. }
  16. class Factory{
  17. private Factory() {} // 没有产生实例化对象的意义,所以构造方法私有化
  18. public static IMessage getInstance(String className) {
  19. if( "NetMessage".equalsIgnoreCase(className)) {
  20. return new NetMessage();
  21. } else if( "CloudMessage".equalsIgnoreCase(className)) {
  22. return new CloudMessage();
  23. }
  24. return null;
  25. }
  26. }
  27. public class TestDemo {
  28. public static void main(String[] args) throws Exception {
  29. IMessage msg = Factory.getInstance( "NetMessage");
  30. IMessage cmsg = Factory.getInstance( "CloudMessage");
  31. cmsg.send();
  32. msg.send();
  33. }
  34. }

工厂设计模式最有效的解决是子类与客户端的耦合问题,但是解决的核心思想是在于提供有一个工厂类作为过渡端,随着项目的进行,IMessage类有可能会有更多的子类,而且随着时间的推移子类产生的会越来越多,这时就意味着工厂类永远都要进行修改,并且永不停止!

这是最好的解决方案就是不使用关键字new来完成,因为关键字new在使用的时候需要有一个确定的类存在,而newInstance()方法只需要有一个明确表示类名称的字符串即可。我们来修改一下工厂类:


  
  1. class Factory{
  2. private Factory() {} // 没有产生实例化对象的意义,所以构造方法私有化
  3. public static IMessage getInstance(String className) {
  4. IMessage instance = null;
  5. try {
  6. instance = (IMessage)Class.forName(className).getDeclaredConstructor().newInstance();
  7. } catch (InstantiationException e) {
  8. // TODO Auto-generated catch block
  9. e.printStackTrace();
  10. } catch (IllegalAccessException e) {
  11. // TODO Auto-generated catch block
  12. e.printStackTrace();
  13. } catch (IllegalArgumentException e) {
  14. // TODO Auto-generated catch block
  15. e.printStackTrace();
  16. } catch (InvocationTargetException e) {
  17. // TODO Auto-generated catch block
  18. e.printStackTrace();
  19. } catch (NoSuchMethodException e) {
  20. // TODO Auto-generated catch block
  21. e.printStackTrace();
  22. } catch (SecurityException e) {
  23. // TODO Auto-generated catch block
  24. e.printStackTrace();
  25. } catch (ClassNotFoundException e) {
  26. // TODO Auto-generated catch block
  27. e.printStackTrace();
  28. }
  29. return instance;
  30. }
  31. }

这时候再进行子类的扩充的时候就不需要再去修改工厂类了,这时候就可以发现,利用反射机制实现的工厂设计模式,最大的优势在于对于子类的扩充将不再影响到工厂类的定义。

首先我们解决了接口实例化的问题,然后我们解决了多个子类的问题,但是现在我们依然要进一步思考,因为在实际的项目开发中可能会有大量的接口,并且这些接口都可能需要通过工厂类来实例化,所以此时我们的工厂设计模式不应该只为一个IMessage接口服务而应该为所有的接口服务。

如果我们现在再实现一个接口:


  
  1. interface IService{
  2. public void service(); //产生服务
  3. }
  4. class HouseService implements IService{
  5. @Override
  6. public void service() {
  7. System.out.println( "{服务} 住宿服务");
  8. }
  9. }

那么要想使用工厂类实现所有子类,我们就要修改工厂类为:


  
  1. class Factory{
  2. private Factory() {} // 没有产生实例化对象的意义,所以构造方法私有化
  3. public static IMessage getInstance(String className) {
  4. IMessage instance = null;
  5. try {
  6. instance = (IMessage)Class.forName(className).getDeclaredConstructor().newInstance();
  7. } catch (Exception e) {
  8. // TODO Auto-generated catch block
  9. e.printStackTrace();
  10. }
  11. return instance;
  12. }
  13. public static IService getServiceInstance(String className) {
  14. IService instance = null;
  15. try {
  16. instance = (IService)Class.forName(className).getDeclaredConstructor().newInstance();
  17. } catch (Exception e) {
  18. // TODO Auto-generated catch block
  19. e.printStackTrace();
  20. }
  21. return instance;
  22. }
  23. }

这时候你可能已经发现了,我们每有一个接口出现,就要修改一次工厂类,而一个好的工厂类应该可以接受所有的接口与子类类型,这样才能保证每个接口与子类都被实现。

最好的方法就是使用泛型:


  
  1. class Factory{
  2. private Factory() {} // 没有产生实例化对象的意义,所以构造方法私有化
  3. /**
  4. * 获取接口实例化对象
  5. * @param className 接口的子类
  6. * @param clazz 接口的类型
  7. * @return 如果子类存在返回指定的接口实例化对象
  8. */
  9. @SuppressWarnings( "unchecked")
  10. public static <T> T getInstance(String className, Class<T> clazz) {
  11. T instance = null;
  12. try {
  13. instance = (T)Class.forName(className).getDeclaredConstructor().newInstance();
  14. } catch (Exception e) {
  15. // TODO Auto-generated catch block
  16. e.printStackTrace();
  17. }
  18. return instance;
  19. }
  20. }
  21. public class TestDemo {
  22. public static void main(String[] args) throws Exception {
  23. IMessage msg = Factory.getInstance( "com.test.NetMessage", IMessage.class);
  24. IService cmsg = Factory.getInstance( "com.test.wang1.HouseService", IService.class);
  25. cmsg.service();
  26. msg.send();
  27. }
  28. }

  
  1. {服务} 住宿服务
  2. 发送消息!

此时的工厂设计模式将不再受限于指定的接口,可以为所有的接口提供实例化服务,这才是一个高可用的工厂设计模式。


反射与单例设计模式

单例设计在之前的一篇文章中整理过,单例设计模式的核心本质为:类内部的构造方法私有化,然后在类的内部产生实例化对象之后通过static方法获取实例化对象进行类中的结构调用,单例设计模式一共有两类:懒汉式和饿汉式。饿汉式的单例不在我们的讨论范围之内,因为饿汉式的单例是在加载的时候就会产生一个实例化对象,而懒汉式是在第一次使用时进行实例化对象。

我们来看一下懒汉式单例设计模式:


  
  1. class Singleton{
  2. private static Singleton instance = null;
  3. private Singleton() {} //构造方法私有化
  4. public static Singleton getInstance() {
  5. if(instance == null) {
  6. instance = new Singleton();
  7. }
  8. return instance;
  9. }
  10. public void print() {
  11. System.out.println( "懒汉式单例设计模式!");
  12. }
  13. }
  14. public class TestDemo {
  15. public static void main(String[] args) throws Exception {
  16. Singleton sinA = Singleton.getInstance();
  17. sinA.print();
  18. }
  19. }

但是我们在整理的时候是在单线程的时候进行编写的,今天我们来看一下多线程的时候:


  
  1. class Singleton{
  2. private static Singleton instance = null;
  3. private Singleton() { //构造方法私有化
  4. System.out.println( "{" + Thread.currentThread().getName() + "}*****实例化Singleton对象*****");
  5. }
  6. public static Singleton getInstance() {
  7. if(instance == null) {
  8. instance = new Singleton();
  9. }
  10. return instance;
  11. }
  12. public void print() {
  13. System.out.println( "懒汉式单例设计模式!");
  14. }
  15. }
  16. public class TestDemo {
  17. public static void main(String[] args) throws Exception {
  18. for( int i = 0; i < 3; i ++) {
  19. new Thread(()->{
  20. Singleton.getInstance().print();
  21. }, "单例消费端" + i).start();
  22. }
  23. }
  24. }

我们为了区分给构造方法中加点东西,但是结果出现后发现:


  
  1. {单例消费端0} *****实例化Singleton对象*****
  2. 懒汉式单例设计模式!
  3. {单例消费端1} *****实例化Singleton对象*****
  4. 懒汉式单例设计模式!
  5. {单例消费端2} *****实例化Singleton对象*****
  6. 懒汉式单例设计模式!

现在问题来了,单例设计模式的最大特点就是在运行过程中只允许产生一个实例化对象,但我们发现当有了若干个线程的时候,程序会产生多个实例化对象,这时就不是单例设计模式了。

我们可以分析一下是什么原因造成这种情况的:我们所写的懒汉式单例设计模式流程是当一个线程进来时判断instance是否为空,如果为空就实例化对象,判断完成后返回instance对象。现在有三个线程同时进来了,那么它们判断的结果肯定是空,所以会同时实例化对象,然后返回instance对象时就会返回三个实例化的对象。

此时问题的核心就是代码本身出现了不同步的情况,那么解决问题的关键就在于同步处理,这时我们就会想到synchronized关键字的处理:


  
  1. public static synchronized Singleton getInstance() {
  2. if(instance == null) {
  3. instance = new Singleton();
  4. }
  5. return instance;
  6. }

  
  1. {单例消费端0} *****实例化Singleton对象*****
  2. 懒汉式单例设计模式!
  3. 懒汉式单例设计模式!
  4. 懒汉式单例设计模式!

这个时候的确是进行了同步处理,但这个同步的代价是效率,如果加了synchronized后整体就会处于线程一个接一个的进行处理,而不加同步时线程可以一起进行处理,但整体代码中只有一个部分进行同步处理,那就是instance的实例化处理部分:

在这个情况下我们会发现,synchronized同步加的有些草率了,我们应该更加合理的进行同步处理:


  
  1. class Singleton{
  2. private static volatile Singleton instance = null;
  3. private Singleton() { //构造方法私有化
  4. System.out.println( "{" + Thread.currentThread().getName() + "}*****实例化Singleton对象*****");
  5. }
  6. public static Singleton getInstance() {
  7. if(instance == null) {
  8. synchronized (Singleton.class) {
  9. if(instance == null) {
  10. instance = new Singleton();
  11. }
  12. }
  13. }
  14. return instance;
  15. }
  16. public void print() {
  17. System.out.println( "懒汉式单例设计模式!");
  18. }
  19. }

当第一次实例化时,线程通过第一个 if 进入同步处理,然后再进行第二个 if 判断是否已经有实例化对象,而后进行返回;但第二次实例化通过第一个 if 时instance已经不为空了,所以就不需要再进行同步了;并且instance对象在被实例化的时候,应该立刻与主内存中的对象保持同步,所以加一个volatile关键字。

如何写一个单例设计模式?编写一个饿汉式单例设计模式,并将构造方法私有化。

在Java中有哪些地方会使用到单例设计模式?Runtime类、Spring框架等。

懒汉式单例设计模式的缺陷?在进行多线程实例化时,因为同步问题会造成多个实例化对象。


反射与类操作

在反射机制的处理过程中不仅仅只是一个实例化对象的处理操作,更多的情况下还有类的组成结构操作,任何一个类的基本组成都是:父类或父接口、包、属性、构造方法和普通方法。


反射获取类的结构信息

一个类的基本信息主要包括类所在的包名称、父类的定义、父接口的定义。

我们定义两个接口、一个抽象类,然后使用上面的Person类来实现接口和抽象类:


  
  1. package com.zijun.service;
  2. public interface IMessageService {
  3. public void send();
  4. }

  
  1. package com.zijun.service;
  2. public interface IChannelService {
  3. public boolean connect();
  4. }

  
  1. package com.zijun.abs;
  2. public abstract class AbstractBase {
  3. }

  
  1. package com.zijun.vo;
  2. import com.zijun.abs.AbstractBase;
  3. import com.zijun.service.IChannelService;
  4. import com.zijun.service.IMessageService;
  5. public class Person extends AbstractBase implements IMessageService, IChannelService{
  6. @Override
  7. public boolean connect() {
  8. return true;
  9. }
  10. @Override
  11. public void send() {
  12. if( this.connect()) {
  13. System.out.println( "紫薇星");
  14. }
  15. }
  16. }

如果此时想要获得类的一些基本信息,可以使用Class类中的如下方法:

  • 获取包名称:public Package getPackage();
  • 获取继承父类: public native Class<? super T> getSuperclass();
  • 获取实现父接口:public Class<?>[] getInterfaces();

  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> cls = Person.class; //获取指定类的对象
  4. Package pack = cls.getPackage(); //获取指定的包名
  5. Class<?> parent = cls.getSuperclass(); //获取父类
  6. Class<?> impl[] = cls.getInterfaces(); //获取接口
  7. System.out.println( "{包名}" + pack.getName() + "\n{父类}" + parent.getName());
  8. for(Class<?> temp : impl) {
  9. System.out.println( "{接口}" + temp.getName());
  10. }
  11. }
  12. }

  
  1. {包名}com.zijun.vo
  2. {父类}com.zijun. abs.AbstractBase
  3. {接口}com.zijun.service.IMessageService
  4. {接口}com.zijun.service.IChannelService

当我们获取了一个类的Class对象之后就意味着可以对象获取这个类的一切基本的结构信息。


反射调用构造方法

在一个类中除了继承关系之外最为重要的操作就是类中的结构处理了,而类中的结构中首先就要观察构造方法的使用问题,实际上在通过反射实例化对象的时候就已经接触到了构造方法的问题了:

  • 实例化方法替代:clazz.getDeclaredConstructor().newInstance(); 

所有类的构造方法的获取都可以直接通过Class类来完成,该类中定义有如下的几个方法:

  • 获取所有构造方法:public Constructor<?>[] getDeclaredConstructors() throws SecurityException;
  • 获取指定构造方法:public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException;
  • 获取所有构造方法:public Constructor<?>[] getConstructors() throws SecurityException;
  • 获取指定构造方法:public Constructor<T> getConstructor(Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException;

现在为了看的清楚些,我们在AbstractBase抽象类中添加:


  
  1. public abstract class AbstractBase {
  2. public AbstractBase() {}
  3. public AbstractBase(String msg) {}
  4. }

然后在Person类中追加:


  
  1. public Person(){}
  2. public Person(String name, int age){}

现在我们就有了父类中的构造和子类中的构造,复制构造都有了,我们来看一下它们的区别:


  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> cls = Person.class; //获取指定类的对象
  4. Constructor<?>[] constructors1 = cls.getDeclaredConstructors();
  5. for(Constructor<?> cons : constructors1) {
  6. System.out.println(cons);
  7. }
  8. Constructor<?>[] constructors2 = cls.getConstructors();
  9. for(Constructor<?> cons : constructors2) {
  10. System.out.println(cons);
  11. }
  12. }
  13. }

  
  1. public com .zijun .vo .Person()
  2. public com .zijun .vo .Person( java .lang .String, int)
  3. public com .zijun .vo .Person()
  4. public com .zijun .vo .Person( java .lang .String, int)

 这样看看不出什么区别,但其实还是有些区别的,如果将Person类中的无参构造改为

private Person(){}

再执行一次:


  
  1. private com .zijun .vo .Person()
  2. public com .zijun .vo .Person( java .lang .String, int)
  3. public com .zijun .vo .Person( java .lang .String, int)

 所以我们可以得出结论,当然这里有我补充的一些:

  • getDeclaredConstructor(Class<?>... parameterTypes) 这个方法会返回制定参数类型的所有构造器,包括public的和非public的,当然也包括private的;
  • getDeclaredConstructors()的返回结果就没有参数类型的过滤了;
  • 再来看getConstructor(Class<?>... parameterTypes)这个方法返回的是上面那个方法返回结果的子集,只返回制定参数类型访问权限是public的构造器;
  • getConstructors()的返回结果同样也没有参数类型的过滤;

代码可以使用getDeclaredConstructors()和getConstructors()调用有参构造,但从实际开发角度来讲,所有是哦也能过反射的类最好都有无参构造,因为这样可以使实例化达到统一性。


反射调用普通方法

在进行反射处理时也可以通过反射来获取类中的全部方法,如果想要通过反射来调用这些方法,必须有一个条件:类中要提供有实例化对象。

在Class类中提供有如下操作来获取方法对象:

  • 获取全部方法:public Method[] getMethods() throws SecurityException;
  • 获取指定方法:public Method getMethod(String name, Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException;
  • 获取本类全部方法:public Method[] getDeclaredMethods() throws SecurityException;
  • 获取本类指定方法:public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
            throws NoSuchMethodException, SecurityException;

获取普通方法操作与获取构造方法使用起来差不多,这里就不做过多阐述。

在Method中有public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException, InvocationTargetException方法,可以作为调用方法。

我们首先修改Person类中的内容:


  
  1. public class Person extends AbstractBase implements IMessageService, IChannelService{
  2. private String name;
  3. private int age;
  4. public Person(){}
  5. public Person(String name, int age){
  6. this.name = name;
  7. this.age = age;
  8. }
  9. //其他方法略
  10. public String getName() {
  11. return name;
  12. }
  13. public void setName(String name) {
  14. this.name = name;
  15. }
  16. }

然后在测试类中修改:


  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> cls = Class.forName( "com.zijun.vo.Person"); //获取指定类的对象
  4. String value = "a"; //要设置的属性内容
  5. //任何情况下要想保存类中的属性或者调用类中的方法都必须保证存在有实例化对象
  6. //既然不允许导包,就反射实例化对象
  7. Object obj = cls.getDeclaredConstructor().newInstance(); //调用无参构造实例化
  8. //进行方法的调用,一定要取得方法得名称
  9. String setMethodName = "setName"; //方法名称
  10. Method setMethod = cls.getDeclaredMethod(setMethodName, String.class); //获取指定的方法
  11. setMethod.invoke(obj, value); //等价于: Person对象.setName(value);
  12. String getMethodName = "getName";
  13. Method getMethod = cls.getDeclaredMethod(getMethodName); //getter没有参数
  14. System.out.println(getMethod.invoke(obj)); //等价于Person对象.getName()
  15. }
  16. }
a

反射调用成员

类结构中最后一个核心组成就是成员,大部分情况下都会称其为成员属性,对于成员信息的获取也是通过Class类来完成的,在这个类中提供有如下两组操作方法:

  • 获取本类全部成员:public Field[] getDeclaredFields() throws SecurityException;
  • 获取本类指定成员:public Field getDeclaredField(String name)  throws NoSuchFieldException, SecurityException;
  • 获取父类全部成员:public Field[] getFields() throws SecurityException;
  • 获取父类指定成员:public Field getField(String name) throws NoSuchFieldException, SecurityException;

这些方法大家应该都能想到了,同样的调用之后就可以得到一个数组,数组中是所有public属性,其他的方法也是返回属性,在这里就不一一举例了。但是在Field中最为重要的操作形式并不是获取全部的成员,而是如下的三个方法:

  • 设置属性内容:public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException;
  • 获取属性内容:public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException;
  • 解除封装:public void setAccessible(boolean flag);

所有成员实在对象实例化之后才进行空间分配的,所以此时一定要有实例化对象后才可以进行成员的操作。


  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> cls = Class.forName( "com.zijun.vo.Person"); //获取指定类的对象
  4. Object obj = cls.getDeclaredConstructor().newInstance(); //必须先实例化对象才会分配空间
  5. Field nameField = cls.getDeclaredField( "name"); //获取成员对象
  6. nameField.set(obj, "AAA"); //设置属性值,等价于:Person对象.name = "AAA";
  7. System.out.println(nameField.get(obj)); //等价于:Person对象.name
  8. }
  9. }

  
  1. Exception in thread "main" java.lang.NoSuchFieldException: name
  2. at java.base/java.lang.Class.getField(Class.java: 1956)

但这时name属性是private ,所以用到我们第三个方法setAccessible()来解除封装:


  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> cls = Class.forName( "com.zijun.vo.Person"); //获取指定类的对象
  4. Object obj = cls.getDeclaredConstructor().newInstance(); //必须先实例化对象才会分配空间
  5. Field nameField = cls.getDeclaredField( "name"); //获取成员对象
  6. nameField.setAccessible( true);
  7. nameField.set(obj, "AAA"); //设置属性值,等价于:Person对象.name = "AAA";
  8. System.out.println(nameField.get(obj)); //等价于:Person对象.name
  9. }
  10. }
AAA

通过一系列的操作我们可以发现,类之中的构造、方法、成员属性都可以通过反射实现调用,但是对于成员的反射调用,我们很少这样直接处理,大部分操作都应该setter、getter处理,所以以上的代码只是反射的特点而不具备实际开封能力,而对于Field类在实际中只有一个方法最为常用:

  • 获取成员类型:public Class<?> getType();

  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Class<?> cls = Class.forName( "com.zijun.vo.Person"); //获取指定类的对象
  4. Field nameField = cls.getDeclaredField( "name"); //获取成员对象
  5. System.out.println(nameField.getType().getName()); //获取完整类名称
  6. System.out.println(nameField.getType().getSimpleName()); //获取类名称
  7. }
  8. }

  
  1. java .lang .String
  2. String

在以后开发中进行反射处理的时候往往会经常用到Field与Method类实现类中的setter与getter方法的调用以及getType的使用。


Unsafe工具类

反射是Java的第一大特点,一旦打开了反射的大门就可以有更加丰富的设计形式。除了JVM本身能够支持的反射类型之外,在Java中也提供有一个Unsafe类,这个类主要的特点是可以利用反射来获取对象,并且直接使用底层的C++来代替JVM执行,即可以绕过JVM相关的对象管理机制,也就是说如果一旦使用了Unsafe类,那么项目之中将无法继续使用JVM的内存管理机制以及垃圾回收处理。

如果想要使用Unsafe类就要像确认一下这个类之中的定义的构造方法与常量问题:

  • 构造方法:private Unsafe() {}
  • 私有常量:private static final Unsafe theUnsafe = new Unsafe();

需要注意的是,这个类中并没有提供static方法,即不能通过类似于传统的单例设计模式来进行操作,如果想要获得这个类的对象就必须使用反射机制来完成:


  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Field field = Unsafe.class.getDeclaredField( "theUnsafe");
  4. field.setAccessible( true); //解除封装处理
  5. Unsafe unsafeObject = (Unsafe)field.get( null); //static属性不需要实例化对象
  6. }
  7. }

在传统的开发之中,一个程序类必须要通过实例化对象后才可以调用类中的普通方法,尤其是以单例设计模式为例:


  
  1. class Singleton{
  2. private Singleton() {
  3. System.out.println( "*****Singleton类构造*****");
  4. }
  5. public void print() {
  6. System.out.println( "单例设计模式!");
  7. }
  8. }

如果我们有这样一个单例设计模式,那么要想实例化对象就可以使用Unsafe来实现:


  
  1. public class TestDemo {
  2. public static void main(String[] args) throws Exception {
  3. Field field = Unsafe.class.getDeclaredField( "theUnsafe");
  4. field.setAccessible( true); //解除封装处理
  5. Unsafe unsafeObject = (Unsafe)field.get( null); //static属性不需要实例化对象
  6. Singleton instance = (Singleton)unsafeObject.allocateInstance(Singleton.class);
  7. instance.print();
  8. }
  9. }
单例设计模式!

Unsafe只能说为开发提供了一些更加方便的处理机制,但这种操作由于不受JVM的管理,所以如果不是必须的情况下不建议使用,而讲解这个类主要的目的是帮助大家巩固对于反射的理解。


今天先整理这么多,下次会继续整理反射与简单类、与代理模式、与Annotation的关系知识点,我们下次见👋


转载:https://blog.csdn.net/qq_39732867/article/details/105381366
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场