Loading...

文章背景图

Day 009-知识点汇总

2026-02-10
13
-
- 分钟

第一部分:异常处理 (Exception)

1. 异常体系结构

Q: Java异常体系的顶层父类是什么?它下面分为哪两大类?

A:

  • 顶层父类:java.lang.Throwable
  • 两大类:
    1. Error: JVM级别错误,不需要开发人员解决(如内存溢出)
    2. Exception: 程序可能出现的问题,需要程序员处理
// Throwable体系
// java.lang.Throwable
//    ├── java.lang.Error (JVM错误,如StackOverflowError)
//    └── java.lang.Exception (程序异常)
//           ├── RuntimeException及其子类 (运行时异常)
//           └── 其他非RuntimeException (编译时异常)

2. 运行时异常 vs 编译时异常

Q: 运行时异常和编译时异常的区别是什么?各举一个例子。

A:

特性 运行时异常 编译时异常
继承关系 继承 RuntimeException 不继承 RuntimeException
编译阶段 不报错 报错(红色波浪线)
处理要求 不强制处理 必须处理
举例 NullPointerException, ArrayIndexOutOfBoundsException FileNotFoundException, ParseException
// 运行时异常示例 - 编译时不报错,运行时报错
String str = null;
str.length();  // NullPointerException

// 编译时异常示例 - 写代码时就报错,必须处理
// FileInputStream fis = new FileInputStream("d:/demo.txt"); 
// 报错:Unhandled exception: java.io.FileNotFoundException

3. 异常处理的两种方案

Q: Java中处理异常的两种方案是什么?企业开发推荐如何使用?

A:
方案1:try-catch捕获异常(最外层使用)

try {
    // 可能出现异常的代码
} catch (FileNotFoundException e) {
    // 处理特定异常
    e.printStackTrace();  // 给程序员看
    System.out.println("文件不存在");  // 给用户看
} catch (Exception e) {
    // 兜底异常
}

方案2:throws抛出异常(非最外层使用)

public void method() throws FileNotFoundException, IOException {
    // 抛出异常给调用者处理
}

// 简化写法:直接抛父类
public void method() throws Exception {
}

企业推荐:底层方法 throws抛出,最外层 try-catch捕获,给用户友好提示。


4. 异常处理的注意事项

Q: catch多个异常时,顺序有什么要求?为什么?

A:
子类异常在前,父类异常在后。因为异常匹配是从上到下,如果父类在前,子类异常永远匹配不到。

try {
    method();
} catch (FileNotFoundException e) {  // ✓ 子类在前
    // 处理文件未找到
} catch (IOException e) {              // 父类在后
    // 处理IO异常
} catch (Exception e) {                // 最终兜底
    // 处理其他异常
}

// 错误写法:
// } catch (Exception e) {           // ✗ 父类在前
// } catch (FileNotFoundException e) { // 编译报错:已被捕获
// }

5. 异常的作用

Q: 异常在程序中有哪些作用?

A:

  1. 定位Bug:异常信息包含类名、行号,快速定位问题
  2. 作为特殊返回值:通知上层调用者方法执行问题
  3. 恢复程序运行:捕获异常后程序可以继续执行
// 作用2:作为特殊返回值
public static void checkGender(String gender) {
    if ("男".equals(gender)) {
        throw new RuntimeException("男生禁止进入");  // 作为返回值
    }
    System.out.println("欢迎进入");
}

// 作用3:恢复程序运行
public static int getValidAge() {
    while (true) {
        try {
            Scanner sc = new Scanner(System.in);
            return sc.nextInt();  // 输入非数字会抛异常
        } catch (Exception e) {
            System.out.println("输入不合法,请重新输入");  // 恢复,继续循环
        }
    }
}

6. 自定义异常 - 运行时异常(推荐)

Q: 如何自定义运行时异常?什么场景下使用?

A: 推荐方式,代码简洁,编译时不强制处理。

步骤:

  1. 定义类继承 RuntimeException
  2. 重写构造器
  3. throw new创建并抛出
// 1. 定义异常类
public class GenderRuntimeException extends RuntimeException {
    public GenderRuntimeException() {}
  
    public GenderRuntimeException(String message) {
        super(message);  // 调用父类构造器
    }
}

// 2. 使用
public static void checkGender(String gender) {
    if ("男".equals(gender)) {
        throw new GenderRuntimeException("男生禁止进入");  // 抛出自定义异常
    }
}

// 3. 捕获处理
try {
    checkGender("男");
} catch (GenderRuntimeException e) {
    System.out.println(e.getMessage());  // "男生禁止进入"
}

7. 自定义异常 - 编译时异常

Q: 如何自定义编译时异常?与运行时异常的区别是什么?

A: 继承 Exception而非 RuntimeException

区别:

  • 编译时异常:方法必须 throws声明,调用者必须处理
  • 运行时异常:不需要 throws声明,不强制处理
