Java-反射基础

1、啥是反射

定义:JAVA 反射机制是在程序运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。

作用:

​ 动态(运行时)获取类的完整结构信息&调用对象的方法。

​ 我们或多或少都听说过设计框架的时候会用到反射,例如 Spring 的 IOC 就用到了工厂模式和反射来创建对象,BeanUtils 的底层也是使用反射来拷贝属性。所以反射无处不在。

​ 尽管我们日常开发几乎用不到反射,但是我们必须要搞懂反射的原理,因为它能帮我们理解框架设计的原理。

2、特点

2.1 优点

灵活性高。反射属于动态编译,只有到运行时才动态创建&获取对象实例。

编译方式说明:

1、静态编译:在编译时确定类型&绑定对象。如常见的使用new关键字创建对象。

2、动态编译:运行时确定类型&绑定对象。动态编译体现了Java的灵活性、多态特性&降低类之间的藕合性。

2.2 缺点
  • 执行效率低

    因为反射的操作主要通过JVM执行,所以时间成本会高于直接执行相同操作

1、因为接口的通用性,Java的invoke方法是传object和object[]数组的。基本类型参数需要装箱和拆箱,产生大量额外的对象和内存开销,频繁促发GC。

2、编译器难以对动态调用的代码提前做优化,比如方法内联。

3、反射需要按名检索类和方法,有一定的时间开销。

  • 容易破坏类结构

    因为反射操作绕过了源码,容易干扰类原有的内部逻辑

3、JVM加载类

我们写的 Java程序要放到 JVM中运行,所以要学习反射,首先要了解JVM加载类的过程。

image-20220522165946354

  • 1、我们写的.java文件叫做源代码
  • 2、在一个类中写了一个 main 方法,然后点击 IDEA 的 run 按钮,JVM运行时会触发 jdk 的 javac 指令将源代码编译成 .class 文件,这个文件又叫做字节码文件。
  • 3、JVM 的类加载器(可以理解成一个工具)通过一个类的全限定名来获取该类的二进制字节流,然后将该 class 文件加载到 JVM 的方法区中。
  • 4、类加载器加载一个 .class 文件到方法区的同时会在堆中生成一个唯一的 Class 对象,这个 Class 包含这个类的成员变量、构造方法以及成员方法。
  • 5、这个 Class 对象会创建与该类对应的对象实例。

所以表面上你 new 了一个对象,实际上当 JVM 运行程序的时候,真正帮你创建对象的是该类的 Class 对象。

也就是说反射其实就是 JVM 在运行程序的时候将你创建的所有类都封装成唯一一个 Class 对象。这个 Class 对象包含属性、构造方法和成员方法。你拿到了 Class 对象,也就能获取这三个东西。

你拿到了反射之后(Class)的属性,就能获取对象的属性名、属性类别、属性值,也能给属性设置值。

你拿到了反射之后(Class)的构造方法,就能创建对象。

你拿到了反射之后(Class)的成员方法,就能执行该方法。

知道了 JVM 加载类的过程,相信你应该更加深入的了解了反射的概念。

反射:JVM 运行程序 --> .java 文件 --> .class 文件 --> Class 对象 --> 创建对象实例并操作该实例的属性和方法

接下来我就讲一下反射中的相关类以及常用方法。

4、Class对象

4.1 获取Class对象

先建一个User类:

/**
 * @author BaoFeng Huang
 * @date 2022/4/10 13:46
 */
public class User {
    private String name = "abao";
    public String sex = "男";
    
    public User(){
        
    }

