Java八股文——Java基础篇
1、你是怎样理解OOP[面向对象]
面向对象是利用语言对现实事物进行抽象。面向对象具有以下特征:
- 继承:继承是从已有类得到继承信息创建新类的过程
- 封装:封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口
- 多态:多态是指允许不同子类型的对象对同一消息作出不同的响应
2、重载和重写的区别
- 重载发生在本类,重写发生在父类与子类之间
- 重载的方法名必须相同,重写的方法名相同且返回值类型必须相同。
- 重载的参数列表不同,重写的参数列表必须相同
- 重写的访问权限不能比父类中被重写的方法的访问权限更低。
- 构造方法不能被重写
3、接口与抽象类的区别
- 抽象类要被子类继承,接口要被类实现
- 接口可多继承接口,但类只单继承
- 抽象类可以有构造器、接口不能有构造器
- 抽象类:除了不能实例化抽象类之外,它和普通Java类没有任何区别
- 抽象类抽象方法可以有public、protected和default这些修饰符、接口:只能是public
- 抽象类:可以有成员变量;接口:只能声明常量
4、[深拷贝]与浅拷贝的理解
深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。
- 浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的是同一个对象
- 深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向的对象进行复制,深拷贝出来的对象,内部的类指向的不是同一个对象。
- 浅拷贝实现方式
- 深拷贝实现方式
5、sleep和wait区别
| 方法 | 定义 | 常见用途 |
|---|---|---|
wait() | 释放锁并进入等待状态,直到被其他线程 notify() 或 notifyAll() 唤醒 | 线程间协作,等待某个条件的发生 |
sleep() | 暂停当前线程一段时间,但不释放锁 | 模拟延迟,控制线程执行节奏 |
主要区别
| 特性 | wait() | sleep() |
|---|---|---|
| 锁释放 | 释放当前对象锁 | 不释放锁 |
| 调用位置 | 必须在同步块 (synchronized) 中调用 | 可以在任何地方调用 |
| 所属类 | Object 类 | Thread 类 |
| 中断行为 | 会抛出 InterruptedException | 会抛出 InterruptedException |
| 唤醒机制 | 需要 notify() 或 notifyAll() | 自动恢复,无需唤醒 |
| 状态 | 进入 WAITING 或 TIMED_WAITING 状态 | 进入 TIMED_WAITING 状态 |
- sleep方法
- wait方法
6、什么是自动拆装箱,int和Integer有什么区别
自动拆装箱
自动装箱(Autoboxing) 和 自动拆箱(Unboxing) 是 Java 为了简化基本类型和包装类型之间的转换而引入的机制。java为什么要引入自动装箱和拆箱的功能?主要是用于java集合中,Listlist集合如果要放整数的话,只能放对象,不能放基本类型,因此需要将整数自动装箱成对象。
- 自动装箱(Autoboxing) :将 基本类型 转换为 对应的包装类型。
int primitiveInt = 10; Integer wrappedInt = primitiveInt; - 自动拆箱(Unboxing) :将 包装类型 转换为 对应的基本类型。
Integer wrappedInt = 20; int primitiveInt = wrappedInt;
int和Integer的区别
| 特性 | int | Integer |
|---|---|---|
| 类型 | 基本数据类型 | 包装类(java.lang.Integer) |
| 内存分配 | 栈内存 | 堆内存 |
| 初始值 | 0 | null |
| 存储范围 | -2^31 到 2^31-1 | -2^31 到 2^31-1 |
| 性能 | 更高 | 较低 |
| 方法 | 无 | 包含常用方法,如 parseInt()、toString() |
| 比较 | == 比较值 | == 比较引用,.equals() 比较值 |
Integer缓存机制
Integer 在 -128 到 127 之间的值会 缓存,在这个范围内的 Integer 对象会复用已有实例,超出这个范围的值则会创建新的实例。
7、==和equals区别
| 特性 | == | equals() |
|---|---|---|
| 比较内容 | 比较引用地址(基本类型比较值) | 默认比较引用地址,可重写后比较值 |
| 适用类型 | 基本数据类型和引用类型 | 仅适用于引用类型 |
| 性能 | 更快 | 较慢(涉及方法调用) |
| 可重写性 | 不可重写 | 可在类中重写 |
| 空指针安全 | 安全 | 可能抛出 NullPointerException |
String特殊情况
-
虽然
String是引用类型,但在某些情况下可以直接使用==进行比较,这是因为String具有常量池机制。但String还是建议使用.equals()去比较字符串,也可使用Objects.equals()避免空指针异常。
8、String能被继承吗?为什么使用final修饰
- String不能被继承,因为它是一个final修饰的类
为什么String被final修饰
- 保证字符串的不可变性
- 提高字符串常量池的效率
- String 类中有native关键字修饰的调用系统级别的本地方法,调用了操作系统的 API,如果方法可以重写,可能被植入恶意代码,破坏程序。Java 的安全性也体现在这里。
9、StringBuffer和StringBuilder的区别
| 特性 | StringBuffer | StringBuilder |
|---|---|---|
| 线程安全性 | 线程安全(方法同步) | 非线程安全 |
| 性能 | 较慢(同步开销) | 较快 |
| 适用场景 | 多线程环境 | 单线程环境 |
| 初始版本 | JDK 1.0 | JDK 1.5 |
| 常用方法 | append(), insert(), delete(), reverse() | append(), insert(), delete(), reverse() |
| 是否可变 | 可变 | 可变 |
10、final、finally、finalize?
- **final:**修饰符(关键字)有三种用法:修饰类、变量和方法。修饰类时,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用中只能读取不可修改,即为常量。修饰方法时,也同样只能使用,不能在子类中被重写。
- **finally:**通常放在try…catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。
- **finalize:**Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。
11、Object中有哪些方法?
protected Object clone()——创建并返回此对象的一个副本。boolean equals(Object obj)——指示某个其它对象是否与此对象相等。protected void finalize()——当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。Class extends Object> getClass()——返回一个对象的运行时类。int hashCode()——返回该对象的哈希码值。void notify()——唤醒在此对象监视器上等待的单个线程。void notifyAll()——唤醒在此对象监视器上等待的所有线程。String toString()——返回该对象的字符串表示。
12、集合体系
java.util.Collection (顶层接口)
├── java.util.List(有序、可重复)
│ ├── java.util.ArrayList
│ ├── java.util.LinkedList
│ └── java.util.Vector
│ └── java.util.Stack
├── java.util.Set(无序、不可重复)
│ ├── java.util.HashSet
│ ├── java.util.LinkedHashSet
│ └── java.util.TreeSet
└── java.util.Queue(队列,FIFO)
├── java.util.PriorityQueue
└── java.util.Deque(双端队列)
├── java.util.LinkedList
└── java.util.ArrayDeque
java.util.Map(键值对)
├── java.util.HashMap
├── java.util.LinkedHashMap
├── java.util.TreeMap
└── java.util.Hashtable
AI写代码mathematica
123456789101112131415161718192021
13、ArrayList和LinkedList区别
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
- 对于随机访问get和set,ArrayList效率优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinekdList比较占优势,因为ArrayList要移动数据。这一点要看实际情况的。只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于ArrayList,因为ArrayList每插入一条数据,要移动插入点之后的所有数据。
14、HashMap底层是什么,为什么要用这几类结构
- HashMap的底层是数组+链表+红黑树(JDK 8+) 。
HashMap (JDK 8+)
├── 数组(Node[])
│ ├── 链表
│ └── 红黑树
└── 散列函数(hash())
AI写代码scss
12345
- Node
结构
- 为什么要使用数组+链表+红黑树
数组:查询效率高,O(1)时间复杂度;
链表:处理哈希冲突,适合小规模冲突;
红黑树:当链表长度超过8时,转换为红黑树,提高查询效率。
- 哈希函数
static final int hash(Object key) {
int h;
//异或高位扰动,减少哈希冲突
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
AI写代码java
运行
12345
15、HashMap和HashTable的区别
| 特性 | HashMap | Hashtable |
|---|---|---|
| 线程安全 | 非线程安全 | 线程安全 |
| 效率 | 高 | 低 |
| Null 键和值 | 允许 1 个 null 键,多个 null 值 | 不允许 null 键和 null 值 |
| 初始容量 | 16 | 11 |
| 容量要求 | 容量一定是2的整数幂 | 不一定是2的整数幂 |
| 扩容机制 | 容量翻倍 | 容量翻倍 + 1 |
| 负载因子 | 0.75(默认) | 0.75(固定) |
| 遍历方式 | Iterator | Enumerator |
| 底层实现 | 数组 + 链表 + 红黑树 | 数组 + 链表 |
| JDK 版本 | JDK 1.2 | JDK 1.0 |
| 推荐使用 | 大部分场景 | 兼容旧代码 |
16、线程的创建方式
方式
- 继承
Thread类,重写Thread类的run()方法,通过start()方法启动线程
// 继承 Thread 实现线程
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Count: " + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
AI写代码java
运行
12345678910111213141516171819
2. 实现 Runnable 接口,实现 Runnable 接口的 run() 方法,通过 Thread 构造函数传入 Runnable 实例
// 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Count: " + i);
}
}
}
public class RunnableTest {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable());
Thread thread2 = new Thread(new MyRunnable());
thread1.start();
thread2.start();
}
}
AI写代码java
运行
1234567891011121314151617181920
3. 实现 Callable 接口(带返回值),实现 Callable 接口的 call() 方法,使用 FutureTask 包装 Callable 实例,通过 Thread 启动
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return Thread.currentThread().getName() + " - Task Completed";
}
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask task = new FutureTask<>(new MyCallable());
Thread thread = new Thread(task);
thread.start();
// 阻塞等待线程执行完毕
String result = task.get();
System.out.println(result);
}
}
AI写代码java
运行
1234567891011121314151617181920212223
4. 使用线程池(ExecutorService),使用 Executors 创建线程池,提交任务 (submit() / execute())
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " is running");
});
}
executor.shutdown();
}
}
AI写代码java
运行
12345678910111213141516
5. 使用匿名内部类
public class AnonymousThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Count: " + i);
}
});
thread.start();
}
}
AI写代码java
运行
1234567891011
6. 使用Lambda表达式
public class LambdaThreadTest {
public static void main(String[] args) {
new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " - Count: " + i);
}
}).start();
}
}
AI写代码java
运行
12345678910
对比
| 方式 | 优点 | 缺点 |
|---|---|---|
| 继承 Thread | 代码简单,易于理解 | 不能继承其他类 |
| 实现 Runnable | 可实现多继承,解耦逻辑 | 无返回值 |
| 实现 Callable | 支持返回值和异常处理 | 代码稍复杂 |
| 线程池 | 高效管理线程,复用线程资源 | 需要手动关闭线程池 |
| 匿名内部类 | 简化代码结构 | 可读性较差 |
| Lambda | 简洁、高效 | 只能用于函数式接口 |