Java基础复习总结

Java复习总结

Java编程基础

2.1.3 java标识符

规则:

  • 标识符只能由数字、字母(包括中文)、下划线_、美元符号$组成,不能含有其它符号。
  • 标识符不能以数字开头
  • 关键字不能做标识符。
    • 例如:public class static void 这些都是关键字,关键字不能做标识符的。
  • 标识符严格区分大小写。大写A和小写a不一样。
  • 规标识符理论上是没有长度限制的。

2.1.5 Java中的常量

在Java中,final修饰的实例变量一般和static联合使用,称为常量。
例如:

public static final double PI = 3.1415926;

在C语言中,我们可以使用#define预处理指令来定义常量。

#define PI 3.14159

2.2.3 变量的类型转换

1 小容量可以直接赋值给大容量,称为自动类型转换。

  • 容量从小到大的排序为:
  • byte < short(char) < int < long < float < double,
  • 其中 short和 char 都占用两个字节,但是char 可以表示更大的正整数;

2 大容量不能直接赋值给小容量,需要使用强制类型转换符进行强转。

  • 大容量转换成小容量,要想编译通过,必须加强制类型转换符,进行强制类型转换。
底层是怎么进行强制类型转换的呢?
long类型100L:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 01100100
以上的long类型100L强转为int类型,会自动将“前面”的4个字节砍掉:
00000000 00000000 00000000 01100100

需要注意的是:加强制类型转换符之后,虽然编译可以通过,但是运行的时候可能会损失精度。

2.4.3 switch语句

在c语言中,switch里只能判断字符和整型数,Java可以额外判断字符串

2.5.5 跳转语句(break、continue)

break用于跳出本循环语句,执行循环后面的代码

continue用于跳出本次循环,执行下一次循环

2.6 方法

2.6.2 方法的重载

  • 什么时候代码会发生方法重载?
    • 在同一个类当中,方法名相同,参数列表不同
      • 参数列表不同具体指:参数的个数不同,参数的类型不同,参数的顺序不同

只要同时满足以上条件,那么我们可以认定方法和方法之间发生了重载机制。

2.7 数组

相关实验:

猜数字游戏

案例2-7 抽取幸运观众

关于C与Java的不同(总结)

1. 无符号数据类型

  • Java:

    • Java没有无符号的 byteshortintlong数据类型,这一点与C语言有很大的不同。因此,在Java中声明 unsigned int m是错误的。
    unsigned int m = 10;// Java 不支持无符号整型,这是错误的:
    
  • C:

    • C语言支持无符号数据类型。
    unsigned int m = 10;// C 支持无符号整型
    

2. 数组声明

  • Java:

    • 与C语言不同,Java不允许在声明数组时在方括号内指定数组的大小。例如,int a[12];在Java中是非法的。
    int[] a;// Java 中不能在声明数组时指定大小
    a = new int[12]; // 正确的方式
    
  • C:

    • C语言允许在声明数组时在方括号内指定数组的大小。
    int a[12];// C 中可以在声明数组时指定大小
    

3. 数组大小

  • Java:

    • 与C语言不同,Java允许使用 int型变量来指定数组的大小。例如:
    int size = 10;
    double[] number = new double[size];
    
  • C:

    • C语言也允许使用 int型变量来指定数组的大小,但必须在数组声明之前赋值。
    int size = 10;
    double number[size]; // C99 及以上版本支持变长数组(VLA),在标准C中不支持
    

4. 不规则二维数组

  • Java:

    • 与C语言不同,Java允许二维数组的列数可不相同,即Java支持不规则的二维数组。
    int[][] irregularArray = new int[3][];//这里没有定义列,这在Java中可行
    irregularArray[0] = new int[2];
    irregularArray[1] = new int[3];
    irregularArray[2] = new int[4];
    
  • C:

    • C语言的二维数组要求所有行的列数相同,不支持不规则的二维数组。
    int regularArray[3][4]; // C 中的二维数组要求所有行的列数相同
    

这些示例展示了Java和C语言在无符号数据类型、数组声明和使用方面的主要区别。

面向对象

3.1 面向对象的思想

面向对象编程(OOP)包括三大特征:

1. 封装

  • 解释: 封装是将对象的状态(属性)和行为(方法)结合在一起,并隐藏对象的内部实现细节,只暴露必要的接口。这种特性提高了代码的可维护性和安全性。

  • 示例:

    public class Person {
        private String name; // 私有属性,外部不可直接访问
    
        public String getName() { // 公有方法,用于访问私有属性
            return name;
        }
    
        public void setName(String name) { // 公有方法,用于修改私有属性
            this.name = name;
        }
    }
    

2. 继承

  • 解释: 继承是创建新类时可以基于已有类的定义,继承其属性和方法,从而实现代码重用和扩展。子类可以增加新的属性和方法,也可以重写父类的方法。

  • 示例:

    public class Animal {
        public void eat() {
            System.out.println("This animal eats food.");
        }
    }
    
    public class Dog extends Animal {
        public void bark() {
            System.out.println("The dog barks.");
        }
    }
    

3. 多态

  • 解释: 多态是指同一接口可以有不同的实现方式。多态分为编译时多态(方法重载)和运行时多态(方法重写)。多态使得程序在处理不同对象时具有灵活性。

  • 示例:

    public class Animal {
        public void sound() {
            System.out.println("动物发出叫声.");
        }
    }
    
    public class Dog extends Animal {
        @Override
        public void sound() {
            System.out.println("狗吠");
        }
    }
    
    public class Cat extends Animal {
        @Override
        public void sound() {
            System.out.println("猫“喵”");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Animal myDog = new Dog();
            Animal myCat = new Cat();
    
            myDog.sound(); // 输出:狗吠
            myCat.sound(); // 输出:猫“喵”
        }
    }
    

3.2.4 访问控制

Java提供的四种访问控制权限如下:

访问控制修饰符 本类 同包 不同包子类 任意位置
public(公共) 可以 可以 可以 可以
protected(受保护) 可以 可以 可以 不行
default(默认) 可以 可以 不行 不行
private(私有的) 可以 不行 不行 不行

范围从大到小排序:public > protected > default > private

3.3 封装性

封装的代码实现两步:

第一步:属性私有化

第二步:为每个属性提供两个方法:set方法和get方法

  • 外部程序只能通过set方法修改属性值,只能通过get方法读取属性值。
  • 可以在set方法中设立检查机制来保证数据的安全性。

重要说明:

  • set方法和get方法都是实例方法,不能带 static
  • 不带 static的方法称为实例方法,调用实例方法必须先创建对象(使用 new关键字)。

3.4 构造方法

什么是构造方法,有什么用?

  • 构造方法是一个比较特殊的方法,用于完成对象的创建以及实例变量的初始化。换句话说,构造方法是用来创建对象并且同时给对象的属性赋值。
  • 当一个类没有提供任何构造方法时,系统会默认提供一个无参数的构造方法,这个方法不会显示出来,但他存在。

构造方法的调用

  • 使用**new**运算符来调用构造方法。

  • 注意:

    1. 构造方法名和类名必须一致
    2. 构造方法不能(也不需要)指定返回值类型,只能返回0(return 0)作为函数结束标志;

构造方法的重载

  • 和普通方法一样,方法输入参数不同即可

3.5 this关键字

3.5.1. 什么是 this关键字?

  • this是一个引用,指向当前对象
  • 在类的方法和构造方法中,this指向调用该方法的对象。

3.5.2. this关键字的用途

用于区分实例变量和局部变量
  • 当实例变量和局部变量同名时,使用 this来区分它们。

    public class Person {
        private String name;
    
        public void setName(String name) {
            this.name = name; // this.name指的是实例变量,name指的是参数
        }
    }
    
