面试准备之Java反射、代理和异常

简介: 面试准备之Java反射、代理和异常

什么是反射?反射机制的应用场景有哪些?


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


在 Java 环境中运行时,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法。


Java 反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时调用任意一个对象的方法。


反射机制优缺点:

  • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
  • 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。


反射的应用场景:

反射是框架设计的灵魂。在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。


举例:

  • 在使用 JDBC 连接数据库时使用 Class.forName()通过反射加载数据库的驱动程序;
  • Spring 框架也用到很多反射机制,最经典的就是 xml 的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载到内存中; 2)Java 类里面解析 xml 或 properties 里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的 Class 实例; 4)动态配置实例的属性


推荐阅读:


什么是 java 序列化?什么情况下需要序列化?


为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。简单来说就是将 Java 对象转换成字节流的过程。


什么情况下需要序列化:

  • 当你想把内存中的对象状态保存到一个文件中或者数据库中的时候;
  • 当你想用套接字在网络上传送对象的时候;
  • 当你想通过 RMI 传输对象的时候;


序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。


注意事项:

  • 某个类可以被序列化,则其子类也可以被序列化
  • 声明为 static 和 transient 的成员变量,不能被序列化。static 成员变量是描述类级别的属性,transient 表示临时数据
  • 反序列化读取序列化对象的顺序要保持一致


推荐阅读:www.cnblogs.com/yangjian-ja…


Java 序列化中如果有些字段不想进⾏序列化,怎么办?


声明为 static 和 transient 的成员变量,不能被序列化。static 成员变量是描述类级别的属性,transient 表示临时数据。transient 只能修饰变量,不能修饰类和方法。


代理模式分类


代理机制通过代理类创建时间的不同分为了静态代理和动态代理:


  • 静态代理:代理类在编译阶段生成,程序运行前就已经存在,那么这种代理方式被称为静态代理,这种情况下的代理类通常都是我们在 Java 代码中定义的。
  • 动态代理:代理类在程序运行时创建,也就是说,这种情况下,代理类并不是在 Java 代码中定义的,而是在运行时根据我们在 Java 代码中的“提示”动态生成的。


目前,静态代理主要有 AspectJ 静态代理、JDK 静态代理技术 ,而动态代理有 JDK 动态代理、Cglib 动态代理技术,而 Spring AOP 是整合使用了 JDK 动态代理和 Cglib 动态代理两种技术。


动态代理是什么?有哪些应用?


动态代理


当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。


动态代理的应用:Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程,加事务,加权限,加日志。


怎么实现动态代理?


