# java测开面试题
# java基础面试题
# 1. 什么是Java的四大特性?解释每个。
答案: Java的四大特性是封装、继承、多态和抽象。
- 封装:将数据(属性)和行为(方法)包装在一个类中,并通过访问修饰符(如
private,protected,public) 来控制对数据和方法的访问级别。目的是隐藏对象的内部实现细节,仅暴露必要的接口,从而提高安全性、降低耦合度。 - 继承:允许一个类(子类/派生类)基于另一个类(父类/基类)来构建。子类继承父类的属性和方法,并可以添加新的或重写已有的方法。目的是实现代码的复用和层次关系的建立。
- 多态:指同一个行为具有多个不同表现形式或形态的能力。在Java中主要通过方法重写(Override)和接口实现来实现。多态允许父类引用指向子类对象,并在运行时确定调用哪个方法(动态绑定),提高了代码的灵活性和可扩展性。
- 抽象:忽略一个主题中与当前目标无关的细节,只关注与当前目标相关的方面。在Java中通过抽象类(
abstract class)和接口(interface)实现。抽象类可以包含抽象方法和实现方法,而接口在Java 8之前只能包含抽象方法,之后可以包含默认方法和静态方法。
# 2. 解释Java平台无关性。
答案: Java的平台无关性,也称为“一次编写,到处运行”,指的是Java程序编译后生成的字节码文件可以在任何安装了Java虚拟机(JVM)的平台上运行,而无需重新编译源代码。 实现原理:
- 编译过程:Java源代码(
.java文件)被javac编译器编译成与特定平台无关的字节码(.class文件),而不是直接编译成机器码。 - 运行过程:不同平台(Windows, Linux, macOS)上的JVM负责将字节码解释或编译(JIT编译器)成当前平台能够执行的本地机器码。 JVM充当了一个抽象层,它屏蔽了底层操作系统和硬件的差异,从而使得Java程序只需编译一次,就能在任何拥有兼容JVM的平台上运行。
# 3. 什么是JVM?简要说明其组成。
答案: JVM(Java Virtual Machine,Java虚拟机)是一个虚拟的计算机,它具有指令集并使用不同的存储区域,负责执行Java字节码。 其主要组成部分包括:
- 类加载器子系统:负责加载、链接和初始化
.class文件。 - 运行时数据区:JVM管理的内存区域,用于存储程序运行时的数据。
- 方法区:存储已被加载的类信息、常量、静态变量等。
- 堆:是JVM中最大的一块内存,所有对象实例和数组都在这里分配内存。是垃圾回收的主要区域。
- Java栈:线程私有,生命周期与线程相同。每个方法执行时会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接和方法出口等信息。
- 程序计数器:线程私有,指向当前线程正在执行的字节码指令的地址。
- 本地方法栈:为JVM使用到的本地(Native)方法服务。
- 执行引擎:负责执行字节码指令,通常包含解释器、即时编译器(JIT Compiler)和垃圾回收器。
- 本地方法接口:用于调用其他语言(如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"
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. String、StringBuilder和StringBuffer之间的区别?
答案:
| 特性 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 是(因其不可变) | 否 | 是(方法都用synchronized修饰) |
| 性能 | 低(修改会产生新对象) | 高 | 中(因线程安全有开销) |
| 使用场景 | 操作少量的数据或字符串常量 | 单线程下大量字符串操作 | 多线程下大量字符串操作 |
核心区别:String是不可变对象,任何对String的修改(如拼接、替换)都会产生一个新的String对象。而StringBuilder和StringBuffer是可变字符序列,可以在原对象上进行修改,效率更高。
# 8. 什么是Java的访问修饰符?它们有哪些?
答案: 访问修饰符用于定义Java中类、方法、变量的访问权限和可见性。
private:仅在当前类的内部可见。提供最高级别的封装。default(默认,不写任何修饰符):在同一个包内可见。有时也称为包级私有。protected:在同一个包内和不同包的子类中可见。public:对所有类都可见。提供最低级别的封装。
访问权限从大到小:public > protected > default > private。
# 9. final关键字在Java中有什么作用?
答案: final关键字可用于修饰类、方法和变量,含义不同:
final修饰类:表示这个类是最终类,不能被继承。例如,String类就是final类。final修饰方法:表示该方法是最终方法,不能被子类重写。可以用于防止子类修改其核心实现。final修饰变量:- 修饰基本类型变量:表示该变量是一个常量,一旦初始化后其值就不能再改变。
- 修饰引用类型变量:表示该引用(即内存地址)不能再指向另一个对象,但对象本身内部的值是可以修改的。
# 10. 什么是方法重载(Overload)和方法重写(Override)?
答案:
方法重载:
- 定义:在同一个类中,允许存在多个方法名相同但参数列表不同(参数类型、个数、顺序至少有一个不同)的方法。
- 与返回值、访问修饰符无关:仅返回值类型不同或访问修饰符不同,不能构成重载。
- 目的:提供处理不同类型数据的统一方法名,方便调用。
- 示例:
System.out.println()可以打印各种类型的数据,就是典型的重载。
方法重写:
- 定义:在子类中,提供一个与父类方法具有相同方法名、相同参数列表、相同返回类型(或子类返回类型)的方法实现。
- 访问权限不能更严格:子类方法的访问权限不能比父类方法更严格(例如,父类是
protected,子类可以是protected或public,但不能是private)。 @Override注解:建议使用此注解来显式声明重写,编译器会帮助检查重写是否正确。- 目的:实现多态,子类可以根据需要定义特定于自己的行为。
# 11. 抽象类和接口的区别是什么?
答案:
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 关键字 | abstract class | interface |
| 成员变量 | 可以是各种类型(常量、变量) | 默认是 public static final(常量) |
| 方法 | 可以有抽象方法和具体实现方法 | Java 8前只能是抽象方法;之后可以有default和static方法 |
| 构造方法 | 有 | 没有 |
| 继承 | 单继承,一个类只能继承一个抽象类 | 多继承,一个类可以实现多个接口 |
| 设计理念 | "is-a" 关系,表示类的本质 | "has-a" / "can-do" 关系,表示类的功能 |
| 访问修饰符 | 方法可以是任意访问修饰符 | 方法默认是 public |
# 12. Java中什么是异常?异常处理的机制是怎样的?
答案:
- 异常:是程序运行时发生的不正常事件,它会中断正常的指令流。Java中所有异常都继承自
Throwable类,主要分为Error和Exception。- Error:是程序无法处理的严重错误,如系统崩溃、虚拟机错误。应用程序不应尝试捕获。
- Exception:是程序本身可以处理的异常,又分为运行时异常和受检异常。
- 异常处理机制:
- 抛出异常:当方法遇到无法处理的情况时,可以使用
throw关键字抛出一个异常对象。 - 捕获异常:使用
try-catch-finally语句块来捕获和处理异常。try:包裹可能会发生异常的代码。catch:捕获并处理特定类型的异常。可以有多个catch块,异常类型应从小到大。finally:无论是否发生异常,都会执行的代码块,常用于释放资源(如关闭文件、数据库连接)。
- 声明异常:在方法签名中使用
throws关键字声明该方法可能抛出的受检异常,通知调用者需要处理这些异常。
- 抛出异常:当方法遇到无法处理的情况时,可以使用
# 13. 运行时异常和受检异常有什么区别?
答案:
| 特性 | 运行时异常 | 受检异常 |
|---|---|---|
| 继承自 | RuntimeException | Exception(但不是RuntimeException) |
| 处理要求 | 非强制处理。编译器不检查是否进行了捕获或声明 | 强制处理。必须用try-catch捕获或在方法上用throws声明 |
| 发生时机 | 通常在程序运行时由逻辑错误引起 | 通常由外部因素引起,程序本身可能没问题 |
| 示例 | NullPointerException, ArrayIndexOutOfBoundsException | IOException, SQLException, ClassNotFoundException |
# 14. throw和throws有什么区别?
答案:
throw:- 是一个语句,用在方法体内部。
- 用于主动抛出一个具体的异常对象。
- 语法:
throw new SomeException("message");
throws:- 是一个关键字,用在方法声明处。
- 用于声明该方法可能抛出的异常类型,通知调用者需要处理这些异常。
- 语法:
public void method() throws SomeException1, SomeException2 { ... }
# 15. try-catch-finally块中,finally块一定会执行吗?
答案:
在绝大多数情况下,finally块中的代码一定会执行,即使try或catch块中有return语句,也会先执行finally再返回。
但是,有以下几种极端情况finally块不会执行:
- 在
try或catch块中调用了System.exit(int status)方法,直接终止了JVM。 - 守护线程在执行
finally之前被中断或终止。 - 服务器断电、系统崩溃等不可抗力因素。
# 16. 什么是Java的集合框架?列出一些核心接口。
答案: Java集合框架是一个用来表示和操作集合的统一架构,包含了接口、实现类和算法。它主要用于存储、检索和操作一组对象。 核心接口:
Collection:集合层次结构的根接口。List:有序、可重复的集合。Set:无序、不可重复的集合。Queue:队列,遵循FIFO(先进先出)或优先级等规则。
Map:存储键值对的集合,键不可重复。
# 17. ArrayList和LinkedList的区别是什么?
答案:
| 特性 | ArrayList | LinkedList |
|---|---|---|
| 底层实现 | 动态数组 | 双向链表 |
| 访问效率 | 高。支持随机访问,通过索引获取元素很快(O(1)) | 低。需要从头或尾遍历查找(O(n)) |
| 增删效率 | 低。在中间插入或删除元素需要移动后续元素(O(n)) | 高。只需要修改指针指向(O(1)),但找到位置需要O(n) |
| 内存占用 | 较小。只存储数据本身和数组结构开销 | 较大。每个节点都需要存储前后节点的指针 |
| 适用场景 | 频繁访问元素、尾部操作多的场景 | 频繁在任意位置进行插入和删除操作的场景 |
# 18. HashSet是如何保证元素不重复的?
答案: HashSet基于HashMap实现,其元素实际上是HashMap的key。它保证元素不重复的机制依赖于两个方法:
hashCode():当向HashSet添加一个对象时,首先会调用该对象的hashCode()方法计算哈希值,用于确定对象在底层数组中的存储位置。equals():- 如果该位置没有其他元素,则直接存入。
- 如果该位置已有其他元素(哈希冲突),则会调用新元素的
equals()方法与已有元素逐个比较。 - 如果
equals()返回true,则认为元素重复,新元素不会被添加。 - 如果
equals()返回false,则会将新元素以链表(或红黑树)的形式链接到该位置。
因此,如果要正确地将自定义对象放入HashSet并去重,必须同时重写该对象的hashCode()和equals()方法,且必须保证逻辑上相等的对象具有相同的哈希码。
# 19. HashMap的工作原理是什么?
答案: HashMap基于哈希表实现,存储键值对。
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)。get()过程: a. 计算键的hashCode(),找到对应的bucket。 b. 如果该bucket只有一个Node,直接返回。 c. 如果是一个链表或红黑树,则使用equals()方法遍历查找对应的key。
在Java 8中,当链表长度超过8且数组容量大于64时,链表会转换为红黑树以提高查询效率;当树节点数小于6时,会退化为链表。
# 20. HashMap和Hashtable的区别?
答案:
| 特性 | HashMap | Hashtable |
|---|---|---|
| 线程安全 | 非线程安全 | 线程安全(方法用synchronized修饰) |
| 性能 | 高(无同步开销) | 低(同步有性能开销) |
| Null键/值 | 允许一个null键和多个null值 | 不允许键或值为null(会抛NullPointerException) |
| 继承 | 继承AbstractMap | 继承Dictionary |
| 初始容量 | 16 | 11 |
| 扩容 | 2n | 2n+1 |
| 迭代器 | 快速失败迭代器 | 枚举器和迭代器 |
注意:Hashtable是遗留类,现在通常使用ConcurrentHashMap来实现线程安全的Map。
# 21. 什么是Java的泛型?为什么要使用泛型?
答案: 泛型是Java 5引入的特性,允许在定义类、接口或方法时使用类型参数,在使用时再指定具体的类型。 好处:
- 类型安全:在编译时进行严格的类型检查,避免在运行时出现
ClassCastException。 - 消除强制类型转换:使代码更加简洁清晰,无需手动进行类型转换。
- 代码复用:可以编写更通用、可复用的代码。
示例:
// 不使用泛型
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); // 无需强制转换,编译时已知类型
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修饰的代码段(同步代码块或同步方法),从而避免多线程环境下的数据不一致问题。
它可以用于:
- 实例方法:锁是当前对象实例(
this)。 - 静态方法:锁是当前类的
Class对象。 - 代码块:可以指定任意对象作为锁,提供更细粒度的锁控制。
# 25. volatile关键字的作用是什么?
答案: volatile关键字用于修饰变量,提供了一种轻量级的同步机制。
它的两大核心作用是:
- 保证可见性:当一个线程修改了
volatile变量的值,新值会立即被刷新到主内存中。当其他线程读取该变量时,会从主内存中重新读取最新值,而不是使用自己工作内存中的缓存值。 - 禁止指令重排序:编译器和大纲在优化时,不会对
volatile变量相关的指令进行重排序,从而保证代码的执行顺序与预期一致。
注意:volatile不保证原子性(例如i++操作不是原子操作)。如果需要保证原子性,仍需使用synchronized或java.util.concurrent.atomic包中的原子类。
# 26. 创建线程有哪几种方式?
答案:
继承
Thread类:- 定义一个类继承
Thread,并重写run()方法。 - 创建该子类的实例,调用
start()方法启动线程。 - 缺点:Java是单继承,继承了
Thread就不能再继承其他类。
- 定义一个类继承
实现
Runnable接口:- 定义一个类实现
Runnable接口,并实现run()方法。 - 创建该实现类的实例,并将其作为
Thread类的构造参数来创建Thread对象,最后调用start()方法。 - 优点:避免了单继承的局限性;更适合多个线程共享一个资源的情况。
- 定义一个类实现
实现
Callable接口:- 定义一个类实现
Callable接口,并实现call()方法(可以有返回值,可以抛出异常)。 - 创建
FutureTask对象,包装Callable实现类。 - 将
FutureTask对象作为Thread类的构造参数创建线程并启动。 - 通过
FutureTask.get()方法可以获取线程执行后的返回值。
- 定义一个类实现
使用线程池:通过
ExecutorService线程池来创建和管理线程,这是最推荐的方式,可以复用线程,减少创建和销毁线程的开销。
# 27. 线程的start()和run()方法有什么区别?
答案:
start()方法:- 是
Thread类的方法。 - 它的作用是启动一个新线程,新线程会去执行
run()方法中的代码。start()不能被多次调用,否则会抛出IllegalThreadStateException。
- 是
run()方法:- 是
Runnable接口定义的方法,由子类重写。 - 如果直接调用
run()方法,它会在当前线程(如main线程)中同步执行,而不会启动新线程。这只相当于普通的方法调用,失去了多线程的意义。
- 是
# 28. 什么是线程安全?如何实现线程安全?
答案: 线程安全是指当多个线程同时访问某个类、对象或方法时,这个类、对象或方法总能表现出正确的行为,数据保持一致。 实现方式:
- 不可变对象:使用
final关键字修饰变量和类,使对象创建后状态不可改变。 - 同步(互斥):
synchronized关键字(同步代码块或同步方法)。java.util.concurrent.locks.Lock接口及其实现类(如ReentrantLock),提供比synchronized更灵活的锁操作。
- 线程安全类:使用Java并发包(
java.util.concurrent)提供的线程安全集合类,如ConcurrentHashMap,CopyOnWriteArrayList等。 - 原子变量:使用
java.util.concurrent.atomic包下的原子类(如AtomicInteger),利用CAS(Compare-And-Swap)操作保证单个变量的原子性。 - 线程本地存储:使用
ThreadLocal为每个线程创建变量的副本,从而避免共享。
# 29. sleep()和wait()方法有什么区别?
答案:
| 特性 | sleep() | wait() |
|---|---|---|
| 所属类 | Thread类的静态方法 | Object类的实例方法 |
| 作用对象 | 对当前线程操作 | 对对象实例操作(必须在synchronized块中) |
| 释放锁 | 不会释放持有的锁 | 会释放持有的锁,让其他线程可以进入同步代码块 |
| 唤醒方式 | 睡眠时间到自动唤醒 | 需要其他线程调用notify()或notifyAll()来唤醒 |
| 使用场景 | 用于暂停当前线程的执行 | 用于线程间的通信和协调 |
# 30. 什么是死锁?如何避免死锁?
答案: 死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,它们都将无法进行下去。 死锁产生的四个必要条件(必须同时满足):
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程强行剥夺。
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
避免死锁:只要破坏上述四个条件中的任意一个即可。
- 破坏互斥条件:通常无法破坏,因为锁本身就是互斥的。
- 破坏请求与保持条件:一次性申请所有所需的资源。
- 破坏不剥夺条件:允许在特定条件下剥夺线程已占有的资源。
- 破坏循环等待条件:对资源进行线性排序,要求线程按固定的顺序申请资源(这是最常用的方法)。
# 31. Java中IO流分为哪几种?
答案: 按照数据流向分:
- 输入流:从数据源(如文件、网络)读取数据到程序。
- 输出流:从程序写出数据到目的地(如文件、网络)。
按照操作数据类型分:
- 字节流:以字节(8 bit)为单位进行读写。顶层抽象类是
InputStream和OutputStream。- 常用于处理二进制文件(如图片、视频、音频)。
- 字符流:以字符(16 bit,根据编码)为单位进行读写。顶层抽象类是
Reader和Writer。- 常用于处理文本文件,能更好地处理字符编码问题。
转换流:InputStreamReader和OutputStreamWriter是字节流和字符流之间的桥梁,可以将字节流转换为字符流,并指定字符集。
# 32. 什么是Java中的序列化和反序列化?
答案:
- 序列化:将Java对象转换成字节序列的过程,以便于存储或网络传输。
- 反序列化:将字节序列恢复成Java对象的过程。
实现序列化的类必须实现
java.io.Serializable接口(这是一个标记接口,没有方法)。可以使用ObjectOutputStream的writeObject()方法进行序列化,使用ObjectInputStream的readObject()方法进行反序列化。 可以使用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"));
2
3
4
5
6
7
8
9
10
# 34. 什么是函数式接口?
答案:
函数式接口是有且仅有一个抽象方法的接口(但可以有多个default或static方法)。它可以用作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对象。
← 中间件面试题 Python测开面试题 →