IO
1、Java中IO流分为几种?
流按照不同的特点,有很多种划分方式:
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
Java Io流共涉及40多个类,看上去杂乱,其实都存在一定的关联, Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
IO流用到了什么设计模式?
装饰器模式
2、既然有了字节流,为什么还要有字符流?
- 其实字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还比较耗时,并且,如果我们不知道编码类型就很容易出现乱码问题。
- 所以, I/O 流就干脆提供了一个直接操作字符的接口,方便我们平时对字符进行流操作。如果是音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
3、字节流和字符流的区别?
- 操作单元不同:
- 字符流处理的单元为2个字节的Unicode字符,分别操作字符、字符数组或字符串 ;
- 字节流处理单元为1个字节, 操作字节和字节数组。
- 作用对象不同:
- 字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;
- 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。
- 用途不同:
- 如果是音频文件、图片等媒体文件用字节流比较好,如果涉及到字符的话使用字符流比较好。
4、BIO,NIO,AIO的区别?
BIO
同步并阻塞,在服务器中实现的模式为一个连接一个线程。也就是说,客户端有连接请求的时候,服务器就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然这也可以通过线程池机制改善。
一般适用于连接数目小且固定的架构,这种方式对于服务器资源要求比较高,而且并发局限于应用中,是JDK1.4之前的唯一选择,但好在程序直观简单,易理解。
NIO
同步并非阻塞,在服务器中实现的模式为一个请求一个线程,服务器端用一个线程处理多个连接,客户端发送的连接请求会注册到多路复用器上,多路复用器轮询到连接有IO请求就进行处理:
NIO一般适用于连接数目多且连接比较短(轻操作)的架构,并发局限于应用中,编程比较复杂,从JDK1.4开始支持。
AIO
异步并非阻塞,在服务器中实现的模式为一个有效请求一个线程,也就是说,客户端的IO请求都是通过操作系统先完成之后,再通知服务器应用去启动线程进行处理。
AIO一般适用于连接数目多且连接比较长(重操作)的架构,充分调用操作系统参与并发操作,编程比较复杂,从JDK1.7开始支持。
序列化
1、什么是序列化?什么是反序列化?
什么是序列化,序列化就是把Java对象转为二进制流,方便存储和传输。
所以反序列化就是把二进制流恢复成对象。
2、如果有些变量不想序列化,怎么办?
对于不想进行序列化的变量,使用transient关键字修饰。
transient 关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化时,被 transient 修饰的变量值不会被持久化和恢复。transient 只能修饰变量,不能修饰类和方法。
3、说说有几种序列化方式?
Java序列化方式有很多,常见的有三种:
- Java对象流序列化 :Java原生序列化方法即通过Java原生流(
InputStream
和OutputStream
之间的转化)的方式进行转化,一般是对象输出流ObjectOutputStream
和对象输入流ObjectInputStream
。 - Json序列化:这个可能是我们最常用的序列化方式,Json序列化的选择很多,一般会使用jackson包,通过ObjectMapper类来进行一些操作,比如将对象转化为byte数组或者将json串转化为对象。
- ProtoBuff序列化:ProtocolBuffer是一种轻便高效的结构化数据存储格式,ProtoBuff序列化对象可以很大程度上将其压缩,可以大大减少数据传输大小,提高系统性能。
泛型
1、什么是泛型,有什么作用?
JDK5中引入的一个新特性,泛型就是将类型参数化,其在编译时才确定具体的参数。泛型一般有三种使用方式:泛型类、泛型接口、泛型方法。
泛型的好处:
- 类型安全
- 泛型的主要目标是提高 Java 程序的类型安全
- 编译时期就可以检查出因 Java 类型不正确导致的
ClassCastException
(类转换异常) 异常 - 符合越早出错代价越小原则
- 消除强制类型转换
- 泛型的一个附带好处是,使用时直接得到目标类型,消除许多强制类型转换
- 所得即所需,这使得代码更加可读,并且减少了出错机会
- 潜在的性能收益
- 由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改
- 所有工作都在编译器中完成
- 编译器生成的代码跟不使用泛型(和强制类型转换)时所写的代码几乎一致,只是更能确保类型安全而已
2、泛型擦除是什么?
泛型擦除:使用泛型的时候加上的类型参数,编译器在编译的时候去掉类型参数。
泛型是一种语法糖,泛型这种语法糖的基本原理是类型擦除。Java中的泛型基本上都是在编译器这个层次来实现的,也就是说:泛型只存在于编译阶段,而不存在于运行阶段。
编译后的字节码文件不包含泛型类型信息,因为虚拟机没有泛型类型对象,所有对象都属于普通类。例如定义 List<Object>
或 List<String>
,在编译后都会变成 List
。
3、为什么要泛型擦除?
主要是为了向下兼容,因为JDK5之前是没有泛型的,为了让JVM保持向下兼容,就出了泛型擦除这个策略。
注解
1、说一下你对注解的理解?
注解本质上是一个标记,注解可以标记在类上、方法上、属性上等,标记自身也可以设置一些值,有了标记之后,我们就可以在编译或者运行阶段去识别这些标记,然后做一些事情,这就是注解的用处。例如 @Override
标识一个方法是重写方法。
2、什么是元注解?
元注解是自定义注解的注解,例如:
@Target
:约束作用位置,值是 ElementType 枚举常量,包括 METHOD 方法、VARIABLE 变量、TYPE 类/接口、PARAMETER 方法参数、CONSTRUCTORS 构造方法和 LOACL_VARIABLE 局部变量等。@Rentention
:约束生命周期,值是 RetentionPolicy 枚举常量,包括 SOURCE 源码、CLASS 字节码和 RUNTIME 运行时。@Documented
:表明这个注解应该被 javadoc 记录。
3、注解的实现原理?
- 注解本质是一个继承了
Annotation
的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而 - 我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象
$Proxy1
。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler
的invoke
方法。该方法会从memberValues
这个Map 中索引出对应的值。而memberValues
的来源是Java 常量池。
反射
1、什么是反射?
我们通常都是利用new方式来创建对象实例,这可以说就是一种“正射”,这种方式在编译时候就确定了类型信息。而如果,我们想动态地获取类信息、创建类实例、调用类方法这时候就要用到反射。
反射: 在运行状态中,对于任意一个类都能知道它的所有属性和方法,对于任意一个对象都能调用它的任意方法和属性,这种动态获取信息及调用对象方法的功能称为反射。
反射最核心的四个类:
2、反射的原理?
通过将类对应的字节码文件加载到JVM内存中得到一个Class对象,通过这个Class对象可以反向获取实例的各个属性以及调用它的方法。
3、反射的使用场景?
- 通过反射运行配置文件内容:
- 加载配置文件,并解析配置文件得到相应信息;
- 根据解析的字符串利用反射机制获取某个类的Class实例动态配置属性。
- JDK动态代理,通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口;
jdbc
通过Class.forName()
加载数据的驱动程序;- Spring解析xml装配Bean。
4、Class 类的作用?如何获取一个 Class 对象?
在程序运行期间,Java 运行时系统为所有对象维护一个运行时类型标识,这个信息会跟踪每个对象所属的类,虚拟机利用运行时类型信息选择要执行的正确方法,保存这些信息的类就是 Class,这是一个泛型类。
获取 Class 对象:
类名.class
;- 对象的
getClass
方法; Class.forName(类的全限定名)
。
JDK1.8新特性
1、JDK1.8新特性有哪些?
JDK1.8有不少新特性,我们经常接触到的新特性如下:
接口默认方法:接口可以定义 default
修饰的默认方法,降低了接口升级的复杂性,还可以定义静态方法。
Lambda 表达式:允许把函数作为参数传递到方法,简化匿名内部类代码,使代码更加简洁。
函数式接口:使用 @FunctionalInterface
标识,有且仅有一个抽象方法,可被隐式转换为 lambda 表达式。
Stream API:引入函数式编程风格,提供了很多功能,使代码更加简洁。方法包括 forEach
遍历、count
统计个数、filter
按条件过滤、limit
取前 n 个元素、skip
跳过前 n 个元素、map
映射加工、concat
合并 stream
流等。
新日期 API:增强了日期和时间 API,新的 java.time 包主要包含了处理日期、时间、日期/时间、时区、时刻和时钟等操作。
Optional 类:处理空指针异常,提高代码可读性。
2、Lambda 表达式了解多少?
Lambda 表达式本质上是一段匿名内部类,也可以是一段可以传递的代码。
new Thread(new Runnable() { @Override public void run() { System.out.println("Thread is running before Java8!"); } }).start();
这是通过内部类的方式来重写run方法,使用Lambda表达式,还可以更加简洁:
new Thread( () -> System.out.println("Thread is running since Java8!") ).start();
当然不是每个接口都可以缩写成 Lambda 表达式。只有那些函数式接口才能缩写成 Lambda 表示式。
所谓函数式接口就是只包含一个抽象方法的声明。针对该接口类型的所有 Lambda 表达式都会与这个抽象方法匹配。
3、Java8有哪些内置函数式接口?
- JDK 1.8 API 包含了很多内置的函数式接口。其中就包括我们在老版本中经常见到的 Comparator 和 Runnable,Java 8 为他们都添加了 @FunctionalInterface 注解,以用来支持 Lambda 表达式。
- 除了这两个之外,还有Callable、Predicate、Function、Supplier、Consumer等等。
4、Optional了解吗?
Optional
是用于防范NullPointerException。
可以将 Optional
看做是包装对象(可能是 null
, 也有可能非 null
)的容器。当我们定义了 一个方法,这个方法返回的对象可能是空,也有可能非空的时候,我们就可以考虑用 Optional
来包装它,这也是在 Java 8 被推荐使用的做法。
Optional<String> optional = Optional.of("bam"); optional.isPresent(); // true optional.get(); // "bam" optional.orElse("fallback"); // "bam" optional.ifPresent((s) -> System.out.println(s.charAt(0)));