md_files/自学/Java笔记本.md

2114 lines
61 KiB
Markdown
Raw Permalink Normal View History

2025-03-18 12:46:59 +08:00
## 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` | 查找类 |
| `shift+shift` | 在文件中查找代码 |
| `alt+回车` | service接口类跳转到实现类 |
2025-03-18 12:46:59 +08:00
调试快捷键:
| 快捷键 | 功能 |
| :----------- | :--------------------- |
| `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" 的缩写。这个方法会在输出文本后自动换行.
2025-03-18 12:46:59 +08:00
```java
System.out.println("nihao "+1.3331) #Java 会自动将数值转换为字符串
```
2025-03-18 12:46:59 +08:00
3. 一维数组创建:
2025-03-18 12:46:59 +08:00
```java
// 方式1先声明再指定长度默认值为0、null等
int[] arr1 = new int[10]; // 创建一个长度为10的int数组
2025-03-18 12:46:59 +08:00
// 方式2使用初始化列表直接创建数组
int[] arr2 = {1, 2, 3, 4, 5}; // 创建并初始化一个包含5个元素的int数组
2025-03-18 12:46:59 +08:00
String[] strs = {"eat", "tea", "tan", "ate", "nat", "bat"};
// 方式3结合new关键字和初始化列表创建数组常用于明确指定类型时
int[] arr3 = new int[]{1, 2, 3, 4, 5}; // 与方式2效果相同
```
2025-03-18 12:46:59 +08:00
2025-03-18 12:46:59 +08:00
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); //通过字符数组创建
```
2025-03-18 12:46:59 +08:00
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
//纯状态枚举 常见于 switch-case、简单条件判断。
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 {
//创建7个 DayOfWeek 类型的对象分别传入构造参数chineseName和dayNumber它们叫“枚举常量”
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
}
}
```
**枚举类你只需要使用,而不用创建对象**类内部已经定义好了MONDAY、TUESDAY...对象。
#### 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<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
// 浅拷贝:新列表中的元素引用和原列表中的是同一份
List<Integer> shallowCopy = new ArrayList<>(list);
```
可变对象,浅拷贝修改对象会出错!
```java
List<Box> list = new ArrayList<>();
list.add(new Box(1));
list.add(new Box(2));
list.add(new Box(3));
List<Box> 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<MyObject> 的例子
List<MyObject> originalList = new ArrayList<>();
originalList.add(new MyObject(10));
originalList.add(new MyObject(20));
List<MyObject> deepCopy = new ArrayList<>();
for (MyObject obj : originalList) {
deepCopy.add(new MyObject(obj)); // 每个元素都创建一个新的对象
}
```
2025-03-18 12:46:59 +08:00
#### 日期
在Java中
- 代表年月日的类型是 `LocalDate``LocalDate` 类位于 `java.time` 包下,用于表示没有时区的日期,如年、月、日。
- 代表年月日时分秒的类型是 `LocalDateTime``LocalDateTime` 类也位于 `java.time` 包下,用于表示没有时区的日期和时间,包括年、月、日、时、分、秒。
LocalDateTime.now(),获取当前时间
#### 访问修饰符
**public公共的**
使用public修饰的成员可以被任何其他类访问无论这些类是否属于同一个包。
例如如果一个类的成员被声明为public那么其他类可以通过该类的对象直接访问该成员。
**protected受保护的**
使用protected修饰的成员可以被**同一个包**中的其他类访问,也可以被**不同包中的子类**访问。
与包访问级别相比protected修饰符提供了更广泛的访问权限。
**default (no modifier)(默认的,即包访问级别)**
如果没有指定任何访问修饰符,则默认情况下成员具有包访问权限。
在同一个包中的其他类可以访问默认访问级别的成员,但是在不同包中的类不能访问。
**private**(私有的):
使用private修饰的成员只能在声明它们的**类内部**访问,其他任何类都不能访问这些成员。
这种访问级别提供了最高的封装性和安全性。
如果您在另一个类中实例化了包含私有成员的类,那么您无法直接访问该类的私有成员。但是,您可以通过**公共方法**来间接地访问和操作私有成员。
则每个实例都有自己的一份拷贝,只有当变量被声明为 static 时,变量才是类级别的,会被所有实例共享。
```java
2025-03-18 12:46:59 +08:00
// 文件com/example/PrivateExample.java
package com.example;
public class PrivateExample {
private int privateVar = 30;
// 公共方法,用于访问私有成员
public int getPrivateVar() {
return privateVar;
}
}
```
修饰符不仅可以用来修饰成员变量和方法,也可以用来**修饰类**。顶级类只能使用 `public` 或默认(即不写任何修饰符,称为包访问权限)。内部类可以使用所有访问修饰符(`public``protected``private` 和默认),这使得你可以更灵活地控制嵌套类的访问范围。
```java
2025-03-18 12:46:59 +08:00
public class OuterClass {
// 内部类使用private只能在OuterClass内部访问
private class InnerPrivateClass {
// ...
}
// 内部类使用protected同包以及其他包中的子类可以访问
protected class InnerProtectedClass {
// ...
}
// 内部类使用默认访问权限,只在同包中可见
class InnerDefaultClass {
// ...
}
// 内部类使用public任何地方都可访问但访问时需要通过OuterClass对象
public class InnerPublicClass {
// ...
}
}
```
#### 四种内部类
下面是四种内部类(成员内部类、局部内部类、静态内部类和匿名内部类)的示例代码,展示了如何用每一种方式来实现`Runnable``run()`方法并创建线程。
**1) 成员内部类**
2025-03-18 12:46:59 +08:00
定义位置:成员内部类定义在外部类的**成员位置**。
2025-03-18 12:46:59 +08:00
访问权限:可以无限制地访问外部类的所有成员,**包括私有成员**。
2025-03-18 12:46:59 +08:00
实例化方式:需要先创建外部类的实例,然后才能创建内部类的实例。
2025-03-18 12:46:59 +08:00
修改限制:不能有静态字段和静态方法(除非声明为常量`final static`)。**成员内部类属于外部类的一个实例,不能独立存在于类级别上。**
2025-03-18 12:46:59 +08:00
用途:适用于内部类与外部类关系密切,需要频繁访问外部类成员的情况。
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
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.局部内部类**
2025-03-18 12:46:59 +08:00
定义位置:局部内部类定义在**一个方法或任何块内**if语句、循环语句内
2025-03-18 12:46:59 +08:00
访问权限:只能访问**所在方法**的`final`或事实上的`final`(即不被后续修改的)局部变量和外部类的成员变量(同成员内部类)。
2025-03-18 12:46:59 +08:00
实例化方式:只能在定义它们的块中创建实例。
修改限制:同样不能有静态字段和方法。
用途:适用于只在方法或代码块中使用的类,有助于将实现细节隐藏在方法内部。
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
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`修饰。
访问权限:只能直接访问外部类的静态成员,访问非静态成员需要通过外部类实例。
2025-03-18 12:46:59 +08:00
实例化方式:**可以直接创建,不需要外部类的实例**。
2025-03-18 12:46:59 +08:00
修改限制:可以有自己的静态成员。
2025-03-18 12:46:59 +08:00
用途:适合当内部类工作不依赖外部类实例时使用,常用于实现与外部类关系不那么密切的帮助类。
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
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.匿名内部类**
**在定义的同时直接实例化**,而不需要显式地声明一个子类的名称。
定义位置:在需要使用它的地方立即**定义**和**实例化**。
2025-03-18 12:46:59 +08:00
访问权限:类似局部内部类,只能访问`final`或事实上的`final`局部变量。
2025-03-18 12:46:59 +08:00
实例化方式:在定义时就实例化,不能显式地命名构造器。
2025-03-18 12:46:59 +08:00
修改限制:不能有任何静态成员。
2025-03-18 12:46:59 +08:00
用途:适用于创建一次性使用的实例,通常用于**接口或抽象类**的实现。但**匿名内部类**并不限于接口或抽象类,只要是**非 `final` 的普通类**,都有机会通过匿名内部类来“现场”创建一个**它的子类实例**。
2025-03-18 12:46:59 +08:00
```java
abstract class Animal {
public abstract void makeSound();
2025-03-18 12:46:59 +08:00
}
public class Main {
2025-03-18 12:46:59 +08:00
public static void main(String[] args) {
// 匿名内部类:临时创建一个 Animal 的子类并实例化
Animal dog = new Animal() { // 注意这里的 new Animal() { ... }
@Override
public void makeSound() {
System.out.println("汪汪汪!");
2025-03-18 12:46:59 +08:00
}
};
2025-03-18 12:46:59 +08:00
dog.makeSound(); // 输出:汪汪汪!
2025-03-18 12:46:59 +08:00
}
}
```
如何理解?可以对比**普通子类(显式定义)**,即显示定义了**Dog**来继承Animal
2025-03-18 12:46:59 +08:00
```java
// 抽象类或接口
abstract class Animal {
public abstract void makeSound();
2025-03-18 12:46:59 +08:00
}
// 显式定义一个具名的子类
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪!");
}
}
2025-03-18 12:46:59 +08:00
public class Main {
public static void main(String[] args) {
// 实例化具名的子类
Animal dog = new Dog();
dog.makeSound(); // 输出:汪汪汪!
}
}
2025-03-18 12:46:59 +08:00
```
#### Lambda表达式
函数式接口:有且仅有一个**抽象方法**的接口。
2025-03-18 12:46:59 +08:00
**`@FunctionalInterface` 注解**:这是一个可选的注解,用于表示接口是一个函数式接口。虽然不是强制的,但它可以帮助编译器识别意图,并检查接口是否确实只有一个抽象方法。
这个时候可以用Lambda代替匿名内部类
```java
2025-03-18 12:46:59 +08:00
public class LambdaExample {
// 定义函数式接口doSomething 有两个参数
@FunctionalInterface
interface MyInterface {
void doSomething(int a, int b);
}
public static void main(String[] args) {
// 使用匿名内部类实现接口方法
MyInterface obj = new MyInterface() {
@Override
public void doSomething(int a, int b) {
System.out.println("参数a: " + a + ", 参数b: " + b);
}
};
obj.doSomething(5, 10);
}
2025-03-18 12:46:59 +08:00
public static void main(String[] args) {
// 使用 Lambda 表达式实现接口方法
MyInterface obj = (a, b) -> {
System.out.println("参数a: " + a + ", 参数b: " + b);
};
obj.doSomething(5, 10);
}
}
```
2025-03-18 12:46:59 +08:00
**lambda表达式格式**`(参数列表) -> { 代码块 }`
`(参数列表) ->表达式`
如果上述`MyInterface`接口的`doSomething()`方法不接受任何参数并且没有返回值:
2025-03-18 12:46:59 +08:00
```java
// Lambda 表达式(无参数)
MyInterface obj = () -> {
System.out.println("doSomething 被调用,无参数!");
};
```
2025-03-18 12:46:59 +08:00
**以下是lambda表达式的重要特征:**
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
2025-03-18 12:46:59 +08:00
可选的参数圆括号:**一个参数无需定义圆括号**,但无参数或多个参数需要定义圆括号。
可选的大括号:如果主体**只有一个语句,可以不使用大括号**。
2025-03-18 12:46:59 +08:00
可选的返回关键字如果主体只有一个表达式返回值则编译器会自动返回值使用大括号需显示retrun如果函数是void则不需要返回值。
```java
// 定义一个函数式接口,只有一个抽象方法
2025-03-18 12:46:59 +08:00
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
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
```
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
public class Main {
public static void main(String[] args) {
List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Date");
// 使用 Lambda 表达式迭代列表,这段 lambda就是在“实现” void accept(String item) 这个方法——把每个元素传给 accept然后打印它。
2025-03-18 12:46:59 +08:00
list.forEach(item -> System.out.println(item));
}
}
```
示例2为什么可以使用 **Lambda 表达式自定义排序**
2025-03-18 12:46:59 +08:00
因为**`Comparator<T>` 是一个函数式接口**,**只有一个抽象方法 `compare(T o1, T o2)`**
2025-03-18 12:46:59 +08:00
```java
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2); // 唯一的抽象方法
// 其他方法(如 thenComparing、reversed都是默认方法或静态方法不影响函数式接口特性
}
```
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> names = Arrays.asList("John", "Jane", "Adam", "Dana");
// 使用Lambda表达式排序
Collections.sort(names, (a, b) -> a.compareTo(b));
2025-03-18 12:46:59 +08:00
// 输出排序结果
names.forEach(name -> System.out.println(name));
}
}
```
#### 静态成员变量的初始化
静态成员变量属于类级别,在类加载时完成初始化。初始化方式主要有两种:
**1.静态初始化块Static Initialization Block**
例1
```java
2025-03-18 12:46:59 +08:00
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
2025-03-18 12:46:59 +08:00
静态代码块1执行
静态代码块2执行
main方法执行
```
**说明:**
类加载时依次执行所有静态代码块,然后执行 `main` 方法。
例2
```java
public static final SpaceUserAuthConfig SPACE_USER_AUTH_CONFIG;
static {
String json = ResourceUtil.readUtf8Str("biz/spaceUserAuthConfig.json");
SPACE_USER_AUTH_CONFIG = JSONUtil.toBean(json, SpaceUserAuthConfig.class);
}
```
静态初始化块会在类第一次加载到 JVM 时执行一次,用于对静态变量做复杂的初始化。
2025-03-18 12:46:59 +08:00
**2.在声明时直接初始化**
```java
2025-03-18 12:46:59 +08:00
public class MyClass {
// 直接在声明时初始化静态成员变量
public static int staticVariable = 42;
}
```
静态成员变量的访问不需要创建 `MyClass` 的实例,可以直接通过类名访问:
```java
2025-03-18 12:46:59 +08:00
int value = MyClass.staticVariable;
MyClass obj = new MyClass();
System.out.println("obj.num1 = " + obj.staticVariable); #通过示例访问也可以
```
#### 静态方法
静态方法属于**类级别**,不依赖于任何具体实例
静态方法访问规则:
- **可以直接访问:**
- 类中的其他静态成员变量。
- 类中的静态方法。
- **不能直接访问:**
2025-03-18 12:46:59 +08:00
- 非静态成员变量。
2025-03-18 12:46:59 +08:00
- 非静态方法(必须通过对象实例访问)。
```java
2025-03-18 12:46:59 +08:00
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
2025-03-18 12:46:59 +08:00
MyClass.staticMethod(); // 通过类名直接调用静态方法
```
#### super关键字
`super` 关键字有两种主要的使用方法:访问父类的成员和调用父类的构造方法。
1)访问父类的成员
2025-03-18 12:46:59 +08:00
可以使用 `super` 关键字来引用父类的字段或方法。这在子类中**存在同名的字段或方法**时特别有用。
2025-03-18 12:46:59 +08:00
因为父类的成员变量和方法都是默认的访问修饰符可以继承给子类而子类也定义了同名的xxx发生了**变量隐藏**shadowing
2025-03-18 12:46:59 +08:00
2)调用父类的构造方法
2025-03-18 12:46:59 +08:00
可以使用 `super` 关键字调用父类的构造方法。这通常在子类的构造方法中使用,用于显式地调用父类的构造方法。
```java
2025-03-18 12:46:59 +08:00
class Parent {
int num = 10; // 父类字段
2025-03-18 12:46:59 +08:00
Parent() {
System.out.println("Parent class constructor");
}
void display() {
System.out.println("Parent class method");
}
2025-03-18 12:46:59 +08:00
}
class Child extends Parent {
int num = 20; // 子类同名字段,隐藏了父类的 num
2025-03-18 12:46:59 +08:00
Child() {
super(); // 调用父类构造方法
2025-03-18 12:46:59 +08:00
System.out.println("Child class constructor");
}
void print() {
System.out.println("Child class num: " + num); // 访问子类字段
System.out.println("Parent class num: " + super.num); // 访问父类被隐藏的字段
display(); // 调用子类重写的方法
super.display(); // 明确调用父类的方法
}
2025-03-18 12:46:59 +08:00
}
public class Main {
public static void main(String[] args) {
Child obj = new Child();
System.out.println("---- Now calling print() ----");
obj.print();
2025-03-18 12:46:59 +08:00
}
}
```
运行结果:
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
Parent class constructor
Child class constructor
---- Now calling print() ----
Child class num: 20
Parent class num: 10
Parent class method
Parent class method
2025-03-18 12:46:59 +08:00
```
#### final关键字
final 关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点:
1. 修饰类类不能继承final 类中的所有成员方法都会被隐式的指定为 final 方法;
2. 修饰变量:该变量为常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。
3. 修饰符方法:方法不能重写
#### 变量修饰符的顺序
在Java中变量的修饰符应该按照规定的顺序出现通常是这样的
1. **访问修饰符**public、protected、private或者不写默认为包级访问
2. **非访问修饰符**final、static、abstract、synchronized、volatile等。
3. **数据类型**变量的数据类型如int、String、class等。
4. **变量名**:变量的名称。
```java
2025-03-18 12:46:59 +08:00
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属性**
2025-03-18 12:46:59 +08:00
### 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());
}
}
```
2025-03-18 12:46:59 +08:00
#### **JAVA**三大特性
**封装**
封装指隐藏对象的状态信息属性不允许外部对象直接访问对象的内部信息private实现。但是可以提供一些可以被外界访问的方法(public)来操作属性。
**继承**
```java
2025-03-18 12:46:59 +08:00
[修饰符] class 子类名 extends 父类名{
类体部分
}
//class C extends A, B { } // 错误C 不能同时继承 A 和 B
```
Java只支持单继承不支持多继承。一个类只能有一个父类不可以有多个父类。
Java支持多层继承(A → B → C )。
Java继承了父类**非私有**的成员变量和成员方法,但是请注意:子类是无法继承父类的构造方法的。
**多态**
指在面向对象编程中,同样的消息(方法调用)可以在不同的对象上触发不同的行为。
1. **方法重写Override**:动态多态;子类从父类继承的某个实例方法无法满足子类的功能需要时,需要在子类中对该实例方法进行重新实现,这样的过程称为重写,也叫做覆写、覆盖。
**要求**
- 必须存在继承关系(子类继承父类)。
- 子类重写的方法的访问修饰符不能比父类更严格(可以相同或更宽松)。
- 方法名、参数列表和返回值类型必须与父类中的方法完全相同Java 5 以后支持协变返回类型,即允许返回子类型)。
2. **向上转型Upcasting**:动态多态;子类对象可以赋值给父类引用,这样做可以隐藏对象的真实类型,只能调用**父类中声明的方法**。
```java
2025-03-18 12:46:59 +08:00
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;
}
}
```
2025-03-18 12:46:59 +08:00
#### 抽象类和接口
**抽象类:**
可以包含抽象方法(`abstract`)和具体方法(有方法体)。但至少有一个抽象方法。
**注意:** 抽象类不能被实例化。抽象类中的抽象方法必须显式地用 `abstract` 关键字来声明。而接口中的方法不用`abstract` 。抽象类可以 `implements` 接口,此时无需定义自己的抽象方法也可以。
抽象类可以实现接口中的所有方法,此时它也可以继续保持 `abstract`
2025-03-18 12:46:59 +08:00
如果一个子类继承了抽象类,通常必须实现抽象类中的所有抽象方法,否则该子类也必须声明为抽象类。例如:
```java
2025-03-18 12:46:59 +08:00
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 必须也声明为抽象类
```
**如何使用抽象类**
2025-03-18 12:46:59 +08:00
由于抽象类不能直接实例化,我们通常有两种方法来使用抽象类:
1. **定义一个新的子类**
创建一个子类继承抽象类并实现所有抽象方法,然后使用子类实例化对象:
```java
2025-03-18 12:46:59 +08:00
Animal animal = new Dog();
animal.makeSound(); // 输出Dog barks
```
2. **使用匿名内部类**
使用匿名内部类实现抽象类相当于临时创建了一个**未命名的子类**,并且立即实例化了这个子类的对象。
```java
2025-03-18 12:46:59 +08:00
Animal animal = new Animal() {
@Override
public void makeSound() {
System.out.println("Anonymous animal sound");
}
};
animal.makeSound(); // 输出Anonymous animal sound
```
**如何算作实现抽象方法**
```java
public interface StrategyHandler<T, D, R> {
StrategyHandler DEFAULT = (T, D) -> null;
R apply(T requestParameter, D dynamicContext) throws Exception;
}
public abstract class AbstractStrategyRouter<T, D, R> implements StrategyMapper<T, D, R>, StrategyHandler<T, D, R> {
@Getter
@Setter
protected StrategyHandler<T, D, R> defaultStrategyHandler = StrategyHandler.DEFAULT;
public R router(T requestParameter, D dynamicContext) throws Exception {
StrategyHandler<T, D, R> strategyHandler = get(requestParameter, dynamicContext);
if(null != strategyHandler) return strategyHandler.apply(requestParameter, dynamicContext);
return defaultStrategyHandler.apply(requestParameter, dynamicContext);
}
}
```
这里 `AbstractStrategyRouter` 属于是定义了普通方法 `router` ,但是 从接口继承下来的 `apply``get` 方法扔没有实现将交由继承AbstractStrategyRouter的非抽象子类来实现。
2025-03-18 12:46:59 +08:00
**接口Interface**
定义了一组方法的规范侧重于行为的约定。接口中的所有方法默认是抽象的Java 8 之后可包含默认方法和静态方法),不包含成员变量(除了常量)。
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
// 定义接口
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
}
}
```
**抽象类和接口的区别**
1. *方法实现*
*接口*
- Java 8 前:所有方法都是抽象方法,只包含方法声明。
- Java 8 及以后可包含默认方法default methods和静态方法。
*抽象类*
- 可以同时包含抽象方法(不提供实现)和具体方法(提供实现)。
2. *继承:*
- 类实现接口时,使用关键字 `implements`
- 类继承抽象类时,使用关键字 `extends`
3. *多继承*
- 类可以实现多个接口(多继承)。
- 类**只能继承一个抽象类**(单继承)。
2025-03-18 12:46:59 +08:00
### 容器
#### 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
2025-03-18 12:46:59 +08:00
import java.util.ArrayList;
import java.util.Collection;
public class CollectionExample {
public static void main(String[] args) {
// 创建一个 Collection 对象,使用 ArrayList 作为实现类
Collection<String> 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
2025-03-18 12:46:59 +08:00
import java.util.ArrayList;
import java.util.Iterator;
public class Main {
public static void main(String[] args) {
// 创建一个 ArrayList 集合
ArrayList<Integer> 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<Integer> iterator = list.iterator();
// 使用迭代器遍历集合并输出元素
while (iterator.hasNext()) {
Integer element = iterator.next();
System.out.println(element);
}
}
}
```
#### ArrayList
`ArrayList` 是 List 接口的一种实现,而 List 接口又继承自 Collection 接口。包括 `add()``remove()``contains()` 等。
2025-03-19 18:31:37 +08:00
![image-20240227133714509](https://pic.bitday.top/i/2025/03/19/u6vct4-2.png)
2025-03-18 12:46:59 +08:00
#### HashSet
2025-03-19 18:31:37 +08:00
![image-20240227150219184](https://pic.bitday.top/i/2025/03/19/u6wmxd-2.png)
2025-03-18 12:46:59 +08:00
#### HashMap
2025-03-19 18:31:37 +08:00
![image-20240227152019078](https://pic.bitday.top/i/2025/03/19/u6utmg-2.png)
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
// 使用 entrySet() 方法获取 Map 中所有键值对的集合,并使用增强型 for 循环遍历键值对
System.out.println("Entries in the map:");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println("Key: " + key + ", Value: " + value);
}
```
#### PriorityQueue
默认是小根堆输出1258
```java
2025-03-18 12:46:59 +08:00
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) {
// 创建一个 PriorityQueue 对象
PriorityQueue<Integer> 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
2025-03-18 12:46:59 +08:00
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);
}
}
```
2025-03-18 12:46:59 +08:00
## 好用的方法
### toString()
**Arrays.toString()**转一维数组
2025-03-18 12:46:59 +08:00
**Arrays.deepToString()**转二维数组
2025-03-18 12:46:59 +08:00
这个方法是是用来将数组转换成String类型输出的入参可以是longfloatdoubleintbooleanbyteobject 型的数组。
```java
2025-03-18 12:46:59 +08:00
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}
}
}
2025-03-18 12:46:59 +08:00
```
### 类加载器和获取资源文件路径
在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
2025-03-18 12:46:59 +08:00
```
------------
```java
2025-03-18 12:46:59 +08:00
// 获取 resources 根目录下的 emp.xml 文件路径
String empFileUrl = this.getClass().getClassLoader().getResource("emp.xml").getFile();
2025-03-18 12:46:59 +08:00
// 获取 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用于查找类文件和其他资源文件的一组路径。
类加载器的主要作用之一就是从类路径中加载类文件和其他资源文件。
### 反射
反射技术,指的是加载类的字节码到内存,并以编程的方法解刨出类中的各个成分(**成员变量、方法、构造器**等)。
2025-03-19 18:31:37 +08:00
![1668575796295](https://pic.bitday.top/i/2025/03/19/u75k9n-2.png)
2025-03-18 12:46:59 +08:00
**反射技术例子**IDEA通过反射技术就可以获取到类中有哪些方法并且把方法的名称以提示框的形式显示出来所以你能看到这些提示了。
2025-03-19 18:31:37 +08:00
![1668576426355](https://pic.bitday.top/i/2025/03/19/u6vwpj-2.png)
2025-03-18 12:46:59 +08:00
**1.获取类的字节码Class对象**:有三种方法
```java
2025-03-18 12:46:59 +08:00
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
2025-03-18 12:46:59 +08:00
public class Cat{
private String name;
private int age;
public Cat(){
}
private Cat(String name, int age){
}
}
```
- 获取构造器列表
```java
2025-03-18 12:46:59 +08:00
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.获取类的成员变量**
2025-03-19 18:31:37 +08:00
![1668579517323](https://pic.bitday.top/i/2025/03/19/u6v7a4-2.png)
2025-03-18 12:46:59 +08:00
2025-03-19 18:31:37 +08:00
![1668580075962](https://pic.bitday.top/i/2025/03/19/u760yh-2.png)
2025-03-18 12:46:59 +08:00
不管是设置值还是获取值,都需要:
1. 拿到 `Field` 对象。
2. 指定操作**哪个对象**的该字段。
3. 对于私有字段,还需要调用 `setAccessible(true)` 来关闭访问检查。
**4.获取类的成员方法**
2025-03-19 18:31:37 +08:00
![1668580761089](https://pic.bitday.top/i/2025/03/19/u6wt58-2.png)
2025-03-18 12:46:59 +08:00
获取**单个**指定的成员方法:第一个参数填**方法名**、第二个参数填方法中的**参数类型**
2025-03-19 18:31:37 +08:00
![1668581678388](https://pic.bitday.top/i/2025/03/19/u6w6fd-2.png)
2025-03-18 12:46:59 +08:00
执行:第一个参数传入一个**对象**,然后是若干方法参数(无参可不写)...
2025-03-19 18:31:37 +08:00
![1668581800777](https://pic.bitday.top/i/2025/03/19/u6weqw-2.png)
2025-03-18 12:46:59 +08:00
示例:`Cat` 类与测试类
```java
2025-03-18 12:46:59 +08:00
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
2025-03-18 12:46:59 +08:00
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);
}
}
```
### 注解
2025-03-18 12:46:59 +08:00
在 Java 中,注解用于给程序元素(类、方法、字段等)**添加元数据**,这些元数据可被编译器、工具或运行时**反射读取**以实现配置、检查、代码生成以及框架支持如依赖注入、AOP 等)功能,而不直接影响代码的业务逻辑。
2025-03-18 12:46:59 +08:00
比如Junit框架的@Test注解可以用在方法上,用来标记这个方法是测试方法,被`@Test`标记的方法能够被Junit框架执行。
2025-03-18 12:46:59 +08:00
再比如:@Override注解可以用在方法上,用来标记这个方法是重写方法,被`@Override`注解标记的方法能够被IDEA识别进行语法检查。
2025-03-18 12:46:59 +08:00
#### 使用注解
2025-03-18 12:46:59 +08:00
**定义注解**
2025-03-18 12:46:59 +08:00
使用 `@interface` 定义注解
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
// 定义注解
@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) //指定注解的生命周期,即在运行时有效,可用于反射等用途。
2025-03-18 12:46:59 +08:00
@Target(ElementType.METHOD) //方法上的注解
@Target(ElementType.TYPE) //类上的注解(包含类、接口、枚举等类型)
2025-03-18 12:46:59 +08:00
**简化使用**:如果注解中只有一个属性需要设置,而且该属性名为 `value`,则在使用时可以省略属性名
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
@MyAnnotation(5) // 等同于 @MyAnnotation(value = 5)
public void someMethod() {
// 方法实现
}
```
当需要为注解的多个属性赋值时,传参必须指明属性名称:
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
@MyAnnotation(value = 5, description = "Specific description")
public void anotherMethod() {
// 方法实现
}
```
2025-03-18 12:46:59 +08:00
如果所有属性都使用默认值,可以直接使用注解而不传入任何参数:
```java
@MyAnnotation
public void anotherMethod() {
// 方法实现
}
2025-03-18 12:46:59 +08:00
```
#### 解析注解
2025-03-18 12:46:59 +08:00
```java
2025-03-18 12:46:59 +08:00
public class MyClass {
@MyAnnotation(value = "specific value")
public void myMethod() {
// 方法实现
}
}
```
```java
2025-03-18 12:46:59 +08:00
import java.lang.reflect.Method;
public class AnnotationReader {
public static void main(String[] args) throws NoSuchMethodException {
// 获取MyClass的Class对象
Class<MyClass> 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<User> 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<User> 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中如何快速生成单元测试
选中类名,右键:
<img src="https://pic.bitday.top/i/2025/03/19/u6vlxn-2.png" alt="image-20240815093428359" style="zoom:80%;" />
2025-03-18 12:46:59 +08:00
### 对象拷贝属性
```java
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
//对象属性拷贝
BeanUtils.copyProperties(employeeDTO, employee,"id");
2025-03-18 12:46:59 +08:00
}
```
employeeDTO的内容拷贝给employee,跳过字段为"id"的属性。
```java
StartOrStopDTO dto = new StartOrStopDTO(1, 100L);
// 用 Builder 拷贝 id 和 status
Employee employee = Employee.builder()
.id(dto.getId())
.status(dto.getStatus())
.build();
```
## Java 8 Stream API
```java
SpaceUserRole role = SPACE_USER_AUTH_CONFIG.getRoles()
.stream() // 1
.filter(r -> r.getKey().equals(spaceUserRole)) // 2
.findFirst() // 3
.orElse(null); // 4
```
**`stream()`**
`List<SpaceUserRole>` 转换成一个 `Stream<SpaceUserRole>`Stream 是 Java 8 引入的对集合进行函数式操作的管道。
**`.filter(r -> r.getKey().equals(spaceUserRole))`**
`filter` 接受一个 `Predicate<T>`(这里是从每个 `SpaceUserRole r` 中调用 `r.getKey().equals(...)`),只保留“满足该条件”的元素,其余都丢弃。
**`.findFirst()`**
在过滤后的流中,取第一个元素,返回一个 `Optional<SpaceUserRole>`。即使流是空的,它也会返回一个空的 `Optional`,而不会抛异常。
**`.orElse(null)`**
`Optional` 中取值:如果存在就返回该值,不存在就返回 `null`
等价于下面的老式写法Java 7 及以前):
```java
SpaceUserRole role = null;
for (SpaceUserRole r : SPACE_USER_AUTH_CONFIG.getRoles()) {
if (r.getKey().equals(spaceUserRole)) {
role = r;
break;
}
}
```