第 1 课 类与对象
对应教材第 4 章。面向对象思维的第一步。
类是"图纸",对象是根据图纸造出来的"实体"。类中定义成员变量(状态)与成员方法(行为)。 对象创建时调用构造方法初始化;对象变量保存的是引用,而不是对象本身。 Java 只有值传递:基本类型传值的副本,引用类型传引用的副本——因此方法内修改对象状态会影响原对象。
public class Student {
private String name; // 成员变量(封装:private 隐藏细节)
private int age;
private static int count = 0; // 类变量:被所有对象共享
public Student(String name, int age) { // 构造方法:与类同名,无返回值类型
this.name = name; // this 指代当前对象
this.age = age;
count++;
}
public void introduce() { // 成员方法
System.out.println("我叫" + name + ",今年" + age + "岁");
}
public static int getCount() { return count; } // 类方法:只能访问类成员
}
// 使用
Student s = new Student("张三", 20); // new 创建对象,引用赋给 s
s.introduce();
System.out.println(Student.getCount());
- 一旦自定义了构造方法,系统不再提供默认无参构造方法;需要无参构造时必须显式写出。
- static 成员属于类,被所有对象共享;static 方法中不能用 this,也不能直接访问实例成员。
- 方法重载只看参数列表(个数/类型/顺序),与返回值无关。
- 访问权限:private(本类) < 默认(同包) < protected(子类) < public(全部)。
课堂练习(4 题)
- 为什么自定义了构造方法后
new Student()会报错? 答:系统只在没有定义任何构造方法时才提供默认无参构造方法;自定义后需显式写出无参版本。 - 方法中修改传入对象的内容,调用方的对象会变吗?为什么? 答:会变。传的是引用的副本,副本与原件指向同一个对象,因此方法内对该对象的修改对调用方可见。
- static 方法能否直接访问成员变量? 答:不能。static 方法属于类,没有 this,只能通过对象引用访问实例成员。
- 写方法重载的两个
add,分别求两个 int 与三个 int 的和。 答:int add(int a, int b)与int add(int a, int b, int c),参数个数不同构成重载。
第 2 课 子类与继承
对应教材第 5 章。代码复用与多态的基础。
继承让子类获得父类的成员(private 与构造方法除外),并可以重写父类方法。 上转型对象(父类引用指向子类对象)配合重写,产生动态绑定——运行期才决定调用哪个版本,这就是多态。 注意:成员变量没有"重写",只有隐藏,访问哪个取决于引用的声明类型。
class Animal {
void sound() { System.out.println("..."); }
}
class Dog extends Animal {
@Override void sound() { System.out.println("汪汪"); } // 重写
void fetch() { System.out.println("叼球"); } // 子类新增
}
Animal a = new Dog(); // 上转型对象
a.sound(); // 输出"汪汪"(动态绑定,执行子类版本)
// a.fetch(); // 编译错误:上转型对象不能调用子类新增方法
((Dog) a).fetch(); // 向下转型后可以调用
- 重写要求方法签名相同、返回类型兼容、访问权限不能降低;加 @Override 让编译器帮忙检查。
- 子类构造方法默认先调用父类无参构造;父类没有无参构造时必须写
super(...),且必须是第一条语句。 - final:类不可继承、方法不可重写、变量为常量。
- 抽象类不能实例化,用于规定子类必须实现的行为(抽象方法)。
课堂练习(4 题)
- 重写与重载的区别? 答:重写发生在父子类之间,方法签名相同,体现多态;重载在同一个类中,方法名相同但参数列表不同。
- 父类只有带参构造方法,子类构造方法应如何写?
答:必须在子类构造方法第一行显式调用
super(参数)。 - 上转型对象访问被隐藏的成员变量时,取的是父类还是子类的? 答:取父类的。成员变量不具有多态性,访问哪个由引用的声明类型决定;方法才具有多态性。
- 为什么需要抽象类? 答:用于抽取共性、规定子类必须实现的行为,使面向抽象编程成为可能(依赖抽象而非具体实现)。
第 3 课 接口与面向接口编程
对应教材第 6 章。表达"具有某种能力"。
接口只规定"能做什么",不规定"怎么做"。一个类可以实现多个接口,弥补了单继承的限制。 接口回调是最常用的模式:接口变量存放实现类的引用,调用方法时执行实现类的版本。
interface Shape {
double area(); // 默认 public abstract
}
class Circle implements Shape {
private double r;
Circle(double r) { this.r = r; }
public double area() { return Math.PI * r * r; }
}
class Rect implements Shape {
private double w, h;
Rect(double w, double h) { this.w = w; this.h = h; }
public double area() { return w * h; }
}
// 接口回调:统一处理不同的实现
Shape[] arr = { new Circle(1), new Rect(2, 3) };
double sum = 0;
for (Shape s : arr) sum += s.area(); // 调用各自的实现
- 接口中的方法默认
public abstract,变量默认public static final。 - 实现类必须重写接口中的全部抽象方法,否则该类必须声明为 abstract。
- 接口与抽象类:接口强调"规范/能力",多实现;抽象类强调"共性/代码复用",单继承。
课堂练习(3 题)
- 接口变量能否直接 new 一个接口对象? 答:不能,接口不能被实例化;但可以声明接口类型的引用,指向实现类的对象。
- 用接口回调改写"计算一组图形面积之和"有什么好处? 答:新增图形只需实现 Shape 接口,遍历求和的代码无需改动,符合开闭原则。
- 一个类可以同时继承父类并实现接口吗?
答:可以,写法为
class A extends B implements C, D,继承在前、实现在后。
第 4 课 内部类与异常处理
对应教材第 7 章。让程序更健壮。
内部类定义在另一个类内部,可直接访问外部类的成员;匿名类常用于只需要用一次的场合(如事件监听器)。 异常是程序运行时出现的不正常情况。受检异常必须捕获或声明抛出,运行时异常可不处理。 try-catch-finally 中,finally 的代码总会执行,适合释放资源。
// 匿名类:实现接口的一次性写法
button.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
System.out.println("按钮被点击");
}
});
// 异常处理
try {
int x = Integer.parseInt(sc.next());
if (x < 0) throw new Exception("年龄不能为负"); // 主动抛出
} catch (NumberFormatException e) { // 子类异常在前
System.out.println("请输入数字");
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
sc.close(); // 总会执行
}
// 自定义异常
class AgeException extends Exception {
AgeException(String msg) { super(msg); }
}
- 多个 catch 必须按子类在前、父类在后排列。
- 受检异常(IOException、SQLException 等)编译期强制处理;RuntimeException 及其子类可不处理。
- throw 用于抛出,throws 用于声明该方法可能抛出。
- finally 中的代码总会执行(除非调用 System.exit)。
课堂练习(3 题)
- try 中有 return,finally 还会执行吗? 答:会。finally 在 return 之前执行(先把返回值暂存,执行完 finally 再返回)。
- catch 顺序写反(父类在前)会怎样? 答:编译报错,因为后面的子类 catch 永远不可达。
- 受检异常与运行时异常处理要求有何不同? 答:受检异常必须捕获或声明抛出,否则编译不通过;运行时异常可不处理,通常由代码逻辑避免。
第 5 课 常用实用类与事件处理
对应教材第 8、9 章。
标准库提供了大量可直接使用的类:String / StringBuilder 处理文本、Math / BigInteger 做运算、 Date / Calendar 处理时间、Pattern / Matcher 做正则匹配。 图形界面遵循事件委托模型:组件(事件源)产生事件对象,注册在其上的监听器被调用。
// 字符串
String s = "Java,SQL,Java";
s.split(","); // ["Java","SQL","Java"](参数是正则)
String.valueOf(123); // "123"
StringBuilder sb = new StringBuilder(); // 循环拼接用它
for (int i = 0; i < 1000; i++) sb.append(i);
// 数学与正则
Math.round(3.5); // 4
Pattern.matches("\\d{3}", "123");// true
// GUI 事件处理(Swing)
JFrame frame = new JFrame("示例");
JButton btn = new JButton("点我");
JLabel label = new JLabel("尚未点击");
btn.addActionListener(e -> label.setText("已点击")); // Lambda 简写监听器
frame.add(btn, BorderLayout.NORTH);
frame.add(label, BorderLayout.CENTER);
frame.setSize(300, 200);
frame.setVisible(true);
- String 不可变,循环拼接用 StringBuilder;StringBuffer 线程安全但更慢。
- 事件处理三要素:事件源、事件对象、监听器;监听器需注册到事件源上才生效。
- 布局管理器(BorderLayout / FlowLayout / GridLayout)决定组件位置。
课堂练习(3 题)
- 为什么循环里
s += "a"效率低? 答:String 不可变,每次 += 都新建对象,产生大量垃圾;应使用 StringBuilder.append。 - 按钮点击后没有任何反应,可能的原因有哪些? 答:监听器未注册到按钮;组件未添加到容器;处理方法签名写错;界面未 setVisible(true)。
split(".")为什么切不出结果? 答:split 的参数是正则表达式,"." 在正则中表示任意字符,需写成split("\\.")。
第 6 课 输入输出流
对应教材第 10 章。程序与外部数据交换。
流是数据的有序序列。字节流(InputStream / OutputStream)以字节为单位,适合图片、音频等二进制数据; 字符流(Reader / Writer)以字符为单位,处理文本并涉及编码,中文必须用它(或指定编码)。 缓冲流套在节点流之上,减少实际读写次数——这是提升 IO 性能最简单的方法。
// 读文本文件(字符流 + 缓冲)
try (BufferedReader br = new BufferedReader(new FileReader("in.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
} // try-with-resources:自动关闭资源
// 写文本文件
try (BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"))) {
bw.write("你好,Java");
bw.newLine();
}
// 对象序列化:类需实现 Serializable
class Person implements Serializable { String name; int age; }
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("p.dat"))) {
oos.writeObject(new Person());
}
- try-with-resources 会自动关闭实现了 AutoCloseable 的资源,推荐优先使用。
- 文本用字符流,二进制用字节流;中文乱码多因读写两端编码不一致。
- 序列化要求类实现 Serializable;transient 成员不参与序列化。
课堂练习(3 题)
- 读取文本出现中文乱码,如何处理?
答:明确指定编码,如
new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8),并确保文件本身编码一致。 - 为什么要套一层缓冲流? 答:缓冲流在内存中积累数据后批量读写,显著减少磁盘 IO 次数,提高性能。
- try-with-resources 相比 finally 关闭资源有什么好处? 答:代码简洁,且能自动关闭多个资源(按逆序),避免遗漏与异常覆盖问题。
第 7 课 JDBC 与 MySQL
对应教材第 11 章。Java 操作数据库的标准方式。
JDBC 是 Java 访问数据库的统一接口。固定流程六步:加载驱动 → 建立连接 → 创建语句 → 执行 →
处理结果集 → 关闭资源。
查询用 executeQuery 返回 ResultSet;增删改使用 executeUpdate 返回影响行数。
一律优先使用 PreparedStatement:预编译、效率高、能防 SQL 注入。
String url = "jdbc:mysql://localhost:3306/school?useSSL=false&serverTimezone=UTC";
try (Connection conn = DriverManager.getConnection(url, "root", "pwd")) {
// 查询(PreparedStatement + 占位符)
String sql = "SELECT Sname, Grade FROM Student s JOIN SC c ON s.Sno = c.Sno WHERE c.Cno = ?";
try (PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, "2");
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) { // 必须先 next()
System.out.println(rs.getString("Sname") + " " + rs.getInt("Grade"));
}
}
}
// 事务:关闭自动提交,出错回滚
conn.setAutoCommit(false);
try {
// 多条更新...
conn.commit();
} catch (SQLException e) {
conn.rollback();
}
}
- ResultSet 遍历前必须先调用
next();列可用名称或序号取值。 - 资源关闭顺序与创建顺序相反:ResultSet → Statement → Connection。
- 事务处理需先
setAutoCommit(false),异常时 rollback。 - SQL 注入的根源是拼接字符串,PreparedStatement 通过占位符参数化避免。
课堂练习(3 题)
- 为什么必须用 PreparedStatement 而不是拼接 SQL?
答:拼接会引入 SQL 注入风险(如输入
' or '1'='1),PreparedStatement 把参数与语句分离、预编译执行,既安全又高效。 - executeQuery 与 executeUpdate 的返回值分别是什么? 答:前者返回 ResultSet 结果集;后者返回 int,表示受影响的行数。
- 转账操作为什么要显式管理事务? 答:扣款与加款必须作为一个整体生效,任一步失败都要回滚,否则会出现数据不一致。
第 8 课 多线程与网络编程
对应教材第 12、13 章。
线程是程序内部的执行线索,多个线程共享进程资源。启动线程必须调用 start(),
直接调用 run() 只是普通方法调用。共享资源竞争用 synchronized 解决;
线程间协作用 wait / notify(必须在同步块中使用)。
网络方面,TCP 用 Socket 建立可靠连接,UDP 用 DatagramSocket 发送数据报。
// 创建线程(推荐实现 Runnable)
class MyTask implements Runnable {
public void run() { System.out.println("子线程工作中"); }
}
Thread t = new Thread(new MyTask());
t.start(); // 启动线程(不能写 t.run())
// 同步:保护共享资源
class Counter {
private int n = 0;
synchronized void inc() { n++; } // 同一时刻只允许一个线程执行
}
// wait / notify 协作
synchronized (lock) {
while (!ready) lock.wait(); // 释放锁并等待
lock.notifyAll(); // 唤醒等待方
}
// TCP 服务端片段
try (ServerSocket server = new ServerSocket(8888)) {
Socket s = server.accept(); // 阻塞等待连接
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
System.out.println(in.readLine());
}
- start() 才会启动新线程;run() 只是普通调用,不会并发。
- wait 会释放锁,sleep 不释放锁;wait/notify 必须在同步块中调用。
- 线程状态:新建 → 就绪 → 运行 → 阻塞 → 死亡。
- TCP 面向连接可靠,UDP 无连接、可能丢包但开销小。
课堂练习(3 题)
t.run()与t.start()有何区别? 答:run() 在当前线程中顺序执行方法体,不产生新线程;start() 由 JVM 创建新线程并在其中执行 run()。- 多线程同时对一个计数器加一,结果为何可能偏小?
答:
n++不是原子操作(读—改—写三步),线程切换会覆盖彼此的结果;需加 synchronized 或使用原子类。 - wait 与 sleep 的区别? 答:wait 是 Object 的方法,会释放锁,需被 notify 唤醒,用于线程协作;sleep 是 Thread 的静态方法,不释放锁,到时自动恢复。
第 9 课 泛型与集合框架
对应教材第 15 章。日常开发使用频率最高。
集合框架分两大体系:Collection(单元素)与 Map(键值对)。 List 有序可重复、Set 无序不可重复、Map 按键取值。 泛型在编译期保证类型安全(运行时类型擦除),避免强制转换与类型错误。
List<String> list = new ArrayList<>(); // 有序可重复,随机访问快
Set<String> set = new HashSet<>(); // 无序去重
Map<String, Integer> map = new HashMap<>();// 键值映射
list.add("Java"); list.add("SQL");
for (String s : list) System.out.println(s);
Collections.sort(list); // 排序
map.put("张三", 90);
map.getOrDefault("李四", 0);
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + ":" + e.getValue());
}
// 遍历时删除必须用迭代器
Iterator<String> it = list.iterator();
while (it.hasNext()) if (it.next().isEmpty()) it.remove();
- ArrayList 随机访问快、中间插入慢;LinkedList 插入删除快、随机访问慢。
- HashMap 的键必须正确实现 equals 与 hashCode,且二者一致;TreeMap 会按键排序。
- 增强 for 遍历集合时删除元素会抛 ConcurrentModificationException,应使用 Iterator.remove()。
- 泛型不支持基本类型(用包装类),运行时类型被擦除。
课堂练习(4 题)
- 统计一段文本中每个单词出现的次数,用什么结构最合适?
答:
Map<String, Integer>(HashMap),用 getOrDefault 累加计数。 - 为什么自定义对象作为 HashMap 的键要重写 hashCode 与 equals? 答:HashMap 先按 hashCode 定位桶,再用 equals 判等;只重写其中一个会导致相同逻辑对象被当成不同键(或散列错误)。
- foreach 遍历集合时调用 list.remove() 会怎样? 答:抛 ConcurrentModificationException;应改用 Iterator 的 remove 方法。
- ArrayList 与 LinkedList 如何选择? 答:频繁按下标读取、追加用 ArrayList;频繁在中间插入删除用 LinkedList。