Java反射(Class、反射实例化、反射与单例、获取类结构)附带相关面试题

简介: 1.了解反射,2.Class类的三种实例化方法,3.反射机制与对象实例化,4.反射与单例设计模式,5.通过反射获取类结构的信息


1.了解反射

什么是反射,反射有什么作用

1.在Java中,反射是一种机制,允许程序在运行时动态地获取、使用和修改类的信息。通过反射,可以在编译时不知道类的具体信息的情况下,操作和查看类的属性、方法和构造函数等。

2.反射有以下几个主要的作用:

    1. 动态加载类:使用反射可以在运行时动态地加载需要使用的类,而不需要在编译时将类写死在代码中。这样可以实现更加灵活的代码结构和功能。
    2. 获取类的信息:通过反射,可以获取类的名称、父类、接口、方法、字段等详细信息。这对于编写通用框架、调试工具和JavaBean的序列化等场景非常有用。
    3. 创建对象和执行方法:使用反射可以动态地创建对象,即使在编译时无法确定具体的类。同时,还可以在运行时调用任意对象的方法,甚至是私有方法。
    4. 修改私有字段:反射允许程序访问和修改类的私有字段的值。这在某些特定的应用场景中可能是必要的,但需要小心使用,遵循权限和安全性的原则。

    需要注意的是,反射是一种强大而复杂的机制,在普通的业务代码中并不常用。滥用反射可能会导致性能下降,代码可读性降低,并增加出错的可能性。因此,在使用反射时,需要谨慎权衡利弊,并确保了解其使用方式和限制。

    面试题:什么是反射?

      1. 所谓反射,是java在运行时进行自我观察的能力,通过class、constructor、field、method四个方法获取一个类的各个组成部分。
      2. 在Java运行时环境中,对任意一个类,可以知道类有哪些属性和方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于反射机制。

      2.Class类的三种实例化方法

      1.通过实例化对象调用getclass()方法实现

      2.通过直接调用类名的.Class

      3.通过调用Class.forname("包+类名") throws ClassNoFoundException

      实例化案例:

      package Example1701;
      class Member{
          private String name;
          private int age;
          public void setName(String name) {
              this.name = name;
          }
          public void setAge(int age) {
              this.age = age;
          }
          public String getName() {
              return name;
          }
          public int getAge() {
              return age;
          }
      }
      public class javaDemo {
          public static void main(String[] args)throws Exception {
              Member mem = new Member();
      //        通过实例化getClass()方法得到类
              Class<?> claszz1= mem.getClass();
              System.out.println(claszz1);
      //        通过直接获取类名实例化Class
              Class<?> claszz2 = Member.class;
              System.out.println(claszz2);
      //        通过调用Class里的forname方法实现实例化
              Class<?> claszz3 = Class.forName("Example1701.Member");
              System.out.println(claszz3);
          }
      }

      image.gif

      image.gif编辑


      3.反射机制与对象实例化

      通过反射机制进行对象实例化就能替代new的关键词的使用

      案例代码:实例化对象

      package Example1702;
      class Member{
          Member(){
              System.out.println("自动调用构造函数");
          }
          @Override
          public String toString() {
              return "实现Member对象的创建";
          }
      }
      public class javaDemo {
          public static void main(String[] args)throws Exception {
              Class<?> claszz = Class.forName("Example1702.Member");
      //        创建实例化对象并用Object类进行接收
              Object obj = claszz.getDeclaredConstructor().newInstance();
              System.out.println(obj);
      //        对比区别
              Object obj2 = claszz.newInstance();
              System.out.println(obj2);
          }
      }

      image.gif

      image.gif编辑

      问: 可以发现Object接收两个对象输出后都是一样的,那么

      Object obj = claszz.getDeclaredConstructor().newInstance();与

      Object obj2 = claszz.newInstance();有什么区别吗

      答:

      在Java 9及之前,我们可以使用Class.newInstance()方法来创建一个类的实例对象。这个方法是通过调用类的默认构造函数来创建对象的。例如,claszz.newInstance()会调用Member类的默认构造函数创建对象。

      然而,从Java 9开始,Class.newInstance()方法被标记为过时了,因为它在处理异常和对私有构造函数的访问上存在一些限制。取而代之的是,推荐使用getDeclaredConstructor().newInstance()方法来创建对象。这个方法更为灵活,可以处理带参数的构造函数并且可以处理私有构造函数。

      所以,Object obj = claszz.getDeclaredConstructor().newInstance()Object obj2 = claszz.newInstance()的区别在于:

        1. 创建实例对象的方式不同:claszz.getDeclaredConstructor().newInstance()可以处理带参数的构造函数,而claszz.newInstance()只能调用无参构造函数。
        2. 访问权限不同:claszz.getDeclaredConstructor().newInstance()可以处理私有构造函数,而claszz.newInstance()无法处理私有构造函数。
        3. 兼容性不同:claszz.getDeclaredConstructor().newInstance()是在Java 9及之后引入的,而claszz.newInstance()是在Java 8及之前推荐使用的方法。

        因此,在现代的Java版本中,建议使用getDeclaredConstructor().newInstance()方法来创建类的实例对象,它更加通用和灵活。


        4.反射与单例设计模式

        单例设计模式都不陌生,在以前的文章中有提到过,其实现的方法就是通过私有化构造方法实现外部无法实例化对象,并且在类的内部就定义一个唯一的对象,最后通过函数调用的形式将对象输出出去,并且设计模式有两种一种饿汉模式,一种懒汉模式,分别是饿汉:一开始就定义一个唯一对象,懒汉模式:当需要用到的时候才进行创建对象。

        那么懒汉模式下就有可能出现问题,什么问题呢?关于多线程问题,懒汉是通过if判断是否对象为空,但是多线程可能出现并发问题,大家都同时执行了if判断语句发现对象为空则大家都一起创建一个对象,造成对象不唯一,不符合单例设计模式

        那么如何解决呢?通过反射就能实现多线程下单例设计模式的唯一性

        案例代码:

        package Example1703;
        class Only {
        //    实现单例化设计模式
            private Only(){
            }
            private static volatile Only onlyOne = null;
        //    创建对象或者获取对象
            public static  Only getOnly(){
                if (onlyOne == null){
                    synchronized (Only.class){
                        if (onlyOne == null){
                            try {
        //                        通过反射创建对象
                                onlyOne = Only.class.getDeclaredConstructor().newInstance();
                            }catch (Exception e){
                                e.printStackTrace();
                            }
                            return onlyOne;
                        }
                    }
                }
                return onlyOne;
            }
            public void print(){
                System.out.println("Hello");
            }
        }
        public class javaDemo {
            public static void main(String[] args) {
                //        创建多线程
                for (int i = 0;i<3;i++){
                    new Thread(()->{
                        Only.getOnly().print();
                        System.out.println(Thread.currentThread().getName());
                    },i+"的线程").start();
                }
            }
        }

        image.gif

        image.gif编辑

        问1:synchronized (Only.class)的作用,为什么要写Only.class而不是this?

        synchronized (Only.class)使用类级别的锁来实现同步。

        在Java中,每个类都有一个对应的Class对象,可以通过类名后面加上.class来获取该类的Class对象。而this关键字代表当前实例对象,它是指向当前对象的引用。

        在单例模式中,我们希望通过synchronized来保证只有一个线程能够创建实例对象。使用synchronized (Only.class),即使用类级别的锁,意味着多个线程在访问这段代码时会竞争同一个锁,即类级别的锁。

        而如果使用synchronized (this),则表示使用实例级别的锁。在这个例子中,我们不希望通过实例级别的锁来控制多个线程对实例的访问,因为还没有创建实例对象,所以也不存在实例对象来获得锁。

        因此,为了确保在多线程环境下只创建一个实例对象,需要使用类级别的锁,即synchronized (Only.class),而不是实例级别的锁。

        问2:为什么要进行两次判断是否为空?

        答: 问2:进行两次判断是否为空的目的是为了提高代码执行的效率和性能。在双重检查锁定机制中,第一次判断onlyOne是否为空是为了避免重复进行同步块的加锁和解锁操作,从而提高了代码的执行效率。如果只有一次判断是否为空,那么每次调用getOnly()方法时都会进入同步块,性能开销会增加。

         同时,第二次判断是为了在多个线程同时通过了第一次判断后,只有第一个获得锁的线程才会进入同步块创建实例。其他线程在获取到锁后,再次判断onlyOne是否为空,如果已经不为空,就直接返回已经创建好的实例,避免了重复创建对象。

        如果只有一次判断是否为空,那么即使已经有线程创建了实例,其他线程也会通过第一次判断,进入同步块再次创建实例,这样会造成多个实例的创建,不符合单例模式的要求。

        因此,为了确保只有一个线程创建实例,并提高代码的执行效率和性能,需要进行两次判断是否为空。


        5.通过反射获取类结构的信息

        常用通过反射获取类结构的方法有这几个

        方法名 作用
        getName() 获取类的完整名称(包括包路径)
        getPackage() 获取类所在的包信息
        getSuperclass() 获取类的父类
        getInterfaces() 获取类实现的接口列表

        案例代码:

        package Example1704;
        interface Interface1 {}
        interface Interface2 {}
        abstract class Father {}
        class Test extends Father implements Interface1, Interface2 {
            private String name;
            private int age;
            Test(String name, int age) {
                this.age = age;
                this.name = name;
            }
            public void print() {
                System.out.println("输出信息Test类");
            }
        }
        public class javaDemo {
            public static void main(String[] args) {
        //        获取指定类的class对象
                Class<?> clazz = Test.class;
        //        获取指定类的包的名称
                Package pag = clazz.getPackage();
                System.out.println(pag.getName());
        //        获取类的父类的信息
                Class<?> superClass = clazz.getSuperclass();
                System.out.println(superClass.getName());
        //        获取类的接口的信息
                Class<?>[] interfaces = clazz.getInterfaces();
                for (Class<?> temp : interfaces) {
                    System.out.println(temp.getName());
                }
            }
        }

        image.gif

        image.gif编辑


        目录
        相关文章
        |
        4天前
        |
        安全 Java API
        【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
        String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
        【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
        |
        10天前
        |
        Java 程序员 编译器
        Java的反射技术reflect
        Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
        |
        22天前
        |
        Java
        Java应用结构规范问题之在UnitConvertUtils工具类将千米转换为米的问题如何解决
        Java应用结构规范问题之在UnitConvertUtils工具类将千米转换为米的问题如何解决
        |
        22天前
        |
        Java 应用服务中间件 HSF
        Java应用结构规范问题之配置Logback以仅记录错误级别的日志到一个滚动文件中的问题如何解决
        Java应用结构规范问题之配置Logback以仅记录错误级别的日志到一个滚动文件中的问题如何解决
        |
        22天前
        |
        Java 应用服务中间件 HSF
        Java应用结构规范问题之配置Logback以在控制台输出日志的问题如何解决
        Java应用结构规范问题之配置Logback以在控制台输出日志的问题如何解决
        |
        22天前
        |
        Java 应用服务中间件 HSF
        Java应用结构规范问题之AllLoggers接口获取异常日志的Logger实例的问题如何解决
        Java应用结构规范问题之AllLoggers接口获取异常日志的Logger实例的问题如何解决
        |
        23天前
        |
        Java 应用服务中间件 HSF
        Java应用结构规范问题之dal层中的mapper数据源类型进行组织的问题如何解决
        Java应用结构规范问题之dal层中的mapper数据源类型进行组织的问题如何解决
        |
        17天前
        |
        存储 开发者 C#
        WPF与邮件发送:教你如何在Windows Presentation Foundation应用中无缝集成电子邮件功能——从界面设计到代码实现,全面解析邮件发送的每一个细节密武器!
        【8月更文挑战第31天】本文探讨了如何在Windows Presentation Foundation(WPF)应用中集成电子邮件发送功能,详细介绍了从创建WPF项目到设计用户界面的全过程,并通过具体示例代码展示了如何使用`System.Net.Mail`命名空间中的`SmtpClient`和`MailMessage`类来实现邮件发送逻辑。文章还强调了安全性和错误处理的重要性,提供了实用的异常捕获代码片段,旨在帮助WPF开发者更好地掌握邮件发送技术,提升应用程序的功能性与用户体验。
        20 0
        |
        28天前
        |
        存储 Java
        【IO面试题 四】、介绍一下Java的序列化与反序列化
        Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
        |
        28天前
        |
        XML 存储 JSON
        【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
        除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。