第一天
一、Lambda表达式
1. 核心定义
Lambda表达式是Java 8引入的新特性,本质是匿名函数(没有方法名、修饰符和返回值类型),用于简化函数式接口的实现,让代码更简洁、紧凑,专注于"做什么"而非"怎么实现"。
核心前提:Lambda表达式只能用于实现函数式接口(只有一个抽象方法的接口,可通过@FunctionalInterface注解校验)。
2. 语法格式
基础格式:(参数列表) -> { 方法体 }
说明:
-
参数列表:参数类型可省略(类型推断),多个参数用逗号分隔,无参数则写();
-
->:箭头符号,用于分隔参数列表和方法体;
-
方法体:单个语句可省略{}和return(若有返回值),多个语句必须用{}包裹,且需显式写return。
3. 常见简化写法(示例)
以函数式接口Runnable(无参无返回)、Comparator(有参有返回)为例:
-
原始匿名内部类写法:
// Runnable接口实现 ``Runnable runnable = new Runnable() { `` @Override `` public void run() { `` System.out.println("匿名内部类执行"); `` } ``}; -
Lambda简化(无参无返回,方法体单语句):
Runnable runnable = () -> System.out.println("Lambda执行"); -
有参有返回(Comparator排序,省略参数类型+方法体单语句省略return):
// 原始写法 ``Comparator<Integer> comparator = new Comparator<Integer>() { `` @Override `` public int compare(Integer a, Integer b) { `` return a - b; `` } ``}; ``// Lambda简化 ``Comparator<Integer> comparator = (a, b) -> a - b;
4. 核心注意事项
-
Lambda表达式没有自己的this和super,其this指向外层包围类的对象;
-
Lambda表达式中引用的外部变量,必须是final或事实上的final(即变量声明后未被修改);
-
Lambda表达式不能单独存在,必须赋值给函数式接口变量,或作为方法参数传递。
二、方法引用
1. 核心定义
方法引用是Lambda表达式的"语法糖",当Lambda表达式的方法体,只是调用一个已存在的方法(无需额外逻辑)时,可通过方法引用简化写法,让代码更简洁、可读性更强。
核心逻辑:方法引用本质是"引用一个方法,代替Lambda的方法体",其签名(参数列表、返回值)必须与函数式接口的抽象方法签名一致。
2. 语法格式
方法引用的格式分为4种,核心是类名/对象名 :: 方法名(::为方法引用运算符),具体如下:
(1)对象::实例方法
场景:调用某个对象的实例方法,Lambda的参数列表与该实例方法的参数列表一致。
// 示例:调用String对象的length()方法(无参有返回)
// Lambda写法
Function<String, Integer> function = s -> s.length();
// 方法引用写法(String对象s的实例方法length)
Function<String, Integer> function = String::length;
(2)类::静态方法
场景:调用某个类的静态方法,Lambda的参数列表与该静态方法的参数列表一致。
// 示例:调用Integer类的parseInt()方法(有参有返回)
// Lambda写法
Function<String, Integer> function = s -> Integer.parseInt(s);
// 方法引用写法(Integer类的静态方法parseInt)
Function<String, Integer> function = Integer::parseInt;
(3)类::实例方法
场景:调用某个类的实例方法,但Lambda的第一个参数是该类的对象(即方法的调用者),后续参数是该实例方法的参数。
// 示例:调用String类的equals()方法(有参有返回,第一个参数是调用者)
// Lambda写法
BiFunction<String, String, Boolean> biFunction = (s1, s2) -> s1.equals(s2);
// 方法引用写法(String类的实例方法equals,s1是调用者)
BiFunction<String, String, Boolean> biFunction = String::equals;
(4)构造器引用:类::new
场景:Lambda的方法体是创建一个对象(调用构造器),Lambda的参数列表与该构造器的参数列表一致。
// 示例:调用String的构造器(String(char[] value))
// Lambda写法
Function<char[], String> function = chars -> new String(chars);
// 构造器引用写法
Function<char[], String> function = String::new;
3. 核心注意事项
-
方法引用不能单独使用,必须配合函数式接口,且方法签名(参数、返回值)需与接口抽象方法一致;
-
方法引用是Lambda的简化,只有当Lambda方法体仅调用一个现有方法时,才能使用;
-
构造器引用本质是"引用构造器",等价于Lambda中new对象的逻辑,支持重载(根据函数式接口的抽象方法签名匹配对应构造器)。
三、Lambda与方法引用的关联
-
方法引用是Lambda的简化形式,所有方法引用都能转化为对应的Lambda表达式,但反之不一定(Lambda方法体有复杂逻辑时,无法用方法引用);
-
两者的核心目的一致:简化函数式接口的实现,减少冗余代码,提升代码可读性和简洁性;
-
使用优先级:能使用方法引用时,优先使用(更简洁);Lambda适合方法体有自定义逻辑的场景。
四、核心总结
-
Lambda:匿名函数,简化函数式接口实现,格式为 (参数) -> 方法体;
-
方法引用:Lambda的语法糖,格式为 类名/对象名::方法名,分4种场景;
-
核心前提:两者都依赖函数式接口(仅一个抽象方法)。
第二天
一、算法基础
1. 核心定义
算法是解决特定问题的步骤集合,是对问题求解过程的精准描述,具有明确的输入、输出和有限性(步骤有限且可终止)、确定性(每一步操作唯一)、可行性(每一步可通过有限操作实现)的特点。
简单来说,算法就是"解决问题的思路和步骤",比如计算两数之和、排序数组,都有对应的算法。
2. 算法的核心特性(必记)
-
输入:算法有0个或多个输入(如排序算法需要输入待排序数组);
-
输出:算法至少有1个输出(求解结果),输出与输入对应;
-
有限性:算法的步骤是有限的,不会无限循环,最终会终止;
-
确定性:每一步操作都有明确的含义,不会出现歧义;
-
可行性:每一步操作都可以通过计算机或人工有限次执行完成。
3. 常见基础算法(入门必备)
(1)排序算法(最常用)
核心目的:将一组数据按指定顺序(升序/降序)排列,重点掌握3种基础排序:
-
冒泡排序 :通过相邻元素两两比较、交换,将最大/最小元素逐步"冒泡"到数组末尾,简单易理解,效率较低(适合少量数据)。
// 冒泡排序示例(升序) ``public static void bubbleSort(int[] arr) { `` int n = arr.length; `` for (int i = 0; i < n - 1; i++) { `` for (int j = 0; j < n - 1 - i; j++) { `` if (arr[j] > arr[j + 1]) { `` // 交换相邻元素 `` int temp = arr[j]; `` arr[j] = arr[j + 1]; `` arr[j + 1] = temp; `` } `` } `` } ``} -
选择排序:每次从待排序区间选择最小/最大元素,放到已排序区间的末尾,效率略高于冒泡排序。
-
插入排序:将待排序元素逐个插入到已排序区间的合适位置,类似整理扑克牌,适合数据基本有序的场景。
(2)查找算法
-
顺序查找:从数组开头依次遍历,查找目标元素,简单但效率低(适合无序数组);
-
二分查找 :仅适用于有序数组,通过不断将查找区间减半,快速定位目标元素,效率高(时间复杂度O(log₂n))。
4. 算法的时间复杂度与空间复杂度
用于衡量算法的效率,是算法设计的核心评估指标:
-
时间复杂度 :描述算法执行所需的时间与输入规模的关系,常用大O表示(忽略常数、低次项和系数),比如O(1)(常数时间)、O(n)(线性时间)、O(n²)(平方时间)。
-
空间复杂度 :描述算法执行所需的额外空间与输入规模的关系,同样用大O表示,比如冒泡排序空间复杂度为O(1)(仅用临时变量),递归算法通常空间复杂度较高。
二、异常(Java)
1. 核心定义
异常是程序运行过程中出现的意外情况(如除数为0、数组下标越界、文件找不到),会导致程序正常执行流程中断。Java中通过异常机制,捕获并处理这些意外,避免程序崩溃。
异常本质是Java中的对象,继承自Throwable类,分为两大类:Error(错误)和Exception(异常)。
2. 异常的分类(核心区分)
(1)Error(错误)
由JVM生成,程序无法处理,通常是严重的系统级错误(如内存溢出、虚拟机错误),一般不需要编写代码捕获,比如OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出)。
(2)Exception(异常)
程序可以处理的异常,也是我们日常开发中重点关注的类型,分为两大类:
-
编译时异常(受检异常):编译阶段就会报错,必须手动处理(捕获或抛出),否则无法编译,比如IOException(文件操作异常)、SQLException(数据库操作异常)。
-
运行时异常(非受检异常):编译阶段不报错,运行时才会出现,可选择处理或不处理,比如NullPointerException(空指针异常)、ArrayIndexOutOfBoundsException(数组下标越界)、ArithmeticException(除数为0)。
3. 异常的处理方式(3种)
(1)try-catch捕获异常
核心:尝试执行可能出现异常的代码,若出现异常,捕获并处理,避免程序崩溃,格式如下:
try {
// 可能出现异常的代码(如除数为0)
int a = 10 / 0;
} catch (ArithmeticException e) {
// 捕获指定类型的异常,进行处理
System.out.println("异常原因:" + e.getMessage()); // 输出异常信息
} finally {
// 可选:无论是否出现异常,都会执行(常用于释放资源,如关闭文件、连接)
System.out.println("无论是否异常,都会执行");
}
说明:可多个catch捕获不同类型异常(从子类到父类),finally可选,但若有return,finally会在return之前执行。
(2)throws抛出异常
核心:不处理异常,将异常抛给调用者,由调用者处理,格式:在方法声明后加throws 异常类型。
// 抛出编译时异常,由调用该方法的代码处理
public static void readFile() throws IOException {
// 可能出现IOException的代码
FileReader fr = new FileReader("test.txt");
}
(3)throw手动抛出异常
核心:手动创建异常对象,主动抛出,用于自定义异常场景(如参数校验)。
public static void checkAge(int age) {
if (age < 0 || age > 120) {
// 手动抛出运行时异常
throw new IllegalArgumentException("年龄不合法:" + age);
}
}
4. 自定义异常
当Java自带的异常无法满足需求时,可自定义异常,步骤:
-
继承Exception(编译时异常)或RuntimeException(运行时异常);
-
编写构造方法(默认无参、带异常信息的构造);
-
在需要的地方,用throw抛出自定义异常。
// 自定义编译时异常
public class MyException extends Exception {
// 无参构造
public MyException() {}
// 带异常信息的构造
public MyException(String message) {
super(message);
}
}
// 使用自定义异常
public static void checkScore(int score) throws MyException {
if (score < 0 || score > 100) {
throw new MyException("分数不合法:" + score);
}
}
5. 异常处理的注意事项
-
捕获异常时,要避免使用catch (Exception e) 捕获所有异常(无法精准定位问题);
-
finally中避免使用return,否则会覆盖try或catch中的return值;
-
运行时异常可不用强制处理,但编译时异常必须处理(捕获或抛出);
-
异常处理的核心是"精准捕获、合理处理",避免程序崩溃,同时便于排查问题。
三、核心总结
-
算法:解决问题的步骤集合,有5大特性,重点掌握基础排序和查找算法,关注时间/空间复杂度;
-
异常:程序运行中的意外情况,分为Error(不可处理)和Exception(可处理),3种处理方式(try-catch、throws、throw),可自定义异常;
-
核心原则:算法追求高效(低时间/空间复杂度),异常追求精准处理(避免程序崩溃、便于排查)
第三天
一、集合概述(前置基础)
集合是Java中用于存储多个数据的容器,区别于数组(固定长度、只能存储同一种基本类型/引用类型),集合长度可变,可存储不同类型的对象(本质存储对象引用),核心接口是Collection,List和Set是Collection的两大核心子接口。
核心特点:集合只存储对象,不存储基本数据类型(需使用包装类,如int→Integer);长度可动态增减;提供了丰富的方法(添加、删除、遍历、查找等),简化数据操作。
Collection核心通用方法(List和Set均适用):
-
add(E e):添加单个元素,返回boolean(添加成功为true);
-
remove(Object o):删除指定元素,返回boolean;
-
size():返回集合中元素的个数;
-
isEmpty():判断集合是否为空;
-
clear():清空集合中所有元素;
-
contains(Object o):判断集合中是否包含指定元素。
二、List集合
1. 核心定义与特点
List是Collection的子接口,代表有序、可重复的集合,元素有明确的索引(类似数组的下标),可通过索引快速访问、插入、删除元素,适合需要"有序存储、可重复、按索引操作"的场景。
核心特点:有序(元素存入顺序与取出顺序一致)、可重复(允许存储多个相同的元素)、有索引(从0开始,依次递增)。
2. List接口的常用实现类(3个核心)
(1)ArrayList(最常用)
底层基于数组实现,查询效率高(通过索引直接访问,时间复杂度O(1)),增删效率低(需移动数组元素,时间复杂度O(n)),线程不安全,效率高,适合"查询频繁、增删较少"的场景(如展示列表数据)。
// ArrayList示例
List<String> list = new ArrayList<>();
// 添加元素
list.add("Java");
list.add("Python");
list.add("Java"); // 允许重复
// 按索引插入元素(指定位置插入)
list.add(1, "C++"); // 插入后:[Java, C++, Python, Java]
// 按索引访问元素
String element = list.get(0); // 结果:Java
// 按索引修改元素
list.set(2, "JavaScript"); // 修改后:[Java, C++, JavaScript, Java]
// 按索引删除元素
list.remove(3); // 删除索引3的元素,返回删除的元素
// 遍历集合(三种方式)
// 1. 普通for循环(利用索引,最常用)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
// 2. 增强for循环(foreach,无需索引)
for (String str : list) {
System.out.println(str);
}
// 3. 迭代器遍历(安全遍历,可在遍历中删除元素)
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if ("C++".equals(str)) {
iterator.remove(); // 迭代器删除,避免并发修改异常
}
}
(2)LinkedList
底层基于双向链表实现,查询效率低(需从头/尾遍历查找,时间复杂度O(n)),增删效率高(只需修改链表节点的指针,时间复杂度O(1)),线程不安全,适合"增删频繁、查询较少"的场景(如队列、栈的实现)。
额外特性:LinkedList还实现了Deque接口,可作为队列(先进先出)、栈(先进后出)使用,提供了poll()、push()、pop()等方法。
(3)Vector
底层基于数组实现,与ArrayList功能基本一致,但线程安全(方法加了synchronized锁),效率低,目前已基本被ArrayList替代,仅在多线程场景下偶尔使用。
3. List集合的特有方法(区别于Set)
因List有索引,所以拥有Set没有的、基于索引的操作方法:
-
get(int index):获取指定索引的元素;
-
set(int index, E e):修改指定索引的元素,返回被修改的旧元素;
-
add(int index, E e):在指定索引插入元素,后续元素后移;
-
remove(int index):删除指定索引的元素,返回被删除的元素;
-
indexOf(Object o):返回指定元素在集合中第一次出现的索引,没有则返回-1;
-
lastIndexOf(Object o):返回指定元素在集合中最后一次出现的索引,没有则返回-1。
4. List集合的注意事项
-
ArrayList和LinkedList均线程不安全,多线程环境下需手动加锁,或使用Collections.synchronizedList()包装;
-
遍历ArrayList时,优先使用普通for循环(效率高);遍历LinkedList时,优先使用增强for或迭代器(避免频繁通过索引查找);
-
避免在增强for循环中修改集合(添加/删除元素),会抛出ConcurrentModificationException(并发修改异常),需使用迭代器删除。
三、Set集合
1. 核心定义与特点
Set是Collection的子接口,代表无序、不可重复的集合,元素没有索引,无法通过索引访问元素,适合需要"去重存储、无需按顺序访问"的场景(如存储唯一标识、去重数据)。
核心特点:无序(元素存入顺序与取出顺序不一定一致,底层存储无序)、不可重复(不允许存储两个相等的元素,equals()方法判断相等)、无索引(无法通过下标访问)。
补充:Set判断元素是否重复的规则:先通过hashCode()方法判断哈希值,若哈希值不同,则元素不同;若哈希值相同,再通过equals()方法判断,若equals()返回true,则元素重复,不添加;若返回false,则添加。
2. Set接口的常用实现类(3个核心)
(1)HashSet(最常用)
底层基于**哈希表(HashMap)**实现,无序、不可重复,查询和增删效率都很高(时间复杂度O(1)),线程不安全,适合"去重、高效操作"的常规场景。
// HashSet示例
Set<String> set = new HashSet<>();
// 添加元素(不可重复,重复元素添加失败)
set.add("Java");
set.add("Python");
set.add("Java"); // 重复元素,添加失败,集合中仍只有1个Java
// 删除元素
set.remove("Python"); // 删除成功返回true,不存在返回false
// 遍历集合(两种方式,无索引,无法用普通for循环)
// 1. 增强for循环
for (String str : set) {
System.out.println(str); // 输出顺序可能与添加顺序不一致
}
// 2. 迭代器遍历
Iterator<String> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 判断元素是否存在
boolean hasJava = set.contains("Java"); // 结果:true
(2)LinkedHashSet
底层基于哈希表+双向链表 实现,是HashSet的子类,特点:有序、不可重复(有序指"存入顺序与取出顺序一致"),查询和增删效率略低于HashSet,线程不安全,适合"去重且需要保持插入顺序"的场景。
(3)TreeSet
底层基于红黑树 实现,无序(不保证插入顺序)、不可重复,但会对元素进行自然排序(默认升序),也可自定义排序规则,查询和增删效率中等(时间复杂度O(log n)),线程不安全,适合"去重且需要排序"的场景。
// TreeSet示例(自然排序,String类型默认按字典序升序)
Set<String> treeSet = new TreeSet<>();
treeSet.add("Banana");
treeSet.add("Apple");
treeSet.add("Cherry");
// 遍历输出:Apple、Banana、Cherry(自然排序后)
for (String str : treeSet) {
System.out.println(str);
}
// 自定义排序(如整数降序)
Set<Integer> numSet = new TreeSet<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1; // 降序排序
}
});
numSet.add(3);
numSet.add(1);
numSet.add(2);
// 遍历输出:3、2、1
3. Set集合的注意事项
-
HashSet、LinkedHashSet、TreeSet均线程不安全,多线程环境下需使用Collections.synchronizedSet()包装;
-
HashSet存储自定义对象时,需重写hashCode()和equals()方法,否则无法实现去重(默认使用Object类的方法,判断地址是否相同);
-
TreeSet存储自定义对象时,需让对象实现Comparable接口(重写compareTo()方法),或创建TreeSet时传入Comparator,否则会抛出ClassCastException(类型转换异常);
-
Set无索引,无法使用普通for循环遍历,只能用增强for、迭代器遍历。
四、List集合与Set集合的核心区别(重点)
| 对比维度 | List集合 | Set集合 |
|---|---|---|
| 有序性 | 有序(存入与取出顺序一致) | 无序(HashSet、TreeSet);LinkedHashSet有序 |
| 可重复性 | 可重复 | 不可重复 |
| 索引 | 有索引,可通过索引操作 | 无索引,无法通过索引操作 |
| 底层实现 | ArrayList(数组)、LinkedList(链表) | HashSet(哈希表)、TreeSet(红黑树) |
| 适用场景 | 需有序、可重复、按索引操作(如列表展示) | 需去重、无需索引(如唯一标识存储) |
五、核心总结
-
List和Set均继承自Collection,拥有Collection的所有通用方法;
-
List核心:有序、可重复、有索引,重点掌握ArrayList和LinkedList的区别;
-
Set核心:不可重复、无索引,重点掌握HashSet(常规去重)、TreeSet(排序去重)的使用;
-
选型原则:需有序/按索引操作→用List;需去重→用Set;需排序去重→TreeSet;需保持插入顺序去重→LinkedHashSet。