Java语言程序开发笔记
划重点!
1. 内存中的秘密:堆、栈、方法区(非堆)
很多同学搞不清楚对象到底在哪,这里画个简图:
· 栈 (Stack):线程私有,存放局部变量、方法调用栈帧。方法执行完立即弹出。
· 堆 (Heap):所有 new 出来的对象、数组。垃圾回收的主战场。
· 方法区 (Method Area):存储类结构信息(字段、方法字节码)、静态变量、常量池。
```java
Person p = new Person();
// p 在栈上(局部变量),指向堆上的 Person 对象
// Person.class 的类信息在方法区
```
⭐ 关键误区:
· 你以为 String 是基本类型?错,它是对象。
· == 比较基本类型是值,比较对象是地址。想比内容用 .equals()。
2. 异常处理不只是 try-catch
2.1 分类体系
· Error:JVM内部错误(如栈溢出),程序无法处理。
· Exception:
· RuntimeException(非受检):如 NullPointerException,ArithmeticException。编译器不强制处理。
· Checked Exception(受检):如 IOException,SQLException。必须处理或声明 throws。
2.2 实战级写法
```java
try (FileInputStream fis = new FileInputStream("test.txt")) {
// 自动关闭资源(try-with-resources,Java7+)
} catch (FileNotFoundException e) {
System.out.println("文件不存在,请检查路径");
e.printStackTrace(); // 打印栈轨迹,便于排错
} catch (IOException e) {
// 多个catch按子类到父类排列
}
```
⭐ 最佳实践:不要吞掉异常(catch(e){} 留空),这会让线上问题无法排查。
---
3. 集合框架:别只会用 ArrayList
3.1 体系脉络
```
Iterable → Collection → List/Set/Queue
↓
Map(与Collection同级,不是子接口)
```
3.2 底层数据结构对比
实现类 底层结构 特点 线程安全
ArrayList 动态数组 随机访问快,增删慢(除非末尾) 否
LinkedList 双向链表 增删快(两端),随机访问慢 否
HashSet HashMap(玄幻?) 不重复,无序,依赖 hashCode/equals 否
TreeSet 红黑树 有序(Comparable/Comparator) 否
HashMap 数组+链表/红黑树 key-value,允许null 否
Hashtable 类似,但方法同步 较慢,不允许null key 是
3.3 重点:HashMap 的 put 过程(面试高频)
1. 计算 key.hashCode(),再扰动得到桶下标 (n-1) & hash。
2. 若桶为空,直接放。
3. 若不为空,通过 equals() 判断 key 是否相同 → 覆盖 value;否则以链表/红黑树方式挂到后面。
4. 当链表长度超过 TREEIFY_THRESHOLD(8) 且数组长度 ≥64 → 转为红黑树(提升查找效率 O(log n))。
⭐ 重写 equals 必须重写 hashCode,否则在 HashMap 中会找不到相同内容的 key。
---
4. 泛型:编译期的保护伞
```java
// 避免这样(原始类型)
List list = new ArrayList();
list.add("hello");
list.add(123); // 编译通过,但取出时会 ClassCastException
// 应该这样
List<String> list = new ArrayList<>();
list.add("world");
// list.add(123); // 编译错误,类型安全
```
4.1 通配符上下界
· <? extends T>:T 或 T 的子类(读取时可转为 T,不能写入)
· <? super T>:T 或 T 的父类(可写入 T 类型,读取时只能用 Object)
示例:方法参数需要 只读 一个数字列表 -> public void sum(List<? extends Number> list)。
---
5. Lambda 与 Stream:写代码像写SQL
5.1 函数式接口(只有一个抽象方法)
常见的:Predicate<T>,Function<T,R>,Consumer<T>。
5.2 Lambda 简化
```java
// 原匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("run");
}
}).start();
// Lambda 写法
new Thread(() -> System.out.println("run")).start();
```
5.3 Stream 流式操作(不改变原数据源)
```java
List<Student> list = getStudents();
// 筛选出成绩≥90的学生,按分数降序,取前3名姓名
List<String> top3 = list.stream()
.filter(s -> s.getScore() >= 90)
.sorted((a,b) -> b.getScore() - a.getScore())
.limit(3)
.map(Student::getName)
.collect(Collectors.toList());
```
⭐ 惰性求值:filter/map 等中间操作不会立即执行,遇到 collect 或 forEach 才触发。
---
6. 多线程基础:锁的粗浅理解
6.1 创建线程的两种正确方式
· 继承 Thread(不推荐,单继承受限)
· 实现 Runnable(推荐)
6.2 可见性问题与 volatile
多个线程共享一个 boolean 标志时,加上 volatile 保证可见性(禁止指令重排),但它不保证原子性。
6.3 synchronized 用法
· 实例方法 -> 锁当前对象 this
· 静态方法 -> 锁 Class 对象
· 同步代码块:synchronized(lockObj){}
6.4 简单死锁示例(能看懂并避免)
线程A持有锁1,等待锁2;线程B持有锁2,等待锁1 → 卡死。解决方式:按固定顺序申请锁。
---
7. 实用小贴士
1. equals 与 ==:永远用 "常量".equals(变量) 避免空指针。
2. 字符串拼接:循环内不要用 +,用 StringBuilder。
3. 尽早返回:减少嵌套 if,提高可读性。
4. 日志打印:线上用 log.info("用户:{}", user) 而非 System.out.println。
