news 2025/12/23 7:47:46

Java八股文(Java基础面试题)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java八股文(Java基础面试题)
为什么重写 equals() 时必须重写 hashCode() 方法?

因为两个相等的对象的hashCode值一定相等,也就是说如果equals()方法判断两个对象是相等的,那这两个对象的hashCode值也要相等。如果重写equals()时没有重写hashCode()方法的话就可能会导致equals()方法判断是相等的两个对象,hashCode值却不相等。

==和equals()的区别?

==:(1)对于基本数据类型来说,== 比较的是值;(2)对于引用数据类型来说,== 比较的是对象的内存地址,即判断它们是否为同一个对象。

equals()不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。

(1)类没有重写equals()方法:通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,即比较两个对象的内存地址是否相同。(2)类重写了equals()方法:比较两个对象中的内容是否相等(如String)。

String、StringBuffer、StringBuilder的区别?

String:由final修饰,是不可变类,不能被继承,线程安全。

StringBuffer:可变的字符序列、线程安全。

StringBuilder:可变的字符序列、线程不安全、效率高,和StringBuilder有共同的父类AbstractStringBuilder。

String为什么要设计为不可变类?

(1)由于String被广泛使用,会用来存储敏感信息,如果字符串是可变的,容易被篡改,无法保证使用字符串进行操作时,它是安全的。

(2)字符串常量池的需要,字符串常量池是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象;若字符串可变,字符串常量池失去意义。

(3)字符串不变性保证了hash码的唯一性,使得类似HashMap,HashSet等容器才能实现相应的缓存功能。由于String的不可变,避免重复计算hashcode,只要使用缓存的hashcode即可,大大提高了在散列集合中使用String对象的性能。

String字符串修改实现的底层原理?

当用String类型来对字符串进行修改时,其实现方法是首先创建一个 StringBuilder,其次调用StringBuilder的append()方法,最后调用StringBuilder的toString()方法把结果返回。

String str = "abc"和new String("abc")的区别?

String str ="abc":JVM 会首先检查字符串常量池中是否已经存在该字符串对象,如果已经存在,那么就不会再创建了,直接返回该字符串在字符串常量池中的内存地址,将其引用赋值给变量;如果字符串常量池中没有该字符串,那么就会在字符串常量池中创建该字符串对象,然后再返回。

new String("abc"):JVM会首先检查字符串常量池中是否已经存在该字符串,如果已经存在,则不会在字符串常量池中再创建了;如果不存在,则就会在字符串常量池中创建该字符串对象,然后再到堆内存中再创建一份字符串对象,把字符串常量池中的字符串内容拷贝到内存中的字符串对象中,然后返回堆内存中该字符串的内存地址。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho

如何处理异常?

在Java中,处理异常的语句由try、catch、finally三部分组成。使用try将可能出现异常的代码包裹起来,在执行过程中,一旦出现异常,就会生成一个对应异常类的对象,根据此对象的类型,去catch中进行匹配。一旦try中的异常对象匹配到某一个catch时,就进入到catch中进行异常处理。处理完成后,就跳出当前的try-catch结构,继续执行后面的代码。像数据库连接、输入输出流、网络变成Socket等资源,JVM不能自动回收,我们需要手动进行资源释放,此时的资源释放需要声明在finally中。finally中声明的是一定会被执行的代码,即使catch中又出现了异常、try中有return语句或catch中有return语句等情况。

Java也允许程序主动抛出异常。当业务代码中,判断某项错误的条件成立时,可以使用throw关键字向外抛出异常。如果当前方法不知道该如何处理这个异常,指明执行此方法时可能会出现的异常类型,一旦方法体执行时出现异常,会在异常代码处生成一个异常类对象,此对象满足throws后的异常类型时,就会被抛出,后面的代码不会继续执行,则该异常将交给JVM处理。

finally一定会被执行吗?

finally中声明的是一定会被执行的代码,即使catch中又出现了异常、try中有return语句或catch中有return语句等情况。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。

finally可以有return吗?

在通常情况下,不要在finally块中使用return、throw等导致方法终止的语句,一旦在finally块中使用了return、throw语句,将会导致try块、catch块中的return、throw语句失效。

Exception和Error有什么区别?

Error类和Exception类的父类都是Throwable类。主要区别如下:

Exception:程序本身可以处理的异常,可以通过catch来进行捕获。Exception 又可以分为 Checked Exception(受检查异常,必须处理)和 Unchecked Exception (不受检查异常,可以不处理)。

Error:Error属于程序无法处理的错误,例如Java虚拟机运行错误、虚拟机内存不够错误、类定义错误等。这些异常发生时,JVM一般会选择线程终止。

throw和throws的区别?

(1)throw:在方法体内部,表示抛出异常,由方法体内部的语句处理;throw 是具体向外抛出异常的动作,所以它抛出的是一个异常实例;

(2)throws:在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理;表示出现异常的可能性,并不一定会发生这种异常。

什么是泛型?有什么作用?

Java的参数化类型被称为泛型,允许程序在创建集合时指定集合元素的类型,集合自动记住所有集合元素的数据类型,从而无须对集合元素进行强制类型转换。

泛型的类型必须是类,不能是基本数据类型,如果要用到基本数据类型,使用包装类替换;

泛型擦除

当把一个具有泛型信息的对象赋给另一个没有泛型信息的变量时,与泛型相关的信息会被擦除掉。比如一个List<String> 类型被转换为List,则该List对集合元素的类型检查变成了泛型参数的上限(即Object)。

List<? super T>和List<? extends T>有什么区别?

? 是类型通配符,List<?> 可以表示各种泛型List的父类,意思是元素类型未知的List;

List<? super T> 用于设定类型通配符的下限,此处? 代表一个未知的类型,但它必须是T的父类型,即可以接受任何T 的父类构成的 List;

List<? extends T> 用于设定类型通配符的上限,此处 ? 代表一个未知的类型,但它必须是T的子类型,即可以接受任何继承自T的类型的List。

什么是反射?

每个类都有一个Class对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class文件,该文件内容保存着Class对象。类加载相当于 Class对象的加载,类在第一次使用时才动态加载到 JVM 中。反射可以提供运行时的类信息,并且这个类可以在运行时才加载进来,甚至在编译时期该类的 .class不存在也可以加载进来。

具体来说,程序运行时,通过反射机制,我们可以通过反射获得任意一个类的Class对象,并通过这个对象查看这个类的信息;可以通过反射创建任意一个类的实例,并访问该实例的成员;可以通过反射机制生成一个类的动态代理类或动态代理对象。

Java反射在实际项目中有哪些应用场景?

使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;

多数框架都支持注解/XML配置,从配置中解析出来的类是字符串,需要利用反射机制实例化;

面向切面编程(AOP)的实现方案,是在程序运行时创建目标对象的代理类,这必须由反射机制来实现。

continue、break和return的区别是什么?

continue:指跳出当前的这一次循环,继续下一次循环。

break:指跳出整个循环体,继续执行循环下面的语句。

return:用于跳出所在方法,结束该方法的运行。

final、finally、finalize 的区别?

final:用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖、被其修饰的类不可继承;

finally:异常处理语句结构的一部分,表示总是执行;

finallize:Object类的一个方法,在垃圾回收时会调用被回收对象的finalize

如何实现对象的克隆?

(1)实现Cloneable接口并重写Object类中的clone()方法;

(2)实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。

深克隆和浅克隆的区别?

(1)浅克隆:拷贝对象和原始对象的引用类型引用同一个对象。浅克隆只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化。

(2)深克隆:拷贝对象和原始对象的引用类型引用不同对象。深拷贝是将对象和值都复制过来,会完全复制整个对象,两个对象修改其中任意的值另一个值不会改变。

Java中有哪些容器(集合)?

Java容器主要包括Collection和Map,Collection主要用于存放单一元素; Map主要用于存放键值对。

Collection接口又有三个主要的子接口:List、Set和Queue。(1)List是元素有序、可重复的集合,主要的实现类有ArrayList、LinkedList、Vector;(2)Set是元素无序、不可重复的集合,主要的实现类有HashSet,LinkedHashSet,TreeSet;(3)Queue是先进先出(FIFO)的队列,存储的元素是有序、可重复的,主要的实现类有ArrayQueue、LinkedList、PriorityQueue。

Map用于存储key - value对,是无序的,其中key不能重复,主要的实现类有HashMap、TreeMap、Hashtable、ConcurrentHashMap。