用于调用类的其他构造方法
  • 在一个构造方法中调用另一个构造方法,可以避免重复代码。

  • 调用时必须是方法中的第一句

    public class Person {
        private String name;
        private int age;
    
        public Person() {
            this("Unknown", 0); // 调用另一个构造方法,且在方法的第一句
        }
    
        public Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
返回当前对象
  • 方法可以通过返回 this来实现方法链。

    public class Person {
        private String name;
    
        public Person setName(String name) {
            this.name = name;
            return this; // 返回当前对象
        }
    
        public void printName() {
            System.out.println(this.name);
        }
    }
    
    // 使用方法
    Person person = new Person();
    person.setName("John").printName();
    

3.5.3 注意事项

  • this不能在静态方法中使用,因为静态方法属于类,不属于对象。
  • this只能在实例方法和构造方法中使用。

4.1 类的继承

4.1.1什么是继承?

  • 继承是一种面向对象编程的特性,允许子类继承父类的属性和方法。
  • 继承使得代码可以重用,子类可以扩展和修改父类的行为。

继承的限制

  • Java只支持单继承,即一个类只能继承一个直接父类。
  • 但是,一个类可以实现多个接口,这被称为多实现

继承的优点

  • 提高代码的复用性,可维护性。
  • 提供了多态的实现基础。

继承的语法:

  • 使用 extends关键字来实现继承。

    public class ParentClass {
        // 父类的属性和方法
    }
    
    public class ChildClass extends ParentClass {
        // 子类的属性和方法
    }
    

继承的特点

子类拥有父类的属性和方法

  • 子类可以直接使用父类的属性和方法。

    public class Animal {
        public void eat() {
            System.out.println("This animal eats food.");
        }
    }
    
    public class Dog extends Animal {
        public void bark() {
            System.out.println("The dog barks.");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Dog dog = new Dog();
            dog.eat(); // 调用父类的方法
            dog.bark(); // 调用子类的方法
        }
    }
    
4.1.2 方法重写(Override)
  • 子类可以重写父类的方法,以提供特定的实现。重写的方法必须具有相同的方法签名(方法名、参数列表和返回类型)。

  • 使用 @Override注解来标识重写的方法。

    public class Animal {
        public void makeSound() {
            System.out.println("Some generic animal sound.");
        }
    }
    
    public class Dog extends Animal {
        @Override
        public void makeSound() {
            System.out.println("The dog barks.");
        }
    }
    
4.1.3 使用 super关键字
  • super关键字用于访问父类的属性和方法,特别是在子类重写了父类的方法时。

  • super也可以用来调用父类的构造方法。

    public class Animal {
        public void makeSound() {
            System.out.println("一些动物在叫。");
        }
    }
    
    public class Dog extends Animal {
        @Override
        public void makeSound() {
            super.makeSound(); // 调用父类的方法
            System.out.println("狗在叫。");
        }
    }
    
    public class Main {
        public static void main(String[] args) {
            Dog dog = new Dog();
            dog.makeSound();
        }
    }
    

superthis的区别:

特点 super this
指向什么 指向父类的成员或构造方法 指向当前对象的成员或构造方法,没有就找父类
使用范围 在子类中使用 在类的任何地方使用
调用构造 调用父类的构造方法,必须放在子类构造方法首行 调用本类的构造方法,必须放在构造方法首行

总结:super主要用于访问父类的成员和调用父类的构造方法,而 this用于访问当前对象的成员和调用自身的构造方法。

4.2 final关键字

类用final关键字修饰后,该类不可被继承(不能有子类)

4.3 抽象类和接口

注意事项

  • 抽象类和接口都是用于实现类的多态性,提供了规范和约束。
  • 抽象类适用于具有共同特征的类之间的继承关系,而接口适用于不同类之间的共性行为和规范定义。
    在Java中,抽象类和接口都是用来实现抽象化的工具,但它们有不同的用途和特点。
    下面通过一个例子来展示它们的区别。

抽象类

抽象类可以包含抽象方法(没有方法体的方法)和具体方法(有方法体的方法)。抽象类不能被实例化,只能被继承。

abstract class Animal {
    public abstract void makeSound();// 抽象方法
    public void sleep() {// 具体方法
        System.out.println("动物在睡觉");
    }
}
class Dog extends Animal {// 继承并实现实现抽象方法
    public void makeSound() {
        System.out.println("Woof");
    }
}

接口

接口只能包含抽象方法~~(Java 8及以后的版本中可以包含默认方法和静态方法)~~。接口中的方法默认是 publicabstract,可以被多个类实现。

interface Animal {
    void makeSound();//不用再写abstract
}
class Dog implements Animal{// 实现接口中的方法
    public void makeSound() {
        System.out.println("汪");
    }
}

区别总结

  1. 抽象类可以有构造方法、成员变量和具体方法,而接口不能有实例变量~~(Java 8及以后可以有静态变量)~~。
  2. 一个类只能继承一个抽象类,但可以实现多个接口。
  3. 抽象类适用于共享代码的情况,而接口适用于定义一组方法,让不同类实现。

案例4-5 图形的面积与周长计算程序

4.4 多态

什么是多态:

  • 多态指的是同一个方法在不同对象上具有不同的表现形式,可以实现不同对象的同名方法的多种不同行为。
  • 例如子类重载或重写父类的方法

向上转型和向下转型的概念:

  • 向上转型(upcasting):子类对象可以自动转换为父类类型。

    • 示例:Animal a = new Cat();
  • 向下转型(downcasting):父类对象需要强制转换为子类类型。

    • 示例:Cat c = (Cat)a;,需要添加强制类型转换符。

    • 向下转型容易出现 ClassCastException(类型转换异常)。

    • 如何避免这个风险?

      • 可以使用 instanceof运算符,在程序运行阶段动态判断某个引用指向的对象是否为某一种类型。

        • 示例:
        Animal a = new Cat();
        if (a instanceof Cat) {
            Cat c = (Cat) a;
            // 调用 Cat 类的特有方法
        }
        

4.7 异常

4.7.1 什么是异常

异常是指程序在运行过程中遇到的不正常情况,可能导致程序无法继续执行的问题。

Java中的异常是以对象的形式存在的,它们都是 Throwable 类或其子类的实例。

4.7.2 try...catch和finally

  • try...catch块:用于捕获可能引发异常的代码块,捕获到异常后进行相应的处理。

    try {
        // 可能引发异常的代码
    } catch (ExceptionType1 e1) {
        // 处理异常类型1的代码
    } catch (ExceptionType2 e2) {
        // 处理异常类型2的代码
    } finally {
        // 无论是否发生异常,都会执行的代码块
    }
    
  • finally块:用于在不管是否发生异常的情况下都执行的代码块,通常用于释放资源等操作。

4.7.3 throws关键字

throws关键字用于在方法声明中标识该方法可能会抛出的异常,表示该方法不处理异常,而是将异常抛给调用者处理。

public void doSomething() throws SomeException {
    // 可能抛出 SomeException 的代码
}

Java API

5.1 字符串类

字符串类在Java中非常重要,用于表示和操作字符串数据。常用的字符串类是 String类,它提供了丰富的方法用于字符串的操作,例如拼接、截取、替换等。

String str = "Hello, World!";
System.out.println(str.length()); // 输出:13
System.out.println(str.substring(7)); // 输出:World!
System.out.println(str.replace("Hello", "Hi")); // 输出:Hi, World!

案例5-3 模拟用户注册

5.3 Math和Random类

Math类

Math类包含了各种数学运算的静态方法,例如取整、取绝对值、计算平方根、求最大最小值等。

int num1 = 10;
int num2 = -5;

System.out.println(Math.abs(num2)); // 输出:5
System.out.println(Math.max(num1, num2)); // 输出:10
System.out.println(Math.sqrt(num1)); // 输出:3.1622776601683795

Random类

Random类用于生成随机数,可以生成不同范围的整数或浮点数随机数。

Random random = new Random();
int randomInt = random.nextInt(100); // 生成0到99之间的随机整数
double randomDouble = random.nextDouble(); // 生成0.0到1.0之间的随机浮点数

System.out.println(randomInt);
System.out.println(randomDouble);

5.4 日期和时间类

日期和时间类用于表示和操作日期、时间和时间戳。Java中常用的日期和时间类有 Date类、Calendar类和 SimpleDateFormat类。

Date date = new Date(); // 创建当前日期时间对象
System.out.println(date);

Calendar calendar = Calendar.getInstance(); // 获取当前日期时间的 Calendar 对象
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1; // 注意月份从0开始计算,所以要加1
int day = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println(year + "-" + month + "-" + day);

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 定义日期时间格式化对象
String formattedDate = sdf.format(date); // 格式化日期时间对象
System.out.println(formattedDate);

集合

6.1 集合简述

Java集合框架提供了一组用于存储和操作数据的类和接口。集合可以动态地存储、检索和操作对象。集合框架主要包含以下接口:ListSetQueueMap,每个接口有不同的实现类。

6.3 List集合

List接口表示一个有序的集合,允许存储重复的元素。常用的 List实现类有 ArrayListLinkedListVector

6.3.2 ArrayList集合

ArrayListList接口的实现类,使用动态数组来存储元素。它提供了快速的随机访问时间,但在插入和删除元素时性能较低。

ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Apple");
arrayList.add("Banana");
arrayList.add("Cherry");

System.out.println(arrayList); // 输出:[Apple, Banana, Cherry]

arrayList.remove("Banana");
System.out.println(arrayList); // 输出:[Apple, Cherry]

String fruit = arrayList.get(1);
System.out.println(fruit); // 输出:Cherry

6.3.4 Iterator接口

Iterator接口用于遍历集合中的元素。它提供了 hasNext()next()remove()方法。

List<String> list = new ArrayList<>();
list.add("狗");
list.add("猫");
list.add("兔");

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String animal = iterator.next();
    System.out.println(animal);
    if ("猫".equals(animal)) {
        iterator.remove(); // 从集合中删除当前元素
    }
}

