八股_java基础
概念
1.说说java的特点
面向对象
平台无关性:一次编译到处运行
内存管理:自动管理内存和回收不再使用的对象
2.java的优势和劣势是什么?
优势:
跨平台,一次编译到处运行
生态丰富,有各种框架和类库
内存管理,垃圾自动回收
严格的面向对象,复用性和可维护性优秀
天然支持多线程,方便并发编程
安全性方面:java有安全模型,比如支持沙箱机制,适合网络编程
稳定性方面:企业级应用长期使用,版本更新比较注重向后兼容
劣势
- 内存占用大,jvm本身也占用内存
- 性能开销较c++,Rust大
- 执行效率较慢,比如微服务启动时间比go慢
- 语法繁琐,开发效率比较低
3.Java为什么是跨平台的?
因为java运行在jvm上,jvm会将编译后的class文件根据平台的不同翻译成不同平台对应操作系统的机器码。
因此不同平台需要安装各自版本的jvm才能运行java程序。
4.JVM,JDK,JRE三者的关系
jdk包括jre,jre包括jvm。
5.为什么java解释和编译都有?
Java 同时采用 解释执行 和 编译执行(JIT 编译)的结合方式,主要是为了在 启动速度 和 长期运行性能 之间取得平衡。Java 的混合模式(解释 + JIT)既保证了跨平台性,又通过运行时优化达到高性能,适应不同场景需求。
补充:AOT 编译(Java 9+)
现代 Java 还支持 AOT 编译(如 jaotc
工具),直接将字节码编译为机器码,进一步减少启动时间,但牺牲了跨平台灵活性和动态优化能力。
6.jvm是什么
jvm是java虚拟机,主要工作是解释自己的指令集(字节码)并映射到本地cpu指令集和OS的系统调用。
7.编译型语言和解释型语言的区别?
编译型语言:在程序执行之前,整个源代码会被编译成机器码或者字节码,生成可执行文件。执行时直接运行编译后的代码,速度快,但跨平台性较差。
解释型语言:在程序执行时,逐行解释执行源代码,中间不额外生成可执行文件。通常由解释器动态解释并执行源代码,跨平台性好,但执行速度较慢。
常见的编译型语言:c , c++常见的解释型语言:python,JavaScript
8.python和java的区别是什么?
python是解释型语言,java是混合型语言。python简洁,运行时确定类型,Java严谨,有显式的类型和分号,编译前确定类型。
数据类型
1.八种基本的数据类型
整型:byte1,short2,int4,long8
浮点型:float4,double8
布尔型:boolean(理论上一位)
字符型:char2
2.long和int可以互转吗?
可以,long比int范围大,因此将int
转换为long
是安全的,而将long
转换为int
可能会导致数据丢失或溢出。
3.数据类型转换你知道哪些?
自动类型转换(隐式转换):当目标类型范围大于源类型范围时,自动发生类型转换。
强制类型转换(显示转换):当目标类型范围小于源类型范围时,可以强制类型转换,多少如果超出了目标类型范围就会发生数据丢失或溢出。
字符串转换:java提供了将字符串表示的数据转化为其他类型数据的方法,例如将字符串String转换为int的方法可以使用Integer.parseInt(str)。如果是将数字转换为字符串可以用String.valueOf(123)
4.类型互转会出现什么问题?
数据丢失
数据溢出
精度损失
类型不匹配导致的错误
5.为什么用bigDecimal不用double
在Java中进行浮点数运算的时候,会出现丢失精度的问题。而 Decimal 是精确计算 , 所以一般牵扯到金钱的计算 , 都使用 Decimal。
6.装箱和拆箱是什么?
装箱(Boxing)和拆箱(Unboxing)是将基本数据类型和对应的包装类之间进行转换的过程。
自动装箱拆箱一般发生在赋值(Java1.5以后)时 和 方法调用时。
自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的情况,如下面的例子就会创建多余的对象,影响程序的性能。
1 | Integer sum=0; |
7.java为什么会有包装类8.Integer相比int有什么优点?
1.把属性也就是数据跟处理这些数据的方法结合在一起,比如Integer就有parseInt()等方法来专门处理int型相关的数据。
2.在Java中绝大部分方法或类都是用来处理类类型对象的,如ArrayList集合类就只能以类作为他的存储对象,而这时如果想把一个int型的数据存入list是不可能的,必须把它包装成类,也就是Integer才能被List所接受。所以Integer的存在是很必要的。
在泛型中的应用
在Java中,泛型只能使用引用类型,而不能使用基本类型。因此,如果要在泛型中使用int类型,必须使用Integer包装类。例如我们如果想将列表中的元素排序,并将排序结果存储在一个新的列表中。int类型无法直接使用Collections.sort()方法,而如果是Integer就可以
1 | List<Integer> list = new ArrayList<>(); |
在集合中的应用
Java集合中只能存储对象,而不能存储基本数据类型。因此,如果要将int类型的数据存储在集合中,必须使用Integer包装类。
9.那为什么还要保留int类型?
读写效率和存储效率。
基本类型数据在读写效率方面,要比包装类高效。除此之外,在64位JVM上,在开启引用压缩的情况下,一个Integer对象占用16个字节的内存空间,而一个int类型数据只占用4字节的内存空间,前者对空间的占用是后者的4倍。
10.说一下Integer的缓存
Java的Integer类内部实现了一个静态缓存池,用于存储特定范围内的整数值对应的Integer对象,这个值默认是-128-127,如果通过Integer.valueOf(int)创建了一个在这个范围内的整数对象,并不会每次都生成新的对象实例,而是复用缓存中的现有对象,会直接从内存中取出,不需要新建一个对象。可调整范围:可以通过 JVM 参数 -XX:AutoBoxCacheMax=<size>
来调整上限。
面向对象
1.怎么理解面向对象?简单说说封装继承多态
面向对象是一种编程范式,它把现实中的事物抽象成一个个对象,对象具有属性和行为,通过对象间的交互实现程序的功能,具有灵活性和可扩展性,通过封装和继承可以更好的对应需求变化。
封装:指把对象的属性和方法结合在一起,对外隐藏内部细节,暴露接口。封装的目的是增强安全性和简化编程,使得对象更加独立。编写类时一般将成员变量私有化,外部类需要使用Getter和Setter方法来查看和设置变量。
继承:继承是一种可以使子类自动共享父类数据结构和方法的机制。提高了代码的复用性,建立对象之间的层次关系,使结构更加清晰。
多态:多态是指允许不同类对同一消息作出响应。即同一个接口可以使用不同的实例而执行不同的操作。多态分为编译时多态(重载)和运行时多态(重写),提高了代码的灵活性和扩展性。
2.多态体现在哪些方面?
方法重载:一个类中允许相同的方法名,不同的参数列表的方法存在。编译器在编译时根据参数确定调用的方法。
方法重写:子类可以对父类中的同名方法进行重写。在运行时,JVM会根据对象的实际类型来确定调用哪个版本的方法。
接口与实现:接口可以有多个不同的具体实现,并且可以使用接口类型的引用来调用这些类的方法。这使得程序在不同的具体实现时使用了一贯的调用方式。
向上转型和向下转型:
在java中,可以使用父类类型的引用指向子类类型的对象,这是向上转型。通过这种方式可以在运行时使用不同子类实现。
向下转型是将父类引用转回其子类类型,但在执行前需要确认引用实际指向的对象类型以避免ClassCastException
。
3.多态解决了什么问题?
多态提高了代码的复用性和扩展性,是很多设计模式,设计原则,编程技巧的代码实现基础。比如策略模式,基于接口而非实现编程,依赖倒置原则,里氏替换原则等等。
4.面向对象的设计原则你知道哪些吗?
面向对象编程中的六大原则:(七大的话多一个合成复用原则)
单一职责原则:一个类应该只有一个引起它变化的原因,即一个类应该只负责一项职责.
开放封闭原则:软件实体应该对扩展开放,对修改封闭。例子:通过制定接口来实现这一原则
里氏替换原则:子类对象应该能够替换掉所有父类对象。
接口隔离原则:客户端不应该依赖那些它不需要的接口,即接口应该小而专。
依赖倒置原则:高层模块不应该依赖低层模块,二者都应该依赖于抽象;抽象不应该依赖于细节,细节应该依赖于抽象。例子:如果一个公司类包含部门类,应该考虑使用合成/聚合关系,而不是将公司类继承自部门类。
最少知识原则(迪米特法制):一个对象应当对其他对象有最少的了解,只与其直接的朋友交互。
5.java抽象类和接口的区别是什么?
二者特点
抽象类:不能直接实例化,必须被继承,子类必须实现所有抽象方法。可以包含成员变量,可以包含抽象方法和具体方法,可以包含构造方法(用于子类初始化)
接口:java8前所有方法默认是public abstract,java8开始支持默认方法和静态方法,java9开始支持私有方法,不能包含实例字段只能是public static final常量,不能有构造方法,一个类可以实现多个接口。
二者区别:
- 实现方式:实现接口的关键字是implements,继承抽象类的关键字是extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所有使用接口可以间接的实现多重继承。
- 方法方式:接口只有定义不能有方法的实现,java8中可以定义default方法体,而抽象类可以有具体方法,也可以有抽象方法,抽象方法在继承类中实现。
- 访问修饰符:接口成员变量默认为public static final,必须赋初值,不能被修改;其所有的成员方法都是public abstract的。抽象类的成员变量默认default,可在子类中被重新定义,也可被重新赋值。抽象方法被abstract修饰,不能被其他修饰,必须以分号结尾不带花括号。
- 变量:抽象类可以包含实例变量和静态变量,而接口只能包含常量(即静态常量)。
6.抽象类能加final修饰吗?
不能,Java中的抽象类是用来被继承的,而final修饰符用于禁止类被继承或方法被重写,因此它们的互斥的,不能同时使用。
7.接口里面可以定义哪些方法?
java8以前默认包含public abstract修饰的抽象方法。java8引入默认方法和静态方法,默认方法允许接口提供具体实现,静态方法属于接口本身,可以直接调用而无需实现类。java9引入私有方法,用于在接口中为默认方法或其他私有方法提供辅助功能,这些方法不能被实现类访问,只能在接口内部使用。
8.解释java中的静态变量和静态方法
在Java中,静态变量和静态方法是与类本身关联的,而不是与类的实例(对象)关联。它们在内存中只存在一份,可以被类的所有实例共享。可以直接使用而无需通过实例。
9.非静态内部类和静态内部类的区别
特性 | 非静态内部类 | 静态内部类 |
---|---|---|
与外部类实例的关系 | 隐式持有外部类实例的引用 | 不持有外部类实例的引用 |
实例化方式 | 必须先创建外部类实例 | 可直接实例化 |
访问外部类成员 | 可直接访问外部类的所有成员 | 只能访问外部类的静态成员 |
包含this引用 | 有外部类this和内部类this两个引用 | 只有自己的this引用 |
内存泄漏风险 | 可能导致内存泄漏 | 无此风险 |
序列化 | 序列化复杂 | 序列化简单 |
用途 | 紧密关联外部类实例的功能 | 与外部类逻辑相关但独立的功能 |
10.非静态内部类可以直接访问外部方法,编译器是怎么做到的?
非静态内部类可以直接访问外部方法是因为编译器在生成字节码时会为非静态内部类维护一个指向外部类实例的引用。这个引用使得非静态内部类能够访问外部类的实例变量和方法。编译器会在生成非静态内部类的构造方法时,将外部类实例作为参数传入,并在内部类的实例化过程中建立外部类实例与内部类实例之间的联系,从而实现直接访问外部方法的功能。
关键字
java中final的作用是什么?
final修饰符用于禁止类被继承或方法被重写。
当final
修饰基本数据类型的变量时,该变量一旦被赋值就不能再改变。对于引用数据类型,final
修饰意味着这个引用变量不能再指向其他对象,但对象本身的内容是可以改变的。
浅拷贝和深拷贝
1.浅拷贝和深拷贝的区别?
浅拷贝(Shallow Copy)
特点:对于引用类型字段(如对象、数组等):只复制引用地址,不创建新对象。新对象和原对象中的引用类型字段指向同一个内存地址,修改其中一个对象的引用字段内容会影响另一个对象
深拷贝(Deep Copy)
特点:对所有层级引用都创建新对象,新对象和原对象完全独立,修改其中一个对象不会影响另一个对象
其中spring提供的BeanUtil包下的copyProperties方法是浅拷贝,而Apache提供的SerializationUtils包下的clone方法是深拷贝
2.实现深拷贝的三种方式
在Java中,实现深拷贝主要有以下三种常用方法,每种方法适用于不同的场景:
- 实现
Cloneable
接口,重写clone()
方法
原理:逐层创建新对象并复制所有字段,包括引用类型字段的新实例
示例:
1 | class Person implements Cloneable { |
优点:性能较好,代码控制精细
缺点:需要修改所有相关类的代码,多层嵌套时代码较繁琐
- 序列化法(Serialization)
原理:通过对象序列化和反序列化实现深拷贝
示例:
1 | import java.io.*; |
优点:实现简单,不需要修改每个类的clone方法,自动处理复杂的对象图
缺点:性能较差(比手动克隆慢10-100倍),所有相关类必须实现Serializable,无法复制transient字段
- 复制构造方法法(Copy Constructor)
原理:通过专门的构造方法实现深拷贝
示例:
1 | class Person { |
优点:代码清晰直观,不需要实现特殊接口,可以精细控制拷贝过程
缺点:需要为每个类添加构造方法,对复杂对象图实现较麻烦
泛型
什么是泛型?
它是Java的一种特性,它允许类、接口和方法在定义时使用类型参数,这些类型参数在使用时可以被指定为具体的类型。主要目的是提供更强的类型检查,避免在运行时出现类型转换异常。
为什么需要泛型?
1.泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)。
例如:
1 | List list = new ArrayList(); |
上面list中的元素都是Object类型(无法约束其中的类型),取出集合元素时需要人为的强制类型转化到具体的目标类型,可能导致抛出类型转换异常。
1 | List list1 = new ArrayList<String>(); |
这样就会在编译前检查类型是否为String。
2.提高代码复用性
实现int,float,double类型的加法无需重载。使用下面这样的方法就行。
1 | private static <T extends Number> double add(T a, T b) { |
对象
1.java对象创建有哪些方式?
new,反射,clone,反序列化。
1.使用new关键字直接调用类的构造方法来创建对象
2.使用Class类的newInstance()方法:提供反射机制,可以使用Class类的newInstance()方法创建对象
1 | MyClass obj = (MyClass) Class.forName("com.dmw.MyClass").newInstance(); |
3.使用Constructor类的newInstance()方法:同样是通过反射机制,可以使用Constructor类的newInstance()方法创建对象
1 | Constructor<MyClass> constructor = MyClass.class.getConstructor(); |
4.使用clone()方法:如果类实现了Cloneable接口,可以使用clone方法复制对象
1 | MyClass obj1 = new MyClass(); |
5.使用反序列化:通过将对象序列化到文件或流中,然后再进行反序列化来创建对象。
1 | //SerializedObject.java |
2.New出的对象什么时候回收?
通过new创建的对象,由Java中的垃圾回收器(Garbage collector)负责回收。垃圾回收器的工作是在程序运行过程中自动进行的,它会周期性地检测不再被引用的对象,并将其回收释放内存。
具体由几种算法来决定的:
1.引用计数法:某个对象的引用计数为0时,表示该对象不再被引用,可以被回收。
2.可达性分析算法:从根对象(如方法区中的类静态属性、方法中的局部变量等)出发,通过对象之间的引用链进行遍历,如果存在一条引用链到达某个对象,则说明该对象是可达的,反之不可达,不可达的对象将被回收。
3.终结器(Finalizer):如果对象重写了finalize()方法,垃圾回收器会在回收该对象之前调用finalize方法,对象可以在finalize方法中进行一些清理操作,然而,终结器机制的使用不被推荐,它的执行时间不确定,可能会导致不可预测的性能问题。
3.如何获取私有对象?
1.使用公共访问器方法(getter方法)
2.反射,可以绕过private访问修饰符的限制来获取私有对象,setAccessible(true);
4.new的时候,jvm做了什么?
拿这个例子举例
1 | SomeClass obj = new SomeClass(); |
- 类加载检查(Class Loading)
- JVM 会检查 类 是否已经被加载、连接和初始化。
- 若未加载,则通过类加载器(ClassLoader)将其
.class
文件加载到内存中。
- 内存分配
- JVM 向堆内存申请一块空间,用于存放该对象的数据(字段值等)。
- 分配方式可能是:
- 指针碰撞(bump-the-pointer);
- 空闲列表(free-list);
- TLAB(Thread Local Allocation Buffer)。
- 零值初始化
- 将对象的实例字段(非静态变量)全部赋默认值(0、null、false 等)。
- 设置对象头
对象头包含两部分:
- Mark Word:哈希码、GC 分代信息、锁状态等;
- 类型指针:指向类的元数据(即
SomeClass
的 Class 对象)。
- 执行构造方法
- 执行 类 的构造方法(
<init>
),并逐级调用其父类构造方法。 - 构造方法内可以赋初始值或执行逻辑。
- 返回对象引用
- JVM 将堆中分配的对象地址返回,并赋值给变量
obj
。 - 实际上是一个引用变量指向对象在堆中的地址。
5.对象的四种引用你知道吗?
Java 中对象引用根据垃圾回收器的回收策略强弱,可以分为以下 四种引用类型:
🧩 1. 强引用(Strong Reference)
特点:最常见、最强的引用类型,只要存在强引用,GC 不会回收对象。
1 | Object obj = new Object(); // obj 是强引用 |
说明:
- 即使系统内存不足,JVM 也不会回收强引用的对象;
- 必须将引用断开(设为
null
),对象才可能被 GC。
🍃 2. 软引用(Soft Reference)
特点:内存不足时才会被 GC 回收,常用于缓存。
1 | SoftReference<Object> softRef = new SoftReference<>(new Object()); |
说明:
- GC 时如果内存仍然充足,则保留;
- 如果内存紧张,则会被回收;
- 常见于:图片缓存、对象池等。
🌫 3. 弱引用(Weak Reference)
特点:一旦发生 GC,就会立即被回收,不管内存是否充足。
1 | WeakReference<Object> weakRef = new WeakReference<>(new Object()); |
说明:
- 常用于 ThreadLocal 的底层实现;
- 避免内存泄漏(比如 map key 是弱引用,value 可被自动清除);
- 非实时回收,但一定会在 GC 时清除。
👻 4. 虚引用(Phantom Reference)
特点:最弱的引用,不能通过虚引用获取对象实例,主要用于对象被回收前的清理机制。
1 | PhantomReference<Object> phantomRef = new PhantomReference<>(new Object(), new ReferenceQueue<>()); |
说明:
- 总是返回
null
; - 必须配合
ReferenceQueue
使用; - 常用于监控对象被 GC 的时间、资源释放(类似 finalize 的更安全替代)。
📊 总结对比表
类型 | 是否参与 GC 保留 | GC 触发回收时机 | 应用场景 |
---|---|---|---|
强引用 | 否 | 永远不回收 | 普通对象引用 |
软引用 | 是(内存不足) | 内存不足时 | 缓存、内存敏感应用 |
弱引用 | 是(立即) | 一旦 GC 就回收 | ThreadLocal、WeakHashMap |
虚引用 | 是(需监听) | GC 触发前通知(不能访问) | 对象回收前清理、监控 |
🧠 一句话记忆:
强软弱虚,依次变轻,GC 越容易回收,越难访问对象。
反射
1.什么是反射?
Java的反射机制是在运行状态中,对于任何一个类都能够知道这个类中的所有属性和方法,对于任何一个对象都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用方法的功能称为Java语言的反射机制。
2.反射在你平常写代码或者框架中的应用场景有哪些?
加载数据库驱动;
配置文件加载;
Spring 框架的 IoC容器(动态加载管理 Bean),Spring通过配置文件配置各种各样的bean。
注解
1.能讲一讲java注解的原理吗?
注解本质上是一个特殊的接口,继承自Annotation。所以注解也叫声明式接口。编译后,注解会被转换成普通的接口,注解的属性会成为接口的方法。
1 | // 定义注解 |
2.对注解解析的底层实现有了解吗?
注解解析的底层实现主要依赖于Java的反射机制和字节码文件的存储。通过@Retention元注解可以控制注解的保留策略,当使用RetentionPolicy.RUNTIME时,可以在运行时通过反射API来解析注解信息。在JVM层面,会从字节码文件中读取注解信息,并创建注解的代理对象来获取注解的属性值。
3.java注解的作用域呢?
注解的作用域(Scope)指的是注解可以应用在哪些程序元素上,例如类、方法、字段等。
异常
1.介绍一下异常
Java的异常体系主要基于两大类:Throwable类及其子类。Java的异常体系主要基于两大类:Throwable类及其子类。
Error(错误):表示运行时环境的错误。错误是程序无法处理的严重问题,如系统崩溃、虚拟机错误、动态链接失败等。通常,程序不应该尝试捕获这类错误。例如,OutOfMemoryError、StackOverflowError等。
Exception(异常):表示程序本身可以处理的异常条件。异常分为两大类:
- 非运行时异常:这类异常在编译时期就必须被捕获或者声明抛出。它们通常是外部错误,如文件不存在(FileNotFoundException)、类未找到(ClassNotFoundException)等。非运行时异常强制程序员处理这些可能出现的问题,增强了程序的健壮性。
- 运行时异常:这类异常包括运行时异常(RuntimeException)和错误(Error)。运行时异常由程序错误导致,如空指针访问(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。运行时异常是不需要在编译时强制捕获或声明的。
2.java异常处理有哪些?
异常处理是通过使用try-catch语句块来捕获和处理异常。以下是Java中常用的异常处理方式:
- try-catch语句块:用于捕获并处理可能抛出的异常。try块中包含可能抛出异常的代码,catch块用于捕获并处理特定类型的异常。可以有多个catch块来处理不同类型的异常。finally块:用于定义无论是否发生异常都会执行的代码块。通常用于释放资源,确保资源的正确关闭。
- throw语句:用于手动抛出异常。可以根据需要在代码中使用throw语句主动抛出特定类型的异常。
- throws关键字:用于在方法声明中声明可能抛出的异常类型。如果一个方法可能抛出异常,但不想在方法内部进行处理,可以使用throws关键字将异常传递给调用者来处理。
3.抛出异常为什么不用throws
如果异常是未检查异常或者在方法内部被捕获和处理了,那么就不需要使用throws。
- 未检查异常(unchecked exceptions):未检查异常是继承自RuntimeException类或Error类的异常,编译器不强制要求进行异常处理。因此对于这些异常不需要在方法签名中使用throws关键字来声明。例如NullPointerException和ArrayIndexOutOfBoundsException等。
- 方法内部捕获处理异常了:可能已经在方法内部捕获并处理过了。就不需要通过throws将它们传递到调用者。
4.try catch中的语句运行情况
try在的代码将按顺序执行,如果抛出异常,将在catch块中进行匹配和处理,然后将继续执行catch块之后的代码。如果没有匹配的catch块,异常将被传递给上一层调用的方法。
5.try{return “a”}finally{return “b”}这条语句返回啥
finally中的return会覆盖try/catch中的return返回,因此返回”b”;
object
1.==与equals有什么区别?
在比较字符串变量时,==比较的是字符串的内存地址的数值,而equals比较字符串的内容。
在比较非字符串变量时,如果equals方法没有重写,那么二者的作用是相同的,都是比较对象在堆内存中的首地址,即比较这两个引用变量是否指向同一个对象。
2.hashCode和equals方法有什么关系?
在Java中,一般重写equals方法的类也会重写hashCode方法,并且遵循以下两条规定:
- 一致性:如果两个对象使用equals方法比较的结果为true,那么它们的hashCode必须相同。也就是说如果obj1.equals(obj2),那么obj1.hashCode()==obj2.hashCode()。
- 非一致性:如果两个对象的hashCode()相同,它们使用
equals
方法比较的结果不一定为true
。如果不为true,这种情况称为哈希冲突。
**如何正确重写 hashCode()
**
- 通常使用
Objects.hash()
或手动计算哈希值,确保相同的属性组合生成相同的哈希码。
3.String、StringBuffer、StringBuilder的区别和联系
Java新特性
1.Java8的新特性
- Lambda表达式
- 函数式接口
- 接口的默认方法和静态方法
- StreamAPI
- Optional类
- 日期时间API
2.Lambda表达式有了解吗?
用于创建匿名函数,简化匿名内部类写法。
特点:函数式接口:只能用于只有一个抽象方法的接口(函数式接口);参数类型可省略
()->expression :当 Lambda 体只有一个表达式时使用,表达式的结果会作为返回值。
()->{statements}:当 Lambda 体包含多条语句时,需要使用大括号将语句括起来,若有返回值则需要使用 return
语句。
3.Stream流的并行API是什么?
是 ParallelStream。
并行流(ParallelStream)能够自动将流操作并行化以利用多核处理器的优势。底层是使用通用的 fork/join 池来实现。适用于cpu密集型任务。
4.completableFuture怎么用的(待学)?
CompletableFuture
是 Java 8 引入的异步编程工具,属于 java.util.concurrent
包,用于简化异步任务编排,支持链式调用、组合多个异步任务和异常处理。
1. 创建 CompletableFuture
supplyAsync
(有返回值):1
2
3CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "Hello, CompletableFuture!";
});runAsync
(无返回值):1
2
3CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
System.out.println("Task running asynchronously");
});
2. 获取结果
get()
(阻塞获取,可能抛出异常):1
String result = future.get(); // 阻塞直到任务完成
join()
(类似get()
,但不抛出受检异常):1
String result = future.join(); // 阻塞,但只抛 unchecked 异常
3. 链式处理
thenApply
(转换结果):1
CompletableFuture<Integer> lengthFuture = future.thenApply(s -> s.length());
thenAccept
(消费结果):1
future.thenAccept(s -> System.out.println("Result: " + s));
thenRun
(任务完成后执行操作):1
future.thenRun(() -> System.out.println("Task completed"));
4. 组合多个 Future
thenCompose
(串行执行,前一个结果作为下一个的输入):1
2CompletableFuture<String> future2 = future.thenCompose(s ->
CompletableFuture.supplyAsync(() -> s + " (processed)"));thenCombine
(并行执行,合并两个结果):1
2CompletableFuture<String> combined = future.thenCombine(anotherFuture,
(res1, res2) -> res1 + " and " + res2);allOf
(等待所有任务完成):1
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2, future3);
anyOf
(等待任意一个任务完成):1
CompletableFuture<Object> any = CompletableFuture.anyOf(future1, future2);
5. 异常处理
exceptionally
(捕获异常并返回默认值):1
2
3
4CompletableFuture<String> safeFuture = future.exceptionally(ex -> {
System.err.println("Error: " + ex.getMessage());
return "Default Value";
});handle
(同时处理正常结果和异常):1
2
3
4CompletableFuture<String> handled = future.handle((res, ex) -> {
if (ex != null) return "Fallback";
else return res.toUpperCase();
});
5.Java21新特性知道哪些?
语言特性:
1.字符串模板:
1 | String name = "Alice"; |
2.数组模式:将模式匹配扩展到数组中,使开发者能够在条件语句中更高效地解构和检查数组内容。例如,if (arr instanceof int[] {1, 2, 3})
,可以直接判断数组arr
是否匹配指定的模式。
3.Switch 语句的模式匹配::该功能在 Java 21 中也得到了增强。它允许在switch
的case
标签中使用模式匹配,使操作更加灵活和类型安全,减少了样板代码和潜在错误。
并发特性:
1.虚拟线程:由jvm管理的轻量级线程,可以创建数百万个虚拟线程,使用方式与传统使用方式相等。它通过共享堆栈的方式,大大降低了内存消耗,同时提高了应用程序的吞吐量和响应速度。
**2.Scoped Values(范围值):**提供了一种在 线程间共享不可变数据 的新方式,避免使用传统的线程局部存储,促进了更好的封装性和线程安全,可用于在不通过方法参数传递的情况下,传递上下文信息,如用户会话或配置设置。
序列化
1.怎么把一个对象从一个jvm转到另一个jvm?
- 使用序列化和反序列化
- 使用消息传递机制
- 使用远程方法调用(RPC)
- 使用共享数据库或缓存
2.序列化和反序列化让你自己实现你会怎么做?
我会考虑用主流序列化框架,比如FastJson、Jackson、Protobuf等来替代Java 序列化。
3.将对象转为二进制字节流具体怎么实现?
在Java中通过序列化对象流来完成序列化和反序列化:
- ObjectOutputStream:通过writeObject()方法做序列化操作。
- ObjectInputStrean:通过readObject()方法做反序列化操作。
只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常!
设计模式
1.volatile和sychronized如何实现单例模式?
双重检查实现懒汉式单例模式。
volatile关键字:用于变量上,保证线程可见性,禁止指令重排序优化。
1 | public class SingleTon { |
I/O
1.java怎么实现网络IO高并发编程?
可以用 Java NIO ,是一种同步非阻塞的I/O模型,也是I/O多路复用的基础。
或者使用Netty框架。
2.BIO、NIO、AIO区别是什么?
3.NIO是怎么实现的?
NIO是一种同步非阻塞的I0模型,所以也可以叫NON-BLOCKINGIO。同步是指线程不断轮询I/O事件是否就绪,非阻塞是指线程在等待I0的时候,可以同时做其他任务。
同步的核心就Selector(I/O多路复用),Selector代替了线程本身轮询10事件,避免了阳塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当I/O事件就绪时,可以通过写到缓冲区,保证I/O的成功,而无需线程阻塞式地等待。
NIO由一个专门的线程处理所有I/O事件,并负责分发。事件驱动机制,事件到来的时候触发操作,不需要阻塞的监视事件。线程之间通过wait,notify通信,减少线程切换。
NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区)Selector。传统I/O基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。
4.你知道有哪个框架用到了NIO吗?
Netty
- 最流行的NIO框架
- 特点:
- 高性能、高吞吐
- 零拷贝技术
- 丰富的协议支持(HTTP/WebSocket等)
- 完善的异常处理
其他
1.Native方法解释一下(待学)
Native 方法(本地方法)是指用 Java 声明但用其他语言(如 C/C++)实现的方法,通常用于:
- 访问系统特定功能
- 调用已有本地库
- 提升关键代码性能
- 与硬件交互
核心特点
- 跨语言交互:Java 与其他语言(主要是 C/C++)的桥梁
- 性能关键:绕过 JVM 直接执行本地代码
- 系统级访问:可突破 JVM 沙箱限制
- 不安全:可能引起内存泄漏、崩溃等原生代码问题
现代替代方案
JNA (Java Native Access):更简单的本地库调用
1
2
3public interface CLibrary extends Library {
int printf(String format, Object... args);
}Project Panama(Java 未来特性):
- 更安全高效的 native 交互
- 减少 JNI 的复杂性
2.有一个学生类,想按照分数排序,再按照学号排序,应该怎么做?
可以使用Comparable接口来实现按照分数排序,再按照学号排序。首先在学生类中实现Comparable接口,并重写compareTo方法,然后在compareTo方法中实现按照分数排序和按照学号排序的逻辑。
1 | public class Student implements Comparalbe<Student> { |
sql层面:
select * from student order by score desc,id asc;
3.零拷贝
零拷贝是内核直接在文件系统和网络之间搬运数据的技术,绕过用户空间,从而加速大数据传输过程。