## Java笔记本 ### IDEA基础操作 Intellij Ideav创建Java项目: 1. 创建空项目 2. 创建Java module 3. 创建包 package edu.whut.xx 4. 创建类,类名首字母必须大写! IDEA快捷键: | `Ctrl + Alt + L` | 格式化代码 | | ---------------- | ------------------------ | | `Ctrl + /` | 注释/取消注释当前行 | | `Ctrl + D` | 复制当前行或选中的代码块 | | `Ctrl + Y` | 删除当前行 | | `Ctrl + N` | 查找类 | | `Ctrl+shift+F` | 在文件中查找代码 | 调试快捷键: | 快捷键 | 功能 | | :----------- | :--------------------- | | `Shift + F9` | 调试当前程序 | | `F8` | 单步执行(不进入方法) | | `F7` | 单步执行(进入方法) | | `Shift + F8` | 跳出当前方法 | | `Alt + F9` | 运行到光标处 | | `Ctrl + F2` | 停止调试 | | 缩写 | 生成的代码 | 说明 | | :--------------- | :------------------------------------------ | :------------------ | | `psvm` | `public static void main(String[] args) {}` | 生成 `main` 方法 | | `sout` | `System.out.println();` | 打印到控制台 | | `fori` | `for (int i = 0; i < ; i++) {}` | 生成 `for` 循环 | | `iter` | `for (Type item : iterable) {}` | 生成增强 `for` 循环 | | `new Test().var` | `Test test = new Test();` | 自动补全变量声明 | **从exsiting file中导入模块**: **方法一**:复制整个模块到项目文件夹,并导入模块的 *.iml 文件,这种方式保留了模块原有的配置信息。 **方法二**:新建一个模块,然后将原模块的 src 文件夹下的包复制过去,这种方式更灵活,可以手动调整模块设置。 删除模块: 模块右键,remove module,这只是把它从项目中移除,然后!!打开模块所在文件夹,物理删除,才是真正完全删除。 ### 转义符的作用 **防止字符被误解**: - 在字符串中,一些字符(如 `"` 和 `\`)有特殊的含义。例如,双引号用于标识字符串的开始和结束,反斜杠通常用于转义。所以当你希望在字符串中包含这些特殊字符时,你需要使用转义符来告诉解析器这些字符是字符串的一部分,而不是特殊符号。 - 例如,`\"` 表示在字符串中包含一个双引号字符,而不是字符串的结束标志。 `"Hello \"World\""` => 结果是:`Hello "World"` (双引号被转义) `"C:\\Program Files\\App"` => 结果是:`C:\Program Files\App`(反斜杠被转义) 如果只是"C:\\Program Files\\App" 那么路径就会报错 **表示非打印字符**: - 转义符可以用于表示一些不可见的或非打印的控制字符,如换行符(`\n`)、制表符(`\t`)等。这些字符无法直接通过键盘输入,所以使用转义符来表示它们。 ### Java基础语法 1. 二进制:0b 八进制:0 十六进制:0x 2. 在 `System.out.println()` 方法中,"ln" 代表 "line",表示换行。因此,`println` 实际上是 "print line" 的缩写。这个方法会在输出文本后自动换行. ```java System.out.println("nihao "+1.3331); #Java 会自动将数值转换为字符串 ``` 3. 一维数组创建: ```java // 方式1:先声明,再指定长度(默认值为0、null等) int[] arr1 = new int[10]; // 创建一个长度为10的int数组 // 方式2:使用初始化列表直接创建数组 int[] arr2 = {1, 2, 3, 4, 5}; // 创建并初始化一个包含5个元素的int数组 String[] strs = {"eat", "tea", "tan", "ate", "nat", "bat"}; // 方式3:结合new关键字和初始化列表创建数组(常用于明确指定类型时) int[] arr3 = new int[]{1, 2, 3, 4, 5}; // 与方式2效果相同 ``` 4. 字符串创建 ```java String str = "Hello, World!"; //(1)直接赋值 String str = new String("Hello, World!"); //使用 new 关键字 char[] charArray = {'H', 'e', 'l', 'l', 'o'}; String str = new String(charArray); //通过字符数组创建 ``` 5. switch-case ```java public class SwitchCaseExample { public static void main(String[] args) { // 定义一个 int 类型变量,作为 switch 的表达式 int day = 3; String dayName; // 根据 day 的值执行相应的分支 switch(day) { case 1: dayName = "Monday"; // 当 day 为 1 时 break; // 结束当前 case case 2: dayName = "Tuesday"; // 当 day 为 2 时 break; case 3: dayName = "Wednesday"; // 当 day 为 3 时 break; case 4: dayName = "Thursday"; // 当 day 为 4 时 break; case 5: dayName = "Friday"; // 当 day 为 5 时 break; case 6: dayName = "Saturday"; // 当 day 为 6 时 break; case 7: dayName = "Sunday"; // 当 day 为 7 时 break; default: // 如果 day 不在 1 到 7 之间 dayName = "Invalid day"; } // 输出最终结果 System.out.println("The day is: " + dayName); } } ``` 6. 强制类型转换 ```java double sqrted=Math.sqrt(n); int soft_max=(int) sqrted; ``` 7. Math库常用方法 ```java Math.pow(3, 2)); Math.sqrt(9)); Math.abs(a)); Math.max(a, b)); Math.min(a, b)); ``` #### 枚举 ```java public enum OperationType { /** * 更新操作 */ UPDATE, /** * 插入操作 */ INSERT } OperationType opType = OperationType.INSERT; // 声明并初始化 public void execute(OperationType type, Object entity) { switch (type) { case INSERT: insertEntity(entity); break; case UPDATE: updateEntity(entity); break; default: throw new IllegalArgumentException("Unsupported operation: " + type); } } ``` ```java // 定义枚举类型 public enum DayOfWeek { MONDAY("星期一", 1), TUESDAY("星期二", 2), WEDNESDAY("星期三", 3), THURSDAY("星期四", 4), FRIDAY("星期五", 5), SATURDAY("星期六", 6), SUNDAY("星期日", 7); // 枚举属性 private final String chineseName; private final int dayNumber; // 构造方法 DayOfWeek(String chineseName, int dayNumber) { this.chineseName = chineseName; this.dayNumber = dayNumber; } // 方法 public String getChineseName() { return chineseName; } public int getDayNumber() { return dayNumber; } } // 使用示例 public class Main { public static void main(String[] args) { DayOfWeek today = DayOfWeek.MONDAY; System.out.println(today.getChineseName()); // 输出: 星期一 System.out.println(today.getDayNumber()); // 输出: 1 } } ``` #### Java传参方式 基本数据类型(Primitives) - 传递方式:按值传递 每次传递的是变量的值的副本**,对该值的修改不会影响原变量**。例如:`int`、`double`、`boolean` 等类型。 引用类型(对象) - 传递方式:对象引用的副本传递 传递的是对象引用的一个副本,**指向同一块内存区域**。因此,方法内部通过该引用修改对象的状态,会影响到原对象。如数组、集合、String、以及其他所有对象类型。 注意 ```java StringBuilder s = new StringBuilder(); s.append("hello"); String res = s.toString(); // res = "hello" s.append(" world"); // s = "hello world" System.out.println(res); // 输出还是 "hello" ``` **浅拷贝** 拷贝对象本身,但内部成员(例如集合中的元素)只是复制引用,新旧对象的内部成员指向同一份内存。如果内部元素是不可变的(如 Integer、String 等),这种拷贝通常足够。如果元素是可变对象,修改其中一个对象**可能会影响**另一个。 ```java List list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); // 浅拷贝:新列表中的元素引用和原列表中的是同一份 List shallowCopy = new ArrayList<>(list); ``` 可变对象,浅拷贝修改对象会出错! ```java List list = new ArrayList<>(); list.add(new Box(1)); list.add(new Box(2)); list.add(new Box(3)); List shallowCopy = new ArrayList<>(list); shallowCopy.get(0).value = 10; // 修改 shallowCopy 中第一个 Box 的 value System.out.println(list); // 输出: [10, 2, 3],因为同一 Box 对象被修改 System.out.println(shallowCopy); // 输出: [10, 2, 3] ``` **深拷贝** 不仅复制对象本身,还递归地复制其所有内部成员,从而生成一个完全独立的副本。即使内部元素是可变的,修改新对象也不会影响原始对象。 ```java // 深拷贝 List 的例子 List originalList = new ArrayList<>(); originalList.add(new MyObject(10)); originalList.add(new MyObject(20)); List deepCopy = new ArrayList<>(); for (MyObject obj : originalList) { deepCopy.add(new MyObject(obj)); // 每个元素都创建一个新的对象 } ``` #### 日期 在Java中: - 代表年月日的类型是 `LocalDate`。`LocalDate` 类位于 `java.time` 包下,用于表示没有时区的日期,如年、月、日。 - 代表年月日时分秒的类型是 `LocalDateTime`。`LocalDateTime` 类也位于 `java.time` 包下,用于表示没有时区的日期和时间,包括年、月、日、时、分、秒。 LocalDateTime.now(),获取当前时间 #### 访问修饰符 **public(公共的)**: 使用public修饰的成员可以被任何其他类访问,无论这些类是否属于同一个包。 例如,如果一个类的成员被声明为public,那么其他类可以通过该类的对象直接访问该成员。 **protected(受保护的)**: 使用protected修饰的成员可以被**同一个包**中的其他类访问,也可以被**不同包中的子类**访问。 与包访问级别相比,protected修饰符提供了更广泛的访问权限。 **default (no modifier)(默认的,即包访问级别)**: 如果没有指定任何访问修饰符,则默认情况下成员具有包访问权限。 在同一个包中的其他类可以访问默认访问级别的成员,但是在不同包中的类不能访问。 **private**(私有的): 使用private修饰的成员只能在声明它们的**类内部**访问,其他任何类都不能访问这些成员。 这种访问级别提供了最高的封装性和安全性。 如果您在另一个类中实例化了包含私有成员的类,那么您无法直接访问该类的私有成员。但是,您可以通过**公共方法**来间接地访问和操作私有成员。 则每个实例都有自己的一份拷贝,只有当变量被声明为 static 时,变量才是类级别的,会被所有实例共享。 ```java // 文件:com/example/PrivateExample.java package com.example; public class PrivateExample { private int privateVar = 30; // 公共方法,用于访问私有成员 public int getPrivateVar() { return privateVar; } } ``` 修饰符不仅可以用来修饰成员变量和方法,也可以用来**修饰类**。顶级类只能使用 `public` 或默认(即不写任何修饰符,称为包访问权限)。内部类可以使用所有访问修饰符(`public`、`protected`、`private` 和默认),这使得你可以更灵活地控制嵌套类的访问范围。 ```java public class OuterClass { // 内部类使用private,只能在OuterClass内部访问 private class InnerPrivateClass { // ... } // 内部类使用protected,同包以及其他包中的子类可以访问 protected class InnerProtectedClass { // ... } // 内部类使用默认访问权限,只在同包中可见 class InnerDefaultClass { // ... } // 内部类使用public,任何地方都可访问(但访问时需要通过OuterClass对象) public class InnerPublicClass { // ... } } ``` #### 四种内部类 下面是四种内部类(成员内部类、局部内部类、静态内部类和匿名内部类)的示例代码,展示了如何用每一种方式来实现`Runnable`的`run()`方法并创建线程。 1. **成员内部类** 定义位置:成员内部类定义在外部类的**成员位置**。 访问权限:可以无限制地访问外部类的所有成员,**包括私有成员**。 实例化方式:需要先创建外部类的实例,然后才能创建内部类的实例。 修改限制:不能有静态字段和静态方法(除非声明为常量`final static`)。**成员内部类属于外部类的一个实例,不能独立存在于类级别上。** 用途:适用于内部类与外部类关系密切,需要频繁访问外部类成员的情况。 ```java public class OuterClass { class InnerClass implements Runnable { // static int count = 0; // 编译错误 public static final int CONSTANT = 100; // 正确:可以定义常量 public void run() { System.out.println("成员内部类中的线程正在运行..."); } } public void startThread() { InnerClass inner = new InnerClass(); Thread thread = new Thread(inner); thread.start(); } public static void main(String[] args) { OuterClass outer = new OuterClass(); outer.startThread(); } } ``` 2. **局部内部类** 定义位置:局部内部类定义在**一个方法或任何块内**(如:if语句、循环语句内)。 访问权限:只能访问**所在方法**的`final`或事实上的`final`(即不被后续修改的)局部变量和外部类的成员变量(同成员内部类)。 实例化方式:只能在定义它们的块中创建实例。 修改限制:同样不能有静态字段和方法。 用途:适用于只在方法或代码块中使用的类,有助于将实现细节隐藏在方法内部。 ```java public class OuterClass { public void startThread() { class LocalInnerClass implements Runnable { public void run() { System.out.println("局部内部类中的线程正在运行..."); } } LocalInnerClass localInner = new LocalInnerClass(); Thread thread = new Thread(localInner); thread.start(); } public static void main(String[] args) { OuterClass outer = new OuterClass(); outer.startThread(); } } ``` 3. **静态内部类** 定义位置:定义在外部类内部,但使用`static`修饰。 访问权限:只能直接访问外部类的静态成员,访问非静态成员需要通过外部类实例。 实例化方式:**可以直接创建,不需要外部类的实例**。 修改限制:可以有自己的静态成员。 用途:适合当内部类工作不依赖外部类实例时使用,常用于实现与外部类关系不那么密切的帮助类。 ```java public class OuterClass { // 外部类的静态成员 private static int staticVar = 10; // 外部类的实例成员 private int instanceVar = 20; // 静态内部类 public static class StaticInnerClass { public void display() { // 可以直接访问外部类的静态成员 System.out.println("staticVar: " + staticVar); // 下面这行代码会报错,因为不能直接访问外部类的实例成员 // System.out.println("instanceVar: " + instanceVar); // 如果确实需要访问实例成员,可以通过创建外部类的对象来访问 OuterClass outer = new OuterClass(); System.out.println("通过外部类实例访问 instanceVar: " + outer.instanceVar); } } public static void main(String[] args) { // 直接创建静态内部类的实例,不需要外部类实例 OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass(); inner.display(); } } ``` 4. **匿名内部类** 使用匿名内部类实现抽象类相当于临时创建了一个**未命名的子类**,并且立即实例化了这个子类的对象。 定义位置:在需要使用它的地方立即定义和实例化。 访问权限:类似局部内部类,只能访问`final`或事实上的`final`局部变量。 实例化方式:在定义时就实例化,不能显式地命名构造器。 修改限制:不能有任何静态成员。 用途:适用于创建一次性使用的实例,通常用于接口或抽象类的实现。 ```java //eg1 public class OuterClass { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { System.out.println("匿名内部类中的线程正在运行..."); } }; Thread thread = new Thread(runnable); thread.start(); } } //eg2 安卓开发中用过很多次! import javax.swing.*; import java.awt.event.*; public class GUIApp { public static void main(String[] args) { JFrame frame = new JFrame("Demo"); JButton button = new JButton("Click Me!"); // 匿名内部类用于事件监听 button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("Button was clicked!"); } }); frame.add(button); frame.setSize(300, 200); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } } ``` 1. 创建`ActionListener`实例 ```java new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println("Button was clicked!"); } } ``` 这部分代码是匿名内部类的核心。这里发生的事情包括: - **`new ActionListener()`**:这表示创建了`ActionListener`接口的一个实现。由于`ActionListener`是一个接口,我们不能直接实例化它,而是需要提供该接口的一个**具体实现**。在这种情况下,我们通过创建一个匿名内部类来提供实现。 - **大括号 `{ ... }`**:在这对大括号内,我们定义了接口中需要实现的方法。对于`ActionListener`接口,必须实现`actionPerformed(ActionEvent e)`方法。 - **方法实现**: - **`public void actionPerformed(ActionEvent e)`**:这是`ActionListener`接口要求实现的方法,用于响应事件。这里的方法定义了当事件发生时(例如,用户点击按钮)执行的操作。 - **`System.out.println("Button was clicked!");`**:这是`actionPerformed`方法的具体实现,即当按钮被点击时,控制台将输出一条消息。 2. 将匿名内部类添加为事件监听器 ```java button.addActionListener(...); ``` **`button.addActionListener(EventListener)`**:这是`JButton`类的一个方法,用于添加事件监听器。这个方法的参数是`ActionListener`类型的对象,这里传入的正是我们刚刚创建的匿名内部类的实例。 #### Lambda表达式 Lambda表达式特别适用于**只有单一抽象方法**的接口(也即**函数式接口**)。lambda 表达式主要用于**实现**函数式接口。 **`@FunctionalInterface` 注解**:这是一个可选的注解,用于表示接口是一个函数式接口。虽然不是强制的,但它可以帮助编译器识别意图,并检查接口是否确实只有一个抽象方法。 ```java public class LambdaExample { // 定义函数式接口,doSomething 有两个参数 @FunctionalInterface interface MyInterface { void doSomething(int a, int b); } public static void main(String[] args) { // 使用 Lambda 表达式实现接口方法 MyInterface obj = (a, b) -> { System.out.println("参数a: " + a + ", 参数b: " + b); }; obj.doSomething(5, 10); } } ``` **lambda表达式格式**:`(参数列表) -> { 代码块 }` ​ 或 (参数列表) ->表达式; 在上述Lambda表达式中,因为`MyInterface`接口的`doSomething()`方法不接受任何参数并且没有返回值,所以Lambda表达式的参数列表为空(`()`),后面跟的是执行的代码块。 **以下是lambda表达式的重要特征:** 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。 可选的参数圆括号:**一个参数无需定义圆括号**,但无参数或多个参数需要定义圆括号。 可选的大括号:如果主体只有一个语句,可以不使用大括号。 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,使用大括号需显示retrun;如果函数是void则不需要返回值。 ```java // 定义一个函数式接口 interface Calculator { int add(int a, int b); } public class LambdaReturnExample { public static void main(String[] args) { // 例子1:单个表达式,不使用大括号和 return 关键字 Calculator calc1 = (a, b) -> a + b; System.out.println("calc1: " + calc1.add(5, 3)); // 输出:8 // 例子2:使用大括号,需要显式使用 return 关键字 Calculator calc2 = (a, b) -> { return a + b; }; System.out.println("calc2: " + calc2.add(5, 3)); // 输出:8 } } ``` 示例1: `list.forEach`这个方法接受一个**函数式接口**作为参数。它只有一个抽象方法 `accept(T t)`因此,可以使用 lambda 表达式来**实现**。 ```java import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List list = Arrays.asList("Apple", "Banana", "Cherry", "Date"); // 使用 Lambda 表达式迭代列表 list.forEach(item -> System.out.println(item)); } } ``` 示例2: `Collections.sort` 方法用于对列表进行排序,它接受两个参数 第一个参数:要排序的列表(这里是一个 `List`)。 第二个参数:一个实现了 `Comparator` 接口的比较器,用于指定排序规则。 ​ 如果返回负数,表示 `a` 应该排在 `b` 的前面; ​ 如果返回正数,则 `a` 应该排在 `b` 的后面。 ```java import java.util.Arrays; import java.util.Collections; import java.util.List; public class Main { public static void main(String[] args) { List names = Arrays.asList("John", "Jane", "Adam", "Dana"); // 使用Lambda表达式排序 Collections.sort(names, (String a, String b) -> a.compareTo(b)); // 输出排序结果 names.forEach(name -> System.out.println(name)); } } ``` #### 静态成员变量的初始化 静态成员变量属于类级别,在类加载时完成初始化。初始化方式主要有两种: **1.静态初始化块(Static Initialization Block)** ```java public class MyClass { static int num1, num2; // 第一个静态代码块 static { num1 = 1; System.out.println("静态代码块1执行"); } // 主方法 public static void main(String[] args) { System.out.println("main方法执行"); } // 第二个静态代码块 static { num2 = 3; System.out.println("静态代码块2执行"); } } ``` 输出: ```java 静态代码块1执行 静态代码块2执行 main方法执行 ``` **说明:** 类加载时依次执行所有静态代码块,然后执行 `main` 方法。 **2.在声明时直接初始化** ```java public class MyClass { // 直接在声明时初始化静态成员变量 public static int staticVariable = 42; } ``` 静态成员变量的访问不需要创建 `MyClass` 的实例,可以直接通过类名访问: ```java int value = MyClass.staticVariable; MyClass obj = new MyClass(); System.out.println("obj.num1 = " + obj.staticVariable); #通过示例访问也可以 ``` #### 静态方法 静态方法属于**类级别**,不依赖于任何具体实例 静态方法访问规则: - **可以直接访问:** - 类中的其他静态成员变量。 - 类中的静态方法。 **不能直接访问:** - 非静态成员变量。 - 非静态方法(必须通过对象实例访问)。 ```java public class MyClass { private static int staticVar = 10; private int instanceVar = 20; // 静态方法:可以直接访问静态成员 public static void staticMethod() { System.out.println(staticVar); // 正确:访问静态成员变量 // System.out.println(instanceVar); // 错误:不能直接访问非静态成员变量 // 如需要访问非静态成员,必须先创建对象实例 MyClass obj = new MyClass(); System.out.println(obj.instanceVar); // 正确:通过对象实例访问非静态成员变量 } // 非静态方法:可以访问所有成员 public void instanceMethod() { System.out.println(staticVar); // 正确:访问静态成员变量 System.out.println(instanceVar); // 正确:访问非静态成员变量 } } ``` 调用静态方法: ```java MyClass.staticMethod(); // 通过类名直接调用静态方法 ``` #### super关键字 `super` 关键字有两种主要的使用方法:访问父类的成员和调用父类的构造方法。 1. 访问父类的成员 可以使用 `super` 关键字来引用父类的字段或方法。这在子类中**存在同名的字段或方法**时特别有用。 因为父类的成员变量和方法都是默认的访问修饰符,可以继承给子类,而子类也定义了同名的xxx,发生了**变量隐藏**(shadowing)。 ```java class Parent { int num = 10; void display() { System.out.println("Parent class method"); } } class Child extends Parent { int num = 20; void display() { System.out.println("Child class method"); } void print() { System.out.println("Child class num: " + num); // 访问子类中的num System.out.println("Parent class num: " + super.num); // 使用super关键字访问父类中的num super.display(); // 调用父类中的display方法 } } public class Main { public static void main(String[] args) { Child obj = new Child(); obj.print(); } } ``` 输出: ```java Child class num: 20 Parent class num: 10 Parent class method ``` 2. 调用父类的构造方法 可以使用 `super` 关键字调用父类的构造方法。这通常在子类的构造方法中使用,用于显式地调用父类的构造方法。 ```java class Parent { Parent() { System.out.println("Parent class constructor"); } } class Child extends Parent { Child() { super(); // 调用父类的构造方法 System.out.println("Child class constructor"); } } public class Main { public static void main(String[] args) { Child obj = new Child(); } } ``` 输出: ```java Parent class constructor Child class constructor ``` #### final关键字 final 关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点: 1. 修饰类:类不能继承,final 类中的所有成员方法都会被隐式的指定为 final 方法; 2. 修饰变量:该变量为常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。 3. 修饰符方法:方法不能重写 #### 变量修饰符的顺序 在Java中,变量的修饰符应该按照规定的顺序出现,通常是这样的: 1. **访问修饰符**:public、protected、private,或者不写(默认为包级访问)。 2. **非访问修饰符**:final、static、abstract、synchronized、volatile等。 3. **数据类型**:变量的数据类型,如int、String、class等。 4. **变量名**:变量的名称。 ```java public static final int MAX_COUNT = 100; #定义常量 protected static volatile int counter; #定义成员变量 ``` #### 全限定名 全限定名(Fully Qualified Name,简称 FQN)指的是一个类或接口在 Java 中的完整名称,包括它所在的包名。例如: - 对于类 `Integer`,其全限定名是 `java.lang.Integer`。 - 对于自定义的类 `DeptServiceImpl`,如果它位于包 `edu.zju.zy123.service.impl` 中,那么它的全限定名就是 `edu.zju.zy123.service.impl.DeptServiceImpl`。 使用全限定名可以消除歧义,确保指定的类型在整个项目中唯一无误。 使用场景: Spring AOP 的 Pointcut 表达式 MyBatis的XML映射文件的**namespace属性** ### JAVA面向对象 ```java public class Dog { // 成员变量 private String name; // 构造函数 public Dog(String name) { this.name = name; } // 一个函数:让狗狗“叫” public void bark() { System.out.println(name + " says: Woof! Woof!"); } // (可选)获取狗狗的名字 public String getName() { return name; } // 测试主方法 public static void main(String[] args) { Dog myDog = new Dog("Buddy"); myDog.bark(); // 输出:Buddy says: Woof! Woof! System.out.println("Name: " + myDog.getName()); } } ``` #### **JAVA**三大特性 **封装** 封装指隐藏对象的状态信息(属性),不允许外部对象直接访问对象的内部信息(private实现)。但是可以提供一些可以被外界访问的方法(public)来操作属性。 **继承** ```java [修饰符] class 子类名 extends 父类名{ 类体部分 } //class C extends A, B { } // 错误:C 不能同时继承 A 和 B ``` Java只支持单继承,不支持多继承。一个类只能有一个父类,不可以有多个父类。 Java支持多层继承(A → B → C )。 Java继承了父类**非私有**的成员变量和成员方法,但是请注意:子类是无法继承父类的构造方法的。 **多态** 指在面向对象编程中,同样的消息(方法调用)可以在不同的对象上触发不同的行为。 1. **方法重写(Override)**:动态多态;子类从父类继承的某个实例方法无法满足子类的功能需要时,需要在子类中对该实例方法进行重新实现,这样的过程称为重写,也叫做覆写、覆盖。 **要求**: - 必须存在继承关系(子类继承父类)。 - 子类重写的方法的访问修饰符不能比父类更严格(可以相同或更宽松)。 - 方法名、参数列表和返回值类型必须与父类中的方法完全相同(Java 5 以后支持协变返回类型,即允许返回子类型)。 2. **向上转型(Upcasting)**:动态多态;子类对象可以赋值给父类引用,这样做可以隐藏对象的真实类型,只能调用**父类中声明的方法**。 ```java class Animal { public void makeSound() { System.out.println("Animal makes sound"); } } class Dog extends Animal { @Override public void makeSound() { System.out.println("Dog barks"); } public void fetch() { System.out.println("Dog fetches the ball"); } } public class Test { public static void main(String[] args) { Animal animal = new Dog(); // 向上转型 animal.makeSound(); // 调用的是 Dog 重写的 makeSound() 方法 // animal.fetch(); // 编译错误:Animal 类型没有 fetch() 方法 } } ``` **多态实现总结**:继承 + 重写 + 父类引用指向子类对象 = 多态 3. **方法重载(Overload)**:静态多态;在一个类中,可以定义多个同名方法,但参数列表不同。当调用这些方法时,会根据传递的参数类型或数量选择相应的方法。 **与重写的区别**: - **重载**发生在同一个类中,与继承无关; - **重写**发生在子类中,依赖继承关系,实现运行时多态。 ```java class Calculator { int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } } ``` #### 抽象类 **定义** 抽象类是包含至少一个抽象方法的类。抽象方法没有实现,只定义了方法的签名。 **注意:** 抽象类不能被实例化。 **必须实现抽象方法** 如果一个子类继承了抽象类,通常必须实现抽象类中的所有抽象方法,否则该子类也必须声明为抽象类。例如: ```java abstract class Animal { // 抽象方法,没有方法体 public abstract void makeSound(); // 普通方法 public void sleep() { System.out.println("Sleeping..."); } } // 正确:子类实现了所有抽象方法 class Dog extends Animal { @Override public void makeSound() { System.out.println("Dog barks"); } } // 错误:如果不实现 makeSound() 方法,则 Dog 必须也声明为抽象类 ``` **如何使用抽象类** 由于抽象类不能直接实例化,我们通常有两种方法来使用抽象类: 1. **定义一个新的子类** 创建一个子类继承抽象类并实现所有抽象方法,然后使用子类实例化对象: ```java Animal animal = new Dog(); animal.makeSound(); // 输出:Dog barks ``` 2. **使用匿名内部类** 使用匿名内部类实现抽象类相当于临时创建了一个**未命名的子类**,并且立即实例化了这个子类的对象。 ```java Animal animal = new Animal() { @Override public void makeSound() { System.out.println("Anonymous animal sound"); } }; animal.makeSound(); // 输出:Anonymous animal sound ``` #### 接口 **接口(Interface)**: 定义了一组方法的规范,侧重于行为的约定。接口中的所有方法默认是抽象的(Java 8 之后可包含默认方法和静态方法),不包含成员变量(除了常量)。 **抽象类(Abstract Class)**: 可以包含抽象方法和具体实现的方法,还可以拥有成员变量和构造方法,适用于需要部分通用实现的情况。 1. *方法实现*: *接口*: - Java 8 前:所有方法都是抽象方法,只包含方法声明。 - Java 8 及以后:可包含默认方法(default methods)和静态方法。 *抽象类*: - 可以同时包含抽象方法(不提供实现)和具体方法(提供实现)。 2. *继承:* - 类实现接口时,使用关键字 `implements`。 - 类继承抽象类时,使用关键字 `extends`。 3. *多继承*: - 类可以实现多个接口(多继承)。 - 类只能继承一个抽象类(单继承)。 ```java // 定义接口 interface Flyable { void fly(); } interface Swimmable { void swim(); } // 实现多个接口的类 class Bird implements Flyable, Swimmable { // 实现接口中的方法 public void fly() { System.out.println("Bird is flying"); } public void swim() { System.out.println("Bird is swimming"); } } // 主类 public class Main { public static void main(String[] args) { // 创建实现多个接口的对象 Bird bird = new Bird(); // 调用实现的方法 bird.fly(); // 输出: Bird is flying bird.swim(); // 输出: Bird is swimming } } ``` ### 容器 #### Collection 在 Java 中,`Collection` 是一个接口,它表示一组对象的集合。`Collection` 接口是 Java 集合框架中最基本的接口之一,定义了一些操作集合的**通用方法**,例如添加、删除、遍历等。 所有集合类(例如 List、Set、Queue 等)都直接或间接地继承自 Collection 接口。 - `boolean add(E e)`:将指定的元素添加到集合中(可选操作)。 - `boolean remove(Object o)`:从集合中移除指定的元素(可选操作)。 - `boolean contains(Object o)`:如果集合中包含指定的元素,则返回 `true`。 - `int size()`:返回集合中的元素个数。 - `void clear()`:移除集合中的所有元素。 - `boolean isEmpty()`:如果集合为空,则返回 `true`。 ```java import java.util.ArrayList; import java.util.Collection; public class CollectionExample { public static void main(String[] args) { // 创建一个 Collection 对象,使用 ArrayList 作为实现类 Collection fruits = new ArrayList<>(); // 添加元素到集合中 fruits.add("Apple"); fruits.add("Banana"); fruits.add("Cherry"); System.out.println("添加元素后集合大小: " + fruits.size()); // 输出集合大小 // 检查集合是否包含某个元素 System.out.println("集合中是否包含 'Banana': " + fruits.contains("Banana")); // 从集合中移除元素 fruits.remove("Banana"); System.out.println("移除 'Banana' 后集合大小: " + fruits.size()); // 清空集合 fruits.clear(); System.out.println("清空集合后,集合是否为空: " + fruits.isEmpty()); } } ``` #### Iterator 在 Java 中,`Iterator` 是一个接口,遍历集合元素。Collection 接口中定义了 iterator() 方法,返回一个 Iterator 对象。 `Iterator` 接口中包含以下主要方法: 1. `hasNext()`:如果迭代器还有下一个元素,则返回 `true`,否则返回 `false`。 2. `next()`:返回迭代器的下一个元素,并将迭代器移动到下一个位置。 3. `remove()`:从迭代器当前位置删除元素。该方法是可选的,不是所有的迭代器都支持。 ```java import java.util.ArrayList; import java.util.Iterator; public class Main { public static void main(String[] args) { // 创建一个 ArrayList 集合 ArrayList list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); int size = list.size(); // 获取列表大小 System.out.println("Size of list: " + size); // 输出 3 // 获取集合的迭代器 Iterator iterator = list.iterator(); // 使用迭代器遍历集合并输出元素 while (iterator.hasNext()) { Integer element = iterator.next(); System.out.println(element); } } } ``` #### ArrayList `ArrayList` 是 List 接口的一种实现,而 List 接口又继承自 Collection 接口。包括 `add()`、`remove()`、`contains()` 等。 ![image-20240227133714509](https://pic.bitday.top/i/2025/03/19/u6vct4-2.png) #### HashSet ![image-20240227150219184](https://pic.bitday.top/i/2025/03/19/u6wmxd-2.png) #### HashMap ![image-20240227152019078](https://pic.bitday.top/i/2025/03/19/u6utmg-2.png) ```java // 使用 entrySet() 方法获取 Map 中所有键值对的集合,并使用增强型 for 循环遍历键值对 System.out.println("Entries in the map:"); for (Map.Entry entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); System.out.println("Key: " + key + ", Value: " + value); } ``` #### PriorityQueue 默认是小根堆,输出1,2,5,8 ```java import java.util.PriorityQueue; public class Main { public static void main(String[] args) { // 创建一个 PriorityQueue 对象 PriorityQueue pq = new PriorityQueue<>(); // 添加元素到队列 pq.offer(5); pq.offer(2); pq.offer(8); pq.offer(1); // 打印队列中的元素 System.out.println("Elements in the priority queue:"); while (!pq.isEmpty()) { System.out.println(pq.poll()); } } } ``` 1. `offer()` 方法用于将元素插入到队列中 2. `poll()` 方法用于移除并返回队列中的头部元素 3. `peek()` 方法用于返回队列中的头部元素但不移除它。 ### JAVA异常处理 ```java public class ExceptionExample { // 方法声明中添加 throws 关键字,指定可能抛出的异常类型 public static void main(String[] args) throws SomeException, AnotherException { try { // 可能会抛出异常的代码块 if (someCondition) { throw new SomeException("Something went wrong"); } } catch (SomeException e) { // 处理 SomeException 异常 System.out.println("Caught SomeException: " + e.getMessage()); } catch (AnotherException e) { // 处理 AnotherException 异常 System.out.println("Caught AnotherException: " + e.getMessage()); } finally { // 不管是否发生异常,都会执行的代码块 System.out.println("End of try-catch block"); } } } // 自定义异常类,继承自 Exception 类 public class SomeException extends Exception { // 构造方法,用于设置异常信息 public SomeException(String message) { // 调用父类的构造方法,设置异常信息 super(message); } } ``` ## 好用的方法 ### toString() **Arrays.toString()**转一维数组 **Arrays.deepToString()**转二维数组 这个方法是是用来将数组转换成String类型输出的,入参可以是long,float,double,int,boolean,byte,object 型的数组。 ```java import java.util.Arrays; public class Main { public static void main(String[] args) { // 一维数组示例 int[] oneD = {1, 2, 3, 4, 5}; System.out.println("一维数组输出: " + Arrays.toString(oneD)); // 二维数组示例 int[][] twoD = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // 使用 Arrays.deepToString() 输出二维数组 System.out.println("二维数组输出: " + Arrays.deepToString(twoD)); } } ``` **自定义对象的`toString()` 方法** 每个 Java 对象默认都有 `toString()` 方法(可以根据需要覆盖) 当直接打印一个没有重写 `toString()` 方法的对象时,其输出格式通常为: ```java java.lang.Object@15db9742 ``` 当打印重写`toString()` 方法的对象时: ```java class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public String toString() { return "Person{name='" + name + "', age=" + age + "}"; } } public class Main { public static void main(String[] args) { Person person = new Person("Alice", 30); System.out.println(person); //会自动调用对象的 toString() 方法 //Person{name='Alice', age=30} } } ``` ### 类加载器和获取资源文件路径 在Java中,类加载器的主要作用是根据**类路径(Classpath)**加载类文件以及其他资源文件。 **类路径**是JVM在运行时用来查找类文件和资源文件的一组目录或JAR包。在许多项目(例如Maven或Gradle项目)中,`src/main/resources`目录下的内容在编译时会被复制到输出目录(如`target/classes`),`src/main/java` 下编译后的 class 文件也会放到这里。 ```text src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ └── App.java │ └── resources/ │ ├── application.yml │ └── static/ │ └── logo.png └── test/ ├── java/ │ └── com/ │ └── example/ │ └── AppTest.java └── resources/ └── test-data.json 映射到 target/ 后: target/ ├── classes/ ← 主代码和资源的输出根目录 │ ├── com/ │ │ └── example/ │ │ └── App.class ← 编译自 src/main/java/com/example/App.java │ ├── application.yml ← 复制自 src/main/resources/application.yml │ └── static/ │ └── logo.png ← 复制自 src/main/resources/static/logo.png └── test-classes/ ← 测试代码和测试资源的输出根目录 ├── com/ │ └── example/ │ └── AppTest.class ← 编译自 src/test/java/com/example/AppTest.java └── test-data.json ← 复制自 src/test/resources/test-data.json ``` ------------ ```java // 获取 resources 根目录下的 emp.xml 文件路径 String empFileUrl = this.getClass().getClassLoader().getResource("emp.xml").getFile(); // 获取 resources/static 目录下的 tt.img 文件路径 URL resourceUrl = getClass().getClassLoader().getResource("static/tt.img"); String ttImgPath = resourceUrl != null ? resourceUrl.getFile() : null; ``` 1. `this.getClass()`:获取当前对象(即调用该代码的对象)的 Class 对象。 2. `.getClassLoader()`:获取该 Class 对象的类加载器(ClassLoader)。 3. `.getResource("emp.xml")`:从类路径中获取名为 "emp.xml" 的资源,并返回一个 URL 对象,该 URL 对象指向 "emp.xml" 文件的位置。 4. `.getFile()`:从 URL 对象中获取文件路径部分,即获取 "emp.xml" 文件的绝对路径字符串。 **类路径(Classpath)**是 Java 虚拟机(JVM)用于查找类文件和其他资源文件的一组路径。 类加载器的主要作用之一就是从类路径中加载类文件和其他资源文件。 ### 反射 反射技术,指的是加载类的字节码到内存,并以编程的方法解刨出类中的各个成分(**成员变量、方法、构造器**等)。 ![1668575796295](https://pic.bitday.top/i/2025/03/19/u75k9n-2.png) **反射技术例子**:IDEA通过反射技术就可以获取到类中有哪些方法,并且把方法的名称以提示框的形式显示出来,所以你能看到这些提示了。 ![1668576426355](https://pic.bitday.top/i/2025/03/19/u6vwpj-2.png) **1.获取类的字节码(Class对象)**:有三种方法 ```java public class Test1Class{ public static void main(String[] args){ Class c1 = Student.class; System.out.println(c1.getName()); //获取全类名:edu.whut.pojo.Student System.out.println(c1.getSimpleName()); //获取简单类名: Student Class c2 = Class.forName("com.itheima.d2_reflect.Student"); //全类名 System.out.println(c1 == c2); //true Student s = new Student(); Class c3 = s.getClass(); System.out.println(c2 == c3); //true } } ``` **2.获取类的构造器** - 定义类 ```java public class Cat{ private String name; private int age; public Cat(){ } private Cat(String name, int age){ } } ``` - 获取构造器列表 ```java public class TestConstructor { @Test public void testGetAllConstructors() { // 1. 获取类的 Class 对象 Class c = Cat.class; // 2. 获取类的全部构造器(包括public、private等) Constructor[] constructors = c.getDeclaredConstructors(); // 3. 遍历并打印构造器信息 for (Constructor constructor : constructors) { System.out.println( constructor.getName() + " --> 参数个数:" + constructor.getParameterCount() ); } } } ``` `c.getDeclaredConstructors()` 会返回所有声明的构造器(包含私有构造器),而 `c.getConstructors()` 只会返回公共构造器。 `constructor.getParameterCount()` 用于获取该构造器的参数个数。 获取某个构造器:**指定参数类型!** ```java public class Test2Constructor(){ @Test public void testGetConstructor(){ //1、反射第一步:必须先得到这个类的Class对象 Class c = Cat.class; /2、获取private修饰的有两个参数的构造器,第一个参数String类型,第二个参数int类型 Constructor constructor = c.getDeclaredConstructor(String.class,int.class); constructor.setAccessible(true); //禁止检查访问权限,可以使用private构造函数 Cat cat=(Cat)constructor.newInstance("叮当猫",3); //初始化Cat对象 } } ``` `c.getDeclaredConstructor(String.class, int.class)`:根据参数列表获取特定的构造器。 如果构造器是private修饰的,先需要调用`setAccessible(true)` 表示禁止检查访问控制,然后再调用`newInstance(实参列表)` 就可以执行构造器,完成对象的初始化了。 **3.获取类的成员变量** ![1668579517323](https://pic.bitday.top/i/2025/03/19/u6v7a4-2.png) ![1668580075962](https://pic.bitday.top/i/2025/03/19/u760yh-2.png) 不管是设置值还是获取值,都需要: 1. 拿到 `Field` 对象。 2. 指定操作**哪个对象**的该字段。 3. 对于私有字段,还需要调用 `setAccessible(true)` 来关闭访问检查。 **4.获取类的成员方法** ![1668580761089](https://pic.bitday.top/i/2025/03/19/u6wt58-2.png) 获取**单个**指定的成员方法:第一个参数填**方法名**、第二个参数填方法中的**参数类型** ![1668581678388](https://pic.bitday.top/i/2025/03/19/u6w6fd-2.png) 执行:第一个参数传入一个**对象**,然后是若干方法参数(无参可不写)... ![1668581800777](https://pic.bitday.top/i/2025/03/19/u6weqw-2.png) 示例:`Cat` 类与测试类 ```java public class Cat { private String name; public int age; public Cat() { this.name = "Tom"; this.age = 1; } public void meow() { System.out.println("Meow! My name is " + this.name); } private void purr() { System.out.println("Purr... I'm a happy cat!"); } } ``` ```java import org.junit.Test; import java.lang.reflect.Field; import java.lang.reflect.Method; public class FieldReflectionTest { @Test public void testFieldAccess() throws Exception { // 1. 获取 Cat 类的 Class 对象 Class catClass = Cat.class; // 2. 创建 Cat 对象实例 Cat cat = new Cat(); // ---------------------- // A. 获取 public 字段 // ---------------------- Field ageField = catClass.getField("age"); // 只能获取public字段 System.out.println("初始 age = " + ageField.get(cat)); // 读取 age 的值 // 设置 age 的值 ageField.set(cat, 5); System.out.println("修改后 age = " + ageField.get(cat)); // ---------------------- // B. 获取 private 字段 // ---------------------- Field nameField = catClass.getDeclaredField("name"); // 获取私有字段 nameField.setAccessible(true); // 关闭权限检查 System.out.println("初始 name = " + nameField.get(cat)); // 设置 name 的值 nameField.set(cat, "Jerry"); System.out.println("修改后 name = " + nameField.get(cat)); } @Test public void testMethodAccess() throws Exception { // 1. 获取 Cat 类的 Class 对象 Class catClass = Cat.class; // 2. 创建 Cat 对象实例 Cat cat = new Cat(); // ---------------------- // A. 获取并调用 public 方法 // ---------------------- // 获取名为 "meow"、无参数的方法 Method meowMethod = catClass.getMethod("meow"); // 调用该方法 meowMethod.invoke(cat); // ---------------------- // B. 获取并调用 private 方法 // ---------------------- // 获取名为 "purr"、无参数的私有方法 Method purrMethod = catClass.getDeclaredMethod("purr"); purrMethod.setAccessible(true); // 关闭权限检查 purrMethod.invoke(cat); } } ``` ### 注解 在 Java 中,注解用于给程序元素(类、方法、字段等)**添加元数据**,这些元数据可被编译器、工具或运行时**反射读取**,以实现配置、检查、代码生成以及框架支持(如依赖注入、AOP 等)功能,而不直接影响代码的业务逻辑。 比如:Junit框架的@Test注解可以用在方法上,用来标记这个方法是测试方法,被`@Test`标记的方法能够被Junit框架执行。 再比如:@Override注解可以用在方法上,用来标记这个方法是重写方法,被`@Override`注解标记的方法能够被IDEA识别进行语法检查。 #### 使用注解 **定义注解** 使用 `@interface` 定义注解 ```java // 定义注解 @Retention(RetentionPolicy.RUNTIME) // 定义注解的生命周期 @Target(ElementType.METHOD) // 定义注解可以应用的Java元素类型 public @interface MyAnnotation { // 定义注解的元素(属性) String description() default "This is a default description"; int value() default 0; } ``` **元注解** **是修饰注解的注解**。 @Retention(RetentionPolicy.RUNTIME) //指定注解的生命周期,即在运行时有效,可用于反射等用途。 @Target(ElementType.METHOD) //方法上的注解 @Target(ElementType.TYPE) //类上的注解(包含类、接口、枚举等类型) **简化使用**:如果注解中只有一个属性需要设置,而且该属性名为 `value`,则在使用时可以省略属性名 ```java @MyAnnotation(5) // 等同于 @MyAnnotation(value = 5) public void someMethod() { // 方法实现 } ``` 当需要为注解的多个属性赋值时,传参必须指明属性名称: ```java @MyAnnotation(value = 5, description = "Specific description") public void anotherMethod() { // 方法实现 } ``` 如果所有属性都使用默认值,可以直接使用注解而不传入任何参数: ```java @MyAnnotation public void anotherMethod() { // 方法实现 } ``` #### 解析注解 ```java public class MyClass { @MyAnnotation(value = "specific value") public void myMethod() { // 方法实现 } } ``` ```java import java.lang.reflect.Method; public class AnnotationReader { public static void main(String[] args) throws NoSuchMethodException { // 获取MyClass的Class对象 Class obj = MyClass.class; // 获取myMethod方法的Method对象 Method method = obj.getMethod("myMethod"); // 获取方法上的MyAnnotation注解实例 MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); if (annotation != null) { // 输出注解的value值 System.out.println("注解的value: " + annotation.value()); } } } ``` 快速检查某个注解是否存在于`method` 上 ```java if (method.isAnnotationPresent(MyAnnotation.class)) { // 如果存在MyAnnotation注解,则执行相应逻辑 } ``` 检查方法 `method` 上是否存在 `MyAnnotation` 注解。如果存在,就返回该注解的实例,否则返回 `null` ```java MyAnnotation annotation = method.getAnnotation(MyAnnotation.class); ``` ### Junit 单元测试 **步骤** **1.导入依赖** 将 JUnit 框架的 jar 包添加到项目中(注意:IntelliJ IDEA 默认集成了 JUnit,无需手动导入)。 **2.编写测试类** - 为待测业务方法创建对应的测试类。 - 测试类中定义测试方法,要求方法必须为 `public` 且返回类型为 `void`。 **3.添加测试注解** 在测试方法上添加 `@Test` 注解,确保 JUnit 能自动识别并执行该方法。 **4.运行测试** 在测试方法上右键选择“JUnit运行”。 - 测试通过显示绿色标志; - 测试失败显示红色标志。 ```java public class UserMapperTest { @Test public void testListUser() { UserMapper userMapper = new UserMapper(); List list = userMapper.list(); Assert.assertNotNull("User list should not be null", list); list.forEach(System.out::println); } } ``` **注意**,如果需要使用**依赖注入**,需要在测试类上加`@SpringBootTest`注解 它会启动 Spring 应用程序上下文,并在测试期间模拟运行整个 Spring Boot 应用程序。这意味着你可以在集成测试中使用 Spring 的各种功能,例如**自动装配、依赖注入、配置加载**等 ```java @RunWith(SpringRunner.class) @SpringBootTest public class UserMapperTest { @Autowired private UserMapper userMapper; @Test public void testListUser() { List list = userMapper.list(); Assert.assertNotNull("User list should not be null", list); list.forEach(System.out::println); } } ``` 写了`@Test`注解,那么该测试函数就可以直接运行!若一个测试类中写了多个测试方法,可以全部执行! ![image-20240307173454288](https://pic.bitday.top/i/2025/03/19/u6um98-2.png) 原理可能是: ```java //自定义注解 @Retention(RetentionPolicy.RUNTIME) //指定注解在运行时可用,这样才能通过反射获取到该注解。 @Target(ElementType.METHOD) //指定注解可用于方法上。 public @interface MyTest { } public class AnnotationTest4 { @MyTest public void test() { System.out.println("===test4==="); } public static void main(String[] args) throws Exception { AnnotationTest4 a = new AnnotationTest4(); Class c = AnnotationTest4.class; // 获取当前类中声明的所有方法 Method[] methods = c.getDeclaredMethods(); // 遍历方法,检查是否使用了 @MyTest 注解 for (Method method : methods) { if (method.isAnnotationPresent(MyTest.class)) { // 如果标注了 @MyTest,就通过反射调用该方法 method.invoke(a); } } } } ``` 在Springboot中,如何快速生成单元测试? 选中类名,右键: image-20240815093428359 ### 对象拷贝属性 ```java public void save(EmployeeDTO employeeDTO) { Employee employee = new Employee(); //对象属性拷贝 BeanUtils.copyProperties(employeeDTO, employee); } ``` employeeDTO的内容拷贝给employee ```java StartOrStopDTO dto = new StartOrStopDTO(1, 100L); // 用 Builder 拷贝 id 和 status Employee employee = Employee.builder() .id(dto.getId()) .status(dto.getStatus()) .build(); ```