Java容器(集合)的主要实现类有哪些?

ArrayList:线程不安全,效率高;底层使用Object[ ]存储,查找快,增删较慢,随机访问元素的复杂度是O(1)。

LinkedList:线程不安全;底层使用双向链表存储,查找慢、增删快,随机查找元素的复杂度是O(n);LinkedList的节点除了存储数据,还存储了两个指针,比ArrayList更占内存。

Vector:线程安全,效率低;底层使用Object[ ]存储。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho

HashSet:线程不安全;基于哈希表实现,不支持有序性操作;可以存储null;

TreeSet:线程不安全;基于红黑树实现,支持自然排序和定制排序;不可以存储null;

LinkedHashSet:线程不安全;是HashSet的子类,基于链表和哈希表实现;对于频繁的遍历操作,其效率大于HashSet;

ArrayDeque:基于可变长度的数组和双指针实现,不可以存储null。

PriorityQueue:线程不安全;基于二叉堆结构实现,底层使用可变长的数组来存储数据;可以用它来实现优先队列。

HashMap:线程不安全、效率高;JDK1.7中的HashMap是基于数组+链表来实现的,它的底层维护一个Entry数组;JDK1.8中的HashMap,是基于数组+链表+红黑树来实现的,它的底层维护一个Node数组;存储的key和value可以是null;

TreeMap:线程不安全;基于红黑树实现,按照key-value对中的key对数据进行排序,实现排序遍历;

Hashtable:线程安全、效率很低;底层采用数组+链表实现,存储的key和value不能是null。

ConcurrentHashMap:线程安全;JDK1.7的ConcurrentHashMap 底层采用分段的数组+链表实现,JDK1.8采用的数组+链表+红黑树。

LinkedHashMap:作为HashMap的子类;底层使用双向链表存储;

ArrayList实现RandomAccess接口有何作用?为何LinkedList没实现这个接口?

RandomAccess 接口只是一个标志接口,标识实现这个接口的类具有随机访问功能。ArrayList底层是数组,而LinkedList底层是链表。数组天然支持随机访问,时间复杂度为O(1)。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。ArrayList实现了RandomAccess接口,就表明了他具有快速随机访问功能。

HashSet的实现原理?

HashSet是基于HashMap实现的,HashSet的构造方法中会初始化一个HashMap对象,所有放入HashSet中的集合元素实际上由HashMap的key来保存,而HashMap的value则存储了一个PRESENT,它是一个静态的Object对象。

Queue与Deque的区别

Queue 是单端队列,只能从一端插入元素,另一端删除元素,实现上一般遵循 先进先出(FIFO) 规则。

Deque 是双端队列,在队列的两端均可以插入或删除元素。

HashMap的put方法的执行过程?

(1)先判断数组是否为空,若数组为空则进行第一次扩容;(2)通过hash算法,计算键值对在数组中的索引;(3)如果当前位置元素为空,则直接插入数据;如果当前位置元素非空,且key已存在,则直接覆盖其value;如果当前位置元素非空,且key不存在,则将数据链到链表末端;(4)若链表长度达到8,则将链表转换成红黑树,并将数据插入树中;(5)如果数组中元素个数(size)超过threshold,则再次进行扩容操作。

HashMap的扩容机制

(1)数组的初始容量为16,而容量是以2的次方扩充的。

(2)数组是否需要扩充是通过负载因子判断的,如果当前元素个数为数组容量的0.75时,就会扩充数组。这个0.75就是默认的负载因子,可由构造器传入。

(3)为了解决碰撞,数组中的元素是单向链表类型。当链表长度到达一个阈值时(7或8),先检测当前数组是否到达一个阈值(64),如果没有到达这个容量,先去扩充数组,否则会将链表转换成红黑树提高性能。而当链表长度缩小到另一个阈值时(6),又会将红黑树转换回单向链表提高性能。

HashMap为什么线程不安全?

HashMap在并发执行put操作时,可能会导致形成循环链表,从而引起死循环。

HashMap中的循环链表是如何产生的?

在多线程的情况下,当重新调整HashMap大小的时候,就会存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历。如果条件竞争发生了,那么就会产生死循环了。

ConcurrentHashMap的实现原理?

