学校-Java程序设计知识点总结

课时一 对象和封装

现实世界与面向对象

  • 现实世界由对象组成
  • 面向对象的目的:用计算机描述现实世界、解决现实世界问题
  • 面向对象优势:交流流畅,提高设计和开发效率

面向对象描述世界的步骤

  1. 发现类:根据"对象"抽象出"类"(例:class Dog { }
  2. 发现类的属性:只保留与业务相关的属性(例:String name = "旺财"; int health = 100; int love = 0; String strain = "拉布拉多犬";
  3. 发现类的方法:只保留与业务相关的方法(例:public void print() { }

类图简单介绍

  • 作用:用于分析和设计"类",直观易理解
  • 组成:类名、属性(类型)、方法(返回值类型、参数:名字:类型)
  • 访问修饰符:+ 表示 public,- 表示 private

2. 构造方法及其重载

对象初始化

  • 可通过构造方法在创建对象时直接完成赋值

构造方法语法

  • 系统默认提供无参构造方法:public Penguin() { }

构造方法重载(自定义构造方法)

  • 核心要求:方法名相同,参数数据类型或参数个数不同
  • 与返回值、访问修饰符无关
  • 示例:
// 无参构造
public Penguin () {
name = "qq";
love = 20;
sex = "Q 仔";
}
// 带参构造
public Penguin (String name,int health,int love,String sex ) {
this.name = name;
this.health = health;
this.love = love;
this.sex = sex;
}

3. static 关键字介绍

静态变量与静态方法

  • 静态变量:static final String SEX_MALE="Q 仔";(用类名调用:Penguin.SEX_MALE
  • 静态方法:static void print() { }(用类名调用:Penguin.print()
  • final 修饰的变量为常量,值固定不变

static 与非 static 修饰的区别

对比项 static、非 private 修饰 非 static、private 修饰
属性 类属性、类变量 实例属性、实例变量
方法 类方法 实例方法
调用方式 类名。方法 ()、对象。方法 ()、类名。属性、对象。属性 对象。方法 ()、对象。属性
归属 单个对象

4. 封装的概念及其使用

封装的目的

  • 解决属性随意访问导致的不合理赋值问题(例:Dog d = new Dog(); d.health = -1000;

封装的定义

  • 面向对象三大特征之一,将类的某些信息隐藏在类内部,不允许外部直接访问,通过类提供的方法操作和访问隐藏信息

封装的好处

  • 隐藏类的实现细节
  • 只能通过规定方法访问数据
  • 方便加入控制语句
  • 方便修改实现

封装的步骤

  1. 修改属性的可见性,设为 private

  2. 创建公有的 getter/setter 方法,用于属性的读写

  3. 在 getter/setter 方法中加入属性控制语句,对属性值的合法性进行判断

    代码示例

private int health;
// getter 方法
public int getHealth() {
return health;
}
// setter 方法(含合法性判断)
public void setHealth (int health) {
if (health > 100 || health < 0) {
this.health = 40;
System.out.println("健康值应该在0 和100 之间,默认值是 40");
} else {
this.health = health;
}
}
  • 添加 getter/setter 方法的快捷键:Shift+Alt+S+R

this 用法

  1. 调用属性:this.health = 100; this.name = "大黄";
  2. 调用方法:this.print();
  3. 调用构造方法:this(); this("小黑",100,100,"雄");(必须是构造方法中的第一条语句)

5. 类的实现示例

Dog 类

class Dog {
private String name = "无名氏"; // 昵称
private int health = 0; // 健康值
private int love = 0; // 亲密度
private String strain = "聪明的拉布拉多犬"; // 品种

// 无参构造方法
public Dog(){
}

// 带参构造方法(指定昵称、品种)
public Dog(String name, String strain) {
this.name = name;
this.strain = strain;
}

// 吃饭方法(增加健康值)
public void eat() {
if (health >= 100) {
System.out.println("狗狗需要多运动呀!");
} else {
health = health + 3;
System.out.println("狗狗吃饱饭了!");
}
}

// 玩耍方法(增加亲密度,减少健康值)
public void play() {
if (health < 60) {
System.out.println("狗狗生病了!");
} else {
System.out.println("狗狗正在和主人玩耍。");
health = health - 10;
love = love + 5;
}
}

// getter/setter 方法
public void setName(String name) { this.name = name; }
public void setHealth(int health) {
if(health<0||health>100){
System.out.println("健康值应该在0 至100 之间,默认值为60。");
this.health=60;
return;
}
this.health = health;
}
public void setLove(int love) {
if(love<0||love>100){
System.out.println("亲密度应该在0 至100 之间,默认值为0。");
this.love=0;
}
this.love = love;
}
public void setStrain(String strain) { this.strain = strain; }
public String getName() { return name; }
public int getHealth() { return health; }
public int getLove() { return love; }
public String getStrain() { return strain; }

// 输出信息方法
public void print() {
System.out.println("宠物的自白:\n 我的名字叫" + this.getName()
+ ",健康值是" + this.getHealth() + ",和主人的亲密度是"
+ this.getLove() + ",我是一只 " + this.getStrain() + "。");
}
}

Penguin 类

public class Penguin { 
String name = "无名氏"; // 昵称
int health = 100; // 健康值
int love = 20; // 亲密度
static final String SEX_MALE = "雄";
static final String SEX_FEMALE = "雌";
String sex = ""; // 性别

// 输出信息方法
public void print() {
System.out.println("宠物的自白:\n 我的名字叫" + this.name
+ ",健康值是" + this.health + ",和主人的亲密度是"
+ this.love + ",性别是 " + this.sex + "。");
}
}

课时二 继承

1. 继承的优点和实现

继承的目的

  • 解决代码冗余问题,将重复代码抽取到父类中

继承的核心关系

  • 子类与父类是 is-a 关系(例:Dog is a Pet)

继承的语法

  1. 编写父类:
class Pet { 
// 公共的属性和方法
private String name;
private int health;
private int love;

// getter/setter 方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getHealth() { return health; }
public void setHealth(int health) { this.health = health; }
public int getLove() { return love; }
public void setLove(int love) { this.love = love; }

// 公共方法
public void print() {
System.out.println("名字:" + name + ",健康值:" + health + ",亲密度:" + love);
}
}
  1. 编写子类(单继承):
class Dog extends Pet { 
// 子类特有属性和方法
private String strain; // 品种

public String getStrain() { return strain; }
public void setStrain(String strain) { this.strain = strain; }
}
class Penguin extends Pet {
// 子类特有属性和方法
private String sex; // 性别

public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; }
}

子类访问父类成员

  1. 访问父类构造方法:super();super(name);(必须是子类构造方法的第一句)
  2. 访问父类属性:super.name;
  3. 访问父类方法:super.print();

不能被继承的父类成员

  • private 成员
  • 子类与父类不在同包,使用默认访问权限的成员
  • 构造方法

访问修饰符 protected

  • 可以修饰属性和方法
  • 本类、同包、子类可以访问

访问修饰符总结

访问修饰符 本类 同包 子类 其他
private
默认 (friendly)
protected
public

多重继承关系的初始化顺序

  1. 父类属性
  2. 父类构造方法
  3. 子类属性
  4. 子类构造方法

2. 子类重写父类方法

构造方法不可重写

  • 构造方法不能被继承,因此不能重写

方法重写的规则

  1. 方法名相同
  2. 参数列表相同
  3. 返回值类型相同或者是其子类
  4. 访问权限不能严于父类

方法重载与方法重写的区别

对比项 方法重写 方法重载
位置 子类 同类
方法名 相同 相同
参数表 相同 不相同
返回值 相同或是其子类 无关
访问修饰符 不能比父类更严格 无关

super 关键字的使用限制

  1. 只能出现在子类的方法和构造方法中
  2. 调用构造方法时,只能是第一句
  3. 不能访问父类的 private 成员

3. 抽象类和抽象方法

抽象类的目的

  • 限制实例化(例:Pet pet = new Pet("贝贝",20,40); 无意义)

抽象类的语法

public abstract class Pet { 
// 抽象类中可以有普通方法和抽象方法
public abstract void print(); // 抽象方法(无方法体)
}

抽象方法的规则

  1. 抽象方法没有方法体
  2. 抽象方法必须在抽象类里
  3. 抽象方法必须在子类中被实现,除非子类是抽象类

4. final 修饰符

final 修饰类

  • 类不能再被继承(例:public final class Penguin extends Pet { }

final 修饰方法

  • 方法不能被子类重写(例:public final void print() { }

final 修饰变量

  • 变量变成常量,只能在初始化时赋值(例:final String home="南极";,不可再赋值)

5. 继承实现示例

人类与学生类(继承 + 重写)

package homework.extend.java;
public class Person {
// 属性:名字、年龄、性别
private String name;
private int age;
private String sex;

// 有参构造
public Person(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}

// 无参构造
public Person() { }

// 输出信息方法
public void showInfo(){
System.out.println("name:"+name+",age:"+age+",sex:"+sex);
}

// getter/setter 方法
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getSex() { return sex; }
public void setSex(String sex) { this.sex = sex; }
}

package homework.extend.java;
public class Student extends Person{
// 子类特有属性:学号
private String stuNo;

// 构造方法
public Student(String name, int age, String sex, String stuNo) {
super(name, age, sex); // 调用父类有参构造
this.stuNo = stuNo;
}

public Student() {
super(); // 调用父类无参构造
}

// 重写父类方法
@Override
public void showInfo(){
super.showInfo(); // 调用父类方法
System.out.println("stuNo:"+stuNo);
}

// getter/setter 方法
public String getStuNo() { return stuNo; }
public void setStuNo(String stuNo) { this.stuNo = stuNo; }
}

// 测试类
package homework.extend.java;
public class TestStudent {
public static void main(String[] args) {
Student stu = new Student("faker",12,"男","201612160124");
stu.showInfo();
}
}

课时三 多态

1. 多态的优势和应用场合

多态的目的

  • 解决代码扩展性差的问题(例:新增宠物类型无需修改主人类喂食方法)

多态的定义

  • 同一个引用类型,使用不同的实例而执行不同操作
  • 核心:父类引用指向子类对象

实现多态的三个要素

  1. 具有继承关系的父类和子类
  2. 子类重写父类方法
  3. 使用父类的引用指向子类的对象(向上转型,自动类型转换)

向上转型示例

Pet pet = new Dog(); // 自动类型转换

2. 使用父类作为方法形参实现多态

优化主人喂食方法

// 父类 Pet
public abstract class Pet {
public abstract void eat(); // 抽象方法,子类重写
}

// 子类 Dog
public class Dog extends Pet {
@Override
public void eat() {
System.out.println("狗狗吃狗粮,健康值增加3");
}
}

// 子类 Penguin
public class Penguin extends Pet {
@Override
public void eat() {
System.out.println("企鹅吃鱼,健康值增加5");
}
}

// 主人类(多态优化)
public class Master{
public void feed(Pet pet) {
pet.eat(); // 不同子类对象执行不同的 eat 方法
}
}

// 测试类
public class Test {
public static void main(String[] args) {
Master master = new Master();
Pet dog = new Dog();
Pet penguin = new Penguin();
master.feed(dog); // 输出:狗狗吃狗粮,健康值增加3
master.feed(penguin); // 输出:企鹅吃鱼,健康值增加5
}
}

3. 使用父类作为方法返回值实现多态

实现领养宠物功能

public class Master{
// 根据类型编号返回不同宠物对象
public Pet getPet(String typeId) {
if ("dog".equals(typeId)) {
return new Dog();
} else if ("penguin".equals(typeId)) {
return new Penguin();
}
return null;
}
}

// 测试类
public class Test {
public static void main(String[] args) {
Master master = new Master();
Pet dog = master.getPet("dog");
dog.eat(); // 多态调用
}
}

4. 父类到子类转换

向下转型(强制类型转换)

  • 父类引用不能直接调用子类特有方法,需强制转换为子类类型
  • 示例:
Pet pet = new Dog("欧欧","雪纳瑞"); 
Dog dog = (Dog) pet; // 正确转换
// Penguin png = (Penguin) pet; // 错误:父类引用指向 Dog 实例,不能转为 Penguin

instanceof 运算符

  • 语法:对象 instanceof 类或接口
  • 作用:判断对象是否为指定类或接口的实例,避免转型错误
  • 示例:
public class Master { 
public void play(Pet pet){
if (pet instanceof Dog) {
Dog dog = (Dog) pet;
dog.catchingFlyDisc(); // 调用 Dog 特有方法
} else if (pet instanceof Penguin) {
Penguin pgn = (Penguin) pet;
pgn.swimming(); // 调用 Penguin 特有方法
}
}
}

// Dog 类特有方法
public class Dog extends Pet {
public void catchingFlyDisc() {
System.out.println("狗狗接飞盘,健康值减少10,亲密度增加5");
}
}

// Penguin 类特有方法
public class Penguin extends Pet {
public void swimming() {
System.out.println("企鹅游泳,健康值减少10,亲密度增加5");
}
}

5. 多态实现示例(动物叫声)

// 抽象父类 Animal
public abstract class Animal {
private String kind; // 种类

// 构造方法
public Animal(String kind) {
this.kind = kind;
}

// 抽象方法:叫声
public abstract void cry();

// getter/setter
public String getKind() { return kind; }
public void setKind(String kind) { this.kind = kind; }
}

// 子类 Cat
public class Cat extends Animal {
public Cat() {
super("猫");
}

@Override
public void cry() {
System.out.println("小猫的叫声:喵喵喵~~~");
}
}

// 子类 Dog
public class Dog extends Animal {
public Dog() {
super("狗");
}

@Override
public void cry() {
System.out.println("小狗的叫声:汪汪汪~~~");
}
}

// 子类 Sheep
public class Sheep extends Animal {
public Sheep() {
super("羊");
}

@Override
public void cry() {
System.out.println("小羊的叫声:咩咩咩~~~");
}
}

// 测试类(随机生成动物并调用叫声)
public class Test {
public static void main(String[] args) {
Animal[] arr = new Animal[5];
int random;
for (int i = 0; i < arr.length; i++) {
random = (int) (Math.random() * 3); // 0-2 随机数
switch (random) {
case 0:
arr[i] = new Cat();
break;
case 1:
arr[i] = new Dog();
break;
case 2:
arr[i] = new Sheep();
break;
}
}

// 遍历数组,调用叫声方法
for (Animal animal : arr) {
animal.cry();
}
}
}

课时四 接口

1. 接口基础知识

接口的使用场景

  • 解决 Java 单继承限制(例:防盗门需要继承门,同时具备锁的功能)

接口的语法

public interface 接口名 { 
// 全局静态常量(默认 public static final)
String CONSTANT = "value";

// 抽象方法(默认 public abstract)
void method();
}

接口的特性

  1. 接口不能被实例化,仅作为类型使用
  2. 实现类必须实现接口的所有抽象方法(除非实现类是抽象类)
  3. 一个类可以实现多个接口(Java 间接实现多继承)
  4. 接口中的成员变量默认是 public static final,方法默认是 public abstract

USB 接口示例

1. 定义 USB 接口

public interface UsbInterface { 
void service(); // USB 设备提供的服务
}

2. 实现类(U 盘、USB 鼠标)

// U盘实现
public class UDisk implements UsbInterface {
@Override
public void service() {
System.out.println("连接USB口,开始传输数据");
}
}

// USB鼠标实现
public class UsbMouse implements UsbInterface {
@Override
public void service() {
System.out.println("连接USB口,鼠标开始工作");
}
}

3. 使用接口(多态)

public class Test { 
public static void main(String[] args) {
UsbInterface uDisk = new UDisk();
uDisk.service(); // 输出:连接USB口,开始传输数据

UsbInterface usbMouse = new UsbMouse();
usbMouse.service(); // 输出:连接USB口,鼠标开始工作
}
}

2. 接口表示一种能力

核心思想

  • 接口是一种 “能力” 的约定,不关心实现者是谁,只关心是否具备该能力
  • 例:“钳工” 是一种能力,无论是谁,只要具备该能力就能完成对应工作

面向接口编程的优势

  1. 解耦:设计与实现分离
  2. 灵活:支持多实现,便于扩展
  3. 多态:天然支持多态调用

3. 接口应用示例(防盗门)

1. 定义抽象类 Door(门的基础功能)

public abstract class Door { 
// 开门
public abstract void open();

// 关门
public abstract void close();
}

2. 定义接口 Lock(锁的功能)

public interface Lock { 
void lockUp(); // 上锁
void openLock(); // 开锁
}

3. 定义接口 Bell(门铃功能)

public interface Bell { 
void takePhoto(); // 拍照存档
}

4. 防盗门实现(继承 + 多接口)

public class TheftproofDoor extends Door implements Lock, Bell { 
@Override
public void open() {
System.out.println("用力推,门打开了。");
}

@Override
public void close() {
System.out.println("轻轻拉门,门关上了。");
}

@Override
public void lockUp() {
System.out.println("插进钥匙,向左旋转钥匙三圈,锁上了,拔出钥匙。");
}

@Override
public void openLock() {
System.out.println("插进钥匙,向右旋转钥匙三圈,锁打开了,拔出钥匙。");
}

@Override
public void takePhoto() {
System.out.println("铃......咔嚓......照片已存储");
}
}

5. 测试类

public class Test { 
public static void main(String[] args) {
TheftproofDoor door = new TheftproofDoor();
door.close();
door.lockUp();
door.openLock();
door.takePhoto();
door.open();
}
}

4. 接口应用示例(计算器)

// 定义计算接口
public interface ICompute {
int compute(int n, int m); // 计算方法
}

// 加法实现
public class Add implements ICompute {
@Override
public int compute(int n, int m) {
int ret = n + m;
System.out.println("n+m is " + ret);
return ret;
}
}

// 减法实现
public class Minus implements ICompute {
@Override
public int compute(int n, int m) {
int ret = n - m;
System.out.println("n-m is " + ret);
return ret;
}
}

// 乘法实现
public class Mul implements ICompute {
@Override
public int compute(int n, int m) {
int ret = n * m;
System.out.println("n*m is " + ret);
return ret;
}
}

// 除法实现
public class Div implements ICompute {
@Override
public int compute(int n, int m) {
int ret = n / m;
System.out.println("n/m is " + ret);
return ret;
}
}

// 计算器使用类
public class UseCompute {
public void useCom(ICompute com, int one, int two) {
com.compute(one, two);
}
}

// 测试类
public class Test {
public static void main(String[] args) {
UseCompute fun = new UseCompute();
fun.useCom(new Add(), 8, 2);
fun.useCom(new Minus(), 8, 2);
fun.useCom(new Mul(), 8, 2);
fun.useCom(new Div(), 8, 2);
}
}

课时五 异常

1. 异常的概念

异常的定义

  • 程序运行过程中发生的不正常事件(如除零、数组越界),会中断程序执行

异常处理的关键字

  • try:包裹可能产生异常的代码块
  • catch:捕获并处理异常
  • finally:无论是否发生异常,都会执行(除非调用 System.exit(1)
  • throw:手动抛出异常对象
  • throws:声明方法可能抛出的异常类型

2. try-catch-finally 处理异常

基本语法

try { 
// 可能产生异常的代码
} catch (异常类型1 e) {
// 处理异常类型1
} catch (异常类型2 e) {
// 处理异常类型2(注意:子类异常在前,父类异常在后)
} finally {
// 必须执行的代码(如关闭资源)
}

示例(除法异常处理)

import java.util.Scanner;

public class Test {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
try {
System.out.print("请输入被除数:");
int num1 = in.nextInt();
System.out.print("请输入除数:");
int num2 = in.nextInt();
System.out.println(String.format("%d / %d = %d", num1, num2, num1 / num2));
} catch (ArithmeticException e) {
System.err.println("异常:除数不能为零");
e.printStackTrace(); // 输出异常堆栈信息
} catch (InputMismatchException e) {
System.err.println("异常:请输入整数");
} finally {
System.out.println("感谢使用本程序!");
in.close(); // 关闭扫描器
}
}
}

finally 执行规则

  • 无论 try 块是否发生异常、catch 块是否执行、是否有 return,finally 都会执行
  • 唯一不执行的情况:System.exit(1)(终止虚拟机)

3. throw 与 throws 抛出异常

throws 声明异常

  • 语法:方法返回值类型 方法名() throws 异常类型1, 异常类型2 { }
  • 作用:声明方法可能抛出的异常,由调用者处理
  • 示例:
public static void divide(int num1, int num2) throws ArithmeticException { 
if (num2 == 0) {
throw new ArithmeticException("除数不能为零"); // 手动抛出异常
}
System.out.println(num1 / num2);
}

// 调用者处理异常
public static void main(String[] args) {
try {
divide(10, 0);
} catch (ArithmeticException e) {
System.err.println(e.getMessage());
}
}

throw 手动抛出异常

  • 语法:throw new 异常类型("异常信息");
  • 作用:在满足特定条件时,主动抛出异常
  • 示例:
public class Person { 
private String sex = "男";

public void setSex(String sex) throws Exception {
if ("男".equals(sex) || "女".equals(sex)) {
this.sex = sex;
} else {
throw new Exception("性别必须是'男'或者'女'!"); // 手动抛出异常
}
}
}

4. 异常的分类

异常层次结构

Throwable(所有异常/错误的父类)
├── Error(严重错误,程序无法恢复,如 OutOfMemoryError)
└── Exception(可处理的异常)
├── Checked 异常(受检异常,必须处理,如 IOException)
└── RuntimeException(运行时异常,非受检异常,如 NullPointerException)

常见异常类型

异常类型 说明
ArithmeticException 算术错误(如除零)
ArrayIndexOutOfBoundsException 数组下标越界
NullPointerException 访问 null 对象成员
ClassNotFoundException 无法加载指定类
InputMismatchException 输入类型不匹配
ClassCastException 类型转换错误
NumberFormatException 数字格式转换错误(如 “abc” 转 int)

5. 异常处理示例

示例 1:输入 5 个整数(处理输入不匹配和数组越界)

import java.util.Scanner;
import java.util.InputMismatchException;

public class T1 {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入5个整数:");
int[] arr = new int[5];
try {
for (int i = 0; i <= arr.length; i++) { // 故意越界(i <= 5)
int pi = input.nextInt();
arr[i] = pi;
}
for (int num : arr) {
System.out.println(num);
}
} catch (InputMismatchException e) {
System.out.println("请输入整数");
e.printStackTrace();
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("请输入5个整数,数组越界");
e.printStackTrace();
} finally {
System.out.println("程序结束!");
input.close();
}
}
}

示例 2:判断三角形(自定义异常)

// 自定义异常类
class SjException extends Exception {
public SjException(String message) {
super(message);
}

public SjException(String message, Throwable cause) {
super(message, cause);
}
}

// 三角形类
class Sanj {
private int x, y, z; // 三边长

// 构造方法(验证三边合法性)
public Sanj(int x, int y, int z) throws SjException {
if ((x >= y + z) || (x <= Math.abs(y - z)) ||
(y >= x + z) || (y <= Math.abs(x - z)) ||
(z >= x + y) || (z <= Math.abs(x - y)) ||
x == 0 || y == 0 || z == 0) {
throw new SjException(x + "," + y + "," + z + "不能构成三角形");
}
this.x = x;
this.y = y;
this.z = z;
}

// 计算面积(海伦公式)
public double getArea() {
int p = (x + y + z) / 2;
double s = Math.sqrt(p * (p - x) * (p - y) * (p - z));
return s;
}

// 显示三角形信息
public void showInfo() {
System.out.println("三边长分别为:" + x + "," + y + "," + z);
}
}

// 测试类
public class SjTest {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("请输入三角形三边x,y,z的值:");
int x = input.nextInt();
int y = input.nextInt();
int z = input.nextInt();

try {
Sanj s = new Sanj(x, y, z);
System.out.println("三角形面积为:" + s.getArea());
s.showInfo();
} catch (SjException e) {
System.out.println(e.getMessage());
} finally {
input.close();
}
}
}

6. 使用 log4j 记录日志

log4j 作用

  • 记录程序运行日志(包括异常信息、关键操作),支持输出到控制台、文件
  • 控制日志级别(fatal > error > warn > info > debug)

使用步骤

  1. 导入 log4j 的 JAR 包
  2. 创建 log4j.properties 配置文件
  3. 在代码中使用 log4j 记录日志

log4j 配置文件(log4j.properties)

# 设置日志级别和输出目的地(控制台+文件)
log4j.rootLogger = debug, stdout, logfile

# 控制台输出配置
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.err
log4j.appender.stdout.layout = org.apache.log4j.SimpleLayout

# 文件输出配置(输出到 jbit.log)
log4j.appender.logfile = org.apache.log4j.FileAppender
log4j.appender.logfile.File = jbit.log
log4j.appender.logfile.layout = org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} %l %p %m%n

代码中使用 log4j

import org.apache.log4j.Logger;

public class LogTest {
// 获取日志对象
private static Logger logger = Logger.getLogger(LogTest.class);

public static void main(String[] args) {
Scanner input = new Scanner(System.in);
try {
logger.debug("输入被除数:");
int num1 = input.nextInt();
logger.debug("输入除数:");
int num2 = input.nextInt();
logger.debug("输出运算结果:" + num1 + "/" + num2 + "=" + (num1 / num2));
} catch (ArithmeticException e) {
logger.error("除零异常", e);
} catch (InputMismatchException e) {
logger.error("输入类型不匹配", e);
} finally {
input.close();
}
}
}

课时六 集合

1. 集合框架概述

集合的作用

  • 存储数量不确定的对象
  • 支持复杂的对象存储方式(如键值映射)
  • 位于 java.util 包中

核心接口关系

Collection(集合根接口)
├── List(有序、可重复)
│ ├── ArrayList(数组实现,查询快、增删慢)
│ └── LinkedList(链表实现,增删快、查询慢)
└── Set(无序、不可重复)
├── HashSet(哈希表实现)
└── TreeSet(红黑树实现,有序)
Map(键值映射接口,无序)
├── HashMap(哈希表实现,查询快)
└── TreeMap(红黑树实现,按键有序)

核心接口特性

接口 特性 常用实现类
List 有序、可重复、有索引 ArrayList、LinkedList
Set 无序、不可重复 HashSet、TreeSet
Map 键值对、键唯一、值可重复 HashMap、TreeMap

2. ArrayList 和 LinkedList 的使用

ArrayList(数组实现)

常用方法

方法 说明
add(E e) 在末尾添加元素
add(int index, E e) 在指定索引添加元素
get(int index) 获取指定索引元素
remove(int index) 删除指定索引元素
size() 获取元素个数
contains(Object o) 判断是否包含指定元素

示例

import java.util.ArrayList;
import java.util.List;

public class ArrayListTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 添加元素
list.add("Java");
list.add("Python");
list.add(1, "C++"); // 在索引1添加

// 遍历元素(普通for)
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}

// 增强for遍历
for (String s : list) {
System.out.println(s);
}

// 删除元素
list.remove(2);
System.out.println("删除后:" + list);
}
}

LinkedList(链表实现)

特有方法(操作首尾元素)

方法 说明
addFirst(E e) 在首部添加
addLast(E e) 在尾部添加
getFirst() 获取首部元素
getLast() 获取尾部元素
removeFirst() 删除首部元素
removeLast() 删除尾部元素

示例

import java.util.LinkedList;

public class LinkedListTest {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("A");
list.addFirst("B");
list.addLast("C");

System.out.println("首部:" + list.getFirst());
System.out.println("尾部:" + list.getLast());

list.removeFirst();
System.out.println("删除首部后:" + list);
}
}

