《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
- 101. `ConcurrentHashMap`在JDK1.7和JDK1.8中的实现有何不同?
- 102. Java中的`String`、`StringBuffer`、`StringBuilder`有何区别?
- 103. Java中的`static`关键字有哪些用法?`static`方法中能否访问非`static`成员?
- 104. 什么是单例模式?如何实现一个线程安全的单例模式?
- 105. 解释Java中的类加载机制
- 106. 什么是泛型?Java中的泛型擦除是什么意思?
- 107. `Java 8`中的`Stream API`有什么作用?请举例说明。
- 108. `Java 8`中的`Lambda`表达式是什么?它的使用场景有哪些?
- 109. 什么是函数式接口?`Java 8`中有哪些内置的函数式接口?
- 110. Java中的`IO`流分为哪几类?`NIO`与`IO`的区别是什么?
- 111. Java中的HashMap和HashTable有什么区别?
- 112. 什么是Java中的异常链?如何实现?
- 113. 解释Java中的SPI机制(Service Provider Interface)
- 114. 什么是Java中的枚举(Enum)?它与普通类有何区别?
- 115. 解释Java中的动态代理机制,两种实现方式有何区别?
- 116. 什么是Java中的标记接口(Marker Interface)?请举例说明
- 117. 简述Java中的垃圾回收机制,常见的垃圾收集器有哪些?
- 118. 解释Java中的线程池参数,核心参数有哪些?
- 119. 什么是Java中的内部类?有哪些类型?
- 120. 什么是Java中的注解(Annotation)?如何自定义注解?
- 二、120道面试题目录列表
一、本文面试题目录
101. ConcurrentHashMap
在JDK1.7和JDK1.8中的实现有何不同?
答案:
版本 | 实现方式 | 锁粒度 | 并发度 |
---|---|---|---|
JDK1.7 | 分段锁(Segment 数组+HashEntry ) | 对Segment加锁 | 等于Segment数量(默认16) |
JDK1.8 | CAS+synchronized(数组+链表/红黑树) | 对链表头/红黑树节点加锁 | 更高(理论上支持数组长度级别的并发) |
JDK1.8优化:
- 移除
Segment
,直接使用Node
数组,减少锁开销。 - 链表转红黑树(同
HashMap
),提升查询效率。
102. Java中的String
、StringBuffer
、StringBuilder
有何区别?
答案:
特性 | String | StringBuffer | StringBuilder |
---|---|---|---|
可变性 | 不可变(每次修改创建新对象) | 可变(同char数组,线程安全) | 可变(同char数组,线程不安全) |
线程安全 | 安全(不可变) | 安全(方法加synchronized ) | 不安全 |
性能 | 低(频繁修改产生大量垃圾对象) | 中(锁开销) | 高(无锁) |
适用场景:
String
:字符串不频繁修改(如常量定义)。StringBuffer
:多线程下频繁修改(如日志拼接)。StringBuilder
:单线程下频繁修改(如字符串拼接)。
代码示例:
String s = "a";
s += "b"; // 创建新对象,原对象不变
StringBuilder sb = new StringBuilder();
sb.append("a").append("b"); // 高效拼接
103. Java中的static
关键字有哪些用法?static
方法中能否访问非static
成员?
答案:
用法:
- 修饰变量(静态变量):属于类,所有实例共享,可通过类名直接访问。
- 修饰方法(静态方法):属于类,可直接调用,无需实例化。
- 修饰代码块(静态代码块):类加载时执行,仅执行一次(用于初始化静态资源)。
- 修饰内部类(静态内部类):可独立于外部类实例存在,不能访问外部类非静态成员。
static
方法中不能访问非static
成员:非静态成员属于实例,而static
方法属于类,调用时可能无实例。
代码示例:
class StaticDemo {
static int count = 0; // 静态变量
static { // 静态代码块
count = 10;
}
public static void printCount() { // 静态方法
System.out.println(count);
// System.out.println(name); // 错误:不能访问非静态成员
}
private String name; // 非静态成员
}
104. 什么是单例模式?如何实现一个线程安全的单例模式?
答案:
单例模式:保证一个类仅有一个实例,并提供全局访问点。
线程安全的实现方式:
-
饿汉式:类加载时初始化,天生线程安全(可能浪费内存)。
class SingletonHungry { private static final SingletonHungry INSTANCE = new SingletonHungry(); private SingletonHungry() {} // 私有构造 public static SingletonHungry getInstance() { return INSTANCE; } }
-
懒汉式(双重检查锁):延迟初始化,同步优化(推荐)。
class SingletonLazy { // volatile防止指令重排序导致的半初始化问题 private static volatile SingletonLazy INSTANCE; private SingletonLazy() {} public static SingletonLazy getInstance() { if (INSTANCE == null) { // 第一次检查(无锁) synchronized (SingletonLazy.class) { if (INSTANCE == null) { // 第二次检查(有锁) INSTANCE = new SingletonLazy(); } } } return INSTANCE; } }
105. 解释Java中的类加载机制
- 原理:类加载机制是将.class文件中的二进制数据加载到内存中,对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。类加载过程分为5个阶段:加载、验证、准备、解析、初始化(其中验证、准备、解析统称为连接)。
- 加载:通过类的全限定名获取二进制字节流,将其转换为方法区的运行时数据结构,在内存中生成一个代表该类的
java.lang.Class
对象。 - 验证:确保.class文件的字节流包含的信息符合JVM规范,保证安全。
- 准备:为类的静态变量分配内存,并设置默认初始值(如
int
默认0,boolean
默认false)。 - 解析:将常量池中的符号引用替换为直接引用(内存地址)。
- 初始化:执行类构造器
<clinit>()
方法,初始化静态变量和静态代码块(按声明顺序执行)。
- 加载:通过类的全限定名获取二进制字节流,将其转换为方法区的运行时数据结构,在内存中生成一个代表该类的
- 代码示例:
public class ClassLoadingDemo {
// 静态变量(准备阶段赋默认值0,初始化阶段赋10)
private static int a = 10;
// 静态代码块(初始化阶段执行)
static {
System.out.println("静态代码块执行,a=" + a);
}
public static void main(String[] args) {
// 首次使用类时触发初始化
ClassLoadingDemo demo = new ClassLoadingDemo();
}
}
// 输出:静态代码块执行,a=10
106. 什么是泛型?Java中的泛型擦除是什么意思?
答案:
泛型:允许在定义类、接口、方法时指定类型参数(如List<String>
),编译时检查类型安全,避免强制转换。
泛型擦除:Java泛型是编译时特性,运行时类型信息被擦除(替换为原生类型)。例如List<String>
和List<Integer>
在运行时都是List
。
代码示例:
public class GenericDemo<T> {
private T value;
public T getValue() { return value; }
public void setValue(T value) { this.value = value; }
public static void main(String[] args) {
GenericDemo<String> demo = new GenericDemo<>();
demo.setValue("Hello");
// 泛型擦除:运行时getClass()返回GenericDemo,而非GenericDemo<String>
System.out.println(demo.getClass()); // 输出:class GenericDemo
}
}
107. Java 8
中的Stream API
有什么作用?请举例说明。
答案:
Stream API
用于对集合进行高效的聚合操作(过滤、映射、排序等),支持链式调用和并行处理,代码更简洁。
常用操作:
- 中间操作(返回Stream):
filter()
、map()
、sorted()
。 - 终端操作(返回结果):
collect()
、forEach()
、count()
。
代码示例:
List<String> list = Arrays.asList("apple", "banana", "cherry", "date");
// 过滤长度>5的字符串,转为大写,收集到List
List<String> result = list.stream()
.filter(s -> s.length() > 5) // 过滤
.map(String::toUpperCase) // 转换
.sorted() // 排序
.collect(Collectors.toList()); // 收集结果
System.out.println(result); // 输出:[BANANA, CHERRY]
108. Java 8
中的Lambda
表达式是什么?它的使用场景有哪些?
答案:
Lambda表达式:是匿名函数的简化写法,用于替代SAM(Single Abstract Method)接口的匿名实现类,语法为(参数) -> 表达式/代码块
。
使用场景:
- 函数式接口(如
Runnable
、Comparator
)。 Stream API
的中间操作(如filter
、map
)。
代码示例:
// 替代Runnable匿名类
new Thread(() -> System.out.println("Lambda线程")).start();
// 简化Comparator
List<String> list = Arrays.asList("b", "a", "c");
list.sort((s1, s2) -> s1.compareTo(s2)); // 等价于Comparator.naturalOrder()
109. 什么是函数式接口?Java 8
中有哪些内置的函数式接口?
答案:
函数式接口:只包含一个抽象方法的接口(可包含默认方法和静态方法),可用@FunctionalInterface
注解标记。
内置函数式接口:
Consumer<T>
:消费型接口(void accept(T t)
),如forEach
的参数。Supplier<T>
:供给型接口(T get()
),用于提供数据。Function<T, R>
:函数型接口(R apply(T t)
),如map
的参数。Predicate<T>
:断言型接口(boolean test(T t)
),如filter
的参数。
代码示例:
// Consumer示例
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello Lambda"); // 输出:Hello Lambda
// Predicate示例
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // 输出:true
110. Java中的IO
流分为哪几类?NIO
与IO
的区别是什么?
答案:
IO流分类:
- 按流向:输入流(
InputStream
、Reader
)、输出流(OutputStream
、Writer
)。 - 按数据单位:字节流(
InputStream
、OutputStream
)、字符流(Reader
、Writer
)。
NIO与IO的区别:
特性 | IO | NIO |
---|---|---|
模型 | 面向流(Stream) | 面向缓冲区(Buffer) |
阻塞方式 | 阻塞IO(BIO) | 非阻塞IO(通过Selector) |
处理方式 | 单向流 | 双向通道(Channel) |
NIO示例(文件读取):
try (FileChannel channel = new FileInputStream("test.txt").getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
int bytesRead = channel.read(buffer); // 读取到缓冲区
while (bytesRead != -1) {
buffer.flip(); // 切换为读模式
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get()); // 读取缓冲区数据
}
buffer.clear(); // 清空缓冲区
bytesRead = channel.read(buffer);
}
}
No. | 大剑师精品GIS教程推荐 |
---|---|
0 | 地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 |
1 | Openlayers 【入门教程】 - 【源代码+示例 300+】 |
2 | Leaflet 【入门教程】 - 【源代码+图文示例 150+】 |
3 | MapboxGL 【入门教程】 - 【源代码+图文示例150+】 |
4 | Cesium 【入门教程】 - 【源代码+综合教程 200+】 |
5 | threejs 【中文API】 - 【源代码+图文示例200+】 |
111. Java中的HashMap和HashTable有什么区别?
原理:两者均实现Map
接口,核心区别如下:
特性 | HashMap | HashTable |
---|---|---|
线程安全 | 非线程安全 | 线程安全(方法加synchronized ) |
允许null键/值 | 允许1个null键和多个null值 | 不允许null键/值 |
效率 | 高(无锁) | 低(锁竞争) |
底层数组初始化 | 初始容量16,扩容为2倍 | 初始容量11,扩容为2n+1 |
继承关系 | 继承AbstractMap | 继承Dictionary |
代码示例:
import java.util.HashMap;
import java.util.Hashtable;
public class MapCompare {
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(null, "nullKey"); // 允许null键
hashMap.put("key", null); // 允许null值
Hashtable<String, String> hashtable = new Hashtable<>();
// hashtable.put(null, "nullKey"); // 抛出NullPointerException
// hashtable.put("key", null); // 抛出NullPointerException
}
}
112. 什么是Java中的异常链?如何实现?
- 原理:异常链是将捕获的一个异常作为另一个异常的原因(cause)进行包装,形成异常的传递链条。这样可以保留原始异常信息,便于排查问题根源。在实际开发中,常用来将底层异常(如IO异常、SQL异常)包装为业务异常向上传递。
- 代码示例:
public class ExceptionChainDemo {
public static void main(String[] args) {
try {
method1();
} catch (BusinessException e) {
e.printStackTrace();
// 可以通过e.getCause()获取原始异常
System.out.println("原始异常:" + e.getCause().getMessage());
}
}
public static void method1() throws BusinessException {
try {
// 模拟IO操作
int i = 1 / 0;
} catch (ArithmeticException e) {
// 将原始异常包装为业务异常抛出
throw new BusinessException("方法执行失败", e);
}
}
}
class BusinessException extends Exception {
public BusinessException(String message, Throwable cause) {
super(message, cause); // 调用父类构造器传递cause
}
}
113. 解释Java中的SPI机制(Service Provider Interface)
- 原理:SPI是一种服务发现机制,允许第三方为接口提供实现,通过配置文件让系统自动加载实现类。核心思想是“接口定义在调用方,实现类在第三方”,常用于框架扩展(如JDBC驱动加载、日志框架适配)。
- 实现步骤:
- 定义服务接口;
- 第三方提供接口实现类;
- 在
META-INF/services
目录下创建以接口全限定名为文件名的文件,文件内容为实现类的全限定名; - 调用方通过
ServiceLoader
加载实现类。
- 代码示例:
// 1. 定义服务接口
public interface LogService {
void log(String message);
}
// 2. 第三方实现类(LogbackImpl)
public class LogbackImpl implements LogService {
@Override
public void log(String message) {
System.out.println("Logback: " + message);
}
}
// 3. 配置文件:META-INF/services/com.example.LogService,内容为com.example.LogbackImpl
// 4. 调用方加载
import java.util.ServiceLoader;
public class SPIDemo {
public static void main(String[] args) {
ServiceLoader<LogService> serviceLoader = ServiceLoader.load(LogService.class);
for (LogService logService : serviceLoader) {
logService.log("SPI机制测试"); // 输出:Logback: SPI机制测试
}
}
}
114. 什么是Java中的枚举(Enum)?它与普通类有何区别?
- 原理:枚举是一种特殊的类,用于定义固定数量的常量实例(如季节、星期)。枚举默认继承
java.lang.Enum
,且构造器必须为私有(确保无法外部实例化)。 - 与普通类的区别:
- 枚举常量是唯一实例,无法通过
new
创建; - 枚举默认实现
Serializable
和Comparable
接口; - 枚举可以有抽象方法,但每个常量必须实现;
- 枚举不能被继承。
- 枚举常量是唯一实例,无法通过
- 代码示例:
enum Weekday {
MONDAY("周一") {
@Override
public void work() {
System.out.println("开始一周工作");
}
},
FRIDAY("周五") {
@Override
public void work() {
System.out.println("准备周末");
}
};
private String desc;
// 私有构造器
Weekday(String desc) {
this.desc = desc;
}
// 抽象方法,每个常量必须实现
public abstract void work();
public String getDesc() {
return desc;
}
}
public class EnumDemo {
public static void main(String[] args) {
Weekday day = Weekday.MONDAY;
System.out.println(day.getDesc()); // 输出:周一
day.work(); // 输出:开始一周工作
}
}
115. 解释Java中的动态代理机制,两种实现方式有何区别?
- 原理:动态代理是在运行时动态生成代理类,用于增强目标对象的方法(如日志、事务)。Java中有两种动态代理实现:
- JDK动态代理:基于接口,通过
java.lang.reflect.Proxy
和InvocationHandler
实现,要求目标类必须实现接口。 - CGLIB动态代理:基于继承,通过生成目标类的子类实现代理,无需目标类实现接口(但目标类不能是final类)。
- JDK动态代理:基于接口,通过
- 代码示例(JDK动态代理):
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 目标接口
interface UserService {
void addUser();
}
// 目标实现类
class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("添加用户");
}
}
// 代理处理器
class LogHandler implements InvocationHandler {
private Object target;
public LogHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("方法执行前日志");
Object result = method.invoke(target, args); // 调用目标方法
System.out.println("方法执行后日志");
return result;
}
}
public class JdkProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
// 生成代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogHandler(target)
);
proxy.addUser();
// 输出:
// 方法执行前日志
// 添加用户
// 方法执行后日志
}
}
116. 什么是Java中的标记接口(Marker Interface)?请举例说明
- 原理:标记接口是没有任何方法和属性的接口,仅用于标记类的某种特性,告知编译器或JVM该类具有特定行为。
- 常见示例:
Serializable
:标记类可序列化(JVM会对实现该接口的类进行特殊处理);Cloneable
:标记类可克隆(调用clone()
方法时需实现该接口,否则抛CloneNotSupportedException
);RandomAccess
:标记集合支持快速随机访问(如ArrayList
实现该接口,LinkedList
未实现)。
- 代码示例:
import java.io.Serializable;
// 实现Serializable标记接口,表明该类可序列化
class Student implements Serializable {
private String name;
private int age;
// 构造器、getter、setter省略
}
public class MarkerInterfaceDemo {
public static void main(String[] args) {
Student student = new Student();
// 判断是否实现了Serializable接口
if (student instanceof Serializable) {
System.out.println("Student可序列化");
}
}
}
117. 简述Java中的垃圾回收机制,常见的垃圾收集器有哪些?
- 原理:垃圾回收(GC)是JVM自动释放不再被引用的对象内存的过程。核心步骤包括:
- 标记:通过可达性分析(以GC Roots为起点)判断对象是否存活;
- 清除:回收未被标记的对象内存;
- 整理:压缩存活对象,减少内存碎片。
- 常见垃圾收集器:
- SerialGC:单线程收集,适用于单CPU环境;
- ParallelGC:多线程收集,注重吞吐量;
- CMS(Concurrent Mark Sweep):并发收集,注重响应时间(已被G1替代);
- G1(Garbage-First):分区收集,兼顾吞吐量和响应时间;
- ZGC/Shenandoah:低延迟垃圾收集器,适用于大堆场景。
118. 解释Java中的线程池参数,核心参数有哪些?
- 原理:线程池是管理线程的容器,通过复用线程减少创建和销毁线程的开销。
ThreadPoolExecutor
的核心参数包括:- corePoolSize:核心线程数(始终存活);
- maximumPoolSize:最大线程数(核心线程+临时线程);
- keepAliveTime:临时线程空闲存活时间;
- workQueue:任务等待队列;
- threadFactory:线程创建工厂;
- handler:拒绝策略(任务队列满且线程数达最大值时的处理方式)。
- 代码示例:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, // 核心线程数
5, // 最大线程数
60, // 临时线程存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3), // 任务队列(容量3)
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略(抛出异常)
);
// 提交任务
for (int i = 0; i < 9; i++) { // 任务数超过最大线程+队列容量(5+3=8)
int taskNum = i;
executor.execute(() -> {
System.out.println("执行任务:" + taskNum);
try { Thread.sleep(1000); } catch (InterruptedException e) {}
});
}
executor.shutdown();
// 输出:前8个任务执行,第9个任务抛出RejectedExecutionException
}
}
119. 什么是Java中的内部类?有哪些类型?
- 原理:内部类是定义在另一个类内部的类,目的是隐藏实现细节、增强封装性,且能访问外部类的私有成员。
- 类型:
- 成员内部类:定义在外部类成员位置,可访问外部类所有成员(需通过外部类实例创建);
- 静态内部类:用
static
修饰,只能访问外部类静态成员(无需外部类实例); - 局部内部类:定义在方法内部,作用域仅限该方法;
- 匿名内部类:没有类名的局部内部类,常用于简化接口实现。
- 代码示例:
public class InnerClassDemo {
private int outerField = 10;
private static int staticOuterField = 20;
// 成员内部类
class MemberInner {
void print() {
System.out.println(outerField); // 访问外部类非静态成员
}
}
// 静态内部类
static class StaticInner {
void print() {
System.out.println(staticOuterField); // 访问外部类静态成员
}
}
public void method() {
// 局部内部类
class LocalInner {
void print() {
System.out.println("局部内部类");
}
}
new LocalInner().print();
}
public static void main(String[] args) {
InnerClassDemo outer = new InnerClassDemo();
// 创建成员内部类实例
outer.new MemberInner().print(); // 输出10
// 创建静态内部类实例
new StaticInner().print(); // 输出20
// 匿名内部类(实现Runnable接口)
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类线程");
}
}).start(); // 输出:匿名内部类线程
outer.method(); // 输出:局部内部类
}
}
120. 什么是Java中的注解(Annotation)?如何自定义注解?
- 原理:注解是一种标记性代码,用于为程序元素(类、方法、属性等)添加元数据,可通过反射在编译时或运行时解析。注解本身不影响程序逻辑,但工具或框架可基于注解实现特定功能(如
@Override
用于检查方法重写)。 - 自定义注解步骤:
- 用
@interface
声明注解; - 定义注解属性(类似方法);
- 使用元注解(如
@Target
、@Retention
)指定注解的适用范围和保留周期。
- 用
- 代码示例:
import java.lang.annotation.*;
// 元注解:指定注解适用范围(方法)和保留周期(运行时)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Log {
String value() default "操作日志"; // 注解属性,默认值为"操作日志"
}
class UserService {
@Log("用户登录") // 使用自定义注解
public void login() {
System.out.println("执行登录操作");
}
}
public class AnnotationDemo {
public static void main(String[] args) throws Exception {
// 反射获取注解信息
Method method = UserService.class.getMethod("login");
if (method.isAnnotationPresent(Log.class)) {
Log logAnnotation = method.getAnnotation(Log.class);
System.out.println("注解值:" + logAnnotation.value()); // 输出:用户登录
}
new UserService().login();
}
}
二、120道面试题目录列表
文章序号 | Java面试题120道 |
---|---|
1 | Java面试题及详细答案120道(01-20) |
2 | Java面试题及详细答案120道(21-40) |
3 | Java面试题及详细答案120道(41-60) |
4 | Java面试题及详细答案120道(61-80) |
5 | Java面试题及详细答案120道(81-100) |
6 | Java面试题及详细答案120道(5101-120) |