在jdk 1.7中,ConcurrentHashMap是由Segment数据结构和HashEntry数组结构构成,采取分段锁来保证安全性。Segment继承了ReentrantLock,所以它是一种重入锁,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment 数组,一个Segment里包含一个HashEntry数组,Segment的结构和HashMap类似,是一个数组和链表结构。

JDK1.8 的实现已经摒弃了Segment的概念,而是直接用 Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized 和CAS来操作,整个看起来就像是优化过且线程安全的HashMap。

ConcurrentHashMap是怎么分段分组的?

Segment的get操作先经过一次再散列,然后使用这个散列值通过散列运算定位到Segment,再通过散列算法定位到元素。get操作的高效之处在于整个get过程都不需要加锁,除非读到空的值才会加锁重读。

当执行put操作时,首先判断是否需要扩容;然后定位到添加元素的位置,将其放入HashEntry数组中。插入过程会进行第一次key的hash来定位Segment的位置,如果该Segment还没有初始化,即通过CAS操作进行赋值,然后进行第二次hash 操作,找到相应的HashEntry的位置,这里会利用继承过来的锁的特性,在将数据插入指定的HashEntry位置时(尾插法),会通过继承 ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁,超过指定次数就挂起,等待唤醒。

HashTable和ConcurrentHashMap的区别?

HashTable 和 ConcurrentHashMap 相比,效率低。

Hashtable之所以效率低主要是使用了synchronized关键字对put等操作进行加锁,而synchronized关键字加锁是对整张Hash表加锁,即每次锁住整张表让线程独占,致使效率低下;

ConcurrentHashMap在对象中保存了一个Segment数组,即将整个Hash表划分为多个分段;而每个Segment元素,即每个分段则类似于一个Hashtable;这样,在执行put操作时首先根据hash算法定位到元素属于哪个Segment,然后对该Segment加锁即可,因此,ConcurrentHashMap在多线程并发编程中可是实现多线程put操作。

HashMap的get方法的执行过程?能否判断元素是否在map 中?

通过key的hash值找到在table数组中的索引处的Entry,然后返回该key对应的value即可。

不能判断一个key是否包含在map中,因为get返回null有可能是不包含该 key,也有可能该key对应的value为 null。因为HashMap中允许key为null,也允许value为 null。

JDK1.8之后,为什么HashMap头插法改为尾插法?

头插法在并发下时可能形成数据环,get数据时死循环,而在1.8之前因为处理 hash 冲突的方式是用链表存放数据,使用头插法可以提升一定效率。但是在 1.8 之后链表长度达到阈值就要考虑升级红黑树了,所以哪怕进行尾插遍历次数也会很有限,效率影响不大。

Iterator

迭代器可以遍历并选择序列中的对象,并且只能单向移动,而开发人员不需要了解该序列的底层结构。

iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。之后使用next()获得序列中的下一个元素。

Iterator和ListIterator的区别?

Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。

Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。

ListIterator 实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引等等。

Java中的IO流

数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件)的过程即输出。

按照数据流向,可以将流分为输入流和输出流,其中输入流只能读取数据、不能写入数据,而输出流只能写入数据、不能读取数据。

按照数据类型,可以将流分为字节流和字符流,其中字节流操作的数据单元是8位的字节,而字符流操作的数据单元是16位的字符。字符流通常处理文本文件,字节流用来处理图片、视频、音频等非文本文件。

InputStream:字节输入流;Reader:字符输入流

OutputStream:字节输出流;Writer:字符输出流

怎么用流打开一个大文件?

(1)使用缓冲流。缓冲流内部维护了一个缓冲区,通过与缓冲区的交互,减少与设备的交互次数。使用缓冲输入流时,它每次会读取一批数据将缓冲区填满,每次调用读取方法并不是直接从设备取值,而是从缓冲区取值,当缓冲区为空时,它会再一次读取数据,将缓冲区填满。使用缓冲输出流时,每次调用写入方法并不是直接写入到设备,而是写入缓冲区,当缓冲区填满时它会自动写入设备。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案【点击此处即可/免费获取】https://docs.qq.com/doc/DQXdYWE9LZ2ZHZ1ho

(2)使用NIO。NIO采用内存映射文件的方式来处理输入/输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了,通过这种方式来进行输入/输出比传统的输入/输出要快得多。

