# java测开面试题

# java基础面试题

# 1. 什么是Java的四大特性?解释每个。

答案: Java的四大特性是封装、继承、多态和抽象。

  1. 封装:将数据(属性)和行为(方法)包装在一个类中,并通过访问修饰符(如private, protected, public) 来控制对数据和方法的访问级别。目的是隐藏对象的内部实现细节,仅暴露必要的接口,从而提高安全性、降低耦合度。
  2. 继承:允许一个类(子类/派生类)基于另一个类(父类/基类)来构建。子类继承父类的属性和方法,并可以添加新的或重写已有的方法。目的是实现代码的复用和层次关系的建立。
  3. 多态:指同一个行为具有多个不同表现形式或形态的能力。在Java中主要通过方法重写(Override)和接口实现来实现。多态允许父类引用指向子类对象,并在运行时确定调用哪个方法(动态绑定),提高了代码的灵活性和可扩展性。
  4. 抽象:忽略一个主题中与当前目标无关的细节,只关注与当前目标相关的方面。在Java中通过抽象类(abstract class)和接口(interface)实现。抽象类可以包含抽象方法和实现方法,而接口在Java 8之前只能包含抽象方法,之后可以包含默认方法和静态方法。

# 2. 解释Java平台无关性。

答案: Java的平台无关性,也称为“一次编写,到处运行”,指的是Java程序编译后生成的字节码文件可以在任何安装了Java虚拟机(JVM)的平台上运行,而无需重新编译源代码。 实现原理:

  1. 编译过程:Java源代码(.java文件)被javac编译器编译成与特定平台无关的字节码(.class文件),而不是直接编译成机器码。
  2. 运行过程:不同平台(Windows, Linux, macOS)上的JVM负责将字节码解释或编译(JIT编译器)成当前平台能够执行的本地机器码。 JVM充当了一个抽象层,它屏蔽了底层操作系统和硬件的差异,从而使得Java程序只需编译一次,就能在任何拥有兼容JVM的平台上运行。

# 3. 什么是JVM?简要说明其组成。

答案: JVM(Java Virtual Machine,Java虚拟机)是一个虚拟的计算机,它具有指令集并使用不同的存储区域,负责执行Java字节码。 其主要组成部分包括:

  1. 类加载器子系统:负责加载、链接和初始化.class文件。
  2. 运行时数据区:JVM管理的内存区域,用于存储程序运行时的数据。
    • 方法区:存储已被加载的类信息、常量、静态变量等。
    • :是JVM中最大的一块内存,所有对象实例和数组都在这里分配内存。是垃圾回收的主要区域。
    • Java栈:线程私有,生命周期与线程相同。每个方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
    • 程序计数器:线程私有,指向当前线程正在执行的字节码指令的地址。
    • 本地方法栈:为JVM使用到的本地(Native)方法服务。
  3. 执行引擎:负责执行字节码指令,通常包含解释器、即时编译器(JIT Compiler)和垃圾回收器。
  4. 本地方法接口:用于调用其他语言(如C/C++)编写的本地库。

# 4. JDK、JRE和JVM的区别与联系是什么?

答案:

  • JVM:是Java程序运行的核心虚拟机,负责执行字节码。但光有JVM无法单独完成开发或运行,因为它需要核心类库的支持。
  • JRE:是Java运行时环境。它 = JVM + Java核心类库(如java.lang, java.util等包)。如果你只想运行一个已有的Java程序,安装JRE就足够了。
  • JDK:是Java开发工具包。它 = JRE + 开发工具(如编译器javac、调试器jdb、文档生成器javadoc等)。如果你需要进行Java开发(编写、编译、调试),就必须安装JDK。 联系:JDK包含JRE,JRE包含JVM。层层嵌套,范围从大到小是:JDK > JRE > JVM。

# 5. Java中==equals()方法有什么区别?