3. HashMap 的使用(键值映射)

常用方法

方法 说明
put(K key, V value) 存储键值对(键重复则覆盖值)
get(Object key) 根据键获取值(无此键返回 null)
remove(Object key) 根据键删除键值对
size() 获取键值对个数
keySet() 获取所有键的集合
values() 获取所有值的集合
containsKey(Object key) 判断是否包含指定键

示例

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class HashMapTest {
public static void main(String[] args) {
Map<String, String> countries = new HashMap<>();
// 存储键值对
countries.put("CN", "中华人民共和国");
countries.put("US", "美利坚合众国");
countries.put("RU", "俄罗斯联邦");

// 根据键获取值
System.out.println("CN:" + countries.get("CN"));

// 遍历键集
Set<String> keys = countries.keySet();
for (String key : keys) {
System.out.println(key + ":" + countries.get(key));
}

// 判断是否包含键
System.out.println("是否包含FR:" + countries.containsKey("FR"));

// 删除键值对
countries.remove("US");
System.out.println("删除US后:" + countries);
}
}

4. Iterator 的使用(迭代器遍历)

迭代器常用方法

方法 说明
hasNext() 判断是否有下一个元素
next() 获取下一个元素
remove() 删除当前元素

示例(遍历 List)

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class IteratorTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("苹果");
list.add("香蕉");
list.add("橙子");