Java中3种常见IO模型

(1)BIO:BIO属于同步阻塞IO模型,同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。

(2)NIO:NIO主要由Channel,Buffer,Selector组成。它是支持面向缓冲的,基于通道的I/O操作方法。所有的IO在NIO中都从一个Channel开始,数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中。Buffer本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。Selector允许单线程处理多个Channel,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。

(3)AIO:异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

Java的序列化与反序列化

序列化机制可以将对象转换成字节序列,这些字节序列可以保存在磁盘上,也可以在网络中传输,并允许程序将这些字节序列再次恢复成原来的对象。其中,对象的序列化(Serialize)是指将一个Java对象写入IO流中,对象的反序列化(Deserialize),则是指从IO流中恢复该Java对象。

若对象要支持序列化机制,则它的类需要实现Serializable接口,该接口是一个标记接口,它没有提供任何方法,只是标明该类是可以序列化的。

若要实现序列化,则需要使用对象流ObjectInputStream和ObjectOutputStream。其中,在序列化时需要调用ObjectOutputStream对象的writeObject()方法,以输出对象序列。在反序列化时需要调用ObjectInputStream对象的readObject()方法,将对象序列恢复为对象。

Serializable接口为什么需要定义serialVersionUID变量?

serialVersionUID代表序列化的版本,通过定义类的序列化版本,在反序列化时,只要对象中所存的版本和当前类的版本一致,就允许做恢复数据的操作,否则将会抛出序列化版本不一致的错误。

如果不定义序列化版本,在反序列化时可能出现冲突的情况。增加了序列化版本之后,在这种情况下则可以抛出异常,以提示这种矛盾的存在,提高数据的安全性。

除了Java自带的序列化之外,你还了解哪些序列化工具?

Json:简单直观,可读性好,有jackson,gson,fastjson等解析工具

Protobuf:用来序列化结构化数据的技术,可以用来持久化数据或者序列化成网络传输的数据。更加节省空间(以二进制流存储)、速度更快、更加灵活。

Thrift:不仅仅是序列化协议,而是一个RPC框架,能够满足当前大数据量、分布式、跨语言、跨平台数据通讯的需求。

Avro:提供两种序列化格式,即JSON格式或者Binary格式。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2025/12/17 23:23:05

商汤发布如影营销智能体,五大智能体全链协同打造直播电商增长飞轮

近年来&#xff0c;直播电商迈入发展快车道。预计2025年&#xff0c;中国直播电商市场规模将突破6万亿。直播电商爆发增长的背后&#xff0c;离不开主播、场控、直播运营、店铺运营、投流、内容制作等多种岗位紧密配合。然而&#xff0c;随着人力效率逐步见顶&#xff0c;运营成…

作者头像 李华
网站建设 2025/12/17 23:22:11

Flutter:开启跨平台应用开发的新纪元

标题&#xff1a;Flutter&#xff1a;开启跨平台应用开发的新纪元 引言 在移动互联网高速发展的今天&#xff0c;用户对应用的性能、体验和交付速度提出了更高的要求。与此同时&#xff0c;开发者面临着为多个平台&#xff08;如 iOS、Android、Web 和桌面&#xff09;分别开…

作者头像 李华
网站建设 2025/12/17 23:20:58

9个AI写作工具,助你轻松搞定本科论文!

9个AI写作工具&#xff0c;助你轻松搞定本科论文&#xff01; 论文写作的“隐形助手”正在改变你的学习方式 在当今信息化时代&#xff0c;AI 技术已经渗透到我们生活的方方面面&#xff0c;而学术写作也迎来了全新的变革。对于本科生而言&#xff0c;撰写一篇高质量的本科论文…

作者头像 李华
网站建设 2025/12/17 23:20:41

彻底搞懂JavaScript块级作用域与函数作用域:var、let、const的核心区别

彻底搞懂JavaScript块级作用域与函数作用域&#xff1a;var、let、const的核心区别 在JavaScript中&#xff0c;作用域是控制变量访问权限的核心机制&#xff0c;直接影响代码的安全性、可维护性甚至运行结果。尤其是ES6引入块级作用域后&#xff0c;var、let、const 三者的作用…

作者头像 李华