    public User(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public void eat(){
        System.out.println("人要吃饭");
    }
    
    private void run(){
        System.out.println("人要跑步");
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}

获取 Class 对象的三种方式:

  • 1、Class.forName(“全类名”)

    全类名:包名+类名

  Class userClass = Class.forName("com.test.model.User")
  • 2、类名.class
    Class userClass1 = User.class;
  • 3、对象.getClass()
    User user = new User();
    Class userClass2 = user.getClass();

尽管有三种方式获取 Class 对象,但是我们一般采用上述第一种方式。

拿到 Class 对象之后,我们就可以操作与它相关的方法了。

4.2 获取类名

1、获取完整类名:包名+类名

getName()

    Class userClass = Class.forName("com.test.model.User");
    String name = userClass.getName();
    System.out.println(name);

2、获取简单类名:不包括包名

getSimpleName()

            Class userClass = Class.forName("com.test.model.User");
            String simpleName = userClass.getSimpleName();
            System.out.println(simpleName);

5、属性

5.1 获取属性

1、获取所有公有属性:public修饰

getFields()

            Class userClass = Class.forName("com.test.model.User");
            Field[] fields = userClass.getFields();
            for(Field field : fields){
                System.out.println(field);
            }

打印结果:

image-20220522170132073

2、获取单个公有属性

getField("属性名")

            Class userClass = Class.forName("com.test.model.User");
            Field field = userClass.getField("sex");
            System.out.println(field);

3、获取所有属性:公有+私有

getDeclaredFields()

            Class userClass = Class.forName("com.test.model.User");
            Field[] fields = userClass.getDeclaredFields();
            for(Field field : fields){
                System.out.println(field);
            }

打印结果:

image-20220522170332374

4、获取单个属性:公有或者私有

getDeclaredField("属性名")

            Class userClass = Class.forName("com.test.model.User");
            Field nameField = userClass.getDeclaredField("name");
            Field sexField = userClass.getDeclaredField("sex");
            System.out.println(nameField);
            System.out.println(sexField);
5.2 操作属性

1、获取属性名称

getName()

            Class userClass = Class.forName("com.test.model.User");
            Field nameField = userClass.getDeclaredField("name");
            System.out.println(nameField.getName());

image-20220522170455323

2、获取属性类型

getType()

            Class userClass = Class.forName("com.test.model.User");
            Field nameField = userClass.getDeclaredField("name");
            System.out.println(nameField.getType());

image-20220522170522272

3、获取属性值

get(object)

            Class userClass = Class.forName("com.test.model.User");
            Field sexField = userClass.getDeclaredField("sex");
            User user = new User();
            System.out.println(sexField.get(user));

image-20220522170539221

注:通过反射不能直接获取私有属性的值,但是可以通过修改访问入口来获取私有属性的值。

设置允许访问私有属性:

field.setAccessible(true);

例如:

            Class userClass = Class.forName("com.test.model.User");

            Field nameField = userClass.getDeclaredField("name");
            nameField.setAccessible(true);
            User user = new User();
            System.out.println(nameField.get(user));

image-20220522170600725

4、设置属性值

set(object, "属性值")

            Class userClass = Class.forName("com.test.model.User");

            Field nameField = userClass.getDeclaredField("name");
            nameField.setAccessible(true);
            User user = new User();
            nameField.set(user, "吖宝");
            System.out.println(nameField.get(user));

image-20220522170621717

6、构造方法

1、获取所有公有构造方法

getConstructors()

            Class userClass = Class.forName("com.test.model.User");
            Constructor[] constructors = userClass.getConstructors();
            for (Constructor constructor : constructors){
                System.out.println(constructor);
            }

image-20220522170635749

2、获取与参数类型匹配的构造方法

getConstructor(参数类型)

            Class userClass = Class.forName("com.test.model.User");
            Constructor constructor = userClass.getConstructor(String.class, String.class);
            System.out.println(constructor);

image-20220522170657651

7、成员方法

7.1 获取成员方法

1、获取所有公共方法

getMethods()

            Class userClass = Class.forName("com.test.model.User");
            Method[] methods = userClass.getMethods();
            for (Method method : methods){
                System.out.println(method);
            }

image-20220522170716153

我们发现,打印结果除了自定义的公共方法,还有继承自 Object 类的公共方法。

2、获取某个公共方法

getMethod("方法名", 参数类型)

            Class userClass = Class.forName("com.test.model.User");
            Method method = userClass.getMethod("setName", String.class);
            System.out.println(method);

image-20220522170734003

3、获取所有方法:公有+私有

getDeclaredMethods()

        Class userClass = Class.forName("com.test.model.User");
        Method[] methods = userClass.getDeclaredMethods();
        for (Method method : methods){
            System.out.println(method);
        }

image-20220522170748074

4、获取某个方法:公有或者私有

getDeclaredMethods()

            Class userClass = Class.forName("com.test.model.User");
            Method method = userClass.getDeclaredMethod("run");
            System.out.println(method);

image-20220522170803175

7.2 执行成员方法

invoke(object, "方法参数")

            Class userClass = Class.forName("com.test.model.User");
            Method method = userClass.getDeclaredMethod("eat");
            User user = new User();
            method.invoke(user);

image-20220522170817528

注:通过反射不能直接执行私有成员方法,但是可以设置允许访问。

设置允许执行私有方法:

method.setAccessible(true);

8、注解

1、判断类上或者方法上是否包含某个注解

isAnnotationPresent(注解名.class)

例如:

Class userClass = Class.forName("com.test.model.User");
if(userClass.isAnnotationPresent(Component.class)){
    Component annotation = (Component)userClass.getAnnotation(Component.class);
    String value = annotation.value();
    System.out.println(value);
};

2、获取注解

getAnnotation(注解名.class)

例如:

Class userClass = Class.forName("com.test.model.User");
// 获取类上的注解
Annotation annotation1 = userClass.getAnnotation(Component.class);
Method method = userClass.getMethod("eat");
// 获取方法上的某个注解
Annotation annotation2 = userClass.getAnnotation(Component.class);

9、创建类的实例

1、通过Class实例化对象

Class.newInstance()

Class userClass = Class.forName("com.test.model.User");
User user = (User)userClass.newInstance();
System.out.println("姓名:"+user.getName()+" 性别:"+user.getSex());

2、通过构造方法实例化对象

constructor.newInstance(参数值)

Class userClass = Class.forName("com.test.model.User");
Constructor constructor = userClass.getConstructor(String.class, String.class);
User user = (User)constructor.newInstance\("李诗情", "女"\);
System.out.println("姓名:"+user.getName()+" 性别:"+user.getSex());

10、反射案例

设计一个对象的工厂类

原始:

public class ObjectFactory {
    
    public static User getUser() {
        
        User user = null;
        try {
            Class userClass = Class.forName("com.xxl.model.User");
            user = (User) userClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return user;
    }

    public static UserService getUserService() {
        UserService userService = null;
        try {
            Class userClass = Class.forName("com.xxl.service.impl.UserServiceImpl");
            userService = (UserService) userClass.newInstance();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
        return userService;
    }
}

该代码存在两个问题:

1.代码存在大量冗余。如果有一万个类,你是不是要写一万个静态方法?

2.代码耦合度太高。如果这些类存放的包路径发生改变,你再用 forName()获取 Class 对象是不是就会有问题?你还要一个个手动改代码,然后再编译、打包、部署。。你不觉得麻烦吗?

优化后:

  • object.properties

    user=com.xxl.model.User
    userService=com.xxl.service.impl.UserServiceImpl
    
  • ObjectFactory

    public class ObjectFactory {
    
        private static Properties objectProperty = new Properties();
    
        // 静态方法在类初始化时执行,且只执行一次
        static{
            try {
                InputStream inputStream = ObjectFactory.class.getResourceAsStream("/object.properties");
                objectProperty.load(inputStream);
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
        public static Object getObject(String key){
            Object object = null;
            try {
                Class objectClass = Class.forName(objectProperty.getProperty(key));
                object = objectClass.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return object;
        }
    }
    
    

    测试:

    @Test
      void testObject() {
          User user = (User)ObjectFactory.getObject("user");
          UserService userService = (UserService)ObjectFactory.getObject("userService");
          System.out.println(user);
          System.out.println(userService);
      }
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值