答案:

  • == 运算符
    • 用于比较基本数据类型时,比较的是它们的是否相等。
    • 用于比较引用类型(对象)时,比较的是两个对象在内存中的地址(即是否是同一个对象)。
  • equals() 方法
    • 定义在Object类中,默认实现就是使用==比较地址。所以,如果一个类没有重写equals()方法,那么equals()==的效果是一样的。
    • 然而,Java中的很多核心类(如String, Integer, Date等)都重写了equals()方法,使其比较的是对象的内容(即所有属性或字符序列)是否逻辑上相等。
    • 因此,对于自定义类,如果需要比较两个对象的内容是否相同,就必须重写equals()(通常也要重写hashCode())方法。

示例:

String str1 = "hello";
String str2 = new String("hello");
String str3 = "hello";

System.out.println(str1 == str2); // false,比较地址,一个是常量池,一个是堆中新对象
System.out.println(str1 == str3); // true,都指向常量池中的同一个"hello"
System.out.println(str1.equals(str2)); // true,比较内容,都是"hello"
1
2
3
4
5
6
7

# 6. 解释Java中的自动装箱和拆箱。

答案: 自动装箱和拆箱是Java 5引入的特性,目的是简化基本数据类型和其对应的包装类之间的转换。

  • 自动装箱:将基本数据类型自动转换为其对应的包装类对象。
    • Integer i = 10; // 编译器自动转换为 Integer i = Integer.valueOf(10);
  • 自动拆箱:将包装类对象自动转换为对应的基本数据类型。
    • int n = i; // 编译器自动转换为 int n = i.intValue();

注意:虽然自动装箱拆箱很方便,但在循环或大量数据操作时可能会产生不必要的对象,影响性能。

# 7. StringStringBuilderStringBuffer之间的区别?

答案:

特性 String StringBuilder StringBuffer
可变性 不可变 可变 可变
线程安全 是(因其不可变) (方法都用synchronized修饰)
性能 低(修改会产生新对象) 中(因线程安全有开销)
使用场景 操作少量的数据或字符串常量 单线程下大量字符串操作 多线程下大量字符串操作

核心区别String是不可变对象,任何对String的修改(如拼接、替换)都会产生一个新的String对象。而StringBuilderStringBuffer是可变字符序列,可以在原对象上进行修改,效率更高。

# 8. 什么是Java的访问修饰符?它们有哪些?

答案: 访问修饰符用于定义Java中类、方法、变量的访问权限和可见性。

  1. private:仅在当前类的内部可见。提供最高级别的封装。
  2. default(默认,不写任何修饰符):在同一个包内可见。有时也称为包级私有。
  3. protected:在同一个包内和不同包子类中可见。
  4. public:对所有类都可见。提供最低级别的封装。

访问权限从大到小:public > protected > default > private

# 9. final关键字在Java中有什么作用?

答案: final关键字可用于修饰类、方法和变量,含义不同:

  1. final 修饰类:表示这个类是最终类,不能被继承。例如,String类就是final类。
  2. final 修饰方法:表示该方法是最终方法,不能被子类重写。可以用于防止子类修改其核心实现。
  3. final 修饰变量
    • 修饰基本类型变量:表示该变量是一个常量,一旦初始化后其值就不能再改变
    • 修饰引用类型变量:表示该引用(即内存地址)不能再指向另一个对象,但对象本身内部的值是可以修改的。

# 10. 什么是方法重载(Overload)和方法重写(Override)?