// 获取迭代器
Iterator<String> it = list.iterator();
// 遍历
while (it.hasNext()) {
String fruit = it.next();
System.out.println(fruit);
if ("香蕉".equals(fruit)) {
it.remove(); // 删除当前元素
}
}

System.out.println("删除后:" + list);
}
}

5. 泛型集合

泛型的作用

  • 限定集合中元素的类型,避免类型转换异常
  • 编译时检查类型,提高代码安全性

语法

// 泛型 List(只能存储 String 类型)
List<String> list = new ArrayList<>();

// 泛型 Map(键为 String,值为 Integer)
Map<String, Integer> map = new HashMap<>();

示例

import java.util.ArrayList;
import java.util.List;

public class GenericTest {
public static void main(String[] args) {
// 泛型 List(存储 Dog 类型)
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog("欧欧", "雪纳瑞"));
dogs.add(new Dog("亚亚", "拉布拉多"));

// 遍历(无需类型转换)
for (Dog dog : dogs) {
System.out.println(dog.getName() + " - " + dog.getStrain());
}

// 错误:不能添加非 Dog 类型
// dogs.add("hello");
}
}

class Dog {
private String name;
private String strain;

public Dog(String name, String strain) {
this.name = name;
this.strain = strain;
}

// getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getStrain() { return strain; }
public void setStrain(String strain) { this.strain = strain; }
}