System.out.println(list); // 输出:[狗, 兔]

6.4 Set接口

Set接口表示一个不包含重复元素的集合。常用的 Set实现类有 HashSetLinkedHashSetTreeSet

Set<String> set = new HashSet<>();
set.add("1");
set.add("2");
set.add("3");
set.add("2"); // 重复元素不会被添加

System.out.println(set); // 输出:[1, 2, 3]

for (String number : set) {
    System.out.println(number);
}

Java集合框架提供了丰富的数据结构和操作方法,使得数据的存储和处理变得更加灵活和高效。

I/O流式传输

7.2 字节流

字节流是以字节为单位进行数据读写的流,常用于处理二进制数据或者文本文件。Java中的字节流主要包括InputStream和OutputStream两个抽象类,以及它们的具体实现类如FileInputStream、FileOutputStream等。

字节流的使用示例:

import java.io.*;


public class ByteStreamExample {
    public static void main(String[] args) {
        String data = "Hello, 牢大!";

        // 写入数据到文件(输出流)
        try (FileOutputStream fos = new FileOutputStream("example.txt")) {
            fos.write(data.getBytes());
            System.out.println("输出流输出内容到文件里");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 从文件读取数据(输入流)
        try (FileInputStream fis = new FileInputStream("example.txt")) {
            int byteData;
            System.out.print("从文件输入到程序的内容是: ");
            while ((byteData = fis.read()) != -1) {
                System.out.print((char) byteData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7.3 字符流

字符流是以字符为单位进行数据读写的流,常用于处理文本文件。Java中的字符流主要包括Reader和Writer两个抽象类,以及它们的具体实现类如FileReader、FileWriter等。

字符流的使用示例:

import java.io.*;

public class CharStreamExample {
    public static void main(String[] args) {
        String data = "Hello, 坤坤!";

        // 写入数据到文件(输出流)
        try (FileWriter fw = new FileWriter("example.txt")) {
            fw.write(data);
            System.out.println("输出流输出内容到文件里");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 从文件读取数据(输入流)
        try (FileReader fr = new FileReader("example.txt")) {
            int charData;
            System.out.print("从文件输入到程序的内容是: ");
            while ((charData = fr.read()) != -1) {
                System.out.print((char) charData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

字节流适用于处理二进制数据或者文本文件,而字符流适用于处理文本文件,可以更方便地处理字符编码和字符集。

多线程

8.2 线程的创建

在Java中,线程的创建有两种方式:继承Thread类和实现Runnable接口。

8.2.1 继承Thread类

继承Thread类是创建线程的一种方式,需要重写Thread类的run()方法来定义线程的执行逻辑。

示例:

class MyThread1 extends Thread {
    private int breakfast = 10;
    private String name;

    public MyThread1(String name) {
        this.name = name;
    }

    public void run() {
        for (int i = 0; i < 500; i++) {
            if (this.breakfast > 0) {
                System.out.println(this.name + "卖早餐---->" + (this.breakfast--));
            }
        }
    }

    public static void main(String[] args) {
        MyThread1 mt1 = new MyThread1("一号窗口");
        MyThread1 mt2 = new MyThread1("二号窗口");
        MyThread1 mt3 = new MyThread1("三号窗口");

        mt1.start();
        mt2.start();
        mt3.start();
    }
}

8.2.2 实现Runnable接口创建多线程

另一种创建线程的方式是实现Runnable接口,然后将实现了Runnable接口的类传递给Thread类的构造方法。

示例:

class MyThread2 implements Runnable {
    private int breakfast = 10;
    private String name;

    public void run() {
        for (int i = 0; i < 500; i++) {
            if (this.breakfast > 0) {
                System.out.println(Thread.currentThread().getName() + "卖早餐---->" + (this.breakfast--));
            }
        }
    }

    public static void main(String[] args) {
        // 设计三个线程
        MyThread2 mt = new MyThread2();
        Thread t1 = new Thread(mt, "一号窗口");
        Thread t2 = new Thread(mt, "二号窗口");
        Thread t3 = new Thread(mt, "三号窗口");

        t1.start();
        t2.start();
        t3.start();
    }
}

通过实现Runnable接口,可以更灵活地管理线程的生命周期和资源,并且可以避免Java单继承的限制。在实际开发中,通常推荐使用实现Runnable接口的方式来创建多线程,因为它更符合面向对象的设计原则。

继承Thread类 | Runnable接口

两者实现多线程的区别

第一个继承Thread类来实现多线程,其实是相当于

拿出三件事即三个卖早餐10份的任务分别分给三个窗口,他们各做各的事各卖各的早餐各完成各的任务,因为MyThread继承Thread类,所以在newMyThread的时候在创建三个对象的同时创建了三个线程;

而实现Runnable, 相当于是

拿出一个卖早餐10份的任务给三个人去共同完成,newMyThread相当于创建一个任务,然后实例化三个Thread,创建三个线程即安排三个窗口去执行。

**一个类只能继承一个父类,存在局限;一个类可以实现多个接口。**在实现Runnable接口的时候调用Thread的Thread(Runnable run)或者Thread(Runnablerun,String name)构造方法创建进程时,使用同一个Runnable实例,建立的多线程的实例变量也是共享的;但是通过继承Thread类是不能用一个实例建立多个线程,故而实现Runnable接口适合于资源共享;当然,继承Thread类也能够共享变量,能共享Thread类的static变量;
其实,抽象来说,这并不是Thread类和Runnable接口的区别了,这可以看做是接口和继承的问题。我们弄懂了接口和继承,就不难理解Thread和Runnable。
在刚接触的时候可能会有些迷糊这二者的区别于联系,但是实践和总结过后我们会发现这是两个完全不同的实现多线程,

一个是多个线程分别完成自己的任务,一个是多个线程共同完成一个任务。其实,在实现一个任务用多个线程来做也可以用继承Thread类来实现,只是比较麻烦,一般我们用实现Runnable接口来实现。

连接数据库

10.3 实现JDBC程序

import java.sql.*;

public class JDBCTutorial {
    public static void main(String[] args) {
        // Step 1: 导入JDBC相关的包

        // Step 2: 加载数据库驱动
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");//6.0.2版本之前的数据库需要删除“.cj”字样
        } catch (ClassNotFoundException e) {
            System.err.println("驱动加载失败!");
            e.printStackTrace();
            return;
        }

        // Step 3: 建立数据库连接
        Connection connection = null;
        try {
            String url = "jdbc:mysql://localhost:3306/mydatabase";
            String user = "username";
            String password = "password";
            connection = DriverManager.getConnection(url, user, password);
        } catch (SQLException e) {
            System.err.println("连接数据库失败!");
            e.printStackTrace();
            return;
        }

        // Step 4: 创建Statement对象
        Statement statement = null;
        try {
            statement = connection.createStatement();
        } catch (SQLException e) {
            System.err.println("创建statement对象失败!");
            e.printStackTrace();
            try {
                if (connection != null) connection.close();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            return;
        }

        // Step 5: 执行SQL语句
        String sqlQuery = "SELECT * FROM users";
        ResultSet resultSet = null;
        try {
            resultSet = statement.executeQuery(sqlQuery);
        } catch (SQLException e) {
            System.err.println("数据请求失败!");
            e.printStackTrace();
            try {
                if (statement != null) statement.close();
                if (connection != null) connection.close();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }
            return;
        }

        // Step 6: 处理查询结果
        try {
            while (resultSet.next()) {
                String username = resultSet.getString("username");
                int age = resultSet.getInt("age");
                System.out.println("Username: " + username + ", Age: " + age);
            }
        } catch (SQLException e) {
            System.err.println("数据处理失败!");
            e.printStackTrace();
        } finally {
            // Step 7: 关闭资源
            try {
                if (resultSet != null) resultSet.close();
                if (statement != null) statement.close();
                if (connection != null) connection.close();
            } catch (SQLException e) {
                System.err.println("关闭资源失败!");
                e.printStackTrace();
            }
        }
    }
}