Java学习路线 - 第三阶段笔记
Java高级特性(2-3个月)
1. 集合框架深入
1.1 List详解
- ArrayList:基于动态数组实现,随机访问高效,插入删除效率低
- LinkedList:基于双向链表实现,插入删除高效,随机访问效率低
- Vector:线程安全的ArrayList,性能较低
- Stack:基于Vector实现的LIFO(后进先出)栈
1.2 Set详解
- HashSet:基于HashMap实现,无序,不允许重复元素,查找效率高
- LinkedHashSet:在HashSet基础上维护了元素插入顺序
- TreeSet:基于红黑树实现,元素自动排序,查找和遍历效率较高
1.3 Map详解
1.3.1 HashMap详解
-
基本概念:基于哈希表实现的Map接口,存储键值对,允许null键和null值
-
核心特性:
- 无序集合
- 非线程安全
- 查询/插入/删除的平均时间复杂度为O(1)
- 默认初始容量16,负载因子0.75
-
底层数据结构:
- JDK 1.7:数组 + 链表
- JDK 1.8及以后:数组 + 链表 + 红黑树(当链表长度超过8时转为红黑树)
-
工作原理:
- 通过key的hashCode()方法计算hash值
- 将hash值与数组长度-1进行按位与操作确定数组下标
- 如发生hash冲突,使用链表法解决
- 当链表过长(>8)时,转换为红黑树提高查询效率
- 当负载因子超过阈值时(默认0.75),进行扩容(2倍)
-
重要源码解析:
// 哈希值计算 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); } // 获取桶位置 int i = (n - 1) & hash // n为数组长度
-
扩容机制:
- 触发条件:当前元素数量 > 容量 × 负载因子
- 扩容过程:创建2倍大小的新数组,重新计算每个元素的位置
- 性能影响:扩容是耗时操作,合理设置初始容量可减少扩容次数
-
常见面试问题:
- HashMap的put方法流程
- HashMap如何处理哈希冲突
- HashMap和Hashtable的区别
- 为什么HashMap的容量总是2的n次幂
-
代码示例:
// 创建HashMap HashMap<String, Integer> map = new HashMap<>(); // 指定初始容量和负载因子 HashMap<String, Integer> customMap = new HashMap<>(32, 0.8f); // 添加元素 map.put("Java", 95); map.put("Python", 90); map.put("C++", 85); // 获取元素 int javaScore = map.get("Java"); // 95 // 检查键是否存在 boolean hasJava = map.containsKey("Java"); // true boolean has90 = map.containsValue(90); // true // 遍历HashMap - 方式1:通过EntrySet for (Map.Entry<String, Integer> entry : map.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } // 遍历HashMap - 方式2:通过KeySet for (String key : map.keySet()) { System.out.println(key + ": " + map.get(key)); } // 遍历HashMap - 方式3:Java 8 Lambda表达式 map.forEach((k, v) -> System.out.println(k + ": " + v)); // 删除元素 map.remove("Python"); // 替换元素 map.replace("C++", 88); // 清空HashMap map.clear();
-
性能优化:
- 合理设置初始容量,减少扩容次数
- 重写hashCode()和equals()方法保证正确性
- 使用不可变对象作为key
- 使用缓存计算的hashCode值提高性能
-
LinkedHashMap:在HashMap基础上维护了键值对的插入顺序
-
TreeMap:基于红黑树实现,键自动排序
-
Hashtable:线程安全的HashMap,性能较低
-
Properties:用于处理属性配置文件的特殊Map
1.4 集合排序和查找
- Comparable接口:自然排序
- Comparator接口:定制排序
- Collections工具类常用方法
- sort():排序
- binarySearch():二分查找
- reverse():反转
- shuffle():随机排序
- max()/min():查找最大/最小值
1.5 泛型
- 泛型类:
class MyClass<T> {}
- 泛型方法:
public <T> T method(T t) {}
- 泛型通配符:
<?>
: 无限制通配符<? extends T>
: 上限通配符<? super T>
: 下限通配符
- 类型擦除:Java泛型实现原理
- 泛型的限制和最佳实践
1.6 迭代器
- Iterator接口:
hasNext()
,next()
,remove()
- ListIterator接口:双向迭代器
- Iterable接口与for-each循环
- fail-fast机制:并发修改异常ConcurrentModificationException
2. 多线程编程
2.1 线程基础
- 线程的生命周期:新建、就绪、运行、阻塞、死亡
- 创建线程的方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口(有返回值)
- 线程常用方法:
- start():启动线程
- run():线程执行体
- sleep():休眠
- join():等待线程结束
- yield():让出CPU执行权
2.2 线程同步
-
线程安全问题分析:
- 多线程环境下,共享资源可能被多个线程同时访问和修改
- 导致的问题:脏读、丢失更新、不可重复读、幻读等
- 解决方案:互斥(同步)和可见性保证
-
synchronized关键字:
-
同步代码块:
public void method() { // 非同步代码 synchronized(锁对象) { // 锁对象可以是this、类对象或任意对象 // 同步代码,一次只允许一个线程执行 } // 非同步代码 }
-
同步实例方法:
// 相当于synchronized(this) { 方法体 } public synchronized void method() { // 同步代码,锁是当前实例对象(this) }
-
同步静态方法:
// 相当于synchronized(当前类.class) { 方法体 } public static synchronized void method() { // 同步代码,锁是当前类的Class对象 }
-
底层实现原理:
- 基于监视器(Monitor)机制
- 使用对象头中的Mark Word存储锁信息
- 锁的升级过程:偏向锁 -> 轻量级锁 -> 重量级锁
-
代码示例:
public class Counter { private int count = 0; // 同步方法 public synchronized void increment() { count++; } // 同步代码块 public void decrement() { synchronized(this) { count--; } } // 使用类锁 public static synchronized void staticMethod() { // 静态同步方法 } public void anotherMethod() { synchronized(Counter.class) { // 使用类锁的同步代码块 } } public int getCount() { return count; } } // 使用示例 Counter counter = new Counter(); // 线程1 new Thread(() -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }).start(); // 线程2 new Thread(() -> { for (int i = 0; i < 1000; i++) { counter.decrement(); } }).start();
-
-
volatile关键字:
-
作用:
- 保证变量的可见性:对变量的修改立即对其他线程可见
- 禁止指令重排序:保证有序性
- 但不保证原子性:不能替代锁解决竞态条件
-
适用场景:
- 状态标志(如停止线程的标志)
- 单例模式中的双重检查锁定
- 独立于其他变量的操作(无复合操作)
-
工作原理:
- 读操作:直接从主内存读取,不从CPU缓存读取
- 写操作:立即写入主内存,并使其他CPU缓存失效
-
代码示例:
public class VolatileExample { // 使用volatile保证可见性 private volatile boolean flag = false; public void writer() { flag = true; // 对所有线程立即可见 } public void reader() { while (!flag) { // 当flag变为true时,会立即看到变化并退出循环 } } } // 双重检查锁定单例模式 public class Singleton { private static volatile Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); // volatile保证对象完全初始化 } } } return instance; } }
-
volatile的局限性:
public class VolatileLimitation { private volatile int count = 0; // volatile不保证原子性,下面的操作不是线程安全的 public void increment() { count++; // 实际是读-修改-写三步操作 } // 正确做法: 使用synchronized public synchronized void safeIncrement() { count++; } // 或者使用原子类 private AtomicInteger atomicCount = new AtomicInteger(0); public void atomicIncrement() { atomicCount.incrementAndGet(); } }
-
-
Lock接口:
-
基本概念:
- java.util.concurrent.locks包中的显式锁
- 比synchronized更灵活
- 支持非阻塞获取锁、可中断获取锁、超时获取锁等
-
主要实现类:
- ReentrantLock:可重入锁
- ReentrantReadWriteLock:读写锁
- StampedLock:Java 8引入的性能更高的锁
-
ReentrantLock使用:
public class LockExample { private final Lock lock = new ReentrantLock(); private int count = 0; public void increment() { lock.lock(); // 获取锁 try { count++; } finally { lock.unlock(); // 在finally块中释放锁 } } // 可中断获取锁 public void incrementInterruptibly() throws InterruptedException { lock.lockInterruptibly(); // 可被中断的锁获取 try { count++; } finally { lock.unlock(); } } // 尝试获取锁 public boolean tryIncrement() { if (lock.tryLock()) { // 非阻塞尝试获取锁 try { count++; return true; } finally { lock.unlock(); } } return false; } // 超时获取锁 public boolean timedIncrement() throws InterruptedException { if (lock.tryLock(1, TimeUnit.SECONDS)) { // 尝试1秒获取锁 try { count++; return true; } finally { lock.unlock(); } } return false; } }
-
公平锁与非公平锁:
// 公平锁:按请求顺序获取锁 Lock fairLock = new ReentrantLock(true); // 非公平锁(默认):不保证顺序 Lock unfairLock = new ReentrantLock(false);
-
读写锁:允许多个线程同时读,但只允许一个线程写
public class ReadWriteLockExample { private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private final Lock readLock = rwLock.readLock(); private final Lock writeLock = rwLock.writeLock(); private List<String> data = new ArrayList<>(); // 读操作(允许并发) public List<String> readData() { readLock.lock(); try { return new ArrayList<>(data); // 返回副本 } finally { readLock.unlock(); } } // 写操作(独占) public void writeData(String newValue) { writeLock.lock(); try { data.add(newValue); } finally { writeLock.unlock(); } } // 先读后写的复合操作 public void updateIfPresent(String oldValue, String newValue) { // 先获取写锁(隐含获取读锁) writeLock.lock(); try { int index = data.indexOf(oldValue); if (index != -1) { data.set(index, newValue); } } finally { writeLock.unlock(); } } }
-
StampedLock:Java 8引入的性能更高的锁
public class StampedLockExample { private final StampedLock sl = new StampedLock(); private double x, y; // 写操作(独占锁) public void move(double deltaX, double deltaY) { long stamp = sl.writeLock(); // 获取写锁 try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); // 释放写锁 } } // 读操作(悲观读锁) public double distanceFromOrigin() { long stamp = sl.readLock(); // 获取读锁 try { return Math.sqrt(x * x + y * y); } finally { sl.unlockRead(stamp); // 释放读锁 } } // 乐观读操作 public double distanceFromOriginOptimistic() { long stamp = sl.tryOptimisticRead(); // 获取乐观读锁 double currentX = x, currentY = y; // 检查读取后是否有写操作 if (!sl.validate(stamp)) { // 如果有写操作,切换到悲观读锁 stamp = sl.readLock(); try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } }
-
-
死锁问题:
-
死锁形成的四个必要条件:
- 互斥条件:资源不能被共享使用
- 持有并等待条件:线程持有资源的同时等待其他资源
- 不可抢占条件:资源不能被强制释放
- 循环等待条件:形成一个等待环路
-
死锁示例:
public class DeadlockExample { private final Object resource1 = new Object(); private final Object resource2 = new Object(); // 可能导致死锁的方法 public void method1() { synchronized(resource1) { System.out.println("Thread 1: Holding resource 1..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Thread 1: Waiting for resource 2..."); synchronized(resource2) { System.out.println("Thread 1: Holding resource 1 & 2..."); } } } public void method2() { synchronized(resource2) { // 与method1获取锁的顺序相反 System.out.println("Thread 2: Holding resource 2..."); try { Thread.sleep(100); } catch (InterruptedException e) {} System.out.println("Thread 2: Waiting for resource 1..."); synchronized(resource1) { System.out.println("Thread 2: Holding resource 1 & 2..."); } } } // 测试死锁 public static void main(String[] args) { DeadlockExample deadlock = new DeadlockExample(); new Thread(() -> deadlock.method1()).start(); new Thread(() -> deadlock.method2()).start(); } }
-
死锁预防与避免:
- 固定锁的获取顺序:始终以相同的顺序获取锁
- 超时获取锁:使用Lock接口的tryLock方法尝试获取锁
- 使用可中断锁:让线程能够被打断
- 针对资源分级:为资源分配优先级
-
修正上面的死锁示例:
// 方法1:固定锁的获取顺序 public void methodFixed1() { synchronized(resource1) { // 两个方法都先获取resource1 synchronized(resource2) { // 使用资源 } } } public void methodFixed2() { synchronized(resource1) { // 两个方法都先获取resource1 synchronized(resource2) { // 使用资源 } } } // 方法2:使用Lock接口的tryLock避免死锁 public void methodWithTryLock() { Lock lock1 = new ReentrantLock(); Lock lock2 = new ReentrantLock(); boolean gotFirstLock = false; boolean gotSecondLock = false; try { gotFirstLock = lock1.tryLock(); gotSecondLock = lock2.tryLock(); if (gotFirstLock && gotSecondLock) { // 使用资源 } } finally { if (gotSecondLock) { lock2.unlock(); } if (gotFirstLock) { lock1.unlock(); } } }
-
死锁检测:
- 使用jstack命令查看线程堆栈
- 使用jconsole或VisualVM等工具监控检测
- 程序中实现死锁检测算法
-
-
线程同步最佳实践:
- 尽量缩小同步范围,只在必要的代码块上使用同步
- 选择合适的锁:粗粒度锁简单但竞争激烈,细粒度锁减少竞争但复杂
- 避免在持有锁时执行耗时操作
- 使用并发容器而非同步容器提高性能
- 遵循固定的锁获取顺序避免死锁
- 优先使用并发工具类而非低级别的锁机制
- 使用ThreadLocal减少共享变量
- 正确使用volatile关键字
-
面试常见问题:
- synchronized和Lock的区别与选择
- 如何解决死锁问题
- volatile关键字的作用和使用场景
- 如何实现线程安全的单例模式
- 什么是线程安全,如何实现线程安全
- synchronized的底层实现原理和锁升级过程
2.3 线程池
-
线程池基本概念:用于管理和复用线程的资源池,避免频繁创建和销毁线程带来的开销
-
线程池优势:
- 减少资源消耗:复用已创建的线程
- 提高响应速度:任务到达时直接使用线程池中空闲线程
- 提高线程可管理性:统一管理、分配、调优、监控
- 防止系统资源耗尽:限制并发线程数量
-
ThreadPoolExecutor核心参数:
public ThreadPoolExecutor( int corePoolSize, // 核心线程数 int maximumPoolSize, // 最大线程数 long keepAliveTime, // 线程空闲超时时间 TimeUnit unit, // 时间单位 BlockingQueue<Runnable> workQueue, // 工作队列 ThreadFactory threadFactory, // 线程工厂 RejectedExecutionHandler handler) // 拒绝策略
-
线程池工作原理:
- 当线程数小于核心线程数时,新任务到达会创建新线程执行
- 当线程数等于核心线程数时,新任务会进入工作队列等待
- 当工作队列已满,但线程数小于最大线程数时,创建新线程执行任务
- 当工作队列已满,且线程数等于最大线程数时,触发拒绝策略
- 当线程空闲时间超过keepAliveTime,非核心线程会被回收
-
工作队列类型:
- ArrayBlockingQueue:基于数组的有界阻塞队列,FIFO
- LinkedBlockingQueue:基于链表的可选有界阻塞队列,FIFO
- SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待对应的移除操作
- PriorityBlockingQueue:带优先级的无界阻塞队列
-
拒绝策略:
- AbortPolicy:默认策略,丢弃任务并抛出RejectedExecutionException
- DiscardPolicy:丢弃任务但不抛出异常
- DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务
- CallerRunsPolicy:由调用线程处理该任务
-
常用线程池:
// 固定大小线程池 ExecutorService fixedPool = Executors.newFixedThreadPool(5); // 单线程线程池 ExecutorService singlePool = Executors.newSingleThreadExecutor(); // 缓存线程池(无限制) ExecutorService cachedPool = Executors.newCachedThreadPool(); // 定时任务线程池 ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
-
自定义线程池示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, // 核心线程数 10, // 最大线程数 60, TimeUnit.SECONDS, // 线程空闲超时时间 new ArrayBlockingQueue<>(100), // 工作队列 Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy()); // 提交任务 executor.execute(() -> { System.out.println("Task executed by " + Thread.currentThread().getName()); }); // 提交有返回值的任务 Future<String> future = executor.submit(() -> { Thread.sleep(1000); return "Task completed"; }); // 等待任务完成并获取结果 try { String result = future.get(); System.out.println(result); } catch (Exception e) { e.printStackTrace(); } // 关闭线程池 executor.shutdown();
-
线程池监控:
// 获取线程池状态 int activeCount = executor.getActiveCount(); // 活动线程数 int poolSize = executor.getPoolSize(); // 当前线程数 int corePoolSize = executor.getCorePoolSize(); // 核心线程数 int largestPoolSize = executor.getLargestPoolSize(); // 历史最大线程数 long taskCount = executor.getTaskCount(); // 已提交任务数 long completedTaskCount = executor.getCompletedTaskCount(); // 已完成任务数
-
线程池最佳实践:
- 根据任务类型选择合适的队列
- 避免使用无界队列,防止OOM
- 根据CPU核心数、任务性质合理设置线程数
- 为线程池线程指定有意义的名称,便于调试
- 任务应该是独立的,避免共享可变状态
- 线程池关闭前确保所有任务完成
-
面试常见问题:
- 线程池的主要参数及其影响
- 如何合理设置线程池大小
- 线程池中submit()和execute()的区别
- 线程池的状态转换(RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED)
2.4 并发工具类
- CountDownLatch:等待多个线程完成
- CyclicBarrier:等待线程集合同步执行
- Semaphore:信号量,控制并发数
- Exchanger:线程数据交换
- 原子类:AtomicInteger, AtomicLong等
- 并发集合:
- ConcurrentHashMap
- CopyOnWriteArrayList
- BlockingQueue
3. Java8+新特性
3.1 Lambda表达式
-
Lambda基本概念:
- Lambda表达式是Java 8引入的一种匿名函数表达式,可以创建匿名函数实例
- 主要用于简化函数式接口的实现代码
- 语法更简洁,提高代码可读性和可维护性
-
Lambda语法:
(parameters) -> expression
或
(parameters) -> { statements; }
-
Lambda表达式示例:
// 无参数,返回值为空 Runnable r1 = () -> System.out.println("Hello World"); // 单个参数(可省略括号) Consumer<String> c1 = s -> System.out.println(s); // 多个参数 Comparator<String> c2 = (s1, s2) -> s1.compareTo(s2); // 带代码块 ActionListener listener = event -> { System.out.println("Button clicked!"); System.out.println("Event: " + event.getActionCommand()); }; // 带返回值 BinaryOperator<Integer> add = (a, b) -> a + b;
-
函数式接口:
- 任何只包含一个抽象方法的接口,都可以作为Lambda表达式的目标类型
- 可以使用
@FunctionalInterface
注解标记(非必须但推荐) - Java 8在java.util.function包中提供了许多标准函数式接口
-
常用函数式接口详解:
-
Predicate:接收T类型参数,返回boolean值
// 定义 @FunctionalInterface public interface Predicate<T> { boolean test(T t); // 还有其他默认方法: and(), or(), negate() } // 使用示例 Predicate<String> isEmpty = s -> s.isEmpty(); Predicate<String> isNotEmpty = isEmpty.negate(); // 组合Predicate Predicate<String> isLongAndNotEmpty = s -> s.length() > 10 // 长度>10 .and(isNotEmpty); // 且不为空 List<String> filtered = strings.stream() .filter(isLongAndNotEmpty) .collect(Collectors.toList());
-
Consumer:接收T类型参数,无返回值
// 定义 @FunctionalInterface public interface Consumer<T> { void accept(T t); // 还有默认方法: andThen() } // 使用示例 Consumer<String> print = System.out::println; Consumer<String> log = s -> logger.info(s); // 组合Consumer Consumer<String> printAndLog = print.andThen(log); strings.forEach(printAndLog);
-
Function<T, R>:接收T类型参数,返回R类型结果
// 定义 @FunctionalInterface public interface Function<T, R> { R apply(T t); // 还有默认方法: compose(), andThen(), identity() } // 使用示例 Function<String, Integer> toLength = String::length; Function<Integer, Integer> doubled = i -> i * 2; // 组合Function Function<String, Integer> lengthThenDouble = toLength.andThen(doubled); // 结果:10 (先计算"Java"长度为4,然后乘以2) int result = lengthThenDouble.apply("Java");
-
Supplier:无参数,返回T类型结果
// 定义 @FunctionalInterface public interface Supplier<T> { T get(); } // 使用示例 Supplier<LocalDate> today = LocalDate::now; Supplier<User> createUser = User::new; // 调用无参构造器 // 用于延迟计算 Logger logger = LoggerFactory.getLogger(MyClass.class); // 仅当日志级别是DEBUG时才执行计算 logger.debug("Complex object: {}", (Supplier<String>) () -> { return expensiveOperation(); // 仅在需要时执行 });
-
BiFunction<T, U, R>:接收T和U类型参数,返回R类型结果
// 定义 @FunctionalInterface public interface BiFunction<T, U, R> { R apply(T t, U u); // 还有默认方法: andThen() } // 使用示例 BiFunction<String, String, String> concat = (s1, s2) -> s1 + s2; BiFunction<Integer, Integer, Integer> multiply = (a, b) -> a * b; // 结果:"HelloWorld" String result1 = concat.apply("Hello", "World"); // 结果:35 Integer result2 = multiply.apply(5, 7);
-
其他常用函数式接口:
// BiConsumer<T, U>:接收两个参数,无返回值 BiConsumer<String, Integer> printKeyValue = (key, value) -> System.out.println(key + ": " + value); // BinaryOperator<T>:接收两个T类型参数,返回T类型结果 BinaryOperator<Integer> sum = (a, b) -> a + b; // UnaryOperator<T>:接收T类型参数,返回T类型结果 UnaryOperator<String> toUpperCase = String::toUpperCase;
-
-
方法引用:简化Lambda表达式的特殊语法
// 静态方法引用 Function<String, Integer> parseInt = Integer::parseInt; // 实例方法引用(特定对象) Consumer<String> printer = System.out::println; // 实例方法引用(任意对象) Function<String, Integer> lengthFn = String::length; // 构造方法引用 Supplier<ArrayList<String>> listCreator = ArrayList::new; Function<String, User> userCreator = User::new;
-
变量捕获:Lambda表达式可以访问定义它的方法的局部变量和成员变量
int multiplier = 2; // 局部变量 // 捕获局部变量(必须是final或effectively final) Function<Integer, Integer> multiply = n -> n * multiplier; // 修改捕获的变量会导致编译错误 // multiplier = 3; // 错误:变量在Lambda中使用,不能修改 // 成员变量可以正常修改 this.factor = 3; // 成功:成员变量可以修改 Function<Integer, Integer> multiplyByFactor = n -> n * this.factor;
-
与匿名内部类的比较:
// 使用匿名内部类 Comparator<String> comparator1 = new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.compareTo(s2); } }; // 使用Lambda表达式 Comparator<String> comparator2 = (s1, s2) -> s1.compareTo(s2); // 使用方法引用更简洁 Comparator<String> comparator3 = String::compareTo;
-
实际应用场景:
-
事件处理:
// Swing事件处理 JButton button = new JButton("Click Me"); // 传统方式 button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("Button clicked!"); } }); // Lambda方式 button.addActionListener(e -> System.out.println("Button clicked!"));
-
集合遍历和操作:
List<String> names = Arrays.asList("John", "Mary", "Steve"); // 传统方式 for (String name : names) { System.out.println(name); } // Lambda方式 names.forEach(name -> System.out.println(name)); // 方法引用 names.forEach(System.out::println);
-
多线程编程:
// 传统方式 new Thread(new Runnable() { @Override public void run() { System.out.println("Running in another thread"); } }).start(); // Lambda方式 new Thread(() -> System.out.println("Running in another thread")).start();
-
策略模式实现:
// 传统策略模式 public interface ValidationStrategy { boolean validate(String s); } public class IsAllLowerCase implements ValidationStrategy { @Override public boolean validate(String s) { return s.matches("[a-z]+"); } } // 使用传统策略 Validator lowerCaseValidator = new Validator(new IsAllLowerCase()); boolean result = lowerCaseValidator.validate("abc"); // 使用Lambda简化 Validator lowerCaseValidator = new Validator(s -> s.matches("[a-z]+")); Validator numericValidator = new Validator(s -> s.matches("\\d+"));
-
资源管理:
// 自定义函数式接口 @FunctionalInterface interface ResourceHandler<T, R> { R handle(T resource) throws IOException; } // 资源管理工具方法 public static <T extends AutoCloseable, R> R withResource( Supplier<T> resourceSupplier, ResourceHandler<T, R> handler) throws Exception { try (T resource = resourceSupplier.get()) { return handler.handle(resource); } } // 使用方式 String content = withResource( () -> new BufferedReader(new FileReader("file.txt")), reader -> reader.lines().collect(Collectors.joining("\n")) );
-
-
Lambda表达式最佳实践:
- 保持简短和清晰,避免复杂逻辑
- 优先使用方法引用提高可读性
- 避免修改外部变量
- 为复杂Lambda创建描述性方法
- 使用标准函数式接口而不是创建新接口
- 使用
@FunctionalInterface
注解标记函数式接口
-
常见面试问题:
- Lambda表达式和匿名内部类的区别
- 为什么Lambda表达式只能访问final或effectively final的局部变量
- 函数式接口是什么,如何定义自己的函数式接口
- Lambda表达式的底层实现原理
- Lambda表达式的局限性和适用场景
3.3 Optional类
-
Optional基本概念:
- Optional是Java 8引入的容器类,可以存储一个值或空值
- 主要目的是避免空指针异常(NullPointerException)
- 迫使开发者显式地处理可能为null的情况
- 不是为了替代所有null引用,而是为了更优雅地处理null
-
创建Optional对象:
// 创建包含值的Optional Optional<String> opt1 = Optional.of("Hello"); // 创建可能包含null的Optional Optional<String> opt2 = Optional.ofNullable(possiblyNullString); // 创建空的Optional Optional<String> empty = Optional.empty();
-
检查值是否存在:
// 检查是否有值 boolean isPresent = opt1.isPresent(); // true boolean isEmpty = empty.isEmpty(); // true (Java 11+) // 条件执行 opt1.ifPresent(s -> System.out.println("Value: " + s)); // 条件执行带else情况(Java 9+) opt1.ifPresentOrElse( s -> System.out.println("Value: " + s), () -> System.out.println("No value") );
-
获取Optional中的值:
// 直接获取值(如果为空会抛出NoSuchElementException) String value1 = opt1.get(); // 不推荐使用 // 值不存在时返回默认值 String value2 = empty.orElse("Default"); // 值不存在时通过Supplier计算默认值 String value3 = empty.orElseGet(() -> computeDefault()); // 值不存在时抛出指定异常 String value4 = empty.orElseThrow(() -> new IllegalStateException("Value not available"));
-
orElse与orElseGet的区别:
// 使用orElse - 无论Optional是否为空,都会执行computeDefault() String result1 = opt1.orElse(computeDefault()); // 使用orElseGet - 只有当Optional为空时,才会执行computeDefault() String result2 = opt1.orElseGet(() -> computeDefault());
-
转换Optional中的值:
// 使用map转换值 Optional<Integer> length = opt1.map(String::length); // 链式调用 Optional<String> upperCase = opt1 .map(s -> s.trim()) .filter(s -> !s.isEmpty()) .map(String::toUpperCase); // 使用flatMap处理返回Optional的方法 Optional<User> user = Optional.ofNullable(getUser(id)); Optional<Address> address = user.flatMap(User::getAddress); Optional<Street> street = address.flatMap(Address::getStreet); Optional<String> streetName = street.map(Street::getName); // 简化上面的嵌套Optional链(避免了多层null检查) Optional<String> streetName = Optional.ofNullable(getUser(id)) .flatMap(User::getAddress) .flatMap(Address::getStreet) .map(Street::getName);
-
过滤Optional中的值:
// 根据条件过滤 Optional<String> filteredOpt = opt1 .filter(s -> s.length() > 3); // 组合条件 Optional<User> validUser = Optional.ofNullable(user) .filter(u -> u.isActive()) .filter(u -> u.getAge() >= 18);
-
Optional与Stream的结合:
// 将Optional流转换为值流 List<Optional<String>> listOfOptionals = Arrays.asList( Optional.of("A"), Optional.empty(), Optional.of("B")); // Java 9+: Optional的stream()方法 List<String> filteredList = listOfOptionals.stream() .flatMap(Optional::stream) // 只保留有值的Optional .collect(Collectors.toList()); // ["A", "B"] // Java 8: 使用filter()和map() List<String> filteredList = listOfOptionals.stream() .filter(Optional::isPresent) .map(Optional::get) .collect(Collectors.toList()); // ["A", "B"]
-
实际应用场景:
-
避免空指针检查:
// 传统的null检查 public String getUserName(User user) { if (user != null) { String name = user.getName(); if (name != null) { return name.toUpperCase(); } } return "UNKNOWN"; } // 使用Optional public String getUserName(User user) { return Optional.ofNullable(user) .map(User::getName) .map(String::toUpperCase) .orElse("UNKNOWN"); }
-
方法返回值处理:
// 返回Optional而不是null public Optional<User> findUserById(long id) { User user = userRepository.findById(id); return Optional.ofNullable(user); } // 调用方处理 findUserById(123) .ifPresent(user -> sendEmail(user));
-
集合元素处理:
// 查找第一个匹配元素 Optional<User> user = users.stream() .filter(u -> u.getAge() > 30) .findFirst(); // 进一步处理 user.ifPresent(this::processUser);
-
配置值获取:
// 从多个可能的来源获取配置 public Optional<String> getConfigValue(String key) { return Optional.ofNullable(System.getProperty(key)) .or(() -> Optional.ofNullable(environmentVars.get(key))) .or(() -> Optional.ofNullable(defaultProps.get(key))); } // 使用配置值 String timeout = getConfigValue("app.timeout") .map(Integer::parseInt) .orElse(DEFAULT_TIMEOUT);
-
构建验证管道:
public Optional<User> validateUser(User user) { return Optional.ofNullable(user) .filter(u -> u.getName() != null && !u.getName().trim().isEmpty()) .filter(u -> u.getEmail() != null && u.getEmail().contains("@")) .filter(u -> u.getAge() >= 18); } // 使用验证结果 validateUser(user) .ifPresentOrElse( userService::registerUser, () -> throw new ValidationException("Invalid user data") );
-
-
Optional最佳实践:
- 不要将Optional用作字段类型
- 不要将Optional用作方法参数
- 不要创建空的Optional对象集合
- 优先使用orElseGet()而不是orElse()处理计算开销大的默认值
- 不要过度使用Optional替代所有可能为null的场景
- 使用Optional作为返回类型表明值可能不存在
-
常见面试问题:
- Optional的设计目的是什么
- Optional和null的区别
- 为什么不应该序列化Optional
- orElse()和orElseGet()方法的区别
- 如何正确地使用Optional提高代码质量
3.4 新的日期时间API
- LocalDate:不包含时间的日期
- LocalTime:不包含日期的时间
- LocalDateTime:包含日期和时间
- ZonedDateTime:带时区的日期时间
- Instant:时间戳
- Duration:时间段
- Period:日期间隔
- DateTimeFormatter:日期时间格式化
3.5 默认方法
- 接口中的默认方法:default关键字
- 接口中的静态方法
- 默认方法冲突解决策略
4. JDBC数据库编程
4.1 数据库基础知识
-
关系型数据库概念:
- 表(Table):存储数据的基本单元,由行和列组成
- 主键(Primary Key):唯一标识表中的一行数据
- 外键(Foreign Key):表示表之间的关系
- 索引(Index):提高查询性能的数据结构
- 视图(View):虚拟表,基于一个或多个表的查询结果
-
SQL基础:
- DDL(数据定义语言):CREATE, ALTER, DROP
- DML(数据操作语言):INSERT, UPDATE, DELETE
- DQL(数据查询语言):SELECT
- DCL(数据控制语言):GRANT, REVOKE
-
数据库设计三范式:
- 第一范式(1NF):字段值不可再分
- 第二范式(2NF):非主键字段必须依赖于整个主键
- 第三范式(3NF):非主键字段不能相互依赖
-
索引与性能优化:
- B+树索引:最常用的索引类型
- 哈希索引:等值查询更快,但不支持排序和范围查询
- 索引最佳实践:
- 为频繁查询的字段创建索引
- 为外键字段创建索引
- 避免对频繁更新的字段创建索引
- 避免创建过多索引
-
事务ACID特性:
- 原子性(Atomicity):事务是最小执行单位,要么全部执行,要么全部不执行
- 一致性(Consistency):事务执行前后数据库状态一致
- 隔离性(Isolation):多个事务并发执行互不干扰
- 持久性(Durability):事务一旦提交,对数据库的改变是永久的
-
事务隔离级别:
- READ UNCOMMITTED(读未提交)
- READ COMMITTED(读已提交)
- REPEATABLE READ(可重复读)
- SERIALIZABLE(串行化)
4.2 MySQL安装和基本操作
- MySQL基本命令:
-- 登录MySQL mysql -u username -p -- 显示数据库 SHOW DATABASES; -- 创建数据库 CREATE DATABASE db_name CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 使用数据库 USE db_name; -- 创建表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 增加数据 INSERT INTO user (username, password, email) VALUES ('alice', 'password123', 'alice@example.com'); -- 查询数据 SELECT * FROM user WHERE username = 'alice'; -- 更新数据 UPDATE user SET email = 'new_email@example.com' WHERE id = 1; -- 删除数据 DELETE FROM user WHERE id = 1; -- 添加索引 CREATE INDEX idx_username ON user(username);
4.3 JDBC连接数据库
-
JDBC核心组件:
- DriverManager:管理JDBC驱动
- Connection:数据库连接
- Statement/PreparedStatement/CallableStatement:执行SQL语句
- ResultSet:查询结果集
-
JDBC驱动加载方式:
// 方式一:Class.forName (推荐) Class.forName("com.mysql.cj.jdbc.Driver"); // 方式二:DriverManager.registerDriver DriverManager.registerDriver(new com.mysql.cj.jdbc.Driver()); // 方式三:系统属性 System.setProperty("jdbc.drivers", "com.mysql.cj.jdbc.Driver");
-
建立数据库连接:
// MySQL连接URL格式 String url = "jdbc:mysql://localhost:3306/db_name?useSSL=false&serverTimezone=UTC"; String username = "root"; String password = "password"; // 获取连接 Connection connection = DriverManager.getConnection(url, username, password);
-
JDBC优化 - 使用连接池:
// HikariCP连接池配置 HikariConfig config = new HikariConfig(); config.setJdbcUrl("jdbc:mysql://localhost:3306/db_name"); config.setUsername("root"); config.setPassword("password"); config.setMaximumPoolSize(10); config.setMinimumIdle(5); config.setIdleTimeout(30000); // 创建连接池 HikariDataSource dataSource = new HikariDataSource(config); // 获取连接 Connection connection = dataSource.getConnection();
-
常用连接池对比:
连接池 优点 缺点 HikariCP 速度最快、配置简单、轻量级 功能相对简单 Druid 功能丰富、监控统计、SQL防注入 配置相对复杂 C3P0 稳定性好、自动恢复能力强 性能较低 DBCP Apache官方推荐、配置简单 不支持自动恢复连接
4.4 增删改查操作
-
使用Statement:
try ( Connection conn = DriverManager.getConnection(url, username, password); Statement stmt = conn.createStatement() ) { // 执行查询 ResultSet rs = stmt.executeQuery("SELECT * FROM user"); // 处理结果集 while (rs.next()) { int id = rs.getInt("id"); String name = rs.getString("username"); System.out.println(id + " - " + name); } // 执行更新(Insert/Update/Delete) int rowsAffected = stmt.executeUpdate( "UPDATE user SET email = 'new@example.com' WHERE id = 1"); System.out.println("Updated " + rowsAffected + " rows"); } catch (SQLException e) { e.printStackTrace(); }
-
使用PreparedStatement(推荐):
String sql = "SELECT * FROM user WHERE username = ? AND created_at > ?"; try ( Connection conn = DriverManager.getConnection(url, username, password); PreparedStatement pstmt = conn.prepareStatement(sql) ) { // 设置参数 pstmt.setString(1, "alice"); pstmt.setTimestamp(2, Timestamp.valueOf("2022-01-01 00:00:00")); // 执行查询 ResultSet rs = pstmt.executeQuery(); // 处理结果 while (rs.next()) { // 处理每一行数据 } // 插入示例 String insertSql = "INSERT INTO user (username, password, email) VALUES (?, ?, ?)"; try (PreparedStatement insertStmt = conn.prepareStatement(insertSql, Statement.RETURN_GENERATED_KEYS)) { insertStmt.setString(1, "bob"); insertStmt.setString(2, "securepass"); insertStmt.setString(3, "bob@example.com"); int rows = insertStmt.executeUpdate(); // 获取自动生成的主键 try (ResultSet generatedKeys = insertStmt.getGeneratedKeys()) { if (generatedKeys.next()) { long id = generatedKeys.getLong(1); System.out.println("New user ID: " + id); } } } } catch (SQLException e) { e.printStackTrace(); }
-
事务处理:
Connection conn = null; try { conn = DriverManager.getConnection(url, username, password); // 关闭自动提交 conn.setAutoCommit(false); // 执行多个SQL操作 try (PreparedStatement stmt1 = conn.prepareStatement("UPDATE accounts SET balance = balance - ? WHERE id = ?"); PreparedStatement stmt2 = conn.prepareStatement("UPDATE accounts SET balance = balance + ? WHERE id = ?")) { // 转账:从账户1转100到账户2 double amount = 100.0; stmt1.setDouble(1, amount); stmt1.setInt(2, 1); stmt1.executeUpdate(); // 模拟可能出现的异常 if (amount > 50) { // throw new SQLException("Transaction test exception"); } stmt2.setDouble(1, amount); stmt2.setInt(2, 2); stmt2.executeUpdate(); // 提交事务 conn.commit(); } } catch (SQLException e) { // 发生异常,回滚事务 if (conn != null) { try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } e.printStackTrace(); } finally { // 恢复自动提交 if (conn != null) { try { conn.setAutoCommit(true); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
-
批处理操作:
try ( Connection conn = DriverManager.getConnection(url, username, password); PreparedStatement pstmt = conn.prepareStatement( "INSERT INTO user (username, password, email) VALUES (?, ?, ?)") ) { // 关闭自动提交 conn.setAutoCommit(false); // 添加批量操作 for (int i = 0; i < 1000; i++) { pstmt.setString(1, "user" + i); pstmt.setString(2, "pass" + i); pstmt.setString(3, "user" + i + "@example.com"); pstmt.addBatch(); // 每500条执行一次 if (i % 500 == 0) { pstmt.executeBatch(); pstmt.clearBatch(); } } // 执行剩余的批处理 pstmt.executeBatch(); // 提交事务 conn.commit(); } catch (SQLException e) { e.printStackTrace(); }
-
处理大数据类型:
// 存储BLOB(二进制大对象) String insertBlob = "INSERT INTO documents (name, content) VALUES (?, ?)"; try ( Connection conn = DriverManager.getConnection(url, username, password); PreparedStatement pstmt = conn.prepareStatement(insertBlob) ) { pstmt.setString(1, "report.pdf"); // 从文件读取二进制数据 File file = new File("report.pdf"); try (FileInputStream fis = new FileInputStream(file)) { pstmt.setBinaryStream(2, fis, file.length()); pstmt.executeUpdate(); } } // 读取BLOB数据 String selectBlob = "SELECT content FROM documents WHERE id = ?"; try ( Connection conn = DriverManager.getConnection(url, username, password); PreparedStatement pstmt = conn.prepareStatement(selectBlob) ) { pstmt.setInt(1, 1); ResultSet rs = pstmt.executeQuery(); if (rs.next()) { Blob blob = rs.getBlob("content"); try (InputStream is = blob.getBinaryStream(); FileOutputStream fos = new FileOutputStream("downloaded_report.pdf")) { byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = is.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); } } } }
-
数据库连接管理模式:
// 数据访问对象(DAO)模式 public class UserDAO { private final DataSource dataSource; public UserDAO(DataSource dataSource) { this.dataSource = dataSource; } public User findById(long id) throws SQLException { String sql = "SELECT * FROM user WHERE id = ?"; try ( Connection conn = dataSource.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql) ) { pstmt.setLong(1, id); try (ResultSet rs = pstmt.executeQuery()) { if (rs.next()) { User user = new User(); user.setId(rs.getLong("id")); user.setUsername(rs.getString("username")); user.setEmail(rs.getString("email")); user.setCreatedAt(rs.getTimestamp("created_at")); return user; } return null; } } } // 其他CRUD方法... }
-
JDBC最佳实践:
- 始终使用PreparedStatement:防止SQL注入攻击
- 使用连接池:提高性能和资源管理
- 关闭资源:使用try-with-resources确保资源关闭
- 批处理操作:处理大量数据时使用批处理
- 正确处理异常:捕获具体异常而不是通用Exception
- 使用事务:确保数据一致性
- 避免大结果集:使用分页或限制结果数量
- 缓存预编译语句:频繁执行的SQL考虑缓存
- 参数化SQL文件:将SQL语句与Java代码分离
- 定期关闭空闲连接:避免连接泄漏
-
常见面试问题:
- JDBC中Statement和PreparedStatement的区别
- 如何防止SQL注入攻击
- 事务的ACID特性与隔离级别
- JDBC连接池的工作原理和优势
- 批处理操作的性能影响
5. 项目练习
5.1 多线程下载器
- 需求分析:文件分片与多线程下载
- 核心功能:
- HTTP连接管理
- 线程池实现并发下载
- 文件分片与合并
- 断点续传功能
- 下载进度监控
5.2 基于JDBC的商城管理系统
- 数据库设计:
- 用户表
- 商品表
- 订单表
- 购物车表
- 核心功能:
- 用户登录注册
- 商品查询与过滤
- 购物车管理
- 订单处理
- 数据统计报表
5.3 日志分析工具
- 功能设计:
- 日志文件读取
- 多线程解析
- 使用Stream API处理数据
- 结果统计与可视化
- 技术要点:
- NIO文件处理
- 正则表达式解析
- 并发处理
- 统计算法
学习资源推荐
- 《Effective Java》第三版 - Joshua Bloch
- 《Java并发编程实战》- Brian Goetz等
- 《Java 8实战》- Raoul-Gabriel Urma等
- 《高性能MySQL》- Baron Schwartz等
- 中国MOOC的Java进阶课程
- 尚硅谷Java高级部分视频
- 黑马程序员Java并发编程视频