6. 集合应用示例

示例 1:筛选数组中≥10 的数字存入集合

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

public class Test1 {
public static void main(String[] args) {
Random random = new Random();
int[] arr = new int[10];
// 生成1-100的随机数
for (int i = 0; i < arr.length; i++) {
arr[i] = random.nextInt(100) + 1;
}

// 筛选≥10的数字存入集合
List<Integer> list = new ArrayList<>();
for (int num : arr) {
if (num >= 10) {
list.add(num);
}
}

System.out.println("数组:" + Arrays.toString(arr));
System.out.println("≥10的数字:" + list);
}
}

示例 2:TreeSet 存储学生(按成绩排序)

import java.util.TreeSet;
import java.util.Random;

public class Test2 {
public static void main(String[] args) {
TreeSet<Student> treeSet = new TreeSet<>();
Random random = new Random();

// 生成20个学生对象(学号1-40,成绩0-100)
for (int i = 0; i < 20; i++) {
int id = random.nextInt(40) + 1;
int score = random.nextInt(101);
treeSet.add(new Student(id, score));
}

// 遍历(按成绩降序,成绩相同按学号升序)
System.out.println("学号\t成绩");
for (Student s : treeSet) {
System.out.println(s.getId() + "\t\t" + s.getScore());
}

// 最高分和最低分
System.out.println("最高分:" + treeSet.first().getScore());
System.out.println("最低分:" + treeSet.last().getScore());
}
}

