《前后端面试题
》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。
文章目录
- 一、本文面试题目录
-
-
- 41. 什么是工厂模式?简单工厂、工厂方法、抽象工厂的区别?
- 42. Java中的泛型有什么作用?
- 43. Java中的`Comparable`和`Comparator`有何区别?
- 44. 什么是Java中的注解?
- 45. Java中的线程池有什么作用?
- 46. 解释Java中的`双亲委派模型`
- 47. Java中的字符编码有哪些常见类型,它们有什么区别?
- 48. 什么是Java中的函数式接口?如何使用Lambda表达式?
- 49. 简述Java中的泛型擦除机制
- 50. 什么是Java中的线程局部变量(ThreadLocal)?如何使用?
- 51. 解释Java中的动态代理及其实现方式
- 52. 什么是Java中的注解(Annotation)?如何自定义注解?
- 53. 解释Java中的序列化与反序列化
- 54. Java中的`equals()`和`==`有什么区别?
- 55. 什么是Java中的Lambda表达式?适用于什么场景?
- 56. 解释Java中的Stream API及其常用操作
- 57. Java中的`volatile`关键字有什么作用?
- 58. Java中的`try-with-resources`语法有什么作用?
- 59. 解释Java中的深拷贝与浅拷贝
- 60. Java中的`String`、`StringBuilder`、`StringBuffer`有什么区别?
-
- 二、120道面试题目录列表
一、本文面试题目录
41. 什么是工厂模式?简单工厂、工厂方法、抽象工厂的区别?
答案:
工厂模式:用于创建对象,隐藏实例化逻辑,解耦对象创建与使用。
区别:
简单工厂:一个工厂类根据参数创建不同产品(违反开闭原则)。
class CarFactory { public static Car createCar(String type) { if ("Benz".equals(type)) return new Benz(); else if ("BMW".equals(type)) return new BMW(); return null; } }
工厂方法:每个产品对应一个工厂类,通过继承扩展(符合开闭原则)。
interface CarFactory { Car createCar(); } class BenzFactory implements CarFactory { public Car createCar() { return new Benz(); } } class BMWFactory implements CarFactory { public Car createCar() { return new BMW(); } }
抽象工厂:创建一系列相关产品(产品族),如汽车工厂同时生产汽车和发动机。
42. Java中的泛型有什么作用?
- 答案:泛型用于在编译时确保类型安全,可将类型作为参数传递,使代码更通用。例如List表示该列表只能存储String类型元素,避免了类型转换错误,提高了代码的可读性和可维护性。
43. Java中的Comparable
和Comparator
有何区别?
答案:
特性 | Comparable | Comparator |
---|---|---|
定义位置 | 类内部(implements Comparable ) |
类外部(单独实现Comparator 接口) |
方法 | int compareTo(T o) |
int compare(T o1, T o2) |
排序逻辑 | 类自身的默认排序(自然排序) | 外部定义的定制排序 |
适用场景 | 固定排序逻辑 | 灵活切换排序逻辑(如升序/降序) |
代码示例:
// Comparable:类内部实现
class Student implements Comparable<Student> {
int age;
@Override
public int compareTo(Student o) {
return Integer.compare(this.age, o.age); // 按年龄升序
}
}
// Comparator:外部实现
class StudentNameComparator implements Comparator<Student> {
@Override
public int compare(Student s1, Student s2) {
return s1.name.compareTo(s2.name); // 按姓名排序
}
}
44. 什么是Java中的注解?
- 答案:注解是Java 5.0引入的新特性,用于为代码添加元数据。它可以用于类、方法、变量等上面,提供信息给编译器、开发工具或运行时环境。例如@Override注解用于标识方法重写,@Deprecated注解表示方法已过时等。
45. Java中的线程池有什么作用?
- 答案:线程池用于管理和复用线程,避免频繁创建和销毁线程带来的开销,提高系统性能和资源利用率。常见的线程池类有ThreadPoolExecutor,可通过它创建不同类型的线程池,如FixedThreadPool(固定大小线程池)、CachedThreadPool(缓存线程池)等。
46. 解释Java中的双亲委派模型
原理:类加载器的委派机制,当加载类时,先委托父加载器加载,父加载器无法加载时才自己加载,避免类重复加载和安全问题(如自定义java.lang.String
不会被加载)。
类加载器层次:
- 启动类加载器(
Bootstrap ClassLoader
):加载JAVA_HOME/lib
下的类; - 扩展类加载器(
Extension ClassLoader
):加载JAVA_HOME/lib/ext
下的类; - 应用类加载器(
Application ClassLoader
):加载类路径下的类; - 自定义类加载器:继承
ClassLoader
,重写findClass()
。
代码示例(类加载委派):
public class ClassLoaderDemo {
public static void main(String[] args) {
// 获取当前类的类加载器(应用类加载器)
ClassLoader appClassLoader = ClassLoaderDemo.class.getClassLoader();
System.out.println(appClassLoader); // 输出sun.misc.Launcher$AppClassLoader
// 父加载器(扩展类加载器)
ClassLoader extClassLoader = appClassLoader.getParent();
System.out.println(extClassLoader); // 输出sun.misc.Launcher$ExtClassLoader
// 启动类加载器(C++实现,返回null)
ClassLoader bootstrapClassLoader = extClassLoader.getParent();
System.out.println(bootstrapClassLoader); // 输出null
}
}
47. Java中的字符编码有哪些常见类型,它们有什么区别?
- 答案:常见的字符编码有ASCII、UTF - 8、UTF - 16等。ASCII编码用7位二进制数表示一个字符,只能表示英文字母、数字和一些特殊字符;UTF - 8是一种可变长度编码,可表示世界上几乎所有字符,对英文字符用1个字节表示,对其他字符根据需要用2 - 4个字节表示;UTF - 16通常用2个字节表示一个字符,但对于一些特殊字符需用4个字节。
48. 什么是Java中的函数式接口?如何使用Lambda表达式?
- 原理:函数式接口是只包含一个抽象方法的接口(可以有默认方法和静态方法),用
@FunctionalInterface
注解标识。Lambda表达式是函数式接口的实例化方式,用于简化匿名内部类的写法。 - 代码示例:
import java.util.function.Predicate;
// 自定义函数式接口
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
// 使用Lambda表达式实现Calculator接口
Calculator add = (a, b) -> a + b;
System.out.println(add.calculate(2, 3)); // 输出5
// 内置函数式接口Predicate(判断条件)
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4)); // 输出true
System.out.println(isEven.test(5)); // 输出false
}
}
49. 简述Java中的泛型擦除机制
- 原理:Java泛型是“伪泛型”,在编译阶段会进行类型擦除:编译器将泛型类型参数替换为其边界类型(若无边界则替换为
Object
),并在必要时插入类型转换代码。这意味着泛型信息在运行时不存在,无法通过反射获取泛型参数的具体类型(除非通过匿名内部类等特殊方式)。 - 代码示例:
import java.util.ArrayList;
public class GenericErasureDemo {
public static void main(String[] args) {
ArrayList<String> strList = new ArrayList<>();
ArrayList<Integer> intList = new ArrayList<>();
// 类型擦除后,两者的运行时类型相同
System.out.println(strList.getClass() == intList.getClass()); // 输出true
// 编译时泛型检查,运行时无限制(可通过反射绕过)
try {
strList.getClass().getMethod("add", Object.class).invoke(strList, 10);
System.out.println(strList); // 输出[10],说明运行时无泛型限制
} catch (Exception e) {
e.printStackTrace();
}
}
}
50. 什么是Java中的线程局部变量(ThreadLocal)?如何使用?
- 原理:
ThreadLocal
用于为每个线程提供独立的变量副本,确保线程间数据隔离。其内部通过Thread
类中的threadLocals
(类型为ThreadLocalMap
)存储数据,每个ThreadLocal
对象作为key,对应的线程私有值作为value。 - 注意:使用后需手动调用
remove()
方法清理数据,否则可能导致内存泄漏(ThreadLocalMap
中的Entry是弱引用key,但value是强引用,若线程长期存活,value无法回收)。 - 代码示例:
public class ThreadLocalDemo {
// 定义ThreadLocal变量,指定泛型类型为String
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 线程1设置并获取值
new Thread(() -> {
threadLocal.set("线程1的数据");
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
threadLocal.remove(); // 清理数据
}, "线程1").start();
// 线程2设置并获取值
new Thread(() -> {
threadLocal.set("线程2的数据");
System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
threadLocal.remove(); // 清理数据
}, "线程2").start();
}
}
// 输出:
// 线程1:线程1的数据
// 线程2:线程2的数据
No. | 大剑师精品GIS教程推荐 |
---|---|
0 | 地图渲染基础- 【WebGL 教程】 - 【Canvas 教程】 - 【SVG 教程】 |
1 | Openlayers 【入门教程】 - 【源代码+示例 300+】 |
2 | Leaflet 【入门教程】 - 【源代码+图文示例 150+】 |
3 | MapboxGL 【入门教程】 - 【源代码+图文示例150+】 |
4 | Cesium 【入门教程】 - 【源代码+综合教程 200+】 |
5 | threejs 【中文API】 - 【源代码+图文示例200+】 |
51. 解释Java中的动态代理及其实现方式
原理:动态代理允许在运行时创建目标类的代理对象,增强目标方法(如日志、事务)而不修改源码。
实现方式:
JDK动态代理
:基于接口,通过Proxy.newProxyInstance()
生成代理类;CGLIB动态代理
:基于继承,通过生成目标类子类实现代理(需引入CGLIB库)。
代码示例(JDK动态代理):
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 目标接口
interface Target {
void doSomething();
}
// 目标实现类
class TargetImpl implements Target {
@Override
public void doSomething() {
System.out.println("执行目标方法");
}
}
// 代理处理器
class ProxyHandler implements InvocationHandler {
private Object target;
public ProxyHandler(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 DynamicProxyDemo {
public static void main(String[] args) {
Target target = new TargetImpl();
// 生成代理对象(基于接口)
Target proxy = (Target) Proxy.newProxyInstance(
Target.class.getClassLoader(),
new Class[]{Target.class},
new ProxyHandler(target)
);
proxy.doSomething();
}
}
// 输出:代理:方法执行前 → 执行目标方法 → 代理:方法执行后
52. 什么是Java中的注解(Annotation)?如何自定义注解?
原理:注解是代码的元数据,用于标记类、方法等,可通过反射解析。内置注解如@Override
、@Deprecated
,自定义注解需用@interface
声明。
核心元注解:
@Retention
:指定注解保留阶段(源码、字节码、运行时);@Target
:指定注解可修饰的元素(类、方法等)。
代码示例:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 自定义注解
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可通过反射获取
@Target(ElementType.METHOD) // 仅修饰方法
@interface Log {
String value() default "执行方法"; // 注解属性
}
public class AnnotationDemo {
@Log("测试方法") // 使用注解
public void test() {
System.out.println("测试方法执行");
}
public static void main(String[] args) throws Exception {
// 反射获取注解
Log log = AnnotationDemo.class.getMethod("test").getAnnotation(Log.class);
System.out.println(log.value()); // 输出:测试方法
}
}
53. 解释Java中的序列化与反序列化
原理:序列化将对象转换为字节流(便于存储/传输),反序列化则将字节流恢复为对象。需实现Serializable
接口(标记接口,无方法),transient
关键字修饰的字段不参与序列化。
代码示例:
import java.io.*;
class User implements Serializable { // 实现Serializable接口
private String name;
private transient int age; // transient字段不序列化
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{name='" + name + "', age=" + age + "}";
}
}
public class SerializationDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化
User user = new User("Alice", 20);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.ser"));
oos.writeObject(user);
oos.close();
// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.ser"));
User deserializedUser = (User) ois.readObject();
ois.close();
System.out.println(deserializedUser); // 输出:User{name='Alice', age=0}(age被transient修饰,未序列化)
}
}
54. Java中的equals()
和==
有什么区别?
原理:
==
:比较基本类型时为值比较;比较引用类型时为内存地址(对象是否为同一实例)比较。equals()
:Object类默认实现为==
,但可重写(如String类重写为值比较)。
代码示例:
public class EqualsDemo {
public static void main(String[] args) {
// 基本类型比较
int a = 10;
int b = 10;
System.out.println(a == b); // true(值相等)
// 引用类型比较
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false(地址不同)
System.out.println(s1.equals(s2)); // true(String重写equals,比较值)
}
}
55. 什么是Java中的Lambda表达式?适用于什么场景?
原理:Lambda是函数式接口的匿名实现,简化代码。格式:(参数) -> 表达式/代码块
。
适用场景:简化集合遍历(如forEach
)、线程创建、Stream API等。
代码示例:
import java.util.Arrays;
import java.util.List;
public class LambdaDemo {
public static void main(String[] args) {
// 1. 简化线程创建(Runnable是函数式接口)
new Thread(() -> System.out.println("Lambda线程运行")).start();
// 2. 集合遍历
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(str -> System.out.println(str)); // 输出a、b、c
// 3. 简化Comparator
list.sort((s1, s2) -> s2.compareTo(s1)); // 倒序排序
System.out.println(list); // 输出[c, b, a]
}
}
56. 解释Java中的Stream API及其常用操作
原理:Stream API用于处理集合数据,支持链式操作(过滤、映射、聚合等),类似SQL查询,分为中间操作(返回Stream)和终端操作(返回结果)。
常用操作:
filter()
:过滤元素;map()
:转换元素;collect()
:收集结果(如转List);count()
:统计元素数。
代码示例:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamDemo {
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 过滤偶数 → 乘以2 → 收集为List
List<Integer> result = numbers.stream()
.filter(n -> n % 2 == 0) // 中间操作:保留偶数
.map(n -> n * 2) // 中间操作:乘以2
.collect(Collectors.toList()); // 终端操作:转List
System.out.println(result); // 输出[4, 8, 12]
}
}
57. Java中的volatile
关键字有什么作用?
原理:volatile
保证变量的可见性和禁止指令重排序,但不保证原子性。
- 可见性:线程修改
volatile
变量后,立即刷新到主内存,其他线程读取时从主内存加载; - 有序性:禁止编译器和CPU对
volatile
变量相关指令重排序(如单例模式中的双重检查锁)。
代码示例(禁止重排序):
public class VolatileDemo {
private static volatile VolatileDemo instance; // 禁止重排序
private VolatileDemo() {}
public static VolatileDemo getInstance() {
if (instance == null) {
synchronized (VolatileDemo.class) {
if (instance == null) {
// 若不加volatile,可能发生指令重排序:
// 1. 分配内存 → 2. 实例化对象 → 3. 赋值给instance
// 重排序后可能为1→3→2,导致其他线程获取到未初始化的instance
instance = new VolatileDemo();
}
}
}
return instance;
}
}
58. Java中的try-with-resources
语法有什么作用?
原理:try-with-resources
用于自动关闭实现AutoCloseable
接口的资源(如流、连接),替代传统的try-finally
,避免资源泄漏。
代码示例:
import java.io.FileInputStream;
import java.io.IOException;
public class TryWithResources {
public static void main(String[] args) {
// 资源在try块结束后自动关闭
try (FileInputStream fis = new FileInputStream("test.txt")) {
int data = fis.read();
while (data != -1) {
System.out.print((char) data);
data = fis.read();
}
} catch (IOException e) { // 统一处理异常
e.printStackTrace();
}
// 无需手动调用fis.close()
}
}
59. 解释Java中的深拷贝与浅拷贝
原理:
- 浅拷贝:复制对象时,仅复制基本类型字段,引用类型字段仍指向原对象(如
Object.clone()
默认实现); - 深拷贝:复制对象及引用类型字段指向的所有对象,完全独立于原对象(需手动实现,如序列化/反序列化或递归克隆)。
代码示例:
import java.util.Arrays;
class Person implements Cloneable {
private String name;
private int[] ages; // 引用类型
public Person(String name, int[] ages) {
this.name = name;
this.ages = ages;
}
// 浅拷贝(默认clone)
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
// 深拷贝(手动复制引用类型)
public Person deepClone() throws CloneNotSupportedException {
Person clone = (Person) super.clone();
clone.ages = Arrays.copyOf(this.ages, this.ages.length); // 复制数组
return clone;
}
// getter/setter省略
}
public class CopyDemo {
public static void main(String[] args) throws CloneNotSupportedException {
int[] ages = {20, 30};
Person original = new Person("Alice", ages);
// 浅拷贝
Person shallowClone = (Person) original.clone();
shallowClone.ages[0] = 25;
System.out.println(original.ages[0]); // 输出25(原对象被修改)
// 深拷贝
Person deepClone = original.deepClone();
deepClone.ages[0] = 30;
System.out.println(original.ages[0]); // 输出25(原对象未被修改)
}
}
60. Java中的String
、StringBuilder
、StringBuffer
有什么区别?
原理:
String
:不可变字符序列(底层char[]
被final
修饰),每次修改创建新对象,效率低;StringBuilder
:可变字符序列,非线程安全,效率高(单线程推荐);StringBuffer
:可变字符序列,线程安全(方法加synchronized
),效率低(多线程推荐)。
代码示例:
public class StringCompare {
public static void main(String[] args) {
String str = "a";
str += "b"; // 创建新String对象(原对象不变)
StringBuilder sb = new StringBuilder("a");
sb.append("b"); // 直接修改内部数组,无新对象
StringBuffer sbf = new StringBuffer("a");
sbf.append("b"); // 线程安全的append
}
}
二、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) |