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

发布于:2025-07-27 ⋅ 阅读:(18) ⋅ 点赞:(0)

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

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

在这里插入图片描述

一、本文面试题目录

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中的ComparableComparator有何区别?

答案

特性 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中的StringStringBuilderStringBuffer有什么区别?

原理

  • 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)

网站公告

今日签到

点亮在社区的每一天
去签到