Java面试题下
异常
Exception和Error有什么区别
所有的异常都有一个共同的祖先Throwable类。有两者子类:
- Exception:程序可以本身处理的异常,可通过catch来捕获。
Exception
又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。 - Error:属于程序无法处理的错误,不建议通过
catch
捕获。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
受检异常和不受检异常的区别
受检异常在编译过程如果没有被catch就没办法通过编译,除了RunTimeException及其子类是非受检异常,其他都是受检异常
非受检异常:
NullPointerException
(空指针错误)IllegalArgumentException
(参数错误比如方法入参类型错误)NumberFormatException
(字符串转换为数字格式错误,IllegalArgumentException
的子类)ArrayIndexOutOfBoundsException
(数组越界错误)ClassCastException
(类型转换错误)ArithmeticException
(算术错误)SecurityException
(安全错误比如权限不够)UnsupportedOperationException
(不支持的操作错误比如重复创建同一用户)
Throwable类常用方法有哪些
- String getMessage: 返回异常发生时的简述
- toString返回异常发生时的详细信息
- getLocalizedMessage:返回异常对象的本地化信息
- printStackTrace:在控制台上打印Throwable对象封装的异常信息
try-catch-finally怎么使用?
- try用于捕获异常,后可接受0个或者多个catch块,没有catch块必须接上finally
- catach用于处理捕获的异常
- finally:无论是否捕获异常或者处理异常,finally块里的语句都会执行。当在
try
块或catch
块中遇到return
语句时,finally
语句块将在方法返回之前被执行。不要再finally块中执行return
finally中的代码一定会执行吗?
- fuinally块之前,虚拟机被终止运行了的话
- 程序所在的线程死亡
- 关闭CPU
如何使用try-with-resource代替try-catch-finally
try-catch
//读取文本文件的内容
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
try-with-resource
//读取文本文件的内容
Scanner scanner = null;
try {
scanner = new Scanner(new File("D://read.txt"));
while (scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
if (scanner != null) {
scanner.close();
}
}
异常使用有哪些需要注意的点
- 不要把异常定义把异常定义为静态常量
- 抛出的异常信息要有意义
- 建议抛出具体的异常而不是父类
- 避免重复记录日志
泛型
什么是泛型?有什么用?
Java是JDK5引入的一个新特性,可以通过泛型参数指定传入的对象类型,并且原生的List返回类型是Object,需要手动转换类型,使用泛型后编译器自动转换
泛型使用的方式
泛型类
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
//实例化
Generic<Integer> genericInteger = new Generic<Integer>(123456);
泛型接口
public interface Generator<T> {
public T method();
}
//实现泛型接口,不指定类型
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}
//指定类型
class GeneratorImpl 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();
}
//实现
// 创建不同类型数组:Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray );
printArray( stringArray );
项目中哪里用到了泛型
- 自定义接口通用返回结果
CommonResult<T>
通过参数T
可根据具体的返回类型动态指定结果的数据类型 - 定义
Excel
处理类ExcelUtil<T>
用于动态指定Excel
导出的数据类型 - 构建集合工具类(参考
Collections
中的sort
,binarySearch
方法)。
反射
什么是反射?
它赋予了我们在运行时分析类以及执行类中方法的能力,可以通过反射获取任意一个类的所有属性和方法并且调用它们
反射的优缺点
反射更加灵活,但是增加了安全问题,比如无视泛型参数的安全检查,反射的性能较差
反射的应用场景
框架,以及框架中的动态代理就是反射还有注解
反射获取Class对象的四种方式
-
知道具体的类可以使用
Class alunbarClass = TargetObject.class;
-
通过对象实例
TargetObject o = new TargetObject(); Class alunbarClass2 = o.getClass();
-
通过类加载器
//传入类路径 TargetObject o = new TargetObject(); Class alunbarClass2 = o.getClass();
-
通过Class.forName()
//传入类的全路径 Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
反射的具体实践
反射操作类
package cn.javaguide;
public class TargetObject {
private String value;
public TargetObject() {
value = "JavaGuide";
}
public void publicMethod(String s) {
System.out.println("I love " + s);
}
private void privateMethod() {
System.out.println("value is " + value);
}
}
使用反射得到方法以及参数
package cn.javaguide;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException {
/**
* 获取 TargetObject 类的 Class 对象并且创建 TargetObject 类实例
*/
Class<?> targetClass = Class.forName("cn.javaguide.TargetObject");
TargetObject targetObject = (TargetObject) targetClass.newInstance();
/**
* 获取 TargetObject 类中定义的所有方法
*/
Method[] methods = targetClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
/**
* 获取指定方法并调用
*/
Method publicMethod = targetClass.getDeclaredMethod("publicMethod",
String.class);
publicMethod.invoke(targetObject, "JavaGuide");
/**
* 获取指定参数并对参数进行修改
*/
Field field = targetClass.getDeclaredField("value");
//为了对类中的参数进行修改我们取消安全检查
field.setAccessible(true);
field.set(targetObject, "JavaGuide");
/**
* 调用 private 方法
*/
Method privateMethod = targetClass.getDeclaredMethod("privateMethod");
//为了调用private方法我们取消安全检查
privateMethod.setAccessible(true);
privateMethod.invoke(targetObject);
}
}
注解
什么是注解
是Java5引进的新特性,本质是继承了Annotation的特殊接口
注解的解析方法
- 编译期直接扫描:编译器在编译 Java 代码的时候扫描对应的注解并处理,比如某个方法使用
@Override
注解,编译器在编译的时候就会检测当前的方法是否重写了父类对应的方法。 - 运行期通过反射处理:像框架中自带的注解(比如 Spring 框架的
@Value
、@Component
)都是通过反射来进行处理的。
SPI
什么是SPI
专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口
SPI和API的区别
SPI的优缺点
优点:
- 可以提高接口设计的灵活性
缺点:
- 需要遍历加载所有实现类做不到按需加载,效率低
- 多个ServiceLoader同时Loader,有并发问题
序列化和反序列化
- 序列化:将数据结构或对象转换成二进制字节流的过程
- 反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程
序列化和反序列化的场景:
- 网络传输
- 存储到文件,数据库,redis,内存前
序列化的目的就是通过网络传输对象或者说将对象存在数据库,内存,文件系统中
序列化协议属于TCP/IPV4模型哪一层
属于应用层
字段不想序列化怎么办
用transient关键字修饰,阻止变量实例化,当反序列化时,变量值不会被持久化和恢复
关于transient有几点注意:
- 只能修饰变量
- 修饰的变量在反序列化后会被置成默认值,int就是0
- static修饰的变量不会序列化
常见的序列化协议
JDK自带的协议(不支持跨语言调用,不会用,效率低还有安全问题),Hessian、Kryo、Protobuf、ProtoStuff。像Json,xml这种文本序列化方法虽然可读性好,但是性能差,一般不选择
I/O
IO就是Input/Output,数据输入到计算机内存,输出到外部存储。分为输出流和输入流,根据数据的处理方式分为字节流和字符流
I/O流为什么要分字节流和字符流
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
- 字符流是由Java虚拟机转换得到的,过程比较耗时
- 在不知道编码的情况下,字节流会出现乱码问题
语法糖
什么是语法糖
为了方便程序员开发设计的一种特殊的语法,实现相同的功能,能够用更加简洁的代码实现,阅读性高
Java常见的语法糖
buf、ProtoStuff。像Json,xml这种文本序列化方法虽然可读性好,但是性能差,一般不选择
I/O
IO就是Input/Output,数据输入到计算机内存,输出到外部存储。分为输出流和输入流,根据数据的处理方式分为字节流和字符流
I/O流为什么要分字节流和字符流
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
- 字符流是由Java虚拟机转换得到的,过程比较耗时
- 在不知道编码的情况下,字节流会出现乱码问题
语法糖
什么是语法糖
为了方便程序员开发设计的一种特殊的语法,实现相同的功能,能够用更加简洁的代码实现,阅读性高
Java常见的语法糖
Java 中最常用的语法糖主要有泛型、自动拆装箱、变长参数、枚举、内部类、增强 for 循环、try-with-resources 语法、lambda 表达式等