首先必须定义一个接口,还要有一个 InvocationHandler (将实现接口的类的对象传递给它)处理类。再有一个工具类 Proxy(习惯性将其称为代理类,因为调用他的 newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用到 InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。

推荐阅读:代理模式


创建对象的方式


  • 通过 new 关键字。这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象。比如 Object obj = new Object();
  • 通过 Class 类的 newInstance() 方法。这种默认是调用类的无参构造方法创建对象。比如
    Person p2 = (Person) Class.forName("com.ys.test.Person").newInstance();
  • 通过 Constructor 类的 newInstance 方法。这和第二种方法类时,都是通过反射来实现。通过 java.lang.relect.Constructor 类的 newInstance() 方法指定某个构造器来创建对象。
    Person p3 = (Person) Person.class.getConstructors()[0].newInstance();
    实际上第二种方法利用 Class 的 newInstance() 方法创建对象,其内部调用还是 Constructor 的 newInstance() 方法。
  • 利用 Clone 方法。Clone 是 Object 类中的一个方法,通过 对象A.clone() 方法会创建一个内容和对象 A 一模一样的对象 B,clone 克隆,顾名思义就是创建一个一模一样的对象出来。
    Person p4 = (Person) p3.clone();
  • 反序列化。序列化是把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。


深拷贝vs 浅拷贝


浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递(即两份引用指向同一个对象)。关于对象的浅拷贝,必须让类实现 Cloneable 接口,并且覆写 clone 方法 。


深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。


关于对象的深拷贝,创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。


深拷贝的实现方式:

1、让每个引用类型属性内部都重写clone() 方法

2、在类中的 clone 方法中使用 new 关键词创建引用类型属性

3、序列化。注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外。


关于序列化的实现代码:


//深度拷贝
public static Object deepClone(Object o) throws Exception{
    // 序列化
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(o);
    oos.flush();
    // 反序列化
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    return ois.readObject();
}
复制代码


推荐阅读:Java的深拷贝和浅拷贝


Java中的两种异常类型是什么?他们有什么区别?


  • Throwable 是所有异常的根,java.lang.Throwable
  • Error 是错误,java.lang.Error
  • Exception 是异常,java.lang.Exception


Exception 又包含了运行时异常(RuntimeException, 又叫非检查异常)和非运行时异常(又叫检查异常)



  • Error 是程序无法处理了, 比如 OutOfMemoryError 等, 这些异常发生时, java虚拟机一般会终止线程 .
  • 运行时异常都是 RuntimeException 类及其子类,如 NullPointerException、IndexOutOfBoundsException 等, 这些异常是不检查的异常, 是在程序运行的时候可能会发生的, 所以程序可以捕捉, 也可以不捕捉. 这些错误一般是由程序的逻辑错误引起的, 程序应该从逻辑角度去尽量避免.
  • 检查异常是运行时异常以外的异常, 也是 Exception 及其子类, 这些异常从程序的角度来说是必须经过捕捉检查处理的, 否则不能通过编译. 如 IOException、SQLException 等


Java中Exception和Error有什么区别?


Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类错误导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。


Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。


异常处理完成以后,Exception对象会发生什么变化?


某个 Exception 异常被处理后,该对象不再被引用,gc 将其标记,在下一个回收过程中被回收。


throw 和 throws 的区别?



1、Throw用于方法内部,Throws用于方法声明上

2、Throw后跟异常对象,Throws后跟异常类型

3、Throw后只能跟一个异常对象,Throws后可以一次声明多种异常类型


final、finally、finalize 有什么区别?



final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展,final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。


finally 则是 Java 保证重点代码一定要被执行的一种机制。我们可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证 unlock 锁等动作。


finalize 是基础类 java.lang.Object 的一个方法,该方法在Object类中声明: protected void finalize() throws Throwable { }它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。


final学习链接:www.cnblogs.com/dolphin0520…

www.cnblogs.com/ktao/p/8586…

www.cnblogs.com/jpcflyer/p/…


try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?


public class TryStudy {
    public static int test(String str){
        try {
            return str.charAt(0) - '0';
        }catch (Exception e){
            return 1;
        }finally {
            return 2;
        }
    }
    public static int test2(String str){
        try {
            return str.charAt(0) - '0';
        }catch (Exception e){
            return 1;
        }finally {
            System.out.println("finally....");
        }
    }
    public static void main(String[] args) {
        //当finally里有return时,返回finally里return的结果,撤销之前的return语句
        System.out.println(test(null)+","+test("3"));
        System.out.println("****************");
        //当try里无异常,且有返回值时,先执行finally再返回值;当try有异常,catch里有返回,先执行finally在return
        System.out.println(test2(null)+","+test2("3"));
    }
}
//执行结果为:
2,2
****************
finally....
finally....
1,3
复制代码


常见的异常类有哪些?



  • NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
  • SQLException:提供关于数据库访问错误或其他错误信息的异常。
  • IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
  • NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
  • FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
  • IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
  • ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
  • ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。
  • IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
  • ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
  • NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
  • NoSuchMethodException:无法找到某一特定方法时,抛出该异常。
  • SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。
  • UnsupportedOperationException:当不支持请求的操作时,抛出该异常。
  • RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。
目录
相关文章
|
1月前
|
Java
在 Java 中捕获和处理自定义异常的代码示例
本文提供了一个 Java 代码示例,展示了如何捕获和处理自定义异常。通过创建自定义异常类并使用 try-catch 语句,可以更灵活地处理程序中的错误情况。
59 1
|
1月前
|
Java API 调度
如何避免 Java 中的 TimeoutException 异常
在Java中,`TimeoutException`通常发生在执行操作超过预设时间时。要避免此异常,可以优化代码逻辑,减少不必要的等待;合理设置超时时间,确保其足够完成正常操作;使用异步处理或线程池管理任务,提高程序响应性。
62 12
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
1月前
|
Java
在 Java 中,如何自定义`NumberFormatException`异常
在Java中,自定义`NumberFormatException`异常可以通过继承`IllegalArgumentException`类并重写其构造方法来实现。自定义异常类可以添加额外的错误信息或行为,以便更精确地处理特定的数字格式转换错误。
34 1
|
20天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
53 14
|
1月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
25天前
|
Java 编译器 程序员
Java面试高频题:用最优解法算出2乘以8!
本文探讨了面试中一个看似简单的数学问题——如何高效计算2×8。从直接使用乘法、位运算优化、编译器优化、加法实现到大整数场景下的处理,全面解析了不同方法的原理和适用场景,帮助读者深入理解计算效率优化的重要性。
30 6
|
25天前
|
监控 Java
Java基础——反射
本文介绍了Java反射机制的基本概念和使用方法,包括`Class`类的使用、动态加载类、获取方法和成员变量信息、方法反射操作、以及通过反射了解集合泛型的本质。同时,文章还探讨了动态代理的概念及其应用,通过实例展示了如何利用动态代理实现面向切面编程(AOP),例如为方法执行添加性能监控。
|
1月前
|
IDE 前端开发 Java
怎样避免 Java 中的 NoSuchFieldError 异常
在Java中避免NoSuchFieldError异常的关键在于确保类路径下没有不同版本的类文件冲突,避免反射时使用不存在的字段,以及确保所有依赖库版本兼容。编译和运行时使用的类版本应保持一致。
66 7
|
1月前
|
Java 编译器
如何避免在 Java 中出现 NoSuchElementException 异常
在Java中,`NoSuchElementException`通常发生在使用迭代器、枚举或流等遍历集合时,尝试访问不存在的元素。为了避免该异常,可以在访问前检查是否有下一个元素(如使用`hasNext()`方法),或者使用`Optional`类处理可能为空的情况。正确管理集合边界和条件判断是关键。
70 6