1. 反射
附上之前的博客
1.1 反射概述
什么是反射?
- 程序运行时,对于任意一个类,动态地获取其所有属性和方法;对于任意一个对象,动态地访问其所有属性或方法
- 这种动态获取信息、动态调用方法的机制,在Java中被叫做反射
为什么是反射?(自己的理解)
- 反射的两大支柱之一,Class类,实例化后的Class对象对应一个真实的Java类,包含了该类的属性和方法信息。
- 编译Java类生成.class文件,保存的是该类对应的class对象
- class对象就是一个类/对象照镜子的结果,通过照镜子,可以知道有哪些属性、方法,实现了哪些接口等
- 可以说,反射就是利用Class对象和反射机制,动态获取信息、动态调用方法,就像是通过照镜子去了解类/对象,从而让其执行某些动作
反射的作用
- 通过反射,可以动态判断一个对象所属的类:
obj.getClass()
- 通过反射,可以动态实例化对象:获取class对象,或类的contructor对象 —> 通过
newInstance()
实例化对象 - 通过反射,可以动态获取类的信息,以支持一些扩展功能,如类浏览器:获取class对象 —> 然后获取其属性、方法、构造函数
- 通过反射,可以动态调用方法、访问或修改对象属性:获取class对象 —> 实例化对象 —> 获取对应的method对象、field对象 —> 通过invoke动态调用对象的方法,通过
get/set
动态访问或修改属性值
反射的优点
- 灵活性: 通过反射,可以动态创建对象并编译,最大限度地发挥了Java的灵活性
例如,J2EE开发中,新版本的发布不要求用户卸载旧版本。而是通过反射机制,运行时动态创建对象并编译代码 - 可扩展性: 通过
Class.forName()
获取外部的用户自定义类的class对象,然后通过newInstance()
实例化对象 —— (不是很理解) 😂
反射的缺点
- 开销大: 反射涉及动态类型的解析,JVM无法对代码进行优化,反射操作的效率要比非反射操作的效率低得多
- 安全限制: 反射机制只能在无安全限制的环境中使用,在像Applet有安全限制的环境中,无法使用 —— (不是很理解) 😂
- 内部信息暴露: 通过反射可以访问私有成员,使得原本的访问控制权限成为摆设,对象的内部信息暴露
反射的应用
- 类浏览器和IDE: 类浏览器和IDE需要展示一个类的所有属性和方法,通过反射可以做到
- 调试器: 调试器需要检查一个类的私有成员 —— (不是很理解) 😂
- 测试工具: 测试工具需要通过反射调用类中的API,以保证测试的覆盖率
- IOC机制: spring 的IOC底层使用反射技术,构建用户所需的bean实例
- JDBC驱动的加载: 通过类名限定,如
Class.forName('com.mysql.jdbc.Driver')
,指定对应的JDBC驱动,从而创建对应的驱动对象
1.2 反射的使用
-
先介绍反射的两大支柱:
(1)Class类:一个真正的Java类在JVM中的映射,是反射的基础,只有获取类的class对象,才能执行后续的反射操作
(2)java.lang.reflect
包:三个重要类,Field对应成员变量,Method对应成员方法,Constructor对应构造方法。
(3)通过class对象获取上述的三种对象,才能实现访问对象属性、调用方法、实例化对象等 -
反射的使用,在博客Java反射整理-2021.01.02中,有具体的总结
class对象
- class对象的获取
(1)通过对象获取:obj.getClass()
(2)通过类名获取: ClassName.class
(3) 通过完全限定名获取:Class.forName(“package.path.class_name”),如Class.forName('com.mysql.jdbc.Driver')
- 动态实例化对象:class.newInstance()实例化对象,只能调用无参构造函数
Field
getFields()
获取所有的公有属性,getDeclaredFields()
获取所有的属性getField(String name)
获取指定name的公有属性,getDeclaredField(String name)
获取指定name的属性- 获取Field的基本信息:filed对象的
getModifiers()
获取属性的权限修饰符,getName()
获取属性名等 - 访问或修改属性值:
(1)属性为基本数据类型,使用filed对象的getXXX(Object obj)
获取对象中该属性的值;
(2)属性为基本数据类型,使用filed对象的setXXX(Object obj, T value)
更新对象中该属性的值;
(3)属性为引用数据类型,使用filed对象的get(Object obj)
获取对象中该属性的值,T与基本数据类型一致
(4)属性为引用数据类型,使用filed对象的set(Object obj, Object value value)
更新对象中该属性的值;
Method
getMethods()
获取所有的公有成员方法,getDeclaredMethods()
获取所有的成员方法getMethod(String name, Class<?>... parameterTypes)
获取指定方法名和参数类型的公有成员方法,getDeclaredMethod(String name, Class<?>... parameterTypes)
获取指定方法名和参数类型的成员方法- 获取Method的基本信息:使用method对象的
getName()
获取方法名,getReturnType()
获取返回值类型等 - method的调用:通过method对象的
invoke(Object obj, Object... args)
,调用对象obj中的该方法,同时由args传入方法所需的参数
Constructor
getConstructors()
获取所有的公有构造函数,getDeclaredConstructors()
获取所有的构造函数getConstructor(Class<?>... parameterTypes)
获取对应的公有构造函数,getDeclaredConstructor(Class<?>... parameterTypes)
获取对应的构造函数- 获取Constructor的基本信息:具体查看源码,不赘述
- 实例化对象:实际都是调用Constructor对象的
newInstance(Object ... initargs)
方法,只是无参构造函数可以不用传入任何参数,看起来像是还有一个newInstance()
方法
注意: Field、Method、Constructor等,若是private修饰,需要通过setAccessible(true)
去关闭权限控制,使其可以被访问。
1.3 通过反射修改String
- 通过反射修改String对象的值,实际是修改String对象中value属性的值
- 它是一个private属性,需要设置
setAccessible(true)
- 然后通过
set()
方法,完成对象value属性的更新
2. Java的异常
- 附上以前写的渣渣博客:java异常与处理
2.1 java异常体系
- 如图所示,
Throwable
是Java所有可抛出异常的父类,分为Error和Exception - 工作中所说的异常,一般指Exception
Error
- JVM运行时产生并抛出的异常,是程序无法控制和处理的,会使得JVM处于不正常、不可恢复的状态
- JVM出现Error时,一般都会退出运行(终止线程的运行)
Exception
- 表示程序运行时不期望发生的异常情况,是可以被程序处理的异常
- Exception分为运行时异常(RuntimeException)和非运行时异常:
- 运行时异常通过throw抛出时,无需通过throws显式声明
- 非运行时异常也是检查型(checked)异常,必须通过try-catch或者throw/throws语句显式处理
检查型异常和非检查型异常
- 针对
javac
是否强制要求显式处理异常而言 - 非检查型异常:RuntimeException和Error,javac在编译时不会强制要求对其进行处理
- 一般是代码逻辑问题,处理不是最好的方法,而是需要修复代码
- 检查型异常:除了RuntimeException的其他Exception,javac在编译时会强制要求对其进行处理
- 一般与程序的运行环境有关,需要程序时刻为这些可以预见的异常准备着(提前想好处理办法)
Error和Exception的区别与联系
- 二者都是Throwable的子类,只有Throwble类型的实例才可以被throw或catch(可以抛出并捕获的异常)
- 是Java平台对异常类型的两种分类:
- Error是非正常情况,JVM出现的错误,不便于、也不需要catch;
- Exception是程序运行中可能出现的异常情况,需要被处理或通过修复程序bug消除异常
2.2 异常的处理
异常处理的关键字
- try:包裹一段可能抛出异常的代码,如果代码运行时抛出异常,则可以被检测到
- catch:与try一起使用,用于捕获try语句块中抛出的、指定类型的异常,然后对异常进行处理
- finally:无论是否发生异常,最终必须执行的代码块,可以用于资源回收
- throw:用于在代码中抛出一个异常,是一个动词
- throws:用在方法签名中,以声明该方法可能抛出的异常
- 注意:try语句不能单独使用,必须与catch或finally一起使用;可以只使用catch或finally,也可以二者一起使用
异常的处理
- 使用try-catch-finally语句块,对异常进行处理
- 在出现/捕获异常时不处理,而是通过throw/throws语句抛出,交由上层处理
用户自定义异常
- 继承Throwable或Exception类,自定义的异常最好以Exception为后缀,表示这是一个异常类
- 自定义检查型异常,继承Exception类;自定义运行时异常,继承RuntimeException 类
异常信息
- 获取异常信息的方法:
getMessage()
、getLocalizedMessage()
、toString()
- 打印异常堆栈信息:
printStackTrace()
通过标准输出打印异常堆栈信息,正式开发中,一般使用log.error()
或log.warn()
打印异常堆栈信息 - 个人经验: 针对NullPointer这种没有异常信息的异常,最好使用
toString()
获取异常信息
2.3 常见面试问题
throw和throws
- 使用位置: throw位于方法体内, throws位于方法签名中(函数名后、方法提前)
- 意义: throw强调动作,表示抛出异常;throws是一种倾向,表示可能(实际不一定)抛出异常
- 作用对象: throw作用于一个异常对象,表示抛出某个异常实例;throws作用于一个或多个异常类,表示抛出的异常类型
throw和throws的一些使用注意事项
- 如果一个方法中通过throw抛出了异常,应该使用throws在方法上声明抛出的异常类型
- 个人经验:检查型异常必须声明,非检查型异常不要求
- 如果一个方法通过throws声明了抛出的异常,应该通过try-catch进行异常处理
- 通过throw抛出异常,throw之后的代码将不再执行
- 注意: finally语句中的代码仍会执行
finally、final、finalize
- finally是异常处理中的关键字,表示不管是否发生异常,最终一定会执行的代码
- final用于声明变量、方法或类,表示变量不可变、方法不可被重写、类不可被继承
- finalize:Object类中的方法,JVM垃圾回收时被调用
包含finally的代码执行逻辑
-
如下代码,不管是否发生异常,返回结果均为1
String s = "hello"; try { File file = new File("test.txt"); return 0; } catch(Exception e) { e.printStackTrace(); } finally { return 1; }
-
finally中的代码块,会在方法返回前执行,即使之前的代码已经有了返回语句,终将被finally中的返回语句覆盖;
-
例外: 若是通过
System.ext(0)
退出代码执行,则finally中的代码不会被执行
JDK7异常新特性
-
multi-catch特性: 允许在一个catch语句中,声明捕获多种类型的异常(异常类型应该不同,且不能存在异常的父类型)
catch(IOException | SQLException | NullPointerException ex){ logger.error(ex); throw new MyException(ex.getMessage()); }
-
ARM特性: 自动化资源管理,又称try-with-resource。可以在try-catch语句执行完后,自动回收try中创建资源,避免通过finally回收资源时的遗漏
try (MyResource mr = new MyResource()) { System.out.println("MyResource created in try-with-resources"); } catch (Exception e) { e.printStackTrace(); }
-
ARM,要求资源必须实现了
java.lang.AutoCloseable
或其子类java.io.Closeable
接口
异常调用链
- 面试中尚未遇到
- 通过
e2.initCase(e1)
设置异常原因,或new Exception("exception a", e1)
将上一个异常e1包裹到新的异常e2中
异常处理心得
- 异常处理要优雅: 既要避免给用户带来不友好的体验,又要能帮助程序员定位异常位置与原因 —— 自己也在苦苦探索中
- 资源回收: 要么使用ARM,要么使用finally,避免资源泄漏
- 使用特定异常: 不要使用异常的父类,而是使用异常本身,可以有效地帮助定位问题
- 程序要尽早抛出异常,交由上层调用者进行处理
- 运行时异常:避免出现运行时异常,例如空指针异常,使用前需要检查对象是否为空
3. 其他
3.1 Java与C++的区别
- 面向对象:Java时纯粹的面向对象语言,所有类都继承
java.lang.Object
;C++为了兼容C语言,既支持面向对象,又支持面向过程 - 跨平台:Java通过JVM实现跨平台特性,C++依赖于特性的平台
- 继承:Java不支持多重继承,需要通过实现多个接口达到相同目的;C++支持多重继承
- 指针:Java没有指针,其引用可以理解为安全的指针;C++具有和C语言一样的指针
- 操作符重载:Java不支持操作符重载,string对象的相加操作是语言内置操作,而非操作符重载;C++支持操作符重载
- 垃圾回收:Java支持自动垃圾回收;C++中,需要手动回收
- goto关键字:goto是Java的保留字,但不能使用goto;C++中,可以使用goto实现代码跳转
- 这样的问题不会单独提问,可能会结合某些场景提问
- 即使单独提问,能回答几个就行,不要求都能回答
- 所以,结合具体场景,灵活记忆即可
3.2 JRE和JDK
JRE
- Java Runtime Environment:Java运行环境,为Java代码的运行提供所需环境
- JRE是一个JVM程序,包含了JVM的标准实现和Java的基本类库
JDK
- Java Development Kit:Java开发工具包,提供了Java开发以及运行环境
- JDK是Java的核心,集成了JRE及一些其他的工具,如Java代码编译工具javac