答案:

  • 方法重载

    • 定义:在同一个类中,允许存在多个方法名相同参数列表不同(参数类型、个数、顺序至少有一个不同)的方法。
    • 与返回值、访问修饰符无关:仅返回值类型不同或访问修饰符不同,不能构成重载。
    • 目的:提供处理不同类型数据的统一方法名,方便调用。
    • 示例System.out.println()可以打印各种类型的数据,就是典型的重载。
  • 方法重写

    • 定义:在子类中,提供一个与父类方法具有相同方法名、相同参数列表、相同返回类型(或子类返回类型)的方法实现。
    • 访问权限不能更严格:子类方法的访问权限不能比父类方法更严格(例如,父类是protected,子类可以是protectedpublic,但不能是private)。
    • @Override注解:建议使用此注解来显式声明重写,编译器会帮助检查重写是否正确。
    • 目的:实现多态,子类可以根据需要定义特定于自己的行为。

# 11. 抽象类和接口的区别是什么?

答案:

特性 抽象类 接口
关键字 abstract class interface
成员变量 可以是各种类型(常量、变量) 默认是 public static final(常量)
方法 可以有抽象方法和具体实现方法 Java 8前只能是抽象方法;之后可以有defaultstatic方法
构造方法 没有
继承 单继承,一个类只能继承一个抽象类 多继承,一个类可以实现多个接口
设计理念 "is-a" 关系,表示类的本质 "has-a" / "can-do" 关系,表示类的功能
访问修饰符 方法可以是任意访问修饰符 方法默认是 public

# 12. Java中什么是异常?异常处理的机制是怎样的?

答案:

  • 异常:是程序运行时发生的不正常事件,它会中断正常的指令流。Java中所有异常都继承自Throwable类,主要分为ErrorException
    • Error:是程序无法处理的严重错误,如系统崩溃、虚拟机错误。应用程序不应尝试捕获。
    • Exception:是程序本身可以处理的异常,又分为运行时异常受检异常
  • 异常处理机制
    1. 抛出异常:当方法遇到无法处理的情况时,可以使用throw关键字抛出一个异常对象。
    2. 捕获异常:使用try-catch-finally语句块来捕获和处理异常。
      • try:包裹可能会发生异常的代码。
      • catch:捕获并处理特定类型的异常。可以有多个catch块,异常类型应从小到大。
      • finally:无论是否发生异常,都会执行的代码块,常用于释放资源(如关闭文件、数据库连接)。
    3. 声明异常:在方法签名中使用throws关键字声明该方法可能抛出的受检异常,通知调用者需要处理这些异常。

# 13. 运行时异常和受检异常有什么区别?

答案:

特性 运行时异常 受检异常
继承自 RuntimeException Exception(但不是RuntimeException)
处理要求 非强制处理。编译器不检查是否进行了捕获或声明 强制处理。必须用try-catch捕获或在方法上用throws声明
发生时机 通常在程序运行时由逻辑错误引起 通常由外部因素引起,程序本身可能没问题
示例 NullPointerException, ArrayIndexOutOfBoundsException IOException, SQLException, ClassNotFoundException

# 14. throwthrows有什么区别?

答案:

  • throw
    • 是一个语句,用在方法体内部
    • 用于主动抛出一个具体的异常对象。
    • 语法:throw new SomeException("message");
  • throws
    • 是一个关键字,用在方法声明处。
    • 用于声明该方法可能抛出的异常类型,通知调用者需要处理这些异常。
    • 语法:public void method() throws SomeException1, SomeException2 { ... }

# 15. try-catch-finally块中,finally块一定会执行吗?

答案: 在绝大多数情况下,finally块中的代码一定会执行,即使trycatch块中有return语句,也会先执行finally再返回。 但是,有以下几种极端情况finally块不会执行:

  1. trycatch块中调用了System.exit(int status)方法,直接终止了JVM。
  2. 守护线程在执行finally之前被中断或终止。
  3. 服务器断电、系统崩溃等不可抗力因素。

# 16. 什么是Java的集合框架?列出一些核心接口。

答案: Java集合框架是一个用来表示和操作集合的统一架构,包含了接口、实现类和算法。它主要用于存储、检索和操作一组对象。 核心接口

  • Collection:集合层次结构的根接口。
    • List有序、可重复的集合。
    • Set无序、不可重复的集合。
    • Queue:队列,遵循FIFO(先进先出)或优先级等规则。
  • Map:存储键值对的集合,键不可重复。

