Java
根据网站JavaGuide整理的Java面试题
异常
Exception 和 Error 有什么区别?
在Java中, 所有的异常都有一个共同的祖先类 java.lang.Throwable 类。Throwable类有两个重要的子类:
- Exception:程序本身可以处理的异常,可以通过catch进行捕获,Exception本身又分为Checked Exception 和 Unchecked Exception(不受检查的异常,可以不处理);
- Error: Error属于程序无法处理的错误,例如Java虚拟机运行错误(Virtual MachineError)、虚拟机内存不够(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等,这些异常发生时,Java虚拟机一般会选择线程终止。
Checked Exception 和 UnChecked Exception有什么区别?
-
Checked Exception:即受检查异常,Java代码在编译的过程中,如果受检查异常没有被catch或者throws 关键字处理的话,就没法通过编译;
-
UnChecked Exception: 不受检查的异常,Java在编译过程中,我们即使不处理也可以正常通过编译。
-
RuntimeException 及其子类统称为非受检查异常,常见的有:
- NullPointerException:空指针错误
- IllegalArgumentException:参数错误比如入参类型错误
- NumberFormatException:字符串转换为数字格式错误,IllegalArgumentException 的子类
- ArrayIndexOutOfBoundsException: 数组越界错误
- ArithmeticException:算术错误
- SecurityException: 安全错误比如权限不够
- UnsupportedOperationException:不支持的操作错误比如重复创建同一用户
Throwable 类的常用方法有哪些?
//返回发生异常时的简要描述
String getMessage();
//返回发生异常时的详细信息
String toString();
//返回异常对象的本地化信息,使用Throwable 的子类覆盖这个方法,可以生成本地化信息,如果子类没有覆盖该方法,则该方法返回的信息与getMessage()返回的结果相同
String getLocalizedMessage();
//在控制台上打印 Throwable 对象封装的异常信息
void printStackTrace();
如何使用 try-with-resources 代替 try-catch-finally ?
- 当多个资源需要关闭的时候,使用try-with-resources实现起来比较简单,如果还是使用 try-catch-finally 可能会带来许多问题。
try (Scanner scanner = new Scanner(new File("test.txt"))) {
while(scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
}
异常使用有哪些需要注意的地方?
- 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱,每次手动抛出异常,我们都需要手动new 一个异常对象抛出;
- 抛出的异常信息一定要有意义;
- 建议抛出更加具体的异常比如字符串转换为数字格式错误的时候不应该抛出父类异常;
- 使用日志打印日常之后就不要再抛出异常了,两个不要存在于同一段代码逻辑中。
泛型
泛型及其作用
使用泛型可以增强代码的可读性以及稳定性。编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型,并且原生List 的返回类型时 Object ,需要手动转换类型才能使用,使用泛型后编译器自动转换。
泛型的使用方式
- 泛型类
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}
- 泛型接口
public interface Generator<T> {
public T method();
}
//实现泛型接口,不指定类型
class GenericImpl<T> implements Generator<T> {
@Override
public T method() {
return null;
}
}
//实现泛型接口,指定类型
class GenericImpl<T> implements Generator<String> {
@Override
public String method() {
return "hello";
}
}
- 泛型方法
public static <E> void printArray(E[] inputArray) {
for (E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
项目中使用泛型的场景
-
自定义接口通用返回结果通过 T 就可以根据具体的返回类型动态指定结果的数据类型;
-
定义 Excel 处理类 ExcelUtil 用于动态指定导出的数据类型;
-
构建集合工具类,参考 Collection 中的sort,BinarySearch 方法 。
反射
反射的定义
通过反射可以获取到任意一个类的所有属性和方法,还可以调用这些属性和方法。
反射可以让我们的代码更加灵活、为各种框架提供了开箱即用的功能提供了便利。但是反射再让我们运行时有了分析操作类的能力的同时,也增加了安全问题,比如可以无视泛型参数的安全检查。
应用场景: 因为反射,我们可以轻松地使用各种框架,像spring/springboot、Mybatis 等等框架都大量使用了反射机制。这些框架中大量使用了动态代理,而动态代理的实现也依赖反射, 注解也用到了反射。
//JDk 实现动态代理的示例代码
public class DebugInvocationHandler implements InvocationHandler {
private final Object target;
public DebugInvocationHandler (Object object) {
this.target = target;
}
public Object invoke (Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException{
System.out.println("before method " + method.getName());
Object result = method.invoke(target, args);
System.out.println("after method " + method.getName());
return result;
}
}
注解
注解的解析方法有哪几种
注解只有被解析后才能生效,常见的解析方法有两种:
- 编译器直接扫描:编译器在编译 Java 代码时扫描对应的注解并处理,比如某个方法使用 @Override 注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法;
- 运行期间通过反射处理:像框架中自带的注解(比如Spring 框架的@Value、 @Component)都是通过反射来进行处理的。
IO
序列化与反序列化
- 序列化:将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
Java IO流
- InputStream/Reader:所有输入流的基类,前者是字节输入流,后者是字符输入流
- OututStream/Reader:所有输出流的基类,前者是字节输出流,后者是字符输出流
不管是文件读写还是网络发送接收,信息的最小存储单元都是自己饿,I/O流操作为什么要分字节流操作和字符流操作呢?
- 字符流是由Java虚拟机将字节转换得到的,这个过程比较耗时;
- 如果我们不知道编码类型的话,使用字节流的过程中比较容易出现乱码问题
语法糖
什么是语法糖?
语法糖指的是编程语言为了方便程序员开发程序而设计的一种特殊语法,基于语法糖写出的代码往往更加简洁且容易阅读。
Unsafe 魔法类
unsafe介绍
unsafe是主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源。但是由于Unsafe类使得Java语言拥有了而类似C语言指针一样的操作内存空间的能力,增加了程序发生相关指针问题的风险。
Unsafe提供这些功能的实现需要依赖本地方法,本地方法使用native关键字修饰,Java 代码中知识声明方法头,具体的实现规则交给本地代码。