// 1. 定义编译时异常
public class GenderException extends Exception {
    public GenderException() {}
    public GenderException(String message) {
        super(message);
    }
}

// 2. 使用 - 必须声明throws
public static void checkGender(String gender) throws GenderException {
    if ("男".equals(gender)) {
        throw new GenderException("男生禁止进入");
    }
}

// 3. 调用 - 必须处理(try-catch或继续throws)
public static void main(String[] args) {
    try {
        checkGender("男");  // 不try-catch会编译报错
    } catch (GenderException e) {
        e.printStackTrace();
    }
}

第二部分:泛型 (Generics)

8. 泛型的本质与作用

Q: 什么是泛型?泛型的本质是什么?

A: 泛型是JDK5引入的特性,可以在编译阶段约束数据类型,避免强制类型转换。

本质:把具体的数据类型作为参数传给类型变量。

// 不使用泛型 - 需要强制转换,可能类型转换异常
ArrayList list = new ArrayList();
list.add("hello");
list.add(100);  // 也能存进去
String str = (String) list.get(0);  // 需要强转
// String str2 = (String) list.get(1);  // 运行时报ClassCastException

// 使用泛型 - 编译时检查类型,无需强转
ArrayList<String> list2 = new ArrayList<>();
list2.add("hello");
// list2.add(100);  // 编译报错:类型不匹配
String str = list2.get(0);  // 直接获取,无需强转

9. 自定义泛型类

Q: 如何定义泛型类?类型变量命名有什么规范?

A:
语法修饰符 class 类名<类型变量, ...> { }

常用类型变量名

  • E - Element(元素)
  • T - Type(类型)
  • K - Key(键)
  • V - Value(值)
// 定义泛型类
public class MyArrayList<E> {
    private ArrayList<E> list = new ArrayList<>();
  
    public void add(E e) {      // E作为参数类型
        list.add(e);
    }
  
    public E get(int index) {   // E作为返回值类型
        return list.get(index);
    }
}

// 使用泛型类
MyArrayList<String> strList = new MyArrayList<>();
strList.add("黑马");      // 只能存String
String s = strList.get(0);  // 返回就是String,无需强转

MyArrayList<Integer> intList = new MyArrayList<>();
intList.add(100);         // 只能存Integer

10. 自定义泛型接口

Q: 如何实现泛型接口?不同实现类可以指定不同类型吗?

A: 实现类可以指定具体类型继续保持泛型

// 1. 定义泛型接口
public interface DataOperator<E> {
    void add(E e);
    E get(int index);
}

// 2. 实现类指定具体类型 - StudentDataOperator只能操作Student
public class StudentDataOperator implements DataOperator<Student> {
    private ArrayList<Student> list = new ArrayList<>();
  
    @Override
    public void add(Student student) {
        list.add(student);
    }
  
    @Override
    public Student get(int index) {
        return list.get(index);
    }
}

// 3. 实现类指定具体类型 - TeacherDataOperator只能操作Teacher
public class TeacherDataOperator implements DataOperator<Teacher> {
    // ... 类似实现
}

// 4. 使用
DataOperator<Student> operator = new StudentDataOperator();
operator.add(new Student("张三", 20));  // 只能传Student

11. 泛型方法

Q: 泛型方法在泛型类和非泛型类中的写法有什么区别?

A:

场景 语法
泛型类中 修饰符 E 方法名(E e) - 直接使用类定义的E
非泛型类中 修饰符 <T> T 方法名(T t) - 必须声明 <T>
// 非泛型类中的泛型方法
public class Demo {
  
    // 必须声明<T>,表示这是泛型方法
    public static <T> void printArray(T[] array) {
        for (T t : array) {
            System.out.println(t);
        }
    }
}

// 使用
String[] strs = {"a", "b"};
Integer[] ints = {1, 2};
Demo.printArray(strs);   // T自动推断为String
Demo.printArray(ints);   // T自动推断为Integer

12. 泛型通配符与上下限

Q: ?? extends T? super T分别表示什么?

A:

通配符 含义 可接收类型
? 任意类型 任意类型
? extends Car 上限 Car及其子类
? super Car 下限 Car及其父类
class Che {}           // 祖父类
class Car extends Che {}  // 父类
class BMW extends Car {}  // 子类
class Cat {}           // 无关类

// ? - 任意类型
public static void print1(List<?> list) {}  
print1(new ArrayList<Che>());   // ✓
print1(new ArrayList<Cat>());   // ✓

// ? extends Car - Car及其子类
public static void print2(List<? extends Car> list) {}
print2(new ArrayList<Car>());   // ✓
print2(new ArrayList<BMW>());   // ✓ (BMW是Car子类)
// print2(new ArrayList<Che>()); // ✗ (Che是父类)

// ? super Car - Car及其父类
public static void print3(List<? super Car> list) {}
print3(new ArrayList<Che>());   // ✓ (Che是父类)
print3(new ArrayList<Car>());   // ✓
// print3(new ArrayList<BMW>()); // ✗ (BMW是子类)

