Java面试题及详细答案120道之(101-120)

前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。

前后端面试题-专栏总目录

在这里插入图片描述

一、本文面试题目录

101. ConcurrentHashMap在JDK1.7和JDK1.8中的实现有何不同?

答案

版本实现方式锁粒度并发度
JDK1.7分段锁(Segment数组+HashEntry对Segment加锁等于Segment数量(默认16)
JDK1.8CAS+synchronized(数组+链表/红黑树)对链表头/红黑树节点加锁更高(理论上支持数组长度级别的并发)

JDK1.8优化

  • 移除Segment,直接使用Node数组,减少锁开销。
  • 链表转红黑树(同HashMap),提升查询效率。

102. Java中的StringStringBufferStringBuilder有何区别?

答案

特性StringStringBufferStringBuilder
可变性不可变(每次修改创建新对象)可变(同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成员?

答案
用法

  1. 修饰变量(静态变量):属于类,所有实例共享,可通过类名直接访问。
  2. 修饰方法(静态方法):属于类,可直接调用,无需实例化。
  3. 修饰代码块(静态代码块):类加载时执行,仅执行一次(用于初始化静态资源)。
  4. 修饰内部类(静态内部类):可独立于外部类实例存在,不能访问外部类非静态成员。

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. 什么是单例模式?如何实现一个线程安全的单例模式?

答案
单例模式:保证一个类仅有一个实例,并提供全局访问点。

线程安全的实现方式

  1. 饿汉式:类加载时初始化,天生线程安全(可能浪费内存)。

    class SingletonHungry {
        private static final SingletonHungry INSTANCE = new SingletonHungry();
        private SingletonHungry() {} // 私有构造
        public static SingletonHungry getInstance() {
            return INSTANCE;
        }
    }
    
  2. 懒汉式(双重检查锁):延迟初始化,同步优化(推荐)。

    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)接口的匿名实现类,语法为(参数) -> 表达式/代码块

使用场景

  • 函数式接口(如RunnableComparator)。
  • Stream API的中间操作(如filtermap)。

代码示例

// 替代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流分为哪几类?NIOIO的区别是什么?

答案
IO流分类

  • 按流向:输入流(InputStreamReader)、输出流(OutputStreamWriter)。
  • 按数据单位:字节流(InputStreamOutputStream)、字符流(ReaderWriter)。

NIO与IO的区别

特性IONIO
模型面向流(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 教程】
1Openlayers 【入门教程】 - 【源代码+示例 300+】
2Leaflet 【入门教程】 - 【源代码+图文示例 150+】
3MapboxGL【入门教程】 - 【源代码+图文示例150+】
4Cesium 【入门教程】 - 【源代码+综合教程 200+】
5threejs【中文API】 - 【源代码+图文示例200+】

111. Java中的HashMap和HashTable有什么区别?

原理:两者均实现Map接口,核心区别如下:

特性HashMapHashTable
线程安全非线程安全线程安全(方法加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驱动加载、日志框架适配)。
  • 实现步骤
    1. 定义服务接口;
    2. 第三方提供接口实现类;
    3. META-INF/services目录下创建以接口全限定名为文件名的文件,文件内容为实现类的全限定名;
    4. 调用方通过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创建;
    • 枚举默认实现SerializableComparable接口;
    • 枚举可以有抽象方法,但每个常量必须实现;
    • 枚举不能被继承。
  • 代码示例
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.ProxyInvocationHandler实现,要求目标类必须实现接口。
    • CGLIB动态代理:基于继承,通过生成目标类的子类实现代理,无需目标类实现接口(但目标类不能是final类)。
  • 代码示例(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自动释放不再被引用的对象内存的过程。核心步骤包括:
    1. 标记:通过可达性分析(以GC Roots为起点)判断对象是否存活;
    2. 清除:回收未被标记的对象内存;
    3. 整理:压缩存活对象,减少内存碎片。
  • 常见垃圾收集器
    • 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用于检查方法重写)。
  • 自定义注解步骤
    1. @interface声明注解;
    2. 定义注解属性(类似方法);
    3. 使用元注解(如@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道
1Java面试题及详细答案120道(01-20)
2Java面试题及详细答案120道(21-40)
3Java面试题及详细答案120道(41-60)
4Java面试题及详细答案120道(61-80)
5Java面试题及详细答案120道(81-100)
6Java面试题及详细答案120道(5101-120)
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

还是大剑师兰特

打赏一杯可口可乐

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值