// 学生类(实现 Comparable 接口,自定义排序规则)
class Student implements Comparable<Student> {
private int id;
private int score;

public Student(int id, int score) {
this.id = id;
this.score = score;
}

// 排序规则:成绩降序,成绩相同则学号升序
@Override
public int compareTo(Student o) {
if (this.score == o.score) {
return this.id - o.id;
} else {
return o.score - this.score;
}
}

// getter/setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public int getScore() { return score; }
public void setScore(int score) { this.score = score; }
}

课时七 多线程

1. 线程的概念

进程与线程的区别

对比项 进程 线程
定义 应用程序的执行实例 进程中最小的执行单位
资源 有独立的内存空间和资源 共享进程的内存空间和资源
调度 操作系统调度的基本单位 CPU 调度的基本单位

多线程的优势

  • 充分利用 CPU 资源(多任务并发)
  • 简化编程模型
  • 提升用户体验(如后台加载数据不阻塞界面)

主线程

  • main() 方法是主线程的入口
  • 主线程是其他子线程的父线程
  • 主线程最后执行完毕(负责资源清理)

2. 线程的创建和启动

方式 1:继承 Thread 类

步骤

  1. 继承 java.lang.Thread
  2. 重写 run() 方法(线程执行体)
  3. 创建线程对象,调用 start() 方法启动(不能直接调用 run()

示例

public class MyThread extends Thread { 
// 重写 run() 方法(线程执行体)
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}

public static void main(String[] args) {
// 创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
// 设置线程名
t1.setName("线程A");
t2.setName("线程B");
// 启动线程
t1.start();
t2.start();
}
}