13. 包装类与自动装箱拆箱(面试重点)

Q: 为什么泛型不支持基本数据类型?Integer i = 100Integer j = 100i == j的结果是什么?200呢?

A: 泛型只支持对象类型,基本类型需用包装类替代。

Integer缓存池:-128~127的整数会缓存,超出范围每次新建对象。

// 泛型不支持基本类型
// List<int> list;  // 编译报错
List<Integer> list = new ArrayList<>();  // 必须使用包装类

// 自动装箱:int -> Integer
Integer i1 = 100;  // 等价于 Integer.valueOf(100)

// 自动拆箱:Integer -> int
int i2 = i1;       // 等价于 i1.intValue()

// 面试题核心
Integer i3 = 100;
Integer i4 = 100;
System.out.println(i3 == i4);  // true(-128~127从缓存池取,同一对象)

Integer i5 = 200;
Integer i6 = 200;
System.out.println(i5 == i6);  // false(超出缓存范围,新建不同对象)

// 包装类常用功能
String str = Integer.toString(100);     // "100"
int num = Integer.parseInt("100");      // 100
double d = Double.parseDouble("3.14");  // 3.14

第三部分:集合 (Collection)

14. Collection集合体系

Q: List和Set系列集合的特点分别是什么?

A:

特性 List系列 Set系列
有序性 有序(添加顺序) HashSet无序,LinkedHashSet有序,TreeSet排序
重复性 可重复 不重复
索引 有索引 无索引
实现类 ArrayList, LinkedList HashSet, LinkedHashSet, TreeSet
// List:有序、可重复、有索引
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("a");  // 可重复
System.out.println(list);  // [a, b, a]
System.out.println(list.get(0));  // a,有索引

// Set:无序、不重复、无索引
Set<String> set = new HashSet<>();
set.add("a");
set.add("b");
set.add("a");  // 添加失败,不重复
System.out.println(set);  // 无序输出,如 [a, b]
// set.get(0);  // 编译报错,无索引

15. Collection常用方法

Q: Collection接口提供了哪些通用方法?

A:

方法 作用
add(E e) 添加元素
size() 返回元素个数
remove(Object o) 删除指定元素
isEmpty() 判断是否为空
clear() 清空集合
contains(Object o) 判断是否包含
toArray(T[] a) 转换为数组
Collection<String> c = new ArrayList<>();
c.add("黑马");           // 添加
c.add("java");
System.out.println(c.size());      // 2
System.out.println(c.contains("黑马"));  // true

c.remove("java");        // 删除
System.out.println(c.isEmpty());   // false

// 转数组(指定类型)
String[] arr = c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(arr));  // [黑马]

c.clear();               // 清空
System.out.println(c.isEmpty());   // true

16. 集合遍历的6种方式

Q: List集合有哪些遍历方式?写出迭代器和Lambda方式的代码。

A:

List<String> list = Arrays.asList("a", "b", "c");

// 方式1:迭代器(集合专有)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    System.out.println(s);
}

// 方式2:fori(有索引才能用)
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 方式3:增强for
for (String s : list) {
    System.out.println(s);
}

// 方式4:forEach + 匿名内部类
list.forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println(s);
    }
});

// 方式5:forEach + Lambda(推荐)
list.forEach(s -> System.out.println(s));

// 方式6:方法引用
list.forEach(System.out::println);

17. 并发修改异常及解决方案

Q: 使用fori遍历ArrayList并删除元素会有什么问题?如何解决?

A: 问题:删除元素后,后续元素前移,导致漏删

解决方案

  1. fori删除后 i--
  2. fori倒序遍历删除
  3. 使用迭代器的remove方法
ArrayList<String> list = new ArrayList<>();
list.add("Java入门");
list.add("宁夏枸杞");
list.add("黑枸杞");
list.add("人字拖");

// 问题代码:漏删
for (int i = 0; i < list.size(); i++) {
    if (list.get(i).contains("枸杞")) {
        list.remove(i);  // 删除后i++,会跳过下一个元素
    }
}
// 结果:[Java入门, 黑枸杞, 人字拖] - 黑枸杞没删掉!

// 方案1:i--
for (int i = 0; i < list.size(); i++) {
    if (list.get(i).contains("枸杞")) {
        list.remove(i);
        i--;  // 关键:不让i递增
    }
}

// 方案2:倒序(推荐)
for (int i = list.size() - 1; i >= 0; i--) {
    if (list.get(i).contains("枸杞")) {
        list.remove(i);  // 删除不影响前面元素的索引
    }
}

// 方案3:迭代器(最标准)
Iterator<String> it = list.iterator();
while (it.hasNext()) {
    String s = it.next();
    if (s.contains("枸杞")) {
        it.remove();  // 用迭代器的remove,不是list的remove
    }
}

评论交流

文章目录