2026年开学第一个学期,我开始了 Java 基础的学习。
Java与Csharp的语法结构十分相似,本文中 Java 使用的 IDE 是IntelliJ IDEA。
环境与第一个程序
创建一个项目,选择 Java 模块,不需要模板,自行选择路径与命名,不要出现中文。 项目创建完成后,在 src 目录下新建一个 软件包,软件包 内创建 类 文件,IDEA 会默认生成如下结构:
public class Main {
public static void main(String[] args) {
// 代码写在这里
}
}与 C# 类似,main 方法是程序的入口,代码从这里开始执行。 在 main 方法中输入以下代码,点击左侧绿色运行按钮或按 Shift + F10 运行:
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}控制台输出:
Hello, World!System.out.println() 是 Java 中最基础的输出语句,作用与 C# 的 Console.WriteLine() 相同,输出内容后自动换行。 System.out.print() 则不会自动换行,可以配合 \n(换行)、\t(制表符)手动控制格式。
注释与标识符
// 单行注释
/*
多行注释
*/
/**
* 文档注释
*/关键字:Java 中赋予了特定用途和含义的保留词,编辑器会高亮显示,如 public、class、int 等。
标识符(自己命名的词)需满足:
- 不能使用除
_、$以外的符号 - 不能以数字开头
- 不能使用关键字
- 区分大小写,建议见名知意
常见命名规范:
| 场景 | 规范 | 示例 |
|---|---|---|
| 包名 | 全小写,用 . 隔开 | com.example.util |
| 类名、接口名 | 大驼峰 | MyClass |
| 变量名、方法名 | 小驼峰 | myVariable |
| 常量名 | 全大写 | MAX_SIZE |
数据类型
Java 的数据类型分为基本数据类型和引用数据类型。
基本数据类型
// 整数类型
byte a = 127; // 字节
short b = 10000; // 短整型
int c = 1000000000; // 整型
long d = 1000000000000000000L; // 长整型,末尾加 L
// 浮点类型
float e = 1.111f; // 单精度,末尾加 f
double f = 1.222; // 双精度
// 字符类型
char g = '牢'; // 存单个字符,可通过 Unicode 编码转为 int
c = g;
System.out.println(c); // 输出对应 Unicode 数值
// 布尔类型
boolean h = true;
boolean i = 5 > 3; // 可以直接存逻辑运算的结果
System.out.println(i); // true引用数据类型
String j = "张三"; // 字符串
// 还有类类型、数组等数据类型转换
自动转换:小类型转大类型,直接赋值即可。
强制转换:大类型转小类型,需要手动指定,小数转整数只保留整数部分。
int i1 = 20;
byte i2 = 10;
i2 = (byte) i1; // 强制转换:大转小
System.out.println(i2); // 20变量
变量是内存中有类型、标识名、值的一块空间。
// 先声明后赋值
float chenji;
chenji = 98.5f;
// 声明同时赋值
float chenji = 98.5f;
String name = "张三";
char sex = '男';
System.out.println(chenji); // 98.5
System.out.println(name); // 张三
System.out.println(sex); // 男运算符
常用运算符一览
// 赋值运算符
int a = 10;
// 算数运算符:+ - * / % += -= ++ --
int c = a++; // 先把 a 的值赋给 c,再自增
System.out.println(a); // 11
System.out.println(c); // 10
// 关系运算符:> < >= <= != ==(结果都是布尔值)
// 逻辑运算符:&& || !
boolean f = 1 < 2 && 2 > 1; // true
boolean f1 = 2 == 3 || 1 == 1; // true
boolean f2 = !(1 < 2); // false优先级(从高到低):! > 算数 > 比较 > && > || > 赋值
小括号优先级最高,可用于调整执行顺序。
字符串比较
字符串不能用 == 比较内容,应使用 .equals() 方法:
String s1 = "aa";
String s2 = "aa";
boolean s3 = s1.equals(s2);
System.out.println("s3:" + s3); // s3:true键盘输入
Java 通过 Scanner 类从键盘读取用户输入:
import java.util.Scanner;
Scanner input = new Scanner(System.in);
// 在线运行时可以在输入框填写:111 张三 12.5
// 读取整数
System.out.println("请输入整数");
if (input.hasNextInt()) {
int k = input.nextInt();
System.out.println("整数:" + k);
} else {
System.out.println("没有读取到整数");
}
// 读取字符串
System.out.println("请输入姓名");
if (input.hasNext()) {
String name = input.next();
System.out.println("姓名:" + name);
} else {
System.out.println("没有读取到姓名");
}
// 读取浮点数(带输入校验)
System.out.println("请输入金额");
if (input.hasNextDouble()) {
double j = input.nextDouble();
System.out.println("金额:" + j);
} else {
System.out.println("没有读取到金额");
}选择结构
if 语句
// 单分支
if (true) {
System.out.println("成立时执行");
}
// 双分支
if (true) {
System.out.println("成立");
} else {
System.out.println("不成立");
}
// 多分支
int score = 90;
if (score >= 90) {
System.out.println("优秀");
} else if (score >= 80) {
System.out.println("良好");
} else if (score >= 70) {
System.out.println("中等");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}if 语句可以相互嵌套:
int s = 9;
int sex = 0; // 0:男,-1:女
if (s < 10) {
if (sex == 0) {
System.out.println("男子组");
} else {
System.out.println("女子组");
}
} else {
System.out.println("out");
}switch 语句
switch 的表达式需要是整型或可自动转为整型的数据类型(如 char)。
int i = 1;
switch (i) {
case 1:
System.out.println("周一");
break;
case 2:
System.out.println("周二");
break;
// ...
default:
System.out.println("无效的日期");
}循环结构
while 循环
先判断条件,再执行。
int a = 1;
while (a < 10) {
System.out.println(a);
a++;
}do-while 循环
先执行一次,再判断条件。
int a = 1;
do {
System.out.println(a);
a++;
} while (a < 10);for 循环
for (int a = 1; a < 10; a++) {
System.out.println(a);
}循环中可以使用 break 结束整个循环,continue 跳过当前次进入下一次。
类与对象
这部分是面向对象的起点,Java 里几乎所有东西都围着
class转。
什么是类和对象
类:一类事物的统称,是抽象的概念,由思维产生。如:人、学生、汽车。
对象:实际存在的某个事物,能看得见摸得着。如:张三、李四。
类是对象的抽象,对象是类的实例化。
定义一个类
Java 使用 class 关键字定义类,类中包含属性(静态特征)和方法(动态特征):
public class Dog {
// 静态特征:属性
String name; // 昵称
String strain; // 品种
double weight; // 体重
// 动态特征:方法
public void show() {
System.out.println("我的狗的名字是:" + name + " 品种是:" + strain + " 体重是:" + weight + "KG");
}
}创建对象
class Dog {
String name;
String strain;
double weight;
public void show() {
System.out.println("我的狗的名字是:" + name + " 品种是:" + strain + " 体重是:" + weight + "KG");
}
}
// 类名 对象名 = new 类名();
Dog dog = new Dog();
// 调用属性赋值
dog.name = "kobe";
dog.strain = "拉得不多";
dog.weight = 5.45;
// 调用方法
dog.show();方法的定义与返回值
public 返回值类型 方法名() {
// 方法体
return 表达式;
}- 返回值类型决定了
return后返回的值的类型,二者要匹配 - 没有返回值使用
void,不需要return语句 return是方法体的最后一条语句,只能返回一个值
public class Student {
String name;
double score;
public void study() {
System.out.println(name + "的语文成绩为" + score);
}
public double sum() {
double a = 350.00;
return a * 1.2; // 返回计算结果
}
}变量的作用域
全局变量(成员变量)
定义在类中,Java 会自动给初始值:String 为 null,整型为 0,浮点为 0.0,布尔为 false。在整个类中都可以使用。
局部变量
定义在方法或代码块中,Java 不会给初始值,只在定义该变量的方法或代码块中有效。
全局变量和局部变量可以重名,局部变量的调用优先级更高。
有参方法
public 返回值类型 方法名(参数1类型 参数1名, 参数2类型 参数2名) {
方法体;
}调用时传入的是实际参数,定义时括号里的是形式参数,两者的数量、数据类型、顺序要一一对应。
public class Cal {
// 计算两个数的和,有返回值
public int sum(int a, int b) {
int c = a + b;
return c;
}
// 计算两个数的差,无返回值
public void gap(int a, int b) {
int c = a - b;
System.out.println(c);
}
}
// 调用
Cal cal = new Cal();
int result = cal.sum(10, 20); // 传入实参
System.out.println(result);包
包名全部小写,多级用 . 隔开,例如 team.shiro.javaoop。包可以解决文件名冲突,方便管理类和接口。
// 声明包,写在第一行
package team.shiro;
// 导入包,写在 package 后
import java.util.*;数组
数组是什么
用来存储一组相同数据类型的数据。
特点:能存储多个值,所有值数据类型相同,操作方便。
相关概念:
- 标识符:数组的名称
- 数组元素:存在数组中的每个数据
- 数组下标:数据的位置编号,从
0开始 - 数据类型:数组可存放的数据类型
声明与使用
// 声明方式
int[] a;
int b[];
// 声明时指定大小
int[] c = new int[5];
// 声明时直接赋值
int[] d = {1, 2, 3, 4, 5};
// 分配空间
a = new int[5];
// 通过下标存取元素
a[0] = 10;
a[0] = a[0] + 10;数组遍历
数组遍历通过循环 + 下标实现,数组名.length 获取数组长度:
import java.util.Scanner;
Scanner input = new Scanner(System.in);
double[] arr = new double[5];
double[] defaults = {12.5, 36.0, 18.8, 7.5, 20.0};
double sum = 0.0;
for (int i = 0; i < arr.length; i++) {
System.out.println("请输入第 " + (i + 1) + " 笔金额");
if (input.hasNextDouble()) {
arr[i] = input.nextDouble();
} else {
arr[i] = defaults[i];
}
sum += arr[i];
}
System.out.println("序号\t\t金额");
for (int i = 0; i < arr.length; i++) {
System.out.println((i + 1) + "\t\t" + arr[i]);
}
System.out.println("总金额\t" + sum);数组排序
Java 提供了 Arrays 类,sort() 方法可对数组做升序排序:
import java.util.Arrays;
int[] a = {4, 8, 2, 9, 1, 6, 7, 3};
Arrays.sort(a);
// 排序后:[1, 2, 3, 4, 6, 7, 8, 9]最大值与最小值
将第一个值存入新变量,用循环逐个比较赋值:
int[] arr = {8, 4, 2, 1, 23, 344, 12};
int min = arr[0];
for (int i = 1; i < arr.length; i++) {
if (min > arr[i]) {
min = arr[i];
}
}
System.out.println(min); // 1二维数组与多维数组
二维数组可以理解为“数组里再放数组”,常用于表格、矩阵、成绩单这类行列结构:
int[][] scores = {
{90, 88, 95},
{76, 80, 72},
{100, 98}
};
// 外层循环控制行,内层循环控制每一行中的列
for (int i = 0; i < scores.length; i++) {
for (int j = 0; j < scores[i].length; j++) {
System.out.print(scores[i][j] + "\t");
}
System.out.println();
}需要注意:Java 的二维数组每一行长度可以不同,所以遍历列时应使用 scores[i].length,不要默认每一行都和第一行一样长。
三维数组就是在二维数组外再套一层,语法上可以继续嵌套:
int[][][] data = {
{
{1, 2, 3},
{4, 5, 6}
}
};
System.out.println(data[0][1][2]); // 6字符串操作
String 提供了一系列实用方法,StringBuffer 是它的增强版。
String str = "张三";
// length():计算字符串长度
System.out.println(str.length()); // 2
// equals():比较两个字符串的值(== 比较的是内存地址)
String s1 = new String("man");
String s2 = new String("man");
System.out.println(s1.equals(s2)); // true
System.out.println(s1 == s2); // false
// equalsIgnoreCase():忽略大小写比较
"ABC".equalsIgnoreCase("abc"); // true
// toLowerCase() / toUpperCase():转大小写
"ABc".toLowerCase(); // "abc"
"ABc".toUpperCase(); // "ABC"
// concat():连接字符串(也可以用 +)
"kobe".concat("24"); // "kobe24"
// indexOf():某字符第一次出现的位置
// lastIndexOf():某字符最后一次出现的位置
String s = "aubdiubaiucb";
System.out.println(s.indexOf("b")); // 1
System.out.println(s.lastIndexOf("b")); // 11
// substring(n, m):截取字符串,n 开始,m 结束(不含)
s.substring(3, 5); // "di"
// split():按指定字符拆分,返回数组
String s7 = "abc cde fgh ijk";
String[] arr = s7.split(" ");
// ["abc", "cde", "fgh", "ijk"]
// StringBuffer:String 的增强类,适合频繁修改字符串
StringBuffer sb = new StringBuffer("man");
System.out.println(sb); // man构造方法
是什么
方法名和类名一致,没有返回值(也不写 void)的方法。在 new 对象时自动调用,用于初始化对象。
// 语法
public 类名(参数) {
// 初始化逻辑
}特点
- 方法名与类名一致
- 没有返回值,没有
return语句 - 可以有参数
new对象时自动调用- Java 会默认生成一个无参构造方法,一旦自己写了构造方法,默认的就失效了,需要手动补回无参构造
public class Car {
String color;
double speed;
int num;
// 无参构造(自己写了有参构造后需要手动补上)
public Car() {
System.out.println("空构造");
}
// 有参构造
public Car(String color) {
this.color = color;
}
public Car(String color, double speed) {
this.color = color;
this.speed = speed;
}
public Car(String color, double speed, int num) {
this.color = color;
this.speed = speed;
this.num = num;
}
}this 关键字
this在方法中表示当前正在调用该方法的对象- 可以在方法内部访问对象的属性
- 可以调用当前类中的其他普通方法(直接调用或
this.方法名()效果相同) - 在构造方法中可以调用其他构造方法,但必须写在第一行:
this(参数列表)
public class Employee {
String id;
String name;
int age;
String phone;
double sal;
public void input() {
System.out.println("姓名:" + this.name + " 电话:" + this.phone + " 工资:" + this.sal);
}
public void show() {
input(); // 也可以写 this.input();
System.out.println("工号:" + this.id);
}
public Employee() {}
public Employee(String id) {
this.id = id;
}
public Employee(String id, String name) {
this(id); // 调用上面的单参构造,必须第一行
this.name = name;
}
public Employee(String id, String name, int age) {
this(id, name); // 链式调用构造方法
this.age = age;
}
public Employee(String id, String name, int age, String phone, double sal) {
this(id, name, age);
this.phone = phone;
this.sal = sal;
}
}方法重载
在一个类中,方法名相同但参数列表不同的多个方法称为重载。
特点:
- 同一个类中,多个方法名相同
- 参数列表不同(个数或数据类型不同)
- 与返回值类型和访问修饰符无关
- 构造方法和普通方法都可以重载
public class Cal {
// 重载:参数类型不同
public void sum(int a, int b) {
System.out.println(a + b);
}
public double sum(double a, double b) {
return a + b;
}
// 重载:参数个数不同
public void sum(int a, int b, int c) {
System.out.println(a + b + c);
}
}访问修饰符
Java 提供四种访问权限修饰符,控制类、属性、方法的可见范围:
| 修饰符 | 同一个类 | 同一个包 | 不同包的子类 | 不同包的其他类 |
|---|---|---|---|---|
public | ✓ | ✓ | ✓ | ✓ |
protected | ✓ | ✓ | ✓ | |
| 默认(不写) | ✓ | ✓ | ||
private | ✓ |
封装
把类的信息隐藏在类内部,通过公共接口对外提供访问,并在方法中对数据进行合法性校验。
步骤:
- 用
private私有化属性 - 提供
public的get/set方法
public class Student {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age >= 0 && age <= 150) {
this.age = age;
} else {
this.age = 0;
System.out.println("请重新输入");
}
}
}static 关键字
static 可以修饰属性、方法、代码块、内部类,被修饰后属于类本身,而不是某个对象。
public class Student {
static String school; // 静态属性,所有对象共享
int height; // 普通属性,每个对象独有
// 静态方法,通过类名直接调用
public static void show() {
// 静态方法中不能访问非静态属性或方法
System.out.println(school);
}
// 静态代码块,类加载时自动执行一次
static {
System.out.println("Student 类加载");
}
// 静态内部类
public static class Inner {
int a;
}
}
// 通过类名直接调用静态成员
Student.school = "北大";
Student.show();final 关键字
final 表示”不可改变”,可以修饰类、属性、方法:
- 修饰类:该类不能被继承(叶子类)
- 修饰属性:变为常量,声明时必须赋值
- 修饰方法:该方法不能被子类重写
public final class Teacher {
final int MAX = 100; // 常量,不可修改
final char SEX = '男';
public final void show() { // 不可被子类重写
System.out.println("老师");
}
}继承
基本语法
使用 extends 关键字让子类继承父类,子类会拥有父类所有非私有的属性和方法。Java 只支持单继承(一个类只能有一个父类),所有类都直接或间接继承自 Object。
// 父类
public class Pet {
String name;
int health;
public void eat() {
System.out.println(name + "在吃东西");
}
}
// 子类
public class Dog extends Pet {
String strain; // 子类特有属性
public void work() {
System.out.println(name + "能看大门"); // 继承自父类的属性
}
}
// 使用
Dog dog = new Dog();
dog.name = "kobe";
dog.health = 60;
dog.eat(); // 调用继承来的方法
dog.work(); // 调用子类自己的方法super 关键字
当子类与父类存在同名属性或方法时,可以用 super 明确访问父类的成员:
public class Dog extends Pet {
String name = "man"; // 子类同名属性
public void work() {
System.out.println(this.name); // 先找子类:man
System.out.println(super.name); // 找父类:kobe
}
}在子类构造方法中,可用 super(参数) 调用父类构造方法,必须写在第一行:
public class Employee extends Person {
double sal;
public Employee(String name, int age, double sal) {
super(name, age); // 调用父类构造方法,必须第一行
this.sal = sal;
}
public void work() {
super.show(); // 调用父类方法
System.out.println("工资:" + this.sal);
}
}方法重写
子类对父类方法的重新实现,让子类在继承父类结构的同时拥有自己的行为。
规则:
- 方法名、参数列表与父类一致
- 访问修饰符不能比父类更严格(可以更宽松)
- 方法体不同
public class Person {
public void run() {
System.out.println("人类会跑步");
}
}
public class Employee extends Person {
@Override
public void run() { // 重写父类方法
System.out.println("员工竞走");
}
}抽象类
是什么
用 abstract 修饰的类叫抽象类,用 abstract 修饰的方法叫抽象方法。
特点:
- 抽象方法只有声明,没有方法体
- 抽象方法必须定义在抽象类中
- 抽象类中可以有普通方法
- 抽象类不能被实例化(不能
new) - 子类继承抽象类,必须重写所有抽象方法,否则子类也要声明为抽象类
public abstract class Users {
// 抽象方法,没有方法体
public abstract void show();
// 普通方法,可以有实现
public void hello() {
System.out.println("你好");
}
}
// 子类必须实现所有抽象方法
public class BookUsers extends Users {
@Override
public void show() {
System.out.println("书籍用户");
}
}
// 使用
// Users u = new Users(); // 错误,抽象类不能实例化
BookUsers bu = new BookUsers();
bu.show();多态
是什么
同一个方法调用,因对象类型不同而产生不同的结果,就是多态。
多态建立在继承和方法重写的基础上,通过父类引用指向子类对象来实现。
向上转型(子类 → 父类)
class Pet {
public void eat() {
System.out.println("宠物吃东西");
}
public void run() {
System.out.println("宠物会跑");
}
}
class Dog extends Pet {
@Override
public void eat() {
System.out.println("狗吃东西");
}
public void work() {
System.out.println("狗能看大门");
}
}
// 父类引用 = 子类对象(自动转型)
Pet p = new Dog();
p.eat(); // 调用的是 Dog 重写后的 eat()
p.run();
// 通过父类引用调用不到子类特有的方法
// p.work(); // 错误,Pet 中没有 work()向下转型(父类 → 子类)
父类引用转回子类类型,需要强制转换,转换失败会抛出 ClassCastException:
class Pet {
}
class Dog extends Pet {
public void work() {
System.out.println("狗能看大门");
}
}
Pet p = new Dog();
// 强制转换,大转小
Dog d = (Dog) p;
d.work(); // 可以调用 Dog 的特有方法instanceof 运算符
在强制转换前,用 instanceof 判断对象的实际类型,保证代码健壮性:
class Pet {
}
class Dog extends Pet {
public void work() {
System.out.println("狗能看大门");
}
}
class Cat extends Pet {
public void play() {
System.out.println("猫会玩耍");
}
}
Pet p = new Dog();
if (p instanceof Dog) {
Dog dog = (Dog) p;
dog.work();
} else if (p instanceof Cat) {
Cat cat = (Cat) p;
cat.play();
}多态的三种体现
1. 父类引用指向子类对象
class Person {
public void show() {
System.out.println("Person");
}
}
class Student extends Person {
@Override
public void show() {
System.out.println("Student");
}
}
class Teacher extends Person {
@Override
public void show() {
System.out.println("Teacher");
}
}
Person p = new Student();
p.show(); // 执行 Student 的 show()
p = new Teacher();
p.show(); // 执行 Teacher 的 show()2. 父类类型作为方法参数
形参声明为父类类型,传入子类对象,方法内部自动调用对应的重写方法:
class Pet {
public void eat() {
System.out.println("宠物吃东西");
}
}
class Dog extends Pet {
@Override
public void eat() {
System.out.println("狗吃东西");
}
}
class Cat extends Pet {
@Override
public void eat() {
System.out.println("猫吃东西");
}
}
public class Master {
// 形参为父类,实参传子类对象
public void food(Pet pet) {
pet.eat();
}
}
Master ma = new Master();
ma.food(new Dog()); // 匿名对象
ma.food(new Cat());3. 父类类型作为返回值
方法声明返回父类类型,实际返回子类对象:
class Pet {
public void eat() {
System.out.println("宠物吃东西");
}
}
class Dog extends Pet {
@Override
public void eat() {
System.out.println("狗吃东西");
}
}
public class PetAgree {
public Pet buy() {
return new Dog(); // 返回子类对象
}
}
PetAgree pa = new PetAgree();
Pet pet = pa.buy();
pet.eat(); // 调用 Dog 的 eat()Object 数组
Object 是所有类的父类,所以 Object[] 可以保存不同类型的引用数据,也可以保存自动装箱后的基本类型:
Object[] arr = new Object[4];
arr[0] = "张三";
arr[1] = 18; // int 自动装箱为 Integer
arr[2] = 98.5;
arr[3] = new Dog();
for (Object item : arr) {
System.out.println(item);
}不过 Object[] 会丢失具体类型信息,取出后如果要调用子类特有方法,通常需要配合 instanceof 判断后再强制转换。
接口
是什么
接口是一种“规则”或“能力”的定义,只规定应该有什么方法,不关心具体怎么实现。类通过 implements 实现接口,实现后必须重写接口中的抽象方法,否则这个类也要声明为抽象类。
public interface USB {
void work();
}
public class MouseUSB implements USB {
@Override
public void work() {
System.out.println("鼠标开始工作");
}
}
public class DiskUSB implements USB {
@Override
public void work() {
System.out.println("U 盘开始工作");
}
}使用接口类型接收不同实现类对象,就能形成面向接口的多态:
USB usb = new MouseUSB();
usb.work();
usb = new DiskUSB();
usb.work();这里左边的 USB 决定能调用什么方法,右边的实际对象决定方法执行结果。
接口的特点
- 接口使用
interface定义 - 类使用
implements实现接口 - 一个类可以实现多个接口,用逗号隔开
- 接口不能直接
new对象 - 接口可以继承接口,并且支持多继承
- 接口中的抽象方法默认是
public abstract - 接口中的常量默认是
public static final
public interface SuperA {
void a();
}
public interface SuperB {
void b();
}
public interface SuperC {
void c();
}
public class ImpClass implements SuperA, SuperB, SuperC {
@Override
public void a() {
System.out.println("实现 a");
}
@Override
public void b() {
System.out.println("实现 b");
}
@Override
public void c() {
System.out.println("实现 c");
}
}接口表示能力
一个类实现某个接口,就代表它具备接口中规定的能力。比如业务员会讲业务,程序员会写程序,一个软件工程师可以同时具备这两种能力:
public interface Person {
void setName(String name);
String getName();
}
public interface BizAgent extends Person {
void speakWork();
}
public interface Programmer extends Person {
void writeWork();
}
public class SoftEngineer implements BizAgent, Programmer {
private String name;
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void speakWork() {
System.out.println("工程师可以讲解业务");
}
@Override
public void writeWork() {
System.out.println("工程师可以写程序");
}
}接口的意义不只是“多写几个方法”,更重要的是降低耦合。调用方只依赖接口,不依赖具体实现类,将来更换实现时,调用方代码通常不需要改。
接口作为方法参数
接口常用来定义协作规则。下面的打印机不关心具体纸张和墨盒是哪一个类,只要求它们遵守 Paper 和 InkBox 这两个接口:
public interface Paper {
String getPaperSize();
}
public interface InkBox {
String getInkBoxType();
}
public class A4Paper implements Paper {
@Override
public String getPaperSize() {
return "A4";
}
}
public class ColorBox implements InkBox {
@Override
public String getInkBoxType() {
return "彩色";
}
}
public class Print {
public void input(Paper paper, InkBox inkBox) {
System.out.println("使用" + paper.getPaperSize() + "大小的纸打印" + inkBox.getInkBoxType() + "色的内容");
}
}
Print printer = new Print();
Paper paper = new A4Paper();
InkBox inkBox = new ColorBox();
printer.input(paper, inkBox);这样以后要新增 A3Paper、BlackBox,只要实现接口即可,Print 类不需要重写。
异常处理
什么是异常
异常是程序运行过程中出现的不正常情况,例如输入类型不匹配、数组下标越界、空指针、整数除零等。异常如果不处理,程序可能会直接中断。
Scanner input = new Scanner(System.in);
System.out.println("请输入两个整数");
int a = input.nextInt();
int b = input.nextInt();
System.out.println(a / b); // b 为 0 时会出现 ArithmeticException注意:如果是 double 类型除以 0,Java 通常会得到 Infinity 或 NaN,不会像整数除零那样抛出 ArithmeticException。
try-catch-finally
把可能出错的代码放进 try,异常出现后由 catch 捕获处理,finally 中的代码一般用于收尾工作,无论是否发生异常都会尝试执行。
import java.util.InputMismatchException;
import java.util.Scanner;
Scanner input = new Scanner(System.in);
try {
System.out.println("请输入第一个整数");
int a = input.nextInt();
System.out.println("请输入第二个整数");
int b = input.nextInt();
System.out.println("商:" + a / b);
} catch (InputMismatchException e) {
System.out.println("输入的不是整数");
} catch (ArithmeticException e) {
System.out.println("除数不能为 0");
} catch (Exception e) {
System.out.println("出现未知错误:" + e.getMessage());
} finally {
input.close();
System.out.println("计算结束");
}多个 catch 同时存在时,应把更具体的异常写在前面,把父类异常 Exception 写在后面,否则前面的父类会先把所有异常都接住。
throws 与 throw
throws 写在方法声明后面,表示这个方法可能把异常交给调用者处理;throw 写在方法内部,用来主动抛出一个异常对象。
public static int divide(int a, int b) throws ArithmeticException {
if (b == 0) {
throw new ArithmeticException("除数不能为 0");
}
return a / b;
}
public static void main(String[] args) {
try {
System.out.println(divide(10, 0));
} catch (ArithmeticException e) {
System.out.println(e.getMessage());
}
}初学阶段更推荐先学会在合适的位置用 try-catch 处理异常,不要随意把异常一直向外抛。
自定义异常
自定义异常类通常继承 Exception 或 RuntimeException。继承 Exception 的异常属于受检异常,调用者必须处理或继续声明抛出。
public class MyException extends Exception {
public MyException() {
}
public MyException(String message) {
super(message);
}
}
public static void checkAge(int age) throws MyException {
if (age < 0 || age > 150) {
throw new MyException("年龄不合法");
}
}自定义异常的作用是把业务规则表达清楚。相比直接抛出普通 Exception,自定义异常能让调用者更准确地知道哪里出了问题。
finally、return 与 System.exit
正常情况下,即使 try 或 catch 中执行了 return,finally 仍然会先执行,然后方法才真正返回。但如果执行了 System.exit(0) 强制退出 JVM,后续代码和 finally 都不会再执行。
public static int test() {
try {
return 1;
} finally {
System.out.println("finally 仍然会执行");
}
}可以简单记为:System.exit() 会直接结束程序;finally 负责收尾;return 负责结束当前方法。
集合框架
是什么
Java 提供的一套现成的接口和类,可以直接用来存储、操作一组数据对象。与数组相比:
| 对比项 | 数组 | 集合 |
|---|---|---|
| 长度 | 固定 | 可变 |
| 存储内容 | 同一类型(常见基本类型) | 多种类型的对象 |
| 内存 | 连续空间 | 不一定连续 |
集合分类
Java 集合分为两大体系:
Collection(单列集合) Map(双列集合,键值对)
├── List(有序可重复) ├── HashMap(哈希表)
│ ├── ArrayList └── TreeMap(红黑树,有序)
│ └── LinkedList
└── Set(无序不重复)
├── HashSet
└── TreeSet(红黑树,有序)Collection 常用方法
这些方法 List 和 Set 都能用:
| 方法签名 | 说明 |
|---|---|
boolean add(Object o) | 向集合末尾添加一个对象 |
void add(int i, Object o) | 在指定位置 i 插入对象 o(List) |
int size() | 获取集合中元素个数 |
Object get(int i) | 获取 i 位置的对象(List) |
boolean contains(Object o) | 判断集合中是否存在某元素 |
boolean remove(Object o) | 删除集合中的某个对象 |
Object remove(int i) | 删除 i 位置的对象,返回被删除的 |
ArrayList
底层是数组,查询快,增删慢。最常用的 List 实现类。
import java.util.ArrayList;
import java.util.List;
Dog d1 = new Dog("小黑", "白色");
Dog d2 = new Dog("小白", "黑色");
Dog d3 = new Dog("小花", "黄色");
// 使用泛型指定元素类型,省去强制转换
List<Dog> list = new ArrayList<>();
list.add(d1);
list.add(d2);
list.add(1, d3); // 在索引 1 处插入,后面的元素自动后移
// 普通 for 循环
for (int i = 0; i < list.size(); i++) {
Dog dog = list.get(i);
System.out.println(dog.getName());
}
// 增强 for 循环(forEach)
for (Dog dog : list) {
System.out.println(dog.getName());
}
// contains & remove
System.out.println(list.contains(d2)); // true
list.remove(d2); // 删除指定对象
Dog removed = list.remove(0); // 删除索引 0,返回被删除的对象不指定泛型时,
get()返回Object,需要手动强转为Dog;指定泛型后可以直接用Dog接收,更安全。
LinkedList
底层是双向链表,增删快,查询慢。接口同 List,但提供了额外的头尾操作方法:
import java.util.LinkedList;
import java.util.List;
List<Dog> list = new LinkedList<>();
list.add(d1);
list.add(d2);
// LinkedList 独有方法,需要声明为 LinkedList 类型才能调用
LinkedList<Dog> l = new LinkedList<>();
l.add(d1);
l.add(d2);
l.add(d3);
l.addFirst(d1); // 插入到头部
l.addLast(d3); // 插入到尾部
System.out.println(l.getFirst().getName()); // 获取第一个元素
System.out.println(l.getLast().getName()); // 获取最后一个元素HashSet
无序、不重复。Set 没有索引,不能用普通 for 循环,只能用 forEach 或迭代器遍历:
import java.util.HashSet;
import java.util.Set;
Set<Dog> set = new HashSet<>();
set.add(d1);
set.add(d2);
set.add(d3);
System.out.println(set.size()); // 3
for (Dog dog : set) {
System.out.println(dog.getName()); // 顺序不固定
}TreeSet
有序、不重复。底层是红黑树,元素按自然顺序(数字升序、字母字典序)自动排列,不需要手动排序:
import java.util.Set;
import java.util.TreeSet;
Set<Integer> set = new TreeSet<>();
set.add(5);
set.add(1);
set.add(3);
set.add(2);
set.add(4);
for (int n : set) {
System.out.print(n + " "); // 1 2 3 4 5(自动升序)
}字符串同理,按字典序排列:
Set<String> names = new TreeSet<>();
names.add("张三");
names.add("李四");
names.add("王五");
for (String name : names) {
System.out.println(name); // 按字典序输出
}与 HashSet 的区别:
| 对比项 | HashSet | TreeSet |
|---|---|---|
| 底层结构 | 哈希表 | 红黑树 |
| 顺序 | 无序 | 自动按自然顺序排序 |
| 性能 | 增删查更快 | 略慢,但有序 |
需要去重且不关心顺序用
HashSet;需要去重同时保持有序用TreeSet。
迭代器 Iterator
Iterator 提供统一的遍历方式,适用于 List、Set,取出 Map 的键也用到它:
import java.util.Iterator;
// 遍历 List
List<Dog> list = new ArrayList<>();
list.add(d1);
list.add(d2);
list.add(d3);
Iterator<Dog> it = list.iterator();
while (it.hasNext()) { // 是否还有下一个元素
Dog dog = it.next(); // 取出下一个元素
System.out.println(dog.getName());
}Map / HashMap
Map 以键值对方式存储,键唯一,值可重复,通过键获取值:
| 方法签名 | 说明 |
|---|---|
V put(K key, V value) | 添加键值对,键已存在则覆盖原值 |
V get(K key) | 根据键获取值,不存在返回 null |
V remove(K key) | 删除指定键的键值对,返回被删除的值 |
boolean containsKey(K key) | 判断是否包含指定键 |
int size() | 获取键值对的数量 |
Set<K> keySet() | 获取所有键组成的 Set |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对组成的 Set |
import java.util.HashMap;
import java.util.Map;
Map<Integer, String> map = new HashMap<>();
map.put(100000, "北京市");
map.put(200000, "上海市");
map.put(510000, "广州市");
System.out.println(map.get(100000)); // 北京市
System.out.println(map.containsKey(200000)); // true
System.out.println(map.size()); // 3遍历方式一:keySet + get(先取键,再通过键取值)
for (String key : map.keySet()) {
System.out.println(key + " -> " + map.get(key));
}遍历方式二:entrySet + Map.Entry(一次取出键值对,更推荐)
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.println("邮政编码:" + entry.getKey() + ", 地区:" + entry.getValue());
}Map.Entry 代表 Map 中的一个键值对,getKey() 取键,getValue() 取值。entrySet() 方式比 keySet() 少一次 get() 查找,遍历时性能更好,写法也更直接。
JDBC
是什么
JDBC(Java Database Connectivity)是 Java 提供的一套操作数据库的 API,通过它可以用 Java 代码执行 SQL 语句,实现对数据库的增删改查。
使用 JDBC 前需要引入数据库驱动 jar 包(以 MySQL 为例:mysql-connector-java-xxx.jar),将 jar 包添加到项目依赖中。
操作步骤(7步)
1. 加载驱动
2. 创建连接
3. 编写 SQL 语句
4. 预编译 SQL
5. 执行 SQL
6. 处理结果
7. 关闭资源增(INSERT)
import com.mysql.jdbc.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
// 1. 加载驱动
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// 2. 创建连接
Connection con = null;
try {
con = (Connection) DriverManager.getConnection(
"jdbc:mysql://localhost:3306/数据库名", "root", "123456");
} catch (SQLException e) {
e.printStackTrace();
}
// 3. 编写 SQL(字符串变量可以用 + 拼接,但推荐用占位符 ?)
String sql = "insert into student(id, name, age) values(1, '张三', 18)";
// 4. 预编译
PreparedStatement ps = null;
try {
ps = con.prepareStatement(sql);
} catch (SQLException e) {
e.printStackTrace();
}
// 5. 执行(增删改用 executeUpdate,返回受影响行数)
int rows = 0;
try {
rows = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
// 6. 处理结果
if (rows > 0) {
System.out.println("添加成功");
}
// 7. 关闭资源(先开后关)
try { ps.close(); } catch (SQLException e) { e.printStackTrace(); }
try { con.close(); } catch (SQLException e) { e.printStackTrace(); }删(DELETE)/ 改(UPDATE)
步骤与增数据完全相同,只是 SQL 语句不同:
// 删除
String sql = "delete from student where name = '张三'";
// 修改
String sql = "update student set name = '李四' where id = 1";查(SELECT)
查询需要用 executeQuery() 并通过 ResultSet 遍历结果集:
import java.sql.ResultSet;
// 执行查询
String sql = "select * from student";
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
// 遍历结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.println(id + " - " + name + " - " + age);
}
// 关闭资源(rs 也要关,顺序:rs → ps → con)
rs.close();
ps.close();
con.close();占位符 ?
SQL 中用 ? 代替具体值,再用 setXxx() 方法赋值,可以避免 SQL 注入,也让代码更清晰:
// 占位符从 1 开始计数
String sql = "insert into student values(?, ?, ?)";
PreparedStatement ps = con.prepareStatement(sql);
ps.setInt(1, 3); // 第1个 ? 设置为整数 3
ps.setString(2, "王五"); // 第2个 ? 设置为字符串
ps.setInt(3, 19); // 第3个 ? 设置为整数 19
ps.executeUpdate();条件查询也可以用占位符:
String sql = "select * from student where id > ?";
PreparedStatement ps = con.prepareStatement(sql);
ps.setInt(1, 5);
ResultSet rs = ps.executeQuery();BaseDao 模式
实际开发中,加载驱动、创建连接、关闭资源这几步在每个操作里都一样,可以提取到父类 BaseDao 中复用:
import com.mysql.jdbc.Connection;
import java.sql.*;
public class BaseDao {
protected Connection con = null;
protected PreparedStatement ps = null;
protected ResultSet rs = null;
public void openCon() {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try {
con = (Connection) DriverManager.getConnection(
"jdbc:mysql://localhost:3306/数据库名", "root", "123456");
} catch (SQLException e) {
e.printStackTrace();
}
}
public void closeAll() {
try {
if (rs != null) rs.close();
if (ps != null) ps.close();
if (con != null) con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}业务类继承 BaseDao,直接调用 openCon() 和 closeAll():
public class EmpCRUD extends BaseDao {
public void addEmp() {
try {
openCon();
String sql = "insert into employee(e_name, e_age, e_loc) values(?, ?, ?)";
ps = con.prepareStatement(sql);
ps.setString(1, "高八");
ps.setInt(2, 21);
ps.setString(3, "四川");
int rows = ps.executeUpdate();
if (rows > 0) System.out.println("添加成功");
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll(); // 无论成功与否都关闭
}
}
public void findAll() {
try {
openCon();
String sql = "select * from employee";
ps = con.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
System.out.println(
rs.getInt("e_no") + " - " +
rs.getString("e_name") + " - " +
rs.getInt("e_age") + " - " +
rs.getString("e_loc")
);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll();
}
}
}查询结果的处理规则
- 根据主键或唯一键查询 → 结果只有一条 → 封装进一个对象返回
- 查询结果多行 → 每行封装成一个对象 → 所有对象放入
List返回
// 按 id 查单条,返回 Employee 对象
public Employee findById(int id) {
openCon();
Employee emp = null;
try {
String sql = "select * from employee where e_no = ?";
ps = con.prepareStatement(sql);
ps.setInt(1, id);
rs = ps.executeQuery();
if (rs.next()) {
emp = new Employee();
emp.setE_no(rs.getInt("e_no"));
emp.setE_name(rs.getString("e_name"));
emp.setE_age(rs.getInt("e_age"));
emp.setE_loc(rs.getString("e_loc"));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll();
}
return emp;
}
// 查多条,返回 List
public List<Employee> findByAge(int minAge) {
List<Employee> list = new ArrayList<>();
try {
openCon();
String sql = "select * from employee where e_age > ?";
ps = con.prepareStatement(sql);
ps.setInt(1, minAge);
rs = ps.executeQuery();
while (rs.next()) {
Employee emp = new Employee();
emp.setE_no(rs.getInt("e_no"));
emp.setE_name(rs.getString("e_name"));
emp.setE_age(rs.getInt("e_age"));
emp.setE_loc(rs.getString("e_loc"));
list.add(emp);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll();
}
return list;
}模糊查询与分页
LIKE 用于模糊匹配,LIMIT 用于分页,两者配合占位符使用:
// 查询姓"王"的员工,取前 5 条
String sql = "select * from employee where e_name like ? limit ?, ?";
ps = con.prepareStatement(sql);
ps.setString(1, "王%"); // % 表示任意字符
ps.setInt(2, 0); // 从第 0 条开始(offset)
ps.setInt(3, 5); // 取 5 条(count)
rs = ps.executeQuery();LIMIT offset, count:offset 是跳过的行数,count 是取几条。第一页 offset=0,第二页 offset=5(每页5条),以此类推。
分层开发(MVC 三层架构)
为什么要分层
前面的 JDBC 把“连接数据库、拼 SQL、业务判断、界面输出”全堆在一个类里,类一多、表一多就很难维护。分层就是按职责把代码拆开,每一层只干自己的事。
一个标准的三层结构(再加一个实体层)如下:
| 层 | 包名(示例) | 职责 |
|---|---|---|
| 实体层 entity | com.xxx.entity | 和数据库表一一对应的类,一个表一个类,只放属性和 get/set |
| 数据访问层 dao | com.xxx.dao | 只和数据库打交道,执行 SQL,做增删改查 |
| 业务逻辑层 service | com.xxx.service | 处理业务规则,调用 dao,向上返回布尔值或对象 |
| 视图层 view/test | com.xxx.test | 和用户交互:打印菜单、读取输入、展示结果 |
调用方向是单向的:view → service → dao → 数据库,下层不会反过来调用上层。dao 和 service 通常各自再分出一个 impl 子包,接口放外层、实现类放 impl,做到“面向接口编程”。
com.kxg.oop
├── entity 实体层
│ ├── Employee.java
│ └── Department.java
├── dao 数据访问层
│ ├── EmployeeDao.java 接口
│ ├── BaseDao.java 连接工具类
│ └── impl
│ └── EmployeeDaoImpl.java 实现
├── service 业务逻辑层
│ ├── EmployeeService.java 接口
│ └── impl
│ └── EmployeeServiceImpl.java 实现
└── test 视图层(入口)
└── EmployeeTest.java实体层 entity
一个表对应一个类,表的每个字段就是类的一个私有属性,再配上 get/set 和构造方法。
public class Department {
private int depno; // 部门编号
private String depname; // 部门名称
private String deploc; // 部门地址
public Department() {}
public Department(int depno, String depname, String deploc) {
this.depno = depno;
this.depname = depname;
this.deploc = deploc;
}
// 省略 get/set...
}外键怎么表示:员工表里的 dep_no 是指向部门表的外键。实体里不要只存一个 int,而是存一个部门对象,把外键编号放进这个对象里。这样以后想拿部门名称、地址,都能顺着这个对象取到。
public class Employee {
private int eno;
private String ename;
private int eage;
private String eloc;
// 外键字段写成主表的对象,对象里再包含外键引用的编号
private Department department;
// 省略构造和 get/set...
}数据访问层 dao
dao 层只负责数据库操作。先用接口约定“有哪些方法”,再写实现类去落实。
BaseDao:和 JDBC 章节一样,把建连接、关资源提到父类里复用。con、ps、rs 用 protected 修饰,子类(各个 DaoImpl)才能直接拿来用;连接参数也可以提成成员变量,看起来更清楚:
public class BaseDao {
// protected:本包或子类中都能直接使用
protected Connection con = null;
protected PreparedStatement ps = null;
protected ResultSet rs = null;
String driver = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://127.0.0.1:3306/oop230501";
String user = "root";
String pwd = "123456";
public void openCon() {
try {
Class.forName(driver);
con = DriverManager.getConnection(url, user, pwd);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void closeAll() {
try {
if (rs != null) rs.close();
if (ps != null) ps.close();
if (con != null) con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}Dao 接口:只声明方法,不写实现。增删改返回 int(受影响的行数),查询返回 List:
public interface EmployeeDao {
int insert(Employee employee); // 增
int delete(Employee employee); // 删
int update(Employee employee); // 改
List<Employee> selectAll(); // 查所有
List<Employee> selectByNo(Employee employee); // 按编号查
List<Employee> selectByAge(int a, int b); // 按年龄区间查
}Dao 实现类:extends BaseDao implements EmployeeDao,复用父类的连接方法,逐个实现接口里的方法。注意外键的存取——存的时候从对象里取出编号,查的时候把编号塞回对象:
public class EmployeeDaoImpl extends BaseDao implements EmployeeDao {
public int insert(Employee employee) {
openCon();
int i = 0;
try {
String sql = "insert into employee values(?,?,?,?,?)";
ps = con.prepareStatement(sql);
ps.setInt(1, employee.getEno());
ps.setString(2, employee.getEname());
ps.setInt(3, employee.getEage());
ps.setString(4, employee.getEloc());
// 外键:先拿到部门对象,再从对象里取出部门编号
ps.setInt(5, employee.getDepartment().getDepno());
i = ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll();
}
return i;
}
@Override
public List<Employee> selectAll() {
openCon();
List<Employee> list = new ArrayList<>();
try {
String sql = "select * from employee";
ps = con.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
Employee e = new Employee();
e.setEno(rs.getInt("e_no"));
e.setEname(rs.getString("e_name"));
e.setEage(rs.getInt("e_age"));
e.setEloc(rs.getString("e_loc"));
// 结果表里的 dep_no 是外键:
// 先存进部门对象,再把部门对象存进员工对象
Department d = new Department();
d.setDepno(rs.getInt("dep_no"));
e.setDepartment(d);
list.add(e);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeAll();
}
return list;
}
// delete / update / selectByNo / selectByAge 同理
}按年龄区间查询用的是 SQL 的
between ? and ?:select * from employee where e_age between ? and ?。
业务逻辑层 service
service 同样是接口 + 实现。实现类里持有一个 dao 对象,并且私有化——上层调用下层,这个 dao 只在本类内部用,不对外暴露。
增删改的 dao 返回的是受影响行数(int),service 把它转成更好用的 boolean:大于 0 就说明操作成功。
public class EmployeeServiceImpl implements EmployeeService {
// 上层调下层,私有化,只在本类使用
private EmployeeDao employeeDao = new EmployeeDaoImpl();
public boolean add(Employee employee) {
// 受影响行数 > 0 即添加成功,直接返回比较结果
return employeeDao.insert(employee) > 0;
}
@Override
public boolean delete(Employee employee) {
return employeeDao.delete(employee) > 0;
}
@Override
public List<Employee> selectAll() {
return employeeDao.selectAll();
}
// update / selectByNo / selectByAge 同理
}return employeeDao.insert(employee) > 0; 是下面这段的简写,效果完全一样:
int i = employeeDao.insert(employee);
boolean flag = false;
if (i > 0) {
flag = true;
}
return flag;视图层 / 测试类
视图层是程序入口,负责和用户打交道:循环打印菜单,根据用户的选择调用对应的 service 方法。这里不直接碰 dao,也不写 SQL。
public class DeptTest {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
DepartmentService ds = new DepartmentServiceImpl();
int choice = -1;
while (choice != 0) {
System.out.println("=====部门管理系统=====");
System.out.println("1. 查询所有部门");
System.out.println("2. 根据编号查询部门");
System.out.println("3. 添加部门");
System.out.println("4. 修改部门");
System.out.println("5. 删除部门");
System.out.println("0. 退出");
System.out.print("请选择:");
choice = input.nextInt();
switch (choice) {
case 1:
List<Department> all = ds.findAll();
System.out.println("编号\t名称\t位置");
for (Department dept : all) {
System.out.println(dept.getDepno() + "\t" + dept.getDepname() + "\t" + dept.getDeploc());
}
break;
case 3:
System.out.print("请输入部门编号:");
int ano = input.nextInt();
System.out.print("请输入部门名称:");
String aname = input.next();
System.out.print("请输入部门地址:");
String aloc = input.next();
Department adept = new Department(ano, aname, aloc);
System.out.println(ds.add(adept) ? "添加成功" : "添加失败");
break;
// case 2/4/5 略,结构相同
case 0:
System.out.println("退出系统");
break;
default:
System.out.println("无效选项,请重新选择");
}
}
input.close();
}
}串一遍:添加一个部门
以“添加部门”为例,一次完整的调用会穿过三层:
- view:读取用户输入,封装成一个
Department对象,调用ds.add(adept) - service:
add()调用dao.insert(dept),拿到受影响行数后返回行数 > 0 - dao:
insert()打开连接、执行insertSQL、关闭资源,返回受影响行数 - 结果一路返回到 view,view 根据
boolean打印“添加成功 / 失败”
分层的好处就在这里:哪天要换数据库,只动 dao;要改业务规则,只动 service;要换界面(比如做成网页),只动 view,互不影响。
日期类型的处理
学生表里有“出生日期”这种日期字段,而 Java 里和日期相关的 Date 有两个,混用很容易踩坑。
java.util.Date 与 java.sql.Date
| 对比项 | java.util.Date | java.sql.Date |
|---|---|---|
| 用途 | 普通 Java 代码里的日期时间 | JDBC 专用,对应数据库的 date 字段 |
| 关系 | 父类 | 子类(只保留年月日,丢掉时分秒) |
约定:实体类里的日期属性用 java.util.Date,到了 dao 操作数据库时再转成 java.sql.Date。
import java.util.Date;
public class Student {
private int stu_no;
private String stu_name;
private Date stu_born; // 出生日期,用 java.util.Date
// 省略其它字段和 get/set...
}存:util.Date → sql.Date
PreparedStatement 的 setDate() 只认 java.sql.Date,所以存之前要做一次转换:用实体里 util.Date 的 getTime() 拿到毫秒数,再用这个毫秒数 new 一个 sql.Date。
import java.sql.Date; // 注意这里 import 的是 java.sql.Date
// student.getStu_born() 是 java.util.Date
// getTime() 取出毫秒值,再构造成 java.sql.Date
ps.setDate(8, new Date(student.getStu_born().getTime()));因为实体用
java.util.Date、这里又要java.sql.Date,两个同名类不能同时import。一般import java.sql.Date,另一个用全限定名java.util.Date区分。
取:rs.getDate
查询时 rs.getDate("stu_born") 返回的是 java.sql.Date。由于它本身就是 java.util.Date 的子类(向上转型),可以直接塞回实体的 util.Date 属性里:
s.setStu_born(rs.getDate("stu_born")); // sql.Date 自动当作 util.Date 使用字符串 → 日期:SimpleDateFormat
用户从键盘输入的生日是字符串(如 2008-05-01),需要用 SimpleDateFormat 按指定格式把它解析成 Date。解析过程可能因为格式不对抛出 ParseException,要用 try-catch 包住:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
System.out.print("请输入出生日期(yyyy-MM-dd):");
String bornStr = input.next();
Date born = null;
try {
// 把字符串按 yyyy-MM-dd 格式解析成 java.util.Date
born = new SimpleDateFormat("yyyy-MM-dd").parse(bornStr);
} catch (ParseException e) {
e.printStackTrace();
}
student.setStu_born(born);格式字母:yyyy 年、MM 月、dd 日(还有 HH 时、mm 分、ss 秒)。反过来,format() 方法可以把一个 Date 转成指定格式的字符串:
String text = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
// 例如:2026-06-10一句话理清这条链路:键盘输入字符串 →(SimpleDateFormat.parse)→ util.Date 存进实体 →(getTime() 转 sql.Date)→ 存入数据库;查询时 rs.getDate 取出 sql.Date,当作 util.Date 放回实体即可。