# 17. ArrayListLinkedList的区别是什么?

答案:

特性 ArrayList LinkedList
底层实现 动态数组 双向链表
访问效率 。支持随机访问,通过索引获取元素很快(O(1)) 。需要从头或尾遍历查找(O(n))
增删效率 。在中间插入或删除元素需要移动后续元素(O(n)) 。只需要修改指针指向(O(1)),但找到位置需要O(n)
内存占用 较小。只存储数据本身和数组结构开销 较大。每个节点都需要存储前后节点的指针
适用场景 频繁访问元素、尾部操作多的场景 频繁在任意位置进行插入和删除操作的场景

# 18. HashSet是如何保证元素不重复的?

答案: HashSet基于HashMap实现,其元素实际上是HashMapkey。它保证元素不重复的机制依赖于两个方法:

  1. hashCode():当向HashSet添加一个对象时,首先会调用该对象的hashCode()方法计算哈希值,用于确定对象在底层数组中的存储位置。
  2. equals()
    • 如果该位置没有其他元素,则直接存入。
    • 如果该位置已有其他元素(哈希冲突),则会调用新元素的equals()方法与已有元素逐个比较。
    • 如果equals()返回true,则认为元素重复,新元素不会被添加。
    • 如果equals()返回false,则会将新元素以链表(或红黑树)的形式链接到该位置。

因此,如果要正确地将自定义对象放入HashSet并去重,必须同时重写该对象的hashCode()equals()方法,且必须保证逻辑上相等的对象具有相同的哈希码。

# 19. HashMap的工作原理是什么?

答案: HashMap基于哈希表实现,存储键值对。

  1. put()过程: a. 计算键的hashCode(),并通过哈希函数((n-1) & hash)计算出在底层数组(table)中的索引(bucket)。 b. 如果该bucket为空,直接插入Node。 c. 如果不为空,则遍历该bucket上的链表或红黑树: * 如果找到相同的key(hashCode相同且equals返回true),则用新value覆盖旧value。 * 如果没找到,则将新Node插入到链表末尾或红黑树中。 d. 如果插入后size超过阈值(容量*负载因子),则进行扩容(resize),创建一个新数组(通常是原来的2倍),并重新计算所有元素的位置(rehash)。

  2. get()过程: a. 计算键的hashCode(),找到对应的bucket。 b. 如果该bucket只有一个Node,直接返回。 c. 如果是一个链表或红黑树,则使用equals()方法遍历查找对应的key。

在Java 8中,当链表长度超过8且数组容量大于64时,链表会转换为红黑树以提高查询效率;当树节点数小于6时,会退化为链表。

# 20. HashMapHashtable的区别?

答案:

特性 HashMap Hashtable
线程安全 非线程安全 线程安全(方法用synchronized修饰)
性能 (无同步开销) 低(同步有性能开销)
Null键/值 允许一个null键和多个null值 不允许键或值为null(会抛NullPointerException)
继承 继承AbstractMap 继承Dictionary
初始容量 16 11
扩容 2n 2n+1
迭代器 快速失败迭代器 枚举器和迭代器

注意Hashtable是遗留类,现在通常使用ConcurrentHashMap来实现线程安全的Map。

# 21. 什么是Java的泛型?为什么要使用泛型?

答案: 泛型是Java 5引入的特性,允许在定义类、接口或方法时使用类型参数,在使用时再指定具体的类型。 好处

  1. 类型安全:在编译时进行严格的类型检查,避免在运行时出现ClassCastException
  2. 消除强制类型转换:使代码更加简洁清晰,无需手动进行类型转换。
  3. 代码复用:可以编写更通用、可复用的代码。

示例

// 不使用泛型
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 需要强制转换