方式 2:实现 Runnable 接口

步骤

  1. 实现 java.lang.Runnable 接口
  2. 实现 run() 方法(线程执行体)
  3. 创建 Thread 对象,传入 Runnable 实例,调用 start() 启动

示例

public class MyRunnable implements Runnable { 
// 实现 run() 方法(线程执行体)
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}

public static void main(String[] args) {
// 创建 Runnable 实例
MyRunnable myRunnable = new MyRunnable();
// 创建线程对象
Thread t1 = new Thread(myRunnable, "线程A");
Thread t2 = new Thread(myRunnable, "线程B");
// 启动线程
t1.start();
t2.start();
}
}

两种方式对比

对比项 继承 Thread 类 实现 Runnable 接口
优点 编写简单,直接操作线程 避免单继承限制,便于共享资源
缺点 受单继承限制 需通过 Thread 对象操作线程
推荐场景 简单场景,无需共享资源 复杂场景,需共享资源

3. 线程的状态

线程生命周期

  1. 创建状态:创建线程对象(new Thread()
  2. 就绪状态:调用 start() 后,等待 CPU 资源
  3. 运行状态:获得 CPU 资源,执行 run() 方法
  4. 阻塞状态:因 sleep()wait() 等暂时停止执行
  5. 死亡状态run() 执行完毕或调用 stop()(不推荐)

状态转换触发条件

  • 创建 → 就绪:start() 方法
  • 就绪 → 运行:获得 CPU 时间片
  • 运行 → 就绪:yield() 方法或时间片用完
  • 运行 → 阻塞:sleep(long millis)wait()suspend()
  • 阻塞 → 就绪:sleep() 超时、notify()resume()
  • 运行 → 死亡:run() 执行完毕

4. 线程调度的常用方法

方法 说明
setPriority(int newPriority) 设置线程优先级(1~10,默认 5,优先级高获取 CPU 概率大)
static void sleep(long millis) 线程休眠 millis 毫秒(进入阻塞状态,不释放锁)
void join() 等待该线程执行完毕(阻塞当前线程)
static void yield() 线程礼让(暂停当前线程,进入就绪状态,不释放锁)
void interrupt() 中断线程(如唤醒休眠的线程)
boolean isAlive() 判断线程是否处于活动状态

示例 1:线程优先级

public class PriorityTest implements Runnable { 
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "运行:" + i);
}
}

public static void main(String[] args) {
Thread t1 = new Thread(new PriorityTest(), "线程A");
Thread t2 = new Thread(new PriorityTest(), "线程B");
// 设置优先级(最高10,最低1)
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}

示例 2:线程休眠

public class SleepTest { 
public static void main(String[] args) {
new Thread(() -> {
for (int i = 1; i <= 5; i++) {
System.out.println(i + "秒");
try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}

示例 3:线程礼让

public class YieldTest implements Runnable { 
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "运行:" + i);
if (i == 3) {
System.out.print("线程礼让:");
Thread.yield(); // 礼让同优先级线程
}
}
}

public static void main(String[] args) {
Thread t1 = new Thread(new YieldTest(), "线程A");
Thread t2 = new Thread(new YieldTest(), "线程B");
t1.start();
t2.start();
}
}

5. 线程的同步

线程安全问题

  • 多个线程操作同一共享资源时,导致数据不一致(如多窗口购票超卖)
  • 示例(不安全的购票):
// 票类(共享资源)
class Ticket {
private int count = 10; // 剩余票数

public void sale() {
while (true) {
if (count > 0) {
// 模拟网络延时
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + count-- + "张票");
} else {
break;
}
}
}
}

