Java面试宝典-java基础07
- 61、什么是 java 序列化?什么情况下需要序列化?
- 62、序列化使用场景有哪些?
- 63、使用序列化和反序列化的注意事项
- 64、为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?
- 65、Java反射是什么?有哪些应用场景
- 66、java 中都有哪些引用类型?
- 67、枚举是什么,有哪些特点?
- 68、泛型是什么,有哪些应用场景?
- 69、java 中 IO 流分为几种?
- 70、BIO、NIO、AIO 有什么区别?
61、什么是 java 序列化?什么情况下需要序列化?
序列化就是把内存里面的对象转化为字节流,以便用来实现存储或者传输。
序列化的前提是保证通信双方对于对象的可识别性,所以很多时候,我们会把对象先转化为通用的解析格式,比如json、xml等。然后再把他们转化为数据流进行网络传输,从而实现跨平台和跨语言的可识别性。
序列化是通过实现serializable接口,该接口没有需要实现的方法,implement Serializable只是为了标注该对象是可被序列化的。反序列化就是根据从文件或者网络上获取到的对象的字节流,根据字节流里面保存的对象描述信息和状态。
62、序列化使用场景有哪些?
- 像银行卡、密码这些字段不能被序列化;
- 将对象存储到文件中时进行序列化,从文件中读取对象时需要反序列化;
- 分布式传递对象,或者网络传输,需要序列化;
- 存入缓存数据库(如 Redis)时需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
63、使用序列化和反序列化的注意事项
- (1) Java序列化的方式
实现 Serializable 接口:可以自定义 writeObject、readObject、writeReplace、readResolve 方法,会通过反射调用。
实现 Externalizable 接口,它是Serializable接口的子类,用户要实现的writeExternal()和readExternal() 方法,用来决定如何序列化和反序列化。因为序列化哪些字段,需要方法指定,所以transient在这里无效。
- (2)序列化ID问题
虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。
- (3)静态字段不会序列化
序列化时不保存静态变量,这是因为序列化保存的是对象的状态,静态变量属于类的状态,因此序列化并不保存静态变量。
- (4)transient
transient代表对象的临时数据。
如果你不想让对象中的某个成员被序列化可以在定义它的时候加上 transient 关键字进行修饰 ,这样,在对象被序列化时其就不会被序列化。
transient 修饰过的成员反序列化后将赋予默认值,即 0 或 null。
有些时候像银行卡号这些字段是不希望在网络上传输的,transient的作用就是把这个字段的生命周期仅存于调用者的内存中而不会写到磁盘里持久化。
- (5)父类的序列化
当一个父类实现序列化,子类自动实现序列化;而子类实现了 Serializable 接口,父类也需要实现Serializable 接口。
- (6)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化
- (7)并非所有的对象都可以序列化
① 安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行RMI传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的;
② 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现;
- (8)序列化解决深拷贝问题
如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存,这是能用序列化解决深拷贝的重要原因。
64、为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?
- (1)什么要使用克隆?
想对一个对象进行复制,又想保留原有的对象进行接下来的操作,这个时候就需要克隆了。
-
(2)如何实现对象克隆?
-
实现Cloneable接口,重写clone方法;
-
实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆;
-
BeanUtils,apache和Spring都提供了bean工具,只是这都是浅克隆。
-
(3)深拷贝和浅拷贝区别是什么?
浅拷贝,指的是重新分配一块内存,创建一个新的对象,指针指向被复制对象的同一块内存地址,如果原对象发生改变,那么浅拷贝得到的新对象也会反映出这些变化;
深拷贝:它是指重新分配一块内存,创建一个新的对象,并且将原对象中的元素,以递归的方式,通过创建新的子对象拷贝到新对象中。这意味着深拷贝不仅复制了对象本身,还复制了对象所引用的所有其他对象。因此,新对象和原对象没有任何关联,对其中一个对象的修改不会影响另一个对象。
65、Java反射是什么?有哪些应用场景
Java反射是Java语言的一个核心特性,它允许程序在运行时检查类、接口、字段和方法的信息,并能动态地调用对象的方法。反射的主要作用是增强程序的灵活性,使得程序能够在运行时动态地加载、链接和使用类。
但反射的代码比正常调用的代码更多,性能更慢,应避免使用反射。
Java反射应用场景:
- 在框架设计中,反射常被用于实现框架的扩展性和灵活性。框架可以通过反射在运行时加载和使用不同的类,从而实现对不同业务逻辑的支持;
- 通过反射,可以创建动态代理对象,这些代理对象可以在运行时代表其他对象执行操作。这在实现AOP(面向切面编程)等高级功能时非常有用;
- 反射可以解析注解信息,并执行相应的操作;
- 在如Hibernate这样的ORM框架中,对象需要被转化为实体类并存储到数据库中。这个过程中,反射被用于动态创建实体类对象、获取类的属性和方法等;
- 在Java中,序列化和反序列化都需要使用到反射技术。序列化会将对象转化成字节流,反序列化则将字节流还原为对象。在这个过程中,需要借助反射技术来获取对象的属性信息。
Java反射提供了一种强大的机制,使得程序能够在运行时动态地操作类和对象,从而增强了程序的灵活性和可扩展性。然而,反射也有一些潜在的性能开销和安全性问题,因此在使用时需要谨慎考虑。
66、java 中都有哪些引用类型?
- (1)强引用
Java中默认声明的就是强引用,比如:
Object obj = new Object();
obj = null;
只要强引用存在,垃圾回收器将永远不会回收被引用的对象。如果想被回收,可以将对象置为null;
- (2)软引用(SoftReference)
在内存足够的时候,软引用不会被回收,只有在内存不足时,系统才会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会跑出内存溢出异常。
byte[] buff = new byte[1024 * 1024];
SoftReference<byte[]> sr = new SoftReference<>(buff);
- (3)弱引用(WeakReference)
进行垃圾回收时,弱引用就会被回收。
- (4)虚引用(PhantomReference)
- (5)引用队列(ReferenceQueue)
引用队列可以与软引用、弱引用、虚引用一起配合使用。
当垃圾回收器准备回收一个对象时,如果发现它还有引用,就会在回收对象之前,把这个引用加入到引用队列中。
程序可以通过判断引用队列中是否加入了引用,来判断被引用的对象是否将要被垃圾回收,这样可以在对象被回收之前采取一些必要的措施.
67、枚举是什么,有哪些特点?
- 枚举作为一个类,可以有自己的属性(通常应该是常量,我没遇到过不是的情况)以及自己的方法(否则只能用switch来写,实际违反原则)
- 枚举类型检查,有效性检查
- 和常量相比,无需查看文档和源码就能直接知道所有可能返回值,方便编码。
- 与switch配合使用解决ifelse过多的问题,使用switch进行条件判断时,条件参数一般只能是整型,字符型。而枚举型确实也被switch
所支持,在java 1.7后switch也对字符串进行了支持。
68、泛型是什么,有哪些应用场景?
泛型是一种编程范式,其核心思想是将类型参数化,即把数据类型作为参数传递。通过泛型,可以创建灵活且可重用的代码,而无需针对每种数据类型都编写重复的代码。泛型的主要优点包括提高代码的可读性、可维护性和类型安全性。
应用场景:
- 集合类和数据结构:泛型最常见的用途是在集合类(如ArrayList、LinkedList、HashMap等)和数据结构中使用。通过使用泛型,可以创建一个通用的集合类,用于存储不同类型的元素,并在编译时捕获类型错误。
- 自定义数据结构:使用泛型可以创建自定义的数据结构,以适应不同类型的数据。这样可以编写通用的、可重用的代码,减少为不同类型的数据编写不同实现的需求。
- 泛型方法:除了泛型类,还有泛型方法,即在方法级别使用泛型。这对于那些只需要在特定方法中使用泛型的情况非常有用。
- 接口和抽象类:泛型也可以用于接口和抽象类的定义,以创建通用的接口和抽象类,这些接口和类可以被不同类型的实现或子类使用。
- 数据库操作:在数据库操作中,泛型可以用于定义数据库表中各个字段的类型,从而提高程序的类型安全性。
使用泛型的主要目的之一是提供编译时类型检查,这有助于减少在运行时出现类型错误的可能性。此外,泛型还可以提高代码的可读性和重用性,避免不必要的类型转换和强制类型转换,从而提高代码的安全性和可维护性。
Object obj = new Object();
obj = null;
byte[] buff = new byte[1024 * 1024];
SoftReference<byte[]> sr = new SoftReference<>(buff);
69、java 中 IO 流分为几种?
- (1)字节流
在 I/O 操作中,数据被视为一系列按顺序排列的字节流。在 Java 中,这种字节流被称为 InputStream 和 OutputStream。
InputStream 代表一个输入流,它是一个抽象类,不能被实例化。InputStream 定义了一些通用方法,如 read() 和 skip() 等,用于从输入流中读取数据。
OutputStream 代表一个输出流,它也是一个抽象类,不能被实例化。OutputStream 定义了一些通用方法,如 write() 和 flush() 等,用于向输出流中写入数据。
- (2)字符流
除了字节流,Java 还提供字符流,字符流类似于字节流,不同之处在于字符流是按字符读写数据,而不是按字节。Java 中最基本的字符流是Reader 和 Writer,它们是基于 InputStream 和 OutputStream 的转换类,用于完成字节流与字符流之间的转换。
- (3)缓冲流
BufferedInputStream 和 BufferedOutputStream 是 I/O 包中提供的缓冲输入输出流。它们可以提高 I/O 操作的效率,具有较好的缓存机制,能够减少磁盘操作,缩短文件传输时间。使用 BufferedInputStream 和 BufferedOutputStream 进行读取和写入时,Java 会自动调整缓冲区的大小,使其能够适应不同的数据传输速度。
- (4)对象流
可以读取或写入 Java 对象的流,比较典型的对象流包括ObjectInputStream 和ObjectOutputStream。
对象流需要将对象序列化和反序列化为字节序列,使用 ObjectInputStream 和 ObjectOutputStream 可以将 Java 对象转换为字节流进行传输或存储。
在网络传输和文件存储中,ObjectInputStream 和 ObjectOutputStream 通常会被使用到。
70、BIO、NIO、AIO 有什么区别?
- (1)同步阻塞BIO
JDK1.4之前,建立网络连接的时候采用BIO模式,先在启动服务端socket,然后启动客户端socket,对服务端通信,客户端发送请求后,先判断服务端是否有线程响应,如果没有则会一直等待或者遭到拒绝请求,如果有的话会等待请求结束后才继续执行。
线程发起IO 请求,不管内核是否准备好IO 操作,从发起请求起,线程一直阻塞,直到操作完成。
服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善。
- (2)同步非阻塞NIO
NIO主要是想解决BIO的大并发问题,BIO是每一个请求分配一个线程,当请求过多时,每个线程占用一定的内存空间,服务器瘫痪了。JDK1.4开始支持NIO,适用于连接数目多且连接比较短的架构,比如聊天服务器,并发局限于应用中。
线程发起IO 请求,立即返回;内核在做好IO 操作准备之后,通过调用注册回调函数通知线程做IO操作,线程开始阻塞,直到操作完成。
服务器实现模式为一个请求一个线程,即客户端发送连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O 请求时才启动一个线程进行处理。
- (3)异步非阻塞AIO
JDK1.7开始支持AIO,适用于连接数目多且连接比较长的结构,比如相册服务器,充分调用OS参与并发操作。
线程发起IO 请求,立即返回;内存做好IO 操作准备之后,做IO 操作,直到操作完成或者失败,通过调用注册回调函数通知线程做IO操作完成或者失败。
服务器实现模式为一个有效请求一个线程,客户端IO 请求都由OS先完成了再通知服务器应用去启动线程进行处理。