Java基础
1、重载和重写是什么意思,区别是什么?
中文意思容易混淆,用英文记会清晰一些:Overload与Override。
重载(Overload)
在同一个类中,多个方法名称相同,但参数不同(数量或类型不同),这叫做方法重载。
java
class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
重写(Override)
子类中重新定义父类中的方法(方法名、参数都一样),叫做方法重写,目的是修改或扩展父类的行为。
java
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
2、Java中在传参数时是将值进行传递,还是传递引用?
在 Java 中,参数传递机制统一是:
按值传递(Pass by Value)
但这个"值"是变量的值 ,可能是基本类型的值 ,也可能是对象引用的值,这就容易让人混淆。
1. 传递基本类型(int、float、boolean 等)
是值的复制,互不影响:
java
void change(int x) {
x = 10;
}
int a = 5;
change(a);
System.out.println(a); // 输出:5(没有变)
2. 传递 对象引用类型(比如传一个 List、数组、对象等)
java
void changeName(Person p) {
p.name = "Alice"; // 改变对象的内容(有效)
p = new Person("Bob"); // 改变引用本身(无效)
}
Person p1 = new Person("Tom");
changeName(p1);
System.out.println(p1.name); // 输出:Alice,而不是 Bob
分析:
- p 是对象引用,传进去时复制了引用的值(地址)
- p.name = "Alice":通过地址改对象内容,有效
- p = new Person(...):只是改了 p 的副本指向的地址,对外部 p1 没影响,无效
3、sychronied修饰普通方法和静态方法的区别?
js
public synchronized void instanceMethod() {
// 线程安全的实例方法
}
锁的是:当前对象(this)
- 每个对象有一个对象锁(monitor)
- 调用该方法时,线程必须先拿到该对象的锁
js
public static synchronized void staticMethod() {
// 线程安全的静态方法
}
锁的是:类对象(Class对象,即 MyClass.class
)
- 所有这个类的实例共享一把类锁
- 和具体哪个对象无关
4、volatile 能否保证线程安全和synchronize有什么区别?
volatile
volatile 是 Java 的一个关键字,用来修饰变量,表示:
当一个线程修改了这个变量的值,其他线程能立即看到最新值(可见性)。
编译器和 CPU 不会重排序它前后的读写操作(有序性)。
总结:
- 单纯的 volatile 变量的 set(写)和 get(读)操作是线程安全的,能保证可见性和原子性(针对单次赋值)。
- 多线程读写同一个 volatile 变量,其他线程能立刻看到最新值。
- 但如果是复合操作(比如 count++),volatile 无法保证原子性,需要用锁或原子类
synchronized
synchronized 是用来给代码块或方法加锁的,表示: 只有获得锁的线程才能执行被保护的代码(互斥访问)
能保证:
- 原子性(操作不会被中断)
- 可见性(释放锁前写入的内容,对其他线程可见)
- 有序性(锁内代码执行有顺序)
5、Android如何判断对象是否可回收,GC会在什么时候触发
Android 如何判断对象是否可回收?
Android(使用 ART 虚拟机)主要采用的是:
可达性分析算法(Reachability Analysis)
判断逻辑:
从一组称为 GC Roots 的对象出发,沿着引用链向下搜索:
- 如果对象可被 GC Roots 直接或间接引用 → 可达,不可回收
- 如果对象与 GC Roots 没有引用关系 → 不可达,判定为垃圾,可回收
常见 GC Root 包括:
- 活跃线程(Thread 对象)
- 当前方法调用栈中的对象(局部变量)
- 静态字段引用的对象
- JNI 中引用的对象(Native 持有 Java 对象)
Android GC 触发时机? GC 并不会一直运行,而是在满足某些条件时被动或主动触发,常见触发条件如下:
场景 | 描述 |
---|---|
内存不足 | 分配新对象时,如果没有足够空间,就会触发 GC |
阈值触发 | 分配对象数/大小达到阈值 |
系统空闲时 | 系统空闲(Idle)状态下,后台触发 GC 降低内存 |
手动调用 | System.gc() 只是建议 GC,不保证立刻执行 |
内存分析/调试工具 | 使用内存分析工具时强制触发 GC |
6、String、StringBuilder、StringBuffer 有什么区别?
类型 | 定义 |
---|---|
String | 不可变的字符串常量,每次修改都会生成新对象。 |
StringBuilder | 可变字符串,适合单线程场景,效率高。 |
StringBuffer | 可变字符串,线程安全(方法加了 synchronized ),适合多线程。 |
js
@Override
@NeverInline
public StringBuilder append(boolean b) {
super.append(b);
return this;
}
java
@Override
synchronized StringBuffer append(AbstractStringBuilder asb) {
toStringCache = null;
super.append(asb);
return this;
}
在大量拼接字符串的场景中的性能:
dart
StringBuilder > StringBuffer >> String
因为:
String
每次拼接都会创建新对象(低效)StringBuffer
多了同步锁(比 StringBuilder 慢)StringBuilder
无锁,性能最佳(适合绝大多数情况)
7、try-catch-finally 中 finally 一定会执行吗?return 会影响吗?
绝大多数情况下,finally
块一定会执行,无论:
- 是否抛出异常
- 是否有
return
- 是否有
break
、continue
8、HashMap 的工作原理
js
Map<String, String> map = new HashMap<>();
map.put("dog", "旺财");
map.put("cat", "咪咪");
map.put("pig", "佩奇");
内部结构:
js
table[0] → null
table[1] → Entry{key="cat", value="咪咪"}
table[2] → Entry{key="dog", value="旺财"} → Entry{key="pig", value="佩奇"} (链表)
table[3] → null
每个桶(数组索引)可以容纳多个 Entry,形成链表或树。
步骤详解:
-
计算 hash 值 :
hash("dog")
→ 得到一个整数 hash 值 -
确定数组索引 :
index = hash & (table.length - 1)
→ 落入某个 table[i] -
查看该位置是否已有元素:
- 若无,直接插入新 Entry
- 若有,Java 7 及以下永久链表,Java 8起同一桶链表长度 > 8 且 table.length ≥ 64,转为红黑树
Get流程
js
map.get("dog")
- 计算 "dog" 的 hash → 得到 index
- 找到 table[index]
- 遍历链表 / 树,判断 key 是否相等(使用 equals())
- 找到返回对应的 value
9、Java中的线程池有哪些
线程池 | 方法 | 特点 | 应用场景 |
---|---|---|---|
固定线程池 | Executors.newFixedThreadPool(int n) |
固定数量线程 | 控制并发线程数,线程可复用 |
单线程池 | Executors.newSingleThreadExecutor() |
单个线程顺序执行任务 | 保证顺序、线程串行化 |
缓存线程池 | Executors.newCachedThreadPool() |
按需创建线程,空闲线程复用,最多 Integer.MAX_VALUE 个线程 | 执行大量短期异步任务 |
定时线程池 | Executors.newScheduledThreadPool(int n) |
可定时或周期性执行任务 | 定时任务、周期调度 |
工作窃取线程池(Java 8+) | Executors.newWorkStealingPool() |
使用 ForkJoinPool 实现,多个任务队列,自动平衡任务 | 并行计算、多核利用 |
大部分是使用了ThreadPoolExecutor:
js
ExecutorService threadPool = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maximumPoolSize, // 最大线程数
keepAliveTime, // 线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(), // 队列
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
可以结合源码,看看Executors提供的几个线程池,各个参数的区别。
参数 | 含义 |
---|---|
corePoolSize |
核心线程数,常驻线程 |
maximumPoolSize |
最大线程数 |
keepAliveTime |
非核心线程闲置多久被回收 |
workQueue |
阻塞队列,保存待执行任务 |
threadFactory |
创建线程的工厂 |
handler |
拒绝策略,如:抛异常、调用者执行、丢弃任务 |
10、Java 的四种引用类型
特性 | 强引用 | 软引用 | 弱引用 | 虚引用 |
---|---|---|---|---|
是否影响 GC | 不回收 | 低内存时回收 | 可回收 | 可回收 |
回收条件 | 无引用才回收 | 内存不足时回收 | GC 扫描时回收 | GC 后回收并通知 |
主要用途 | 一般对象持有 | 缓存 | 避免泄漏 | 跟踪回收、资源释放 |
是否能 get() | 有值 | 有值 | 有值 | 始终 null |
需配合 ReferenceQueue | 不用 | 不用 | 可选 | 必须 |
引用的核心作用:
帮助 GC 判断一个对象是否可达,是否应当被回收。
GC 会从一组被称为 "GC Roots" 的对象出发,沿着引用链向下遍历对象图。
- 如果某个对象能从 GC Roots 通过引用链访问到,说明它不能被回收;
- 如果无法访问到 ,则认为对象 "不可达" ,可能被回收。