面试题答案
一键面试可能遇到的异常及处理方式
- ClassNotFoundException
- 原因:当使用反射机制通过类名加载类时,如果指定的类在类路径中不存在,就会抛出此异常。
- 处理:在调用
Class.forName()
方法时,使用try - catch
块捕获该异常,并根据业务需求进行处理,例如记录日志并给用户提供友好的错误提示。
try { Class<?> clazz = Class.forName(className); } catch (ClassNotFoundException e) { // 记录日志 Logger.getLogger(Factory.class.getName()).log(Level.SEVERE, null, e); // 给用户提示 System.out.println("指定的类不存在,请检查类名和类路径。"); }
- InstantiationException
- 原因:当试图实例化一个抽象类或接口,或者该类没有无参构造函数时,会抛出此异常。
- 处理:在实例化对象之前,先检查类是否是具体类且具有无参构造函数。若捕获到此异常,可以在日志中记录相关信息,并根据业务情况决定是否尝试其他创建对象的方式,或终止当前操作。
try { Object obj = clazz.newInstance(); } catch (InstantiationException e) { Logger.getLogger(Factory.class.getName()).log(Level.SEVERE, "无法实例化类,可能是抽象类或接口,或无无参构造函数", e); }
- IllegalAccessException
- 原因:当反射机制试图访问一个类的构造函数、方法或字段,但访问权限不足时,会抛出此异常。例如,尝试访问私有构造函数来创建对象。
- 处理:捕获该异常后,可以通过设置访问权限来绕过访问限制(仅适用于可信任的代码),使用
AccessibleObject.setAccessible(true)
方法。同时,在日志中记录相关信息,表明进行了权限突破操作,并进行安全评估。
try { Constructor<?> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); Object obj = constructor.newInstance(); } catch (IllegalAccessException e) { Logger.getLogger(Factory.class.getName()).log(Level.SEVERE, "访问权限不足,尝试设置可访问性", e); }
性能和代码结构优化
- 性能优化
- 缓存类对象:由于反射获取
Class
对象的操作(如Class.forName()
)相对耗时,可以使用HashMap
等集合类缓存已经加载的Class
对象。这样在需要创建对象时,先从缓存中查找,若存在则直接使用,避免重复的类加载操作。
private static final Map<String, Class<?>> classCache = new HashMap<>(); public static Class<?> getClassFromCache(String className) { Class<?> clazz = classCache.get(className); if (clazz == null) { try { clazz = Class.forName(className); classCache.put(className, clazz); } catch (ClassNotFoundException e) { // 处理异常 } } return clazz; }
- 减少反射操作次数:尽量在对象创建前完成所有必要的反射操作,如获取构造函数、设置访问权限等。可以将这些操作封装到一个方法中,在需要创建对象时直接调用该方法,而不是每次都重复进行反射操作。
- 缓存类对象:由于反射获取
- 代码结构优化
- 封装反射逻辑:将反射创建对象的逻辑封装到一个独立的工具类中,这样可以提高代码的复用性,同时使工厂类的代码更加简洁。例如,创建一个
ReflectionUtil
类,包含获取类对象、实例化对象等方法。
public class ReflectionUtil { public static Object createObject(String className) { try { Class<?> clazz = Class.forName(className); return clazz.newInstance(); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) { // 处理异常 return null; } } }
- 使用配置文件:将需要创建对象的类名等信息存储在配置文件(如
properties
文件)中,这样在需要修改创建对象的类型时,只需修改配置文件,而无需修改代码。在工厂类启动时读取配置文件,并根据配置信息进行对象创建。
// 读取properties文件 Properties properties = new Properties(); try (InputStream inputStream = getClass().getResourceAsStream("/config.properties")) { properties.load(inputStream); } catch (IOException e) { // 处理异常 } String className = properties.getProperty("targetClassName");
- 封装反射逻辑:将反射创建对象的逻辑封装到一个独立的工具类中,这样可以提高代码的复用性,同时使工厂类的代码更加简洁。例如,创建一个