Java基础面试题
Java基础面试题
1.八种基本数据类型
- 整数类型:byte,short,int,long
- 浮点类型:double,float
- 布尔类型:boolean
- 字符型:char
2.Java中的自动装箱和拆箱
- 装箱就是将基本数据类型转换为包装类型,调用Integer中的valueOf()方法。
- 拆箱就是将包装类型转换为基本数据类型,调用integer中的intValue方法。
3.Java面向对象三大特性
- 封装: 将对象的属性和方法结合起来,隐藏内部细节,通过调用对象提供的公共接口与外界交互。目的是增强安全性和简化编程。
- 继承: 子类会自动共享父类的属性和方法,并可以添加自己新的属性和方法。代码复用的主要手段。
- 多态: 允许不同类的对象对同一消息做出响应。**父类的引用可以指向子类的对象,并且根据实际指向的对象类型来调用其相应的方法。**多态可以分为运行时多态和编译时多态。使得代码更加灵活。
- 追加:多态体现在哪些方面:方法重载,方法重写,接口与实现,向上转型和向下转型
4.重载与重写
-
重载: 在同一个类中,可以有多个同名的方法,但是他们的参数列表不同。编译器根据调用时的参数类型来决定调用哪个方法。
-
重写: 子类对父类的方法进行重写,方法名和参数列表必须相同。通过@override注解来标明这是对父类方法的重写。
5.抽象类和接口的区别?
抽象类:使用abstract关键字定义,包含抽象方法和具体方法,拥有实例变量和静态变量。可以被各种访问修饰符修饰,它支持单继承
接口:用interface定义,包含的主要是抽象方法,java8之后有默认方法,只能由静态常量。它支持多实现
6.浅拷贝和深拷贝
- 浅拷贝只复制对象本身,而不复制对象引用的其它对象。新旧对象会共享引用对象,修改一个对象的引用属性会影响另一个对象。
- 深拷贝会递归复制对象及其所有引用的对象,创建完全独立地副本。
- 追加:实现深拷贝的方法
- 实现Cloneable接口并重写clone()方法。
- 使用序列化和反序列化:先将对象序列化为字节流,再从字节流反序列化为对象实现深拷贝。
- 手动递归复制:手动递归复制对象及其引用的所有字段。
7.什么是泛型?
- 泛型允许接口,类,方法在定义时使用一个或多个参数类型,使用时再指定为具体的类型。主要目的是在编译时提供更强的类型检查,并且能够在编译后保存类型信息,避免了在运行时出现类型转换异常。
8.什么是反射?
- 反射允许java在运行时检查和操作类的方法和字段,通过反射,我们可以动态地获取类的完整结构信息,并在运行时调用方法或访问字段。还可以通过Class类或Constructor对象的
newInstance方法来动态地创建对象,还可以通过Method类的invoke方法动态地调用对象的方法;也可以通过Filed类的get和set允许程序在运行时访问和修改对象的字段值。 - 应用场景:Spring框架使用反射来动态加载和管理Bean;Java的动态代理机制就使用了反射来创建代理类。
9.Java中的异常
- Throwable是所有异常类型的顶层父类,下面有两个重要的子类是:Exception(异常)和Error(错误)
- Error表示运行时环境的错误。这类异常即使被捕捉也无法让程序恢复正常。例如:OutOfMemoryError(内存溢出),StackOverflowError(栈溢出)
- Exception表示程序本身可以处理的异常,分为两大类:
- 非运行时异常这类异常在编译时就必须try-catch进行捕捉或者通过Throw语句抛给上层调用者处理。
- RuntimeException: 由程序错误导致,例如:空指针异常,数据越界异常。即使不对其进行捕捉或者声明抛出也能运行
10.== 和 equals的区别?
- 比较的对象不同: ==可以用于基本类型和引用类型的比较。而equals()只能用于引用类型的比较。
- 比较的内容不容: 对于基本类型,==比较的是两边的值是否相等;对于引用类型:==比较的是内存地址是否相同,两边是否指向同一对象。而equals()默认比较的也是内存地址,但通常会被重写来比较对象的内容是否相同。
- 注意:在重写equals()方法的时候通常也要重写hashCode()方法,用来保证在使用哈希表等数据结构的时候,对象的相等判断和存储查找工作能正常运行。
11.String,StringBuilder和String Buffer的区别?
- 可变性: String是不可变的,一旦创建,里面的内容无法修改,每次修改之后都会创建一个新对象;而StringBuilder和String Buffer都是可以变的,可以直接对字符串的内容进行更改不会创建新的对象。
- 线程安全性: String因为是不可变的,所有线程安全。StringBuilder是线程不安全的,适用于单线程环境。String Buffer是线程安全的,其方法通过
SynChronized关键字实现同步,适用于多线程环境。 - 性能: String性能最低,在频繁修改字符串时会创建大量的对象,增加了内存开销和垃圾回收的压力。StringBuilder是性能最高的,因为它没有线程安全的开销。String Buffer性能略低于StringBuilder,因为它的线程安全引入了同步开销。
12.BIO,NIO和AIO的区别?
BIO是同步阻塞IO:每个连接都需要独立的线程来处理,在读写操作时会完全阻塞线程直到完成。优点是编程简单,缺点是线程开销大,不适合高并发场景。
NIO是同步非阻塞IO:基于Selector多路复用机制,单线程处理多连接。通过通道(Channel)和缓冲区(Buffer)实现非阻塞IO,适合高并发、短连接的场景,
AIO是异步非阻塞IO:基于事件和回调机制实现的,应用程序发起IO后立即返回,由操作系统完成IO操作后通过回调通知结果。性能最优,但是Linux平台支持有限,生态不完全。
13.NIO是怎么实现的?
NIO是同步非阻塞IO,他有三大核心部分:Selector,Channel,Buffer。同步的核心是Selector代替了线程本身轮询IO事件,避免了线程阻塞同时还减少了不必要的线程开销。非阻塞的核心是通道和缓冲区,当IO事件就绪时,可以通过通道读取缓冲区或者从缓冲区写入到通道中,保证IO的成功,无需线程阻塞式的等待。Selector负责监听多个通道中的事件。因此单个线程可以监听多个数据通道。
14.说说Java中的集合
- 首先Java中的集合主要分为两大接口体系,分别是Collection和Map;Collection下面有三个子接口,分别是List(有序可重复),Set(无序不可重复),Queue(队列);Map是代表键值对的集合
15.Collections和Collection的区别?
- Collection是java集合中的顶层接口,它定义了集合的基本行为,如添加,删除,遍历等操作。
- Collecitons是一个工具类,它提供了一系列静态方法用于实现了Collection接口的集合进行操作,包括查找,排序,反转,序列化等方法。
16.ArrayList和LinkedList的区别?
- 底层数据结构不同:
ArrayList::基于动态数组实现,数据在内存中是连续存储的;Linked List:底层是双向链表,每个节点存储数据和指向前后节点的指针,数据不一定是连续存储的
- 访问速度:
ArrayList:因为底层是数组,所以能通过索引快速访问数据,并且支持随机访问,时间复杂度为O(1);Linked List:不支持随机访问,时间复杂度为O(n);查询元素必须从头或尾开始遍历链表,访问中间元素较慢。
- 插入和删除性能:
Array List:插入或删除操作可能需要移动大量元素,时间复杂度是O(n),但是在尾部插入操作是O(1),但是当数组扩容的时候性能会下降。Linked List:在任意位置的插入和删除效率都比较高,因为只需要调整节点之间的指针,时间复杂度是O(1);
- 线程安全和使用场景:
- 两者都不是线程安全的
ArrayList适用于频繁访问元素或以索引操作为主的场景;LinkedList适用于插入和删除频繁的场景,特别是在列表中间操作时
- 两者都不是线程安全的
17.ArrayList的扩容机制?
Array List在添加元素时,首先判断是否需要进行扩容,如果当前容量+1超过了数组的长度,就会进行扩容;主要操作流程:它会创建一个新的数组,数组容量为原先的1.5倍,然后将原数组的数据拷贝到新数组中,再将指向原数组的引用指向新数组,这样完成了扩容。
18.HashMap实现原理
分为JDK1.7和JDK1.8讲,从哈希桶讲到链表,再讲红黑树的转换,最后讲一下扩容机制:
-
底层原理:
-
JDK1.7的HashMap的数据结构是数组+链表;HashMap通过key的
hashcode经过扰动函数处理过后得到hash值,然后通过(n-1)&hash判断当前元素存放的位置,如果当前位置存在元素,就判断两个元素的hash值以及key是否相同,如果相同就直接覆盖,不相同就采用拉链法解决冲突存入链表当中。 -
JDK1.8的Hash Map的数据结构是数组+链表+红黑树;JDK1.8之后在解决hash冲突时有了很大的变化,当链表长度大于等于8并且哈希表的容量大于等于64时,就会将链表转换为红黑树。红黑树是一种自平衡搜索二叉树,其查询效率为O(log n),提高了查询性能。但是当红黑树节点的数量小于等于6时会退化为单向链表。
-
-
扩容机制:
- 在JDK1.7中,HashMap在扩容时会通过重新计算哈希码和重新分配桶位置的方式来实现扩容,具体操作过程是:
- 当HashMap中的键值对数量超过负载因子(0.75)与桶数组长度的乘积时,会触发扩容操作;扩容时会创建一个新的桶数组,长度为原数组的两倍;然后遍历原桶数组,将其中的键值对重新计算哈希码,并根据新的数组长度,重新分配到新的桶中。
- 在JDK1.8中,除了上述的重新计算哈希码和重新分配桶位置之外,还引入了对红黑树的重建,具体过程是:
- 当链表的长度超过阈值(默认为8),链表会转换为红黑树,以提高查询和删除的效率;扩容的时候会创建一个新的桶数组长度为原数组的两倍,然后遍历原桶数组,将其中的键值对重新计算哈希码,然后根据新的数组长度,重新分配到新的桶中,对于原来的链表节点,仍保持链表结构;对于原来的红黑树节点,会进行重建,用来保持树的平衡。
- 在JDK1.7中,HashMap在扩容时会通过重新计算哈希码和重新分配桶位置的方式来实现扩容,具体操作过程是:
19.Hash冲突的解决办法?
- **链地址法:**对于相同的hash值采用链表进行连接。
- **再哈希法:**提供多个哈希函数,当发生冲突的时候,使用另外一个哈希函数再次计算key的hash值,直到找到一个空的槽位来存储key
- **建立公共溢出法:**将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
- 开放定址法: 当关键字key的哈希地址P=H(key)出现冲突时,以p为基础,产生另外一个哈希地址p1,若p1仍然冲突,就再产生一个哈希地址p2,直到找出一个不冲突的地址,将相应元素存入其中。
20.Hash Map的Put方法的具体流程
首先根据key的哈希码计算在数组中的索引
1、判断table数组是否为空或者length=0,是的话就执行resize()方法进行扩容。
2、不是就根据键值key计算hash值得到插入的数组下标。
3、判断table[i]是否为空,如果是true,直接新建节点进行添加**,如果是false,判断table[i]的首个元素是否和key一样,一样就直接覆盖**。
4、判断table[i]是否为**treenode**,即判断是否是红黑树,如果是红黑树,直接在树中插入键值对。
5、如果不是**treenode,开始遍历链表,判断链表长度是否大于8**,如果大于8就转成红黑树,在树中执行插入操作,如果不是大于8 ,就在链表中执行插入;在遍历过程中判断key是否存在,存在就直接覆盖对应的value值。
6、插入成功后,就需要判断实际存在的键值对数量是否超过了最大容量,如果超过了,执行resize方法进行扩容。
21.为什么HashMap要使用红黑树而不是平衡二叉树?
- 首先平衡二叉树追求完全平衡的状态,任何结点的左右子树高度差不会超过1,优势是树的结点分配很均匀。但是每次进行插入或删除的节点的时候,都需要我们通过左旋和右旋来调整,让它再次成为一颗符合条件的平衡树。导致插入/删除的成本较高。
- 红黑树追求的是一种弱平衡的状态,整颗树的最长路径不会超过最短路径的2倍。当进行插入和删除的时候,不会频繁的破坏树的的规则,所以不需要频繁调整。
22.CurrentHashMap和HashMap的区别
- 它们两个最大的区别是在多线程环境下,HashMap是线程不安全的,它的底层原理是数组+链表的形式,并没有对其加锁,所以在高并发环境下肯定会导致数据不一致,或者是链表形成环状导致死循环的问题。而
CurrentHashMap底层在JDK1.8以后使用CAS(乐观锁)+Synchronized(悲观锁),它会对链表的头节点加锁,这样在其他线程修改值时会等待,它的特点是对数据的读操作不加锁,在写入操作时通过CAS(乐观锁)插入,如果失败就加Synchronized(悲观锁),提高并发率。
23.JDBC连接到数据库的步骤
加载数据库驱动—建立数据库连接—创建Statement对象—执行SQL查询或更新操作—处理查询结构—关闭连接
24.MyBatis里的 # 和 $ 的区别?
- #{}: 是参数占位符,使用预编译机制,把参数替换成 ? ,然后通过绑定机制传递给SQL。参数类型自动转换,自动防止SQL注入。
- {} 中的值替换到SQL中,拼接结果在SQL执行时被数据库解析。适用于动态SQL拼接,但容易导致SQL注入。