// 售票窗口线程
class SaleWindow extends Thread {
private Ticket ticket;

public SaleWindow(String name, Ticket ticket) {
super(name);
this.ticket = ticket;
}

@Override
public void run() {
ticket.sale();
}
}

// 测试类(3个窗口抢票)
public class UnsafeTicketTest {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new SaleWindow("窗口1", ticket).start();
new SaleWindow("窗口2", ticket).start();
new SaleWindow("窗口3", ticket).start();
}
}

同步解决线程安全

方式 1:同步方法(synchronized 修饰方法)

class Ticket { 
private int count = 10;

// 同步方法(锁为 this)
public synchronized void sale() {
while (true) {
if (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + count-- + "张票");
} else {
break;
}
}
}
}

方式 2:同步代码块(synchronized 修饰代码块)

class Ticket { 
private int count = 10;

public void sale() {
while (true) {
// 同步代码块(锁为 this)
synchronized (this) {
if (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到第" + count-- + "张票");
} else {
break;
}
}
}
}
}

线程安全与非线程安全的类

非线程安全(效率高) 线程安全(效率低)
ArrayList Vector
HashMap Hashtable、ConcurrentHashMap
StringBuilder StringBuffer

6. 多线程应用示例(安全购票)

// 票类(同步代码块实现安全售票)
class SafeTicket {
private int count = 200; // 总票数
private boolean flag = false; // 售票结束标志

public void sale() {
while (!flag) {
synchronized (this) {
if (count <= 0) {
flag = true;
System.out.println("票已经卖完啦");
break;
} else {
System.out.println(Thread.currentThread().getName() + "卖的第 " + count++ + " 张票");
count--;
}
}
// 模拟网络延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 售票窗口线程
class SaleWindow extends Thread {
private SafeTicket ticket;

public SaleWindow(String name, SafeTicket ticket) {
super(name);
this.ticket = ticket;
}

@Override
public void run() {
ticket.sale();
}
}

// 测试类
public class SafeTicketDemo {
public static void main(String[] args) {
SafeTicket ticket = new SafeTicket();
new SaleWindow("窗口1", ticket).start();
new SaleWindow("窗口2", ticket).start();
new SaleWindow("窗口3", ticket).start();
}
}

课时八 File I/O

1. File 类操作文件或目录

File 类的作用

  • 操作文件或目录的属性(创建、删除、判断是否存在等)
  • 不负责文件内容的读写

常用方法

方法 说明
exists() 判断文件 / 目录是否存在
isFile() 判断是否为文件
isDirectory() 判断是否为目录
createNewFile() 创建空文件(目录不存在则报错)
mkdir() 创建单级目录
mkdirs() 创建多级目录
delete() 删除文件或空目录
getName() 获取文件 / 目录名称
getPath() 获取相对路径
getAbsolutePath() 获取绝对路径
length() 获取文件长度(字节)

示例

import java.io.File;
import java.io.IOException;

public class FileTest {
public static void main(String[] args) {
// 创建 File 对象
File file = new File("test.txt");

try {
if (!file.exists()) {
boolean created = file.createNewFile();
System.out.println("文件创建成功?" + created);
}

System.out.println("是否为文件?" + file.isFile());
System.out.println("绝对路径:" + file.getAbsolutePath());
System.out.println("文件长度:" + file.length() + "字节");

// 删除文件
boolean deleted = file.delete();
System.out.println("文件删除成功?" + deleted);
} catch (IOException e) {
e.printStackTrace();
}
}
}

2. 字节流读写文件

字节流分类

流类型 基类 常用实现类 用途
字节输入流 InputStream FileInputStream 读取文件内容(文本 / 二进制)
字节输出流 OutputStream FileOutputStream 写入文件内容(文本 / 二进制)

字节流读写文本文件示例

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamTest {
public static void main(String[] args) {
// 写入文件(字节输出流)
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String content = "好好学习Java";
byte[] bytes = content.getBytes(); // 字符串转字节数组
fos.write(bytes);
System.out.println("写入成功");
} catch (IOException e) {
e.printStackTrace();
}

// 读取文件(字节输入流)
try (FileInputStream fis = new FileInputStream("output.txt")) {
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
String content = new String(buffer, 0, len);
System.out.println("读取内容:" + content);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

字节流拷贝二进制文件(如图片、音频)

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BinaryCopyTest {
public static void main(String[] args) {
// 源文件路径和目标文件路径
String srcPath = "D:\\mn.jpg";
String destPath = "copy.jpg";

try (FileInputStream fis = new FileInputStream(srcPath);
FileOutputStream fos = new FileOutputStream(destPath)) {

byte[] buffer = new byte[1024 * 8]; // 8KB 缓冲区
int len;
long start = System.currentTimeMillis();

// 循环读取并写入
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}

long end = System.currentTimeMillis();
System.out.println("拷贝完成,耗时:" + (end - start) + "毫秒");
} catch (IOException e) {
e.printStackTrace();
}
}
}

3. 字符流读写文件

字符流分类

流类型 基类 常用实现类 用途
字符输入流 Reader FileReader、BufferedReader 读取文本文件(避免中文乱码)
字符输出流 Writer FileWriter、BufferedWriter 写入文本文件(避免中文乱码)

课时九 JDBC

1. JDBC 原理

JDBC 定义

  • JDBC(Java Database Connectivity)是 Java 数据库连接技术,提供连接各类数据库的统一接口,位于 java.sqljavax.sql 包中。

JDBC 架构

Java 应用程序 → JDBC API → JDBC DriverManager → JDBC 驱动 → 数据库服务器

核心组件及作用

组件 作用
DriverManager 管理 JDBC 驱动,建立数据库连接
Connection 负责与数据库建立连接,传递数据
Statement 执行 SQL 语句(静态 SQL)
PreparedStatement 执行预编译 SQL 语句(避免 SQL 注入)
ResultSet 保存查询语句的返回结果集

JDBC 操作步骤

  1. 加载 JDBC 驱动(数据库厂商提供)
  2. 建立数据库连接(通过 DriverManager.getConnection ())
  3. 创建 Statement/PreparedStatement 对象
  4. 执行 SQL 语句
  5. 处理结果集(查询操作)
  6. 释放资源(关闭 ResultSet、Statement、Connection)

2. 加载驱动与建立连接

加载驱动

  • 纯 Java 驱动(推荐):Class.forName("com.mysql.jdbc.Driver");(MySQL 5.x)
  • MySQL 8.x 驱动:Class.forName("com.mysql.cj.jdbc.Driver");

建立连接(DriverManager.getConnection ())

  • 语法:Connection conn = DriverManager.getConnection(URL, 用户名, 密码);
  • MySQL URL 格式:jdbc:mysql://localhost:3306/数据库名?useSSL=false&serverTimezone=UTC
  • 示例:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DBConnection {
public static void main(String[] args) {
// 数据库连接信息
String url = "jdbc:mysql://localhost:3306/epet?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";

Connection conn = null;
try {
// 1. 加载驱动(MySQL 8.x)
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接
conn = DriverManager.getConnection(url, user, password);
System.out.println("数据库连接成功");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 5. 释放资源
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}

3. Statement 接口的使用

常用方法

方法 说明
ResultSet executeQuery(String sql) 执行查询 SQL(SELECT),返回 ResultSet
int executeUpdate(String sql) 执行 DML(INSERT/UPDATE/DELETE)或 DDL,返回影响行数
boolean execute(String sql) 执行任意 SQL,返回是否有 ResultSet
void close() 关闭 Statement 对象

执行查询示例(查询狗狗信息)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class StatementQuery {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/epet?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";

Connection conn = null;
Statement stmt = null;
ResultSet rs = null;

try {
// 1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接
conn = DriverManager.getConnection(url, user, password);
// 3. 创建 Statement 对象
stmt = conn.createStatement();
// 4. 执行查询 SQL
String sql = "SELECT id, name, health, love FROM dog";
rs = stmt.executeQuery(sql);
// 5. 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int health = rs.getInt("health");
int love = rs.getInt("love");
System.out.println("id:" + id + ", 姓名:" + name + ", 健康值:" + health + ", 亲密度:" + love);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 6. 释放资源(顺序:ResultSet → Statement → Connection)
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

执行更新示例(修改狗狗健康值)

public class StatementUpdate {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/epet?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";

Connection conn = null;
Statement stmt = null;

try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();

// 执行 UPDATE 语句
String sql = "UPDATE dog SET health = 90 WHERE name = '欧欧'";
int rows = stmt.executeUpdate(sql);
System.out.println("影响的行数:" + rows);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

4. ResultSet 接口的使用

常用方法

方法 说明
boolean next() 游标向下移动一行,返回是否有数据
int getInt(int colIndex) 通过列号获取 int 类型数据(列号从 1 开始)
int getInt(String colLabel) 通过列名获取 int 类型数据
String getString(int colIndex) 通过列号获取 String 类型数据
String getString(String colLabel) 通过列名获取 String 类型数据
void close() 关闭 ResultSet 对象

结果集处理说明

  • 游标初始位置:在第一行数据之前
  • 调用 next() 后游标移动到下一行,返回 true 表示有数据,false 表示到达末尾
  • 支持通过列号或列名获取数据(推荐列名,可读性更强)

5. PreparedStatement 接口的使用

核心优势

  • 预编译 SQL 语句,提高执行效率(适合重复执行的 SQL)
  • 避免 SQL 注入攻击(通过占位符 ? 传递参数)
  • 代码可读性和可维护性更高

常用方法

方法 说明
void setInt(int parameterIndex, int x) 为第 parameterIndex 个占位符设置 int 值
void setString(int parameterIndex, String x) 为第 parameterIndex 个占位符设置 String 值
ResultSet executeQuery() 执行查询(无参数,SQL 已预编译)
int executeUpdate() 执行更新(无参数,SQL 已预编译)
void close() 关闭 PreparedStatement 对象

避免 SQL 注入示例(用户登录)

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Scanner;

public class PreparedStatementLogin {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/epet?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";

Scanner scanner = new Scanner(System.in);
System.out.print("请输入用户名:");
String name = scanner.next();
System.out.print("请输入密码:");
String pwd = scanner.next();

Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;

try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(url, user, password);

// 预编译 SQL(使用占位符 ?)
String sql = "SELECT * FROM master WHERE name = ? AND password = ?";
pstmt = conn.prepareStatement(sql);

// 为占位符设置参数(索引从 1 开始)
pstmt.setString(1, name);
pstmt.setString(2, pwd);

// 执行查询
rs = pstmt.executeQuery();
if (rs.next()) {
System.out.println("登录成功!欢迎您," + name);
} else {
System.out.println("用户名或密码错误");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 释放资源
try {
if (rs != null) rs.close();
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
scanner.close();
}
}
}

执行批量插入示例

public class PreparedStatementBatch {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/epet?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "root";

Connection conn = null;
PreparedStatement pstmt = null;

try {
Class.forName("com.mysql.cj.jdbc.Driver");
conn = DriverManager.getConnection(url, user, password);
// 关闭自动提交,开启批量处理
conn.setAutoCommit(false);

String sql = "INSERT INTO dog (name, health, love, strain) VALUES (?, ?, ?, ?)";
pstmt = conn.prepareStatement(sql);

// 添加批量数据
pstmt.setString(1, "亚亚");
pstmt.setInt(2, 85);
pstmt.setInt(3, 30);
pstmt.setString(4, "拉布拉多");
pstmt.addBatch(); // 添加到批处理

pstmt.setString(1, "菲菲");
pstmt.setInt(2, 90);
pstmt.setInt(3, 25);
pstmt.setString(4, "拉布拉多");
pstmt.addBatch();

// 执行批量插入
int[] rows = pstmt.executeBatch();
System.out.println("批量插入成功,影响行数:" + rows.length);

// 提交事务
conn.commit();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
// 事务回滚
try {
if (conn != null) conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
} finally {
try {
if (pstmt != null) pstmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}

6. JDBC 注意事项

  1. 资源释放:必须关闭 ResultSet、Statement/PreparedStatement、Connection,顺序从后往前
  2. 异常处理:所有 JDBC 操作必须捕获 SQLException
  3. 事务管理:默认自动提交,批量操作时可手动关闭自动提交,执行后提交事务,异常时回滚
  4. 驱动版本:MySQL 5.x 和 8.x 驱动类路径不同,需对应数据库版本
  5. SQL 注入:优先使用 PreparedStatement 替代 Statement,避免直接拼接 SQL 字符串