// 使用泛型
List<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0); // 无需强制转换,编译时已知类型
1
2
3
4
5
6
7
8
9

# 22. 什么是反射?它有什么用途和缺点?

答案: 反射机制允许程序在运行时(而非编译时)动态地获取类的信息(如类名、方法、属性、注解等)并操作类的字段和方法。 用途

  • 动态创建对象Class.forName("ClassName").newInstance()
  • 动态调用方法Method.invoke()
  • 动态操作属性Field.get()/set()
  • 获取注解信息
  • 很多框架(如Spring, Hibernate, JUnit)的核心都使用了反射。

缺点

  • 性能开销:反射操作比直接的Java代码慢很多,因为涉及到动态类型解析。
  • 安全限制:反射需要运行时权限,可能在受限的安全环境下无法运行。
  • 内部暴露:可以打破封装性,访问类的私有成员,可能带来安全隐患。

# 23. 什么是Java注解?列举几个内置注解。

答案: 注解是一种元数据,为代码提供信息,它本身对代码的逻辑没有直接影响,但可以被编译器、开发工具或运行时框架使用。 内置注解

  • @Override:表示方法重写了父类的方法。
  • @Deprecated:表示方法或类已过时,不推荐使用。
  • @SuppressWarnings:指示编译器忽略特定的警告信息。
  • @FunctionalInterface:表示一个接口是函数式接口(只有一个抽象方法)。

# 24. synchronized关键字的作用是什么?

答案: synchronized关键字用于实现线程同步,保证在同一时刻,最多只有一个线程可以执行被synchronized修饰的代码段(同步代码块或同步方法),从而避免多线程环境下的数据不一致问题。 它可以用于:

  1. 实例方法:锁是当前对象实例(this)。
  2. 静态方法:锁是当前类的Class对象。
  3. 代码块:可以指定任意对象作为锁,提供更细粒度的锁控制。

# 25. volatile关键字的作用是什么?

答案: volatile关键字用于修饰变量,提供了一种轻量级的同步机制。 它的两大核心作用是:

  1. 保证可见性:当一个线程修改了volatile变量的值,新值会立即被刷新到主内存中。当其他线程读取该变量时,会从主内存中重新读取最新值,而不是使用自己工作内存中的缓存值。
  2. 禁止指令重排序:编译器和大纲在优化时,不会对volatile变量相关的指令进行重排序,从而保证代码的执行顺序与预期一致。

注意volatile不保证原子性(例如i++操作不是原子操作)。如果需要保证原子性,仍需使用synchronizedjava.util.concurrent.atomic包中的原子类。

# 26. 创建线程有哪几种方式?

答案:

  1. 继承Thread

    • 定义一个类继承Thread,并重写run()方法。
    • 创建该子类的实例,调用start()方法启动线程。
    • 缺点:Java是单继承,继承了Thread就不能再继承其他类。
  2. 实现Runnable接口

    • 定义一个类实现Runnable接口,并实现run()方法。
    • 创建该实现类的实例,并将其作为Thread类的构造参数来创建Thread对象,最后调用start()方法。
    • 优点:避免了单继承的局限性;更适合多个线程共享一个资源的情况。
  3. 实现Callable接口

    • 定义一个类实现Callable接口,并实现call()方法(可以有返回值,可以抛出异常)。
    • 创建FutureTask对象,包装Callable实现类。
    • FutureTask对象作为Thread类的构造参数创建线程并启动。
    • 通过FutureTask.get()方法可以获取线程执行后的返回值。
  4. 使用线程池:通过ExecutorService线程池来创建和管理线程,这是最推荐的方式,可以复用线程,减少创建和销毁线程的开销。

# 27. 线程的start()run()方法有什么区别?

答案:

  • start()方法
    • Thread类的方法。
    • 它的作用是启动一个新线程,新线程会去执行run()方法中的代码。start()不能被多次调用,否则会抛出IllegalThreadStateException
  • run()方法
    • Runnable接口定义的方法,由子类重写。
    • 如果直接调用run()方法,它会在当前线程(如main线程)中同步执行,而不会启动新线程。这只相当于普通的方法调用,失去了多线程的意义。

# 28. 什么是线程安全?如何实现线程安全?

答案: 线程安全是指当多个线程同时访问某个类、对象或方法时,这个类、对象或方法总能表现出正确的行为,数据保持一致。 实现方式

  1. 不可变对象:使用final关键字修饰变量和类,使对象创建后状态不可改变。
  2. 同步(互斥)
    • synchronized关键字(同步代码块或同步方法)。
    • java.util.concurrent.locks.Lock接口及其实现类(如ReentrantLock),提供比synchronized更灵活的锁操作。
  3. 线程安全类:使用Java并发包(java.util.concurrent)提供的线程安全集合类,如ConcurrentHashMap, CopyOnWriteArrayList等。
  4. 原子变量:使用java.util.concurrent.atomic包下的原子类(如AtomicInteger),利用CAS(Compare-And-Swap)操作保证单个变量的原子性。
  5. 线程本地存储:使用ThreadLocal为每个线程创建变量的副本,从而避免共享。

# 29. sleep()wait()方法有什么区别?

答案:

特性 sleep() wait()
所属类 Thread类的静态方法 Object类的实例方法
作用对象 对当前线程操作 对对象实例操作(必须在synchronized块中)
释放锁 不会释放持有的锁 释放持有的锁,让其他线程可以进入同步代码块
唤醒方式 睡眠时间到自动唤醒 需要其他线程调用notify()notifyAll()来唤醒
使用场景 用于暂停当前线程的执行 用于线程间的通信和协调

# 30. 什么是死锁?如何避免死锁?

答案: 死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,它们都将无法进行下去。 死锁产生的四个必要条件(必须同时满足):

  1. 互斥条件:一个资源每次只能被一个线程使用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程强行剥夺。
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

避免死锁:只要破坏上述四个条件中的任意一个即可。

  • 破坏互斥条件:通常无法破坏,因为锁本身就是互斥的。
  • 破坏请求与保持条件:一次性申请所有所需的资源。
  • 破坏不剥夺条件:允许在特定条件下剥夺线程已占有的资源。
  • 破坏循环等待条件:对资源进行线性排序,要求线程按固定的顺序申请资源(这是最常用的方法)。

# 31. Java中IO流分为哪几种?

答案: 按照数据流向分:

  • 输入流:从数据源(如文件、网络)读取数据到程序。
  • 输出流:从程序写出数据到目的地(如文件、网络)。

按照操作数据类型分:

  • 字节流:以字节(8 bit)为单位进行读写。顶层抽象类是InputStreamOutputStream
    • 常用于处理二进制文件(如图片、视频、音频)。
  • 字符流:以字符(16 bit,根据编码)为单位进行读写。顶层抽象类是ReaderWriter
    • 常用于处理文本文件,能更好地处理字符编码问题。

转换流InputStreamReaderOutputStreamWriter是字节流和字符流之间的桥梁,可以将字节流转换为字符流,并指定字符集。

# 32. 什么是Java中的序列化和反序列化?

答案:

  • 序列化:将Java对象转换成字节序列的过程,以便于存储或网络传输。
  • 反序列化:将字节序列恢复成Java对象的过程。 实现序列化的类必须实现java.io.Serializable接口(这是一个标记接口,没有方法)。可以使用ObjectOutputStreamwriteObject()方法进行序列化,使用ObjectInputStreamreadObject()方法进行反序列化。 可以使用transient关键字修饰不想被序列化的变量。

# 33. 什么是Lambda表达式?

答案: Lambda表达式是Java 8引入的一种语法糖,它允许你将功能作为方法参数或将代码本身作为数据来处理。它本质上是一个匿名函数,用于简化函数式接口(只有一个抽象方法的接口)的实现。 语法(parameters) -> expression(parameters) -> { statements; } 示例

