跳到主要内容
Java 基础面试考点精讲 | 极客日志
Java java
Java 基础面试考点精讲 综述由AI生成 系统梳理了 Java 基础面试高频考点,包括跨平台原理、八种基本数据类型、面向对象四大特征、包装类型机制、String 类特性、集合框架对比、常见异常处理及多线程创建方式。通过源码分析与代码示例,深入讲解 HashMap、ArrayList、线程池等核心知识点,助力求职者夯实基础。
Kubernet 发布于 2026/3/27 更新于 2026/5/29 26 浏览1. 简单说下什么是跨平台
术语:操作系统指令集、屏蔽系统之间的差异
由于各种操作系统所支持的指令集不是完全一致,所以在操作系统之上加个虚拟机可以来提供统一接口,屏蔽系统之间的差异。
2. Java 有几种基本数据类型
有八种基本数据类型。
数据类型 字节 默认值 byte 1 0 short 2 0 int 4 0 long 8 0 float 4 0.0f double 8 0.0d char 2 '\u0000' boolean 4 false
各自占用几字节也记一下。
3. 面向对象特征
面向对象的编程语言有封装、继承、抽象、多态等 4 个主要的特征。
封装 :把描述一个对象的属性和行为的代码封装在一个模块中,也就是一个类中,属性用变量定义,行为用方法进行定义,方法可以直接访问同一个对象中的属性。
抽象 :把现实生活中的对象抽象为类。分为过程抽象和数据抽象
数据抽象 --> 鸟有翅膀,羽毛等 (类的属性)
过程抽象 --> 鸟会飞,会叫 (类的方法)
继承 :子类继承父类的特征和行为。子类可以有父类的方法,属性(非 private)。子类也可以对父类进行扩展,也可以重写父类的方法。缺点就是提高代码之间的耦合性。
多态 :多态是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定 (比如:向上转型,只有运行才能确定其对象属性)。方法覆盖和重载体现了多态性。
4. 为什么要有包装类型
术语:让基本类型也具有对象的特征
基本类型 包装器类型 boolean Boolean char Character int Integer byte Byte short Short long Long float Float double Double
为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型 Collection 时就一定要使用包装类型而非基本类型)因为容器都是装 object 的,这是就需要这些基本类型的包装器类了。
自动装箱:new Integer(6);,底层调用:Integer.valueOf(6)
自动拆箱:int i = new Integer(6);,底层调用 i.intValue(); 方法实现。
Integer i = 6 ;
Integer j = 6 ;
System.out.println(i == j);
public static Integer valueOf (int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer (i);
}
二者的区别:
声明方式不同:基本类型不使用 new 关键字,而包装类型需要使用 new 关键字来在堆中分配存储空间 ;
存储方式及位置不同:基本类型是直接将变量值存储在栈中,而包装类型是将对象放在堆中,然后通过引用来使用;
初始值不同:基本类型的初始值如 int 为 0,boolean 为 false,而包装类型的初始值为 null;
使用方式不同:基本类型直接赋值直接使用就好,而包装类型在集合如 Collection、Map 时会使用到。
5. ==和 equals 区别
== 较的是两个引用在内存中指向的是不是同一对象(即同一内存空间),也就是说在内存空间中的存储位置是否一致。如果两个对象的引用相同时(指向同一对象时),"=="操作符返回 true,否则返回 false。
equals 用来比较某些特征 是否一样。我们平时用的 String 类等的 equals 方法都是重写后的,实现比较两个对象的内容是否相等。
我们来看看 String 重写的 equals 方法:
它不止判断了内存地址,还增加了字符串是否相同的比较。
public boolean equals (Object anObject) {
if (this == anObject) {
return true ;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0 ;
while (n-- != 0 ) {
if (v1[i] != v2[i])
return false ;
i++;
}
return true ;
}
}
return false ;
}
6. String、StringBuffer 和 StringBuilder 区别
java 中 String、StringBuffer、StringBuilder 是编程中经常使用的字符串类,他们之间的区别也是经常在面试中会问到的问题。现在总结一下,看看他们的不同与相同。
1. 数据可变和不可变
String 底层使用一个不可变的字符数组 private final char value[]; 所以它内容不可变。
StringBuffer 和 StringBuilder 都继承了 AbstractStringBuilder 底层使用的是可变字符数组:char[] value;
2. 线程安全
StringBuilder 是线程不安全的,效率较高;而 StringBuffer 是线程安全的,效率较低。
通过他们的 append() 方法来看,StringBuffer 是有同步锁,而 StringBuilder 没有:
@Override
public synchronized StringBuffer append (Object obj) {
toStringCache = null ;
super .append(String.valueOf(obj));
return this ;
}
@Override
public StringBuilder append (String str) {
super .append(str);
return this ;
}
3. 相同点 StringBuilder 与 StringBuffer 有公共父类 AbstractStringBuilder。
最后,操作可变字符串速度:StringBuilder > StringBuffer > String,这个答案就显得不足为奇了。
7. 讲一下 Java 中的集合
Collection 下:List 系 (有序、元素允许重复) 和 Set 系 (无序、元素不重复)
set 根据 equals 和 hashcode 判断,一个对象要存储在 Set 中,必须重写 equals 和 hashCode 方法
Map 下:HashMap 线程不同步;TreeMap 线程不安全
Collection 系列和 Map 系列:Map 是对 Collection 的补充,两个没什么关系
8. ArrayList 和 LinkedList 区别?
之前专门有写过 ArrayList 和 LinkedList 源码的文章。
ArrayList 是实现了基于动态数组的数据结构,LinkedList 基于链表的数据结构。
对于随机访问 get 和 set,ArrayList 觉得优于 LinkedList,因为 LinkedList 要移动指针。
对于新增和删除操作 add 和 remove,LinedList 比较占优势,因为 ArrayList 要移动数据。
9. ConcurrentModificationException 异常出现的原因 public class Test {
public static void main (String[] args) {
ArrayList<Integer> list = new ArrayList <Integer>();
list.add(2 );
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
Integer integer = iterator.next();
if (integer==2 )
list.remove(integer);
}
}
}
执行上段代码是有问题的,会抛出 ConcurrentModificationException 异常。
原因 :调用 list.remove() 方法导致 modCount 和 expectedModCount 的值不一致。
final void checkForComodification () {
if (modCount != expectedModCount)
throw new ConcurrentModificationException ();
}
解决办法 :在迭代器中如果要删除元素的话,需要调用 Iterator 类的 remove 方法。
public class Test {
public static void main (String[] args) {
ArrayList<Integer> list = new ArrayList <Integer>();
list.add(2 );
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
Integer integer = iterator.next();
if (integer==2 )
iterator.remove();
}
}
}
10. HashMap 和 HashTable、ConcurrentHashMap 区别?
HashMap 和 HashTable 都实现了 Map 接口
都可以存储 key-value 数据
HashMap 可以把 null 作为 key 或 value,HashTable 不可以
HashMap 线程不安全,效率高。HashTable 线程安全,效率低。
HashMap 的迭代器 (Iterator) 是 fail-fast 迭代器,而 Hashtable 的 enumerator 迭代器不是 fail-fast 的。
什么是 fail-fast?
就是最快的时间能把错误抛出而不是让程序执行。
10.2 如何保证线程安全又效率高? Java 5 提供了 ConcurrentHashMap,它是 HashTable 的替代,比 HashTable 的扩展性更好。
ConcurrentHashMap 将整个 Map 分为 N 个 segment(类似 HashTable),可以提供相同的线程安全,但是效率提升 N 倍,默认 N 为 16。
10.3 我们能否让 HashMap 同步? HashMap 可以通过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);
11. 拷贝文件的工具类使用字节流还是字符流
11.1 什么是字节流,什么是字符流?
11.2 答案 我们并不支持下载的文件有没有包含字节流 (图片、影像、音源),所以考虑到通用性,我们会用字节流。
12. 线程创建方式
方法一:继承 Thread 类,作为线程对象存在(继承 Thread 对象) public class CreatThreadDemo1 extends Thread {
public CreatThreadDemo1 (String name) {
super (name);
}
@Override
public void run () {
while (!interrupted()){
System.out.println(getName()+"线程执行了..." );
try {
Thread.sleep(200 );
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main (String[] args) {
CreatThreadDemo1 d1 = new CreatThreadDemo1 ("first" );
CreatThreadDemo1 d2 = new CreatThreadDemo1 ("second" );
d1.start();
d2.start();
d1.interrupt();
}
}
常规方法,不多做介绍了,interrupted 方法,是来判断该线程是否被中断。(终止线程不允许用 stop 方法,该方法不会施放占用的资源。所以我们在设计程序的时候,要按照中断线程的思维去设计,就像上面的代码一样)。
让线程等待的方法
Thread.sleep(200); //线程休息 2ms
Object.wait(); //让线程进入等待,直到调用 Object 的 notify 或者 notifyAll 时,线程停止休眠
方法二:实现 runnable 接口,作为线程任务存在 public class CreatThreadDemo2 implements Runnable {
@Override
public void run () {
while (true ){
System.out.println("线程执行了..." );
}
}
public static void main (String[] args) {
Thread thread = new Thread (new CreatThreadDemo2 ());
thread.start();
}
}
Runnable 只是来修饰线程所执行的任务,它不是一个线程对象。想要启动 Runnable 对象,必须将它放到一个线程对象里。
方法三:匿名内部类创建线程对象 public class CreatThreadDemo3 extends Thread {
public static void main (String[] args) {
new Thread (){
@Override
public void run () {
System.out.println("线程执行了..." );
}
}.start();
new Thread (new Runnable () {
@Override
public void run () {
System.out.println("线程执行了..." );
}
}).start();
new Thread (new Runnable () {
@Override
public void run () {
System.out.println("runnable run 线程执行了..." );
}
}){
@Override
public void run () {
System.out.println("override run 线程执行了..." );
}
}.start();
}
}
创建带线程任务并且重写 run 方法的线程对象中,为什么只运行了 Thread 的 run 方法。我们看看 Thread 类的源码,可以看到 Thread 实现了 Runnable 接口,而 Runnable 接口里有一个 run 方法。所以,我们最终调用的重写的方法应该是 Thread 类的 run 方法。而不是 Runnable 接口的 run 方法。
方法四:创建带返回值的线程 public class CreatThreadDemo4 implements Callable {
public static void main (String[] args) throws ExecutionException, InterruptedException {
CreatThreadDemo4 demo4 = new CreatThreadDemo4 ();
FutureTask<Integer> task = new FutureTask <Integer>(demo4);
Thread thread = new Thread (task);
thread.start();
System.out.println("我可以在这里做点别的业务逻辑...因为 FutureTask 是提前完成任务" );
Integer result = task.get();
System.out.println("线程中运算的结果为:" +result);
}
@Override
public Object call () throws Exception {
int result = 1 ;
System.out.println("业务逻辑计算中..." );
Thread.sleep(3000 );
return result;
}
}
public interface Callable <V> {
V call () throws Exception;
}
返回指定泛型的 call 方法。然后调用 FutureTask 对象的 get 方法得道 call 方法的返回值。
方法五:定时器 Timer public class CreatThreadDemo5 {
public static void main (String[] args) {
Timer timer = new Timer ();
timer.schedule(new TimerTask () {
@Override
public void run () {
System.out.println("定时器线程执行了..." );
}
},0 ,1000 );
}
}
方法六:线程池创建线程 public class CreatThreadDemo6 {
public static void main (String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(10 );
long threadpoolUseTime = System.currentTimeMillis();
for (int i=0 ;i<10 ;i++){
threadPool.execute(new Runnable () {
@Override
public void run () {
System.out.println(Thread.currentThread().getName()+"线程执行了..." );
}
});
}
long threadpoolUseTime1 = System.currentTimeMillis();
System.out.println("多线程用时" +(threadpoolUseTime1-threadpoolUseTime));
threadPool.shutdown();
threadpoolUseTime = System.currentTimeMillis();
}
}
方法七:利用 java8 新特性 stream 实现并发 lambda 表达式不懂的,可以看看我的 java8 新特性文章。
public class CreatThreadDemo7 {
public static void main (String[] args) {
List<Integer> values = Arrays.asList(10 ,20 ,30 ,40 );
int result = values.parallelStream().mapToInt(p -> p*2 ).sum();
System.out.println(result);
values.parallelStream().forEach(p-> System.out.println(p));
}
}
怎么证明它是并发处理呢,他们并不是按照顺序输出的。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online