非科班如何在校招中突出重围-面试准备篇《Java基础篇》
写在之前
Hello,大家好,好久没有更新文章了,当了很久的咸鱼~
上一篇和大家分享了当初校招时如何艰难的寻找实习,总体写得比较简单,今天给大家分享一下当时准备面试时的一些面试题目,之前秋招过程中得到了多位优秀大佬的指导,也看了很多经典的书籍(比如《Java编程思想》、《深入理解Java虚拟机》、《Java并发编程艺术》等),总结了一些知识点,分享出来与大家共享,同时自己和温习一些知识点。
这篇文章主题主要是《Java基础相关的面试题目》,讲解的结构主要是题目 + 答案展开,大家可以先看题目,再看答案,不建议直接背诵答案。
核心问题十二问
1. String,StringBuilder 以及 StringBuffer三者区别
关于这三个字符串类的异同之处主要关注可变不可变、安全不安全两个方面:
StringBuffer(同步的)和String(不可变的)都是线程安全的,StringBuilder是线程不安全的;String是不可变的,StringBuilder和StringBuffer是可变的;String的连接操作的底层是由StringBuilder实现的;- 三者都是
final的,不允许被继承; StringBuilder以及StringBuffer都是抽象类AbstractStringBuilder的子类,它们的接口是相同的。
为什么String是不可变的?
String主要的三个成员变量char value[], int offset, int count均是private,final的,并且没有对应的getter/setter;String对象一旦初始化完成,上述三个成员变量就不可修改;并且其所提供的接口任何对这些域的修改都将返回一个新对象;
2. 重载,重写
- 重载:类内多态,静态绑定机制(编译时已经知道具体执行哪个方法),方法同名,参数不同
- 重写:类间多态,动态绑定机制(运行时确定),实例方法,遵循两小两同一大原则。
方法签名相同,子类的方法所抛出的异常、返回值的范围不大于父类的对应方法,子类的方法可见性不小于父类的对应方法
3. 抽象,封装,继承,多态
Java 的四大特性总结如下:
- 封装:把对象的属性和行为(数据)封装为一个独立的整体,并尽可能隐藏对象的内部实现细节;
- 继承:一种代码重用机制;
- 多态:分离了做什么和怎么做,从另一个角度将接口和实现分离开来,消除类型之间的耦合关系;表现形式:重载与重写;关于多态,大黄之前拙笔一篇《通俗聊聊什么是多态》
- 抽象:对继承的另一种表述;表现形式:接口(契约)与抽象类(模板)
4. ArrayList(动态数组)、LinkedList(带头结点的双向链表)两者区别
1、ArrayList主要特性如下:
- 默认初始容量为 10
- 扩容机制:添加元素前,先检查是否需要扩容,一般扩为源数组的 1.5 倍 + 1
- 边界检查(即检查
ArrayList的Size):涉及到index的操作; - 调整数组容量(减少容量):将底层数组的容量调整为当前列表保存的实际元素的大小;
- 在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理;
关于ArrayList可以参考之前大黄拙笔一篇《Java面试必考题-ArrayList常见知识点》
2、LinkedList核心点LinkedList 不但实现了List接口,还实现了Dequeue接口。因此,LinkedList不但可以当做List来用,还可以当做Stack(push、pop、peek),Queue(offer、poll、peek)来使用。
3、ArrayList与LinkedList两者比较
ArrayList是基于数组的实现,LinkedList是基于带头结点的双向循环链表的实现;ArrayList支持随机访问,LinkedList不支持;LinkedList可作队列和栈使用,实现了Dequeue接口,而ArrayList没有;ArrayList寻址效率较高,插入/删除效率较低;LinkedList插入/删除效率较高,寻址效率较低
5. 容器的Fail-Fast机制
Fail-Fast 是 Java 集合的一种错误检测机制,为了防止在某个线程在对Collection进行迭代时,其他线程对该Collection进行结构上的修改。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险,抛出 ConcurrentModificationException 异常。
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount = ArrayList.this.modCount;
public boolean hasNext() {
return (this.cursor != ArrayList.this.size);
}
public E next() {
checkForComodification();
/** 省略此处代码 */
}
public void remove() {
if (this.lastRet < 0)
throw new IllegalStateException();
checkForComodification();
/** 省略此处代码 */
}
final void checkForComodification() {
if (ArrayList.this.modCount == this.expectedModCount)
return;
throw new ConcurrentModificationException();
}
} 6. Set (HashSet,LinkedHashSet,TreeSet)各个实现类区别
Set不包含重复的元素,这是Set最大的特点,也是使用Set最主要的原因。
常用到的Set实现有 HashSet、LinkedHashSet 和 TreeSet。一般地,如果需要一个访问快速的Set,你应该使用HashSet;当你需要一个排序的Set,你应该使用TreeSet;当你需要记录下插入时的顺序时,你应该使用LinedHashSet。

1、HashSet:底层通过HashMap进行实现,实现了Set接口。
HashSet是采用hash表来实现的,其中的元素没有按顺序排列,add()、remove()以及contains()等方法都是复杂度为O(1)的方法。
通过观察底层代码发现,HashSet底层基本上都是通过HashMap实现
五个构造方法 = 4(基于HashMap的实现,用于实现HashSet) + 1(基于LinkedHashMap的实现,用于实现LinkedHashSet)
2、LinkedHashSet:是HashSet的子类,底层主要由HashMap的子类LinkedHashMap进行实现,实现了Set接口
LinkedHashSet继承于HashSet,利用下面的HashSet构造函数即可,注意到,其为包访问权限,专门供LinkedHashSet的构造函数调用。LinkedHashSet性能介于HashSet和TreeSet之间,是HashSet的子类,也是一个hash表,但是同时维护了一个双链表来记录插入的顺序,基本方法的复杂度为O(1)
3、TreeSet:底层主要由TreeMap进行实现。
TreeSet是采用树结构实现(红黑树算法),元素是按顺序进行排列,但是add()、remove()以及contains()等方法都是复杂度为O(log (n))的方法,它还提供了一些方法来处理排序的set,如first()、 last()、 headSet()和 tailSet()等。此外,TreeSet不同于HashSet和LinkedHashSet,其所存储的元素必须是可排序的(元素实现Comparable接口或者传入Comparator),并且不能存放null值。
7. Map及Map的三种常用实现
链表数组HashMap,LinkedHashMap(HashMap的子类,带头结点的双向链表 + HashMap),基于红黑树的TreeMap(对key排序)]
关于HashMap极度推荐,美团技术团队的——Java 8系列之重新认识HashMap
8. 如何判断容器中两个对象是否相等
判断两个对象的
hashCode是否相等:如果不相等,认为两个对象也不相等;如果相等,转入第二步;判断两个对象用
equals运算是否相等:如果不相等,认为两个对象也不相等;如果相等,认为两个对象相等
9. ConcurrentHashMap,HashMap 与 HashTable区别
本质:三者都实现了
Map接口,ConcurrentHashMap和HashMap是AbstractMap的子类,HashTable是Dictionary的子类;线程安全性:
HashMap是线程不安全的,但ConcurrentHashMap和HashTable是线程安全的,但二者保证线程安全的策略不同;前者采用的是分段锁机制,默认理想情况下,可支持16个线程的并发写和任意线程的并发读,效率较高;HashTable采用的是同步操作,效率较低键值约束:
HashMap允许键、值为null,但ConcurrentHashMap和HashTable既不允许键为null,也不允许值为null;哈希策略:三者哈希策略不同,
HashTable是key.hashCode取余;ConcurrentHashMap与HashMap都是先对hashCode进行再哈希,然后再与(桶数 - 1)进行取余运算,但是二者的再哈希算法不同;扩容机制:扩容检查机制不同,
ConcurrentHashMap和HashTable在插入元素前检查,HashMap在元素插入后检查;初始容量:
HashTable初始容量 11,扩容 2倍 + 1;HashMap初始容量16,扩容2倍
10. equals, hashCode, ==区别
大黄在之前的文章中已经详细的阐述了三者之间的区别,可以参见《》
1、总结来看有以下几点:
==用于判断两个对象是否为同一个对象或者两基本类型的值是否相等;equals用于判断两个对象内容是否相同;hashCode是一个对象的 消息摘要函数,一种 压缩映射,其一般与equals()方法同时重写;若不重写hashCode方法,默认使用Object类的hashCode方法,该方法是一个本地方法,由Object类定义的hashCode方***针对不同的对象返回不同的整数。
2、集合中使用可变对象作为Key带来的问题
HashMap用Key哈希值来存储和查找键值对,如果HashMap Key的哈希值在存储键值对后发生改变,那么Map可能再也查找不到这个Entry了。也就是说,在HashMap中可变对象作为Key会造成 数据丢失。
因此一般有如下建议:
- 在
HashMap中尽量使用不可变对象作为Key,比如,使用String等不可变类型用作Key是非常明智的或者使用自己定义的不可变类。 - 如果可变对象在
HashMap中被用作键,那就要小心在改变对象状态的时候,不要改变它的哈希值了,例如,可以只根据对象的标识属性生成HashCode。
3、重写equals但不重写HashCode会出现的问题
在使用Set时,若向其加入两个相同(equals返回为true)的对象,由于hashCode函数没有进行重写,那么这两个对象的hashCode值必然不同,它们很有可能被分散到不同的桶中,容易造成重复对象的存在。
11. 什么是不可变对象
一个不可变对象应该满足以下几个条件:
- 基本类型变量的值不可变;
- 引用类型变量不能指向其他对象;
- 引用类型所指向的对象的状态不可变;
- 除了构造函数之外,不应该有其它任何函数(至少是任何public函数)修改任何成员变量;
- 任何使成员变量获得新值的函数都应该将新的值保存在新的对象中,而保持原来的对象不被修改。
12. Java的序列化/反序列化机制
1、使用Serializable序列化/反序列化
将实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象,序列化可以弥补不同操作系统之间的差异。
序列化具体的含义:
- 需要序列化的对象必须实现
Serializable接口; - 只有非静态字段和非
transient字段进行序列化,与字段的可见性无关;
总结
《Offer快到碗里来》面试准备篇——Java基础篇01到这里结束了,这里提到的十二个知识点只是Java知识点中的一小撮,也是从之前准备秋招中挑选出的,尽量避免想与世面的面试题合集重合,后续有什么问题或者有什么想要了解的也可以私信我,我也会陆陆续续的完善Offer快到碗里来系列。
#Java#