// 传统匿名内部类
Thread t1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello");
    }
});

// 使用Lambda表达式
Thread t2 = new Thread(() -> System.out.println("Hello"));
1
2
3
4
5
6
7
8
9
10

# 34. 什么是函数式接口?

答案: 函数式接口是有且仅有一个抽象方法的接口(但可以有多个defaultstatic方法)。它可以用作Lambda表达式的类型。 Java 8提供了@FunctionalInterface注解来显式标识一个接口是函数式接口,编译器会进行检查。 常见的函数式接口:

  • Runnable -> run()
  • Comparator -> compare()
  • Callable -> call()
  • Java 8在java.util.function包中提供了大量内置函数式接口,如:
    • Supplier<T>:无参,返回一个结果。T get()
    • Consumer<T>:接受一个参数,无返回。void accept(T t)
    • Function<T, R>:接受一个参数,返回一个结果。R apply(T t)
    • Predicate<T>:接受一个参数,返回布尔值。boolean test(T t)

# 35. 什么是Stream API?

答案: Stream API是Java 8引入的一套新的处理集合数据的API,它允许你以声明式(类似SQL语句)的方式处理数据集合。它专注于对集合数据进行各种高效的计算(如查找、过滤、映射、归约等),并且可以透明地并行处理。 特点

  • 不是数据结构:它不存储数据,数据源可以是集合、数组等。
  • 不修改源数据:对Stream的操作会产生一个新的Stream,不会影响原始数据源。
  • 惰性执行:中间操作(如filter, map)是惰性的,只有遇到终止操作(如collect, forEach)时,整个操作链才会执行。
  • 可消费性:Stream只能被消费一次,终止操作后流就关闭了。

操作类型

  • 中间操作:返回Stream本身,可以连接起来形成一个管道。例如:filter(), map(), sorted(), distinct()
  • 终止操作:产生一个结果或副作用。例如:forEach(), collect(), reduce(), count()

# 36. Optional类是用来解决什么问题的?

答案: Optional<T>类是一个容器对象,用于解决令人头疼的空指针异常。它可以保存类型T的值,或者仅仅保存null目的:它迫使开发者显式地检查值是否存在,避免潜在的NullPointerException,使代码更健壮、更清晰。 常用方法

  • Optional.ofNullable(T value):创建一个Optional对象,value可以为null。
  • Optional.of(T value):创建一个Optional对象,value不能为null,否则抛异常。
  • isPresent():判断值是否存在。
  • get():如果值存在则返回,否则抛出NoSuchElementException
  • orElse(T other):值存在则返回,否则返回指定的默认值other
  • orElseGet(Supplier other):值存在则返回,否则由Supplier函数生成一个值返回。
  • ifPresent(Consumer consumer):值存在则执行Consumer操作,否则什么都不做。

# 37. 什么是JUnit?列举几个常用的JUnit注解。

答案: JUnit是Java社区最流行的一个单元测试框架,用于编写和运行可重复的、自动化的测试。 常用注解

  • @Test:标记一个方法为测试方法。
  • @Before / @BeforeEach (JUnit 5):在每个测试方法之前执行,用于准备测试环境(如初始化数据)。
  • @After / @AfterEach (JUnit 5):在每个测试方法之后执行,用于清理资源(如关闭连接)。
  • @BeforeClass / @BeforeAll (JUnit 5):在所有测试方法之前执行一次(静态方法),用于耗时的操作(如连接数据库)。
  • @AfterClass / @AfterAll (JUnit 5):在所有测试方法之后执行一次(静态方法)。
  • @Ignore / @Disabled (JUnit 5):忽略该测试方法。
  • @ParameterizedTest (JUnit 5):表示一个参数化测试。
  • @Mock (需要Mockito等框架):创建一个mock对象。