Java笔记本
IDEA基础操作
Intellij Idea创建Java项目:
- 创建空项目
- 创建Java module
- 创建包 package edu.whut.xx
- 创建类,类名首字母必须大写!
IDEA快捷键:
Ctrl + L |
格式化代码 |
|---|---|
Ctrl + / |
注释/取消注释当前行 |
Ctrl + D |
复制当前行或选中的代码块 |
Ctrl + N |
查找类 |
shift+shift |
在文件中查找代码 |
alt+ enter |
“意图操作” “快捷修复” 可以1:service接口类跳转到实现 2:补全函数的返回值 |
调试快捷键:
| 快捷键 | 功能 |
|---|---|
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,这只是把它从项目中移除,然后!!打开模块所在文件夹,物理删除,才是真正完全删除。
Java基础
-
二进制:0b 八进制:0 十六进制:0x
-
在
System.out.println()方法中,"ln" 代表 "line",表示换行。因此,println实际上是 "print line" 的缩写。这个方法会在输出文本后自动换行.
System.out.println("nihao "+1.3331); #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效果相同
- 字符串创建
String str = "Hello, World!"; //(1)直接赋值
String str = new String("Hello, World!"); //使用 new 关键字
char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str = new String(charArray); //通过字符数组创建
- switch-case
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);
}
}
- 强制类型转换
double sqrted=Math.sqrt(n);
int soft_max=(int) sqrted;
- Math库常用方法
Math.pow(3, 2));
Math.sqrt(9));
Math.abs(a));
Math.max(a, b));
Math.min(a, b));
转义符的作用
防止字符被误解:
- 在字符串中,一些字符(如
"和\)有特殊的含义。例如,双引号用于标识字符串的开始和结束,反斜杠通常用于转义。所以当你希望在字符串中包含这些特殊字符时,你需要使用转义符来告诉解析器这些字符是字符串的一部分,而不是特殊符号。 - 例如,
\"表示在字符串中包含一个双引号字符,而不是字符串的结束标志。
"Hello \"World\"" => 结果是:Hello "World" (双引号被转义)
"C:\\Program Files\\App" => 结果是:C:\Program Files\App(反斜杠被转义)
如果只是"C:\Program Files\App" 那么路径就会报错
表示非打印字符:
- 转义符可以用于表示一些不可见的或非打印的控制字符,如换行符(
\n)、制表符(\t)等。这些字符无法直接通过键盘输入,所以使用转义符来表示它们。
枚举
//纯状态枚举 常见于 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);
}
}
// 携带数据的枚举, 适合“常量 + 不变数据”的场景,如 星期、货币、错误码等。
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
}
}
枚举类的构造方法必须是 private的,默认就是private的,这意味着只能在枚举内部使用这个构造方法。
枚举类你只需要使用,而不用创建对象,类内部已经定义好了MONDAY、TUESDAY...对象。
Java传参方式
基本数据类型
- 传递方式:按值传递
每次传递的是变量的值的副本**,对该值的修改不会影响原变量**。例如:
int、double、boolean等类型。
引用类型(对象)
- 传递方式:对象引用的副本传递 传递的是对象引用的一个副本,指向同一块内存区域。因此,方法内部通过该引用修改对象的状态,会影响到原对象。如数组、集合、String、以及其他所有对象类型。
- Integer 属于引用类型,变量
Integer a = 10;中的a是一个引用,它指向堆中存储的Integer对象。
注意
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。
不可变”在代码中的具体表现:所有修改操作都返回新对象
String s1 = "Hello";
String s2 = s1.concat(" World"); // 不是修改s1,而是创建新字符串
字符串常量池
String a = "1";
String b = "1";
变量 a和变量 b会指向同一个内存中的 String对象。
原理:常量池中有,就直接返回其引用;没有,就创建一个放进去再返回。
存放位置:
- Java 7 之前:字符串常量池逻辑上属于方法区(Method Area) 的运行时常量池(Runtime Constant Pool) 的一部分。而方法区的具体实现是 永久代(PermGen)。
- 问题:永久代大小有限且难以调整,容易发生
OutOfMemoryError: PermGen space。
- 问题:永久代大小有限且难以调整,容易发生
- Java 7 开始:字符串常量池被从永久代移动到了 Java 堆(Heap) 中。
- Java 8 及以后:永久代被彻底移除,取而代之的是元空间(Metaspace)(用于存类元信息、方法码等)。而字符串常量池依然留在堆中。
浅拷贝
拷贝对象本身,但内部成员(例如集合中的元素)只是复制引用,新旧对象的内部成员指向同一份内存。如果内部元素是不可变的(如 Integer、String 等),这种拷贝通常足够。如果元素是可变对象,修改其中一个对象可能会影响另一个。
回溯法用的就是浅拷贝,因为List<Integer> path; 中间的Integer是不可变对象。
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
//new ArrayList<>(list)的底层行为:
//1.新建一个空的 ArrayList实例。
//2.将原集合 list中的所有元素引用逐个复制到新集合中。
//3.返回这个新集合。
//4.新集合和原集合是两个完全独立的容器,只是内容(元素引用)相同。
List<Integer> shallowCopy = new ArrayList<>(list);
可变对象,浅拷贝修改对象会出错!
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]
深拷贝
不仅复制对象本身,还递归地复制其所有内部成员,从而生成一个完全独立的副本。即使内部元素是可变的,修改新对象也不会影响原始对象。
// 深拷贝 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)); // 每个元素都创建一个新的对象
}

日期
在Java中:
- 代表年月日的类型是
LocalDate。LocalDate类位于java.time包下,用于表示没有时区的日期,如年、月、日。 - 代表年月日时分秒的类型是
LocalDateTime。LocalDateTime类也位于java.time包下,用于表示没有时区的日期和时间,包括年、月、日、时、分、秒。
LocalDateTime.now(),获取当前时间
静态成员变量与静态方法
一、静态成员变量
- 静态成员变量使用
static修饰,属于类级别,而非对象,即所有对象共享同一份静态变量。 - 在类加载时完成初始化,且只执行一次。
- 可以通过类名直接访问,无需创建对象。
二、静态变量的初始化方式
1)静态代码块,用于对静态变量进行复杂初始化,在类第一次加载到 JVM 时执行一次。
public class MyClass {
static int num1, num2;
static {
num1 = 1;
System.out.println("静态代码块1执行");
}
static {
num2 = 3;
System.out.println("静态代码块2执行");
}
public static void main(String[] args) {
System.out.println("main方法执行");
}
}
输出:
静态代码块1执行
静态代码块2执行
main方法执行
类加载时按代码顺序依次执行所有静态代码块。
由于 main() 是静态方法,调用前类已被加载,因此静态代码块先执行。
示例 2:静态代码块 vs 构造方法
class Demo {
static {
System.out.println("静态代码块");
}
Demo() {
System.out.println("构造方法");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("main开始");
Demo d1 = new Demo();
Demo d2 = new Demo();
}
}
输出:
main开始
静态代码块
构造方法
构造方法
说明:静态代码块仅在类首次加载时执行一次,构造方法在每次 new 对象时执行。
2)声明时直接初始化
public class MyClass {
public static int staticVariable = 42;
}
类加载时 staticVariable 直接被赋值为 42。
3)通过静态方法赋值(运行时)
public class GlobalCounter {
public static int currentCount;
public static void initializeCounter(int startValue) {
currentCount = startValue;
}
public static void incrementCounter() {
currentCount++;
}
}
public class Main {
public static void main(String[] args) {
GlobalCounter.initializeCounter(100);
System.out.println(GlobalCounter.currentCount); // 输出 100
GlobalCounter.incrementCounter();
System.out.println(GlobalCounter.currentCount); // 输出 101
}
}
只有 静态方法 才能在不创建对象的情况下访问或修改静态变量。
三、静态方法
-
用
static修饰的方法,属于类本身,不依赖任何实例。 -
通过类名直接调用,无需创建对象。
public class MathUtils {
public static int square(int n) {
return n * n;
}
}
int result = MathUtils.square(5); // 直接通过类名调用
四、访问静态成员变量
int value = MyClass.staticVariable; // ✅ 推荐
MyClass obj = new MyClass();
System.out.println(obj.staticVariable); // ✅ 可行,但不推荐
静态成员属于类,与对象无关;推荐使用类名访问。
变量修饰符final、static...
在Java中,变量的修饰符应该按照规定的顺序出现,通常是这样的:
- 访问修饰符:public、protected、private,或者不写(默认为包级访问)。
- 非访问修饰符:final、static、abstract、synchronized、volatile等。
- 数据类型:变量的数据类型,如 int、String、class 等。
- 变量名:变量的名称。
public static final int MAX_COUNT = 100; #定义常量
protected static volatile int counter; #定义成员变量
虽然final、static都是非访问修饰符,但是一般都是 static final ,不推荐反过来!!!
final关键字
final 关键字,意思是最终的、不可修改的,最见不得变化 ,用来修饰类、方法和变量,具有以下特点:
- 修饰类:类不能继承,final 类中的所有成员方法都会被隐式的指定为 final 方法;
- 修饰变量:该变量为常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象。
- 修饰符方法:方法不能重写
全限定名
全限定名(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属性
synchronized
概念
它的核心在于选择一个对象作为“锁”(也称为“监视器”或“互斥量”)。
synchronized (lockObject) {
// 需要同步的代码块(临界区)
}
lockObject:这是一个对象引用,它作为锁。任何Java对象都可以充当锁。{ ... }:大括号内的代码就是“临界区”。JVM保证同一时刻,只有一个线程可以持有lockObject这把锁并执行临界区内的代码。其他试图进入的线程必须等待,直到当前线程释放锁。
常见使用形式
| 类型 | 写法 | 锁对象 | 用途 |
|---|---|---|---|
| 同步方法(实例) | public synchronized void foo() |
this |
保护实例变量 |
| 同步方法(静态) | public static synchronized void foo() |
类对象(ClassName.class) |
保护静态变量 |
| 同步代码块 | synchronized (obj) {...} |
任意对象 | 灵活控制锁粒度 |
1)同步代码块:
class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
System.out.println(Thread.currentThread().getName() + " -> " + count);
}
}
}
public class Demo {
public static void main(String[] args) {
Counter counter = new Counter();
Runnable task = counter::increment;
new Thread(task, "A").start();
new Thread(task, "B").start();
}
}
2)同步方法
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
System.out.println(Thread.currentThread().getName() + " -> " + count);
}
}
public class Test {
public static void main(String[] args) {
Counter c = new Counter();
for (int i = 0; i < 5; i++) {
new Thread(c::increment).start();
}
}
}
synchronized修饰实例方法时,锁的是当前对象(this)。 同一个对象的同步方法是互斥的。
同一个对象(p1),T1 和 T2 都调用 p1.print(),因为 synchronized 锁的是 p1,所以 T1 和 T2 必须一个执行完,另一个才能进入方法。
不同对象(p2),T3 和 T4 调用的是另一个对象 p2.print()
因此,T1、T2 顺序执行,T3、T4 也顺序执行,它们之间互不影响。
Lambda表达式
函数式接口:有且仅有一个抽象方法的接口。
@FunctionalInterface 注解:这是一个可选的注解,用于表示接口是一个函数式接口。虽然不是强制的,但它可以帮助编译器识别意图,并检查接口是否确实只有一个抽象方法。
这个时候可以用Lambda代替匿名内部类!!!
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);
}
public static void main(String[] args) {
// 使用 Lambda 表达式实现接口方法
MyInterface obj = (a, b) -> {
System.out.println("参数a: " + a + ", 参数b: " + b);
};
obj.doSomething(5, 10);
}
}
lambda表达式格式:(参数列表) -> { 代码块 }或 (参数列表) ->表达式
如果上述MyInterface接口的doSomething()方法不接受任何参数并且没有返回值:
// Lambda 表达式(无参数)
MyInterface obj = () -> {
System.out.println("doSomething 被调用,无参数!");
};
以下是lambda表达式的重要特征:
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号():一个参数无需定义圆括号,但无参数或多个参数需要定义圆括号。
可选的大括号{}:如果主体只有一个语句,可以不使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,使用大括号需显示retrun;如果函数是void则不需要返回值。
// 定义一个函数式接口,只有一个抽象方法
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 表达式来实现。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
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,然后打印它。
list.forEach(item -> System.out.println(item));
}
}
示例2:为什么可以使用 Lambda 表达式自定义排序?
因为**Comparator<T> 是一个函数式接口**,只有一个抽象方法 compare(T o1, T o2)
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2); // 唯一的抽象方法
// 其他方法(如 thenComparing、reversed)都是默认方法或静态方法,不影响函数式接口特性
}
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));
// 输出排序结果
names.forEach(name -> System.out.println(name));
}
}
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());
}
}
访问修饰符
public(公共的):
使用public修饰的成员可以被任何其他类访问,无论这些类是否属于同一个包。 例如,如果一个类的成员被声明为public,那么其他类可以通过该类的对象直接访问该成员。
protected(受保护的):
使用protected修饰的成员可以被同一个包中的其他类访问,也可以被不同包中的子类访问。 与包访问级别相比,protected修饰符提供了更广泛的访问权限。
default (no modifier)(默认的,即包访问级别):
如果没有指定任何访问修饰符,则默认情况下成员具有包访问权限。 在同一个包中的其他类可以访问默认访问级别的成员,但是在不同包中的类不能访问。
private(私有的):
使用private修饰的成员只能在声明它们的类内部访问,其他任何类(子类也不行!)都不能访问这些成员。 这种访问级别提供了最高的封装性和安全性。
如果您在另一个类中实例化了包含私有成员的类,那么您无法直接访问该类的私有成员。但是,您可以通过公共方法来间接地访问和操作私有成员。
public class PrivateExample {
private int privateVar = 30;
// 公共方法,用于访问私有成员
public int getPrivateVar() {
return privateVar;
}
}
则每个实例都有自己的一份拷贝,只有当变量被声明为 static 时,变量才是类级别的,会被所有实例共享。
修饰符不仅可以用来修饰成员变量和方法,也可以用来修饰类。顶级类只能使用 public 或默认(即不写任何修饰符,称为包访问权限)。内部类可以使用所有访问修饰符(public、protected、private 和默认),这使得你可以更灵活地控制嵌套类的访问范围。
public class OuterClass {
// 内部类使用private,只能在OuterClass内部访问
private class InnerPrivateClass {
// ...
}
// 内部类使用protected,同包以及其他包中的子类可以访问
protected class InnerProtectedClass {
// ...
}
// 内部类使用默认访问权限,只在同包中可见
class InnerDefaultClass {
// ...
}
// 内部类使用public,任何地方都可访问(但访问时需要通过OuterClass对象)
public class InnerPublicClass {
// ...
}
}
JAVA三大特性
封装
封装指隐藏对象的状态信息(属性),不允许外部对象直接访问对象的内部信息(private实现)。但是可以提供一些可以被外界访问的方法(public)来操作属性。
优点:隐藏内部实现细节,提升安全性。
继承
[修饰符] class 子类名 extends 父类名{
类体部分
}
//class C extends A, B { } // 错误:C 不能同时继承 A 和 B
Java只支持单继承,不支持多继承。一个类只能有一个父类,不可以有多个父类。
Java支持多层继承(A → B → C )。
Java继承了父类非私有的成员变量和成员方法,但是请注意:子类是无法继承父类的构造方法的。
优点:实现代码复用,减少重复代码。子类可以在继承基础上扩展或修改功能。
多态
指在面向对象编程中,同样的消息(方法调用)可以在不同的对象上触发不同的行为。
-
方法重写(Override):动态多态;子类从父类继承的某个实例方法无法满足子类的功能需要时,需要在子类中对该实例方法进行重新实现,这样的过程称为重写,也叫做覆写、覆盖。
要求:
- 必须存在继承关系(子类继承父类)。
- 子类重写的方法的访问修饰符不能比父类更严格(可以相同或更宽松)。
- 方法名、参数列表和返回值类型必须与父类中的方法完全相同(Java 5 以后支持协变返回类型,即允许返回子类型)。
-
向上转型(Upcasting):动态多态;子类对象可以赋值给父类引用,这样做可以隐藏对象的真实类型,只能调用父类中声明的方法。
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() 方法 } }多态实现总结:继承 + 重写 + 父类引用指向子类对象 = 多态
-
方法重载(Overload):静态多态;方法名相同但参数列表不同。当调用这些方法时,会根据传递的参数类型或数量选择相应的方法。
参数类型不同;参数数量不同:参数顺序不同;返回类型可以不同,但仅返回类型不同不构成重载
void print(int a) { ... } void print(String a) { ... } // 合法重载 void log(String msg) { ... } void log(String msg, int level) { ... } // 合法重载 void save(int id, String name) { ... } void save(String name, int id) { ... } // 合法重载
优点:提高代码的灵活性和可扩展性。通过统一接口调用不同实现,提高可替换性。
继承与super关键字
类加载与对象实例化顺序
class Parent {
static { System.out.println("A"); }
Parent() { System.out.println("B"); }
}
class Child extends Parent {
static { System.out.println("C"); }
Child() { System.out.println("D"); }
}
加载类时:
A // 父类静态块先执行
C // 再执行子类静态块
实例化子类对象时:
B // 父类构造函数先执行
D // 再执行子类构造函数
记忆口诀:静态先父后子,构造先父后子。(析构则相反,先子后父)
new Child()的时候-> 输出顺序为 ACBD
Super关键字
super 用于在子类中访问父类的内容:
1)访问父类的成员
当子类与父类存在同名成员时,子类中的成员会**隐藏(shadow)**父类的同名成员。可用 super 显式访问父类版本。
2)调用父类的构造方法
创建子类对象时,一定会先调用父类构造函数。若父类没有无参构造函数,则子类必须显式调用父类构造函数,否则编译报错。使用 super(参数) 调用父类构造方法。
示例:
class Parent {
int num = 10;
Parent(int num) {
this.num = num;
System.out.println("Parent constructor: num = " + num);
}
void display() {
System.out.println("Parent method");
}
}
class Child extends Parent {
int num = 20;
Child(int num) {
super(num); // 调用父类构造函数
System.out.println("Child constructor: num = " + num);
}
void print() {
System.out.println("Child num: " + num); // 子类字段
System.out.println("Parent num: " + super.num); // 父类字段
super.display(); // 调用父类方法
}
}
public class Main {
public static void main(String[] args) {
Child obj = new Child(30);
System.out.println("---- 调用 print() ----");
obj.print();
}
}
运行结果:
Parent constructor: num = 30
Child constructor: num = 30
---- 调用 print() ----
Child num: 20
Parent num: 30
Parent method
注意:若父类定义了构造函数(无论是否有参),编译器不会再自动生成默认无参构造函数。
如果父类仅有有参构造函数,子类必须显式调用 super(...) 来选择合适的父类构造函数。
如果有有参+无参,那子类可以不显示调用super(...),系统会自动帮你调用父类的无参构造函数。
抽象类
用 abstract 修饰的类,不能被实例化。可以包含抽象方法(无方法体)和具体方法(有实现)。抽象类可以实现接口,也可以继续保持抽象。
注意:抽象类可以一个抽象方法都没有。
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");
}
}
如何使用抽象类
由于抽象类不能直接实例化,我们通常有两种方法来使用抽象类:
-
通过子类继承并实现抽象方法:
Animal animal = new Dog(); animal.makeSound(); // 输出:Dog barks -
使用匿名内部类
Animal a = new Animal() { @Override public void makeSound() { System.out.println("Anonymous sound"); } };
如何算作实现抽象方法
- 抽象类的子类必须实现全部抽象方法,否则该子类也必须声明为抽象类。
- 实现接口时,可选择性实现方法,未实现的交由子类完成。
接口
一组行为规范或契约。接口中的变量默认是 public static final 常量。方法默认是 public abstract(Java 8 之后可包含 default 和 static 方法)。
interface SmartDevice {
String DEFAULT_BRAND = "Generic";
void turnOn(); // 抽象方法
default void updateFirmware() {
System.out.println("Updating firmware...");
}
static void checkConnection() {
System.out.println("Checking network...");
}
}
class SmartLight implements SmartDevice {
@Override
public void turnOn() {
System.out.println("Light is on");
}
}
接口特性
- 支持 多继承(一个类可以实现多个接口)。
- 接口之间可以使用
extends继承。 - 默认方法(
default)可提供接口级的默认实现。
抽象类 vs 接口
| 特性 | 抽象类 | 接口 |
|---|---|---|
| 关键字 | abstract + class |
interface |
| 实例化 | ❌ 不能 | ❌ 不能 |
| 方法类型 | 抽象 + 具体 | 抽象(默认)、默认方法、静态方法 |
| 变量 | 普通成员变量 | public static final 常量 |
| 继承 | extends(单继承) |
implements(多实现) |
| 关系 | 可实现接口 | 可被类实现、可继承接口 |
| 适用场景 | 表示**“是什么”**(共性与实现) | 表示**“能做什么”**(行为约定) |
四种内部类
1.成员内部类
定义位置:成员内部类定义在外部类的成员位置。
访问权限:可以无限制地访问外部类的所有成员,包括私有成员、静态成员变量。
实例化方式:需要先创建外部类的实例,然后才能创建内部类的实例。
修改限制:不能有静态字段和静态方法(除非声明为常量final static)。成员内部类属于外部类的一个实例,不能独立存在于类级别上。
用途:适用于内部类与外部类关系密切,需要频繁访问外部类成员的情况。
public class Outer {
private static int staticVar = 10; // 外部类静态变量
private int instanceVar = 20; // 外部类实例变量
// 非静态内部类
class Inner {
void print() {
System.out.println("静态变量: " + staticVar); // 直接访问外部类静态变量
System.out.println("实例变量: " + instanceVar); // 直接访问外部类实例变量
}
}
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner(); // 创建内部类实例
inner.print();
}
}
易错题:
class A {
static class B {
void hello() {
System.out.println("hello from static A.B");
}
}
}
class C implements A.B {
public void hello() {
System.out.println("hello from C");
}
}
会编译报错,A.B 是一个 非静态内部类(inner class),它隐式地依赖外部类 A 的实例。
2.静态内部类
没有静态类,但是有静态内部类!
定义位置:定义在外部类内部,但使用static修饰。
访问权限:只能直接访问外部类的静态成员,访问非静态成员需要通过外部类实例。
实例化方式:可以直接创建,不需要外部类的实例。
修改限制:可以有自己的静态成员。
用途:适合当内部类工作不依赖外部类实例时使用,常用于实现与外部类关系不那么密切的帮助类。
public class Solution {
// 外部类的静态成员
private static String author = "AlgoMaster";
// 外部类的实例成员
private int solveCount = 0;
// 静态内部类:二叉树节点
public static class TreeNode {
int val;
TreeNode left, right;
TreeNode(int val) {
this.val = val;
}
public void showInfo() {
// ✅ 可以直接访问外部类的静态成员
System.out.println("Created by: " + author);
// ❌ 不能直接访问外部类的实例成员
// System.out.println("solveCount: " + solveCount);
}
// 算法函数:递归求最大深度
public int maxDepth(TreeNode root) {
solveCount++; // 访问实例成员
if (root == null) return 0;
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}
// 测试
public static void main(String[] args) {
// ✅ 静态内部类可以脱离外部类实例独立创建
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
// 展示静态内部类对外部类成员的访问特性
root.showInfo();
// 使用外部类实例来跑算法
Solution sol = new Solution();
System.out.println("Max Depth: " + sol.maxDepth(root)); // 输出 3
}
}
- 如果把辅助类直接写成顶级类,可能导致包里出现一堆“小工具类”。
- 把它们作为静态内部类放到外部类里,可以让代码结构更紧凑,命名更直观。
例子:在算法题里定义 TreeNode、ListNode 常作为 Solution 的静态内部类。
3.局部内部类
定义位置:局部内部类定义在一个方法或任何块内(如:if语句、循环语句内)。
访问权限:只能访问所在方法的final或事实上的final(即不被后续修改的)局部变量和外部类的成员变量(同成员内部类)。
实例化方式:只能在定义它们的块中创建实例。
修改限制:同样不能有静态字段和方法。
用途:适用于只在方法或代码块中使用的类,有助于将实现细节隐藏在方法内部。
public class OuterClass {
public void startThread() {
class LocalInnerClass implements Runnable {
@override
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();
}
}
4.匿名内部类
new 父类/接口() {
// 实现或重写方法
@Override
void method() { ... }
}
在定义的同时直接实例化,而不需要显式地声明一个子类的名称。
用途:适用于创建一次性使用的实例,通常用于接口或抽象类的实现。但匿名内部类并不限于接口或抽象类,只要是非 final 的普通类,都有机会通过匿名内部类来“现场”创建一个它的子类实例。
abstract class Animal {
public abstract void makeSound();
}
public class Main {
public static void main(String[] args) {
// 匿名内部类:临时创建一个 Animal 的子类并实例化
Animal dog = new Animal() { // 注意这里的 new Animal() { ... }
@Override
public void makeSound() {
System.out.println("汪汪汪!");
}
};
dog.makeSound(); // 输出:汪汪汪!
}
}
如何理解?可以对比普通子类(显式定义),即显示定义了Dog来继承Animal
// 抽象类或接口
abstract class Animal {
public abstract void makeSound();
}
// 显式定义一个具名的子类
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪!");
}
}
public class Main {
public static void main(String[] args) {
// 实例化具名的子类
Animal dog = new Dog();
dog.makeSound(); // 输出:汪汪汪!
}
}
容器
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。
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 接口中包含以下主要方法:
hasNext():如果迭代器还有下一个元素,则返回true,否则返回false。next():返回迭代器的下一个元素,并将迭代器移动到下一个位置。remove():从迭代器当前位置删除元素。该方法是可选的,不是所有的迭代器都支持。
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() 等。

HashSet

HashMap

// 使用 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
默认是小根堆,输出1,2,5,8
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());
}
}
}
offer()方法用于将元素插入到队列中poll()方法用于移除并返回队列中的头部元素peek()方法用于返回队列中的头部元素但不移除它。
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);
}
}
JAVA泛型
在类、接口或方法定义时,用类型参数来替代具体的类型,编译时检查类型安全,运行时通过类型擦除映射到原始类型。
<T>: 用于 定义泛型类型。
- 在 类、接口、方法 的定义中,
<T>是用来指定一个占位符,表示这个类或方法可以接受任何类型。 T在这里是 类型参数,你可以在类、接口或方法内使用它来代替具体的类型。
public class Box<T> { // <T> 定义了一个泛型类,T 是类型参数
private T value; // 使用 T 来表示某种类型
public void set(T value) { // 使用 T 来表示参数类型
this.value = value;
}
public T get() { // 使用 T 来表示返回类型
return value;
}
}
定义一个泛型类
// 定义一个“盒子”类,可以装任何类型的对象
public class Box<T> {
private T value;
public Box() {}
public Box(T value) {
this.value = value;
}
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}
T 是类型参数(Type Parameter),可任意命名(常见还有 E、K、V 等)。
使用:
public class Main {
public static void main(String[] args) {
// 创建一个只装 String 的盒子
Box<String> stringBox = new Box<>();
stringBox.set("Hello Generics");
String s = stringBox.get(); // 自动类型推断为 String
System.out.println(s);
// 创建一个只装 Integer 的盒子
Box<Integer> intBox = new Box<>(123);
Integer i = intBox.get();
System.out.println(i);
}
}
定义一个泛型方法
有时候我们只想让某个方法支持多种类型,而不必为此写泛型类,就可以在方法前加上类型声明:
public class Utils {
//[修饰符] <T> 返回类型 方法名(参数列表) { … }
// 泛型方法:打印任意类型的一维数组
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
}
方法签名中 <T> 表示这是一个泛型方法,注意这里不是指返回值!!!这个返回值是void!!!
调用时,编译器会根据传入实参自动推断 T。
使用
public class Main {
public static void main(String[] args) {
String[] names = {"Alice", "Bob", "Charlie"};
Utils.printArray(names);
// 等价于 Utils.<String>printArray(names);
Integer[] nums = {10, 20, 30};
Utils.printArray(nums);
// 等价于 Utils.<Integer>printArray(nums);
}
}
好用的方法
toString()
**Arrays.toString()**转一维数组
**Arrays.deepToString()**转二维数组 这个方法是是用来将数组转换成String类型输出的,入参可以是long,float,double,int,boolean,byte,object 型的数组。
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.lang.Object@15db9742
当打印重写toString() 方法的对象时:
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}
}
}
对象拷贝属性
public void save(EmployeeDTO employeeDTO) {
Employee employee = new Employee();
//对象属性拷贝
BeanUtils.copyProperties(employeeDTO, employee,"id");
}
employeeDTO的内容拷贝给employee,跳过字段为"id"的属性。
StartOrStopDTO dto = new StartOrStopDTO(1, 100L);
// 用 Builder 拷贝 id 和 status
Employee employee = Employee.builder()
.id(dto.getId())
.status(dto.getStatus())
.build();
Java 8 Stream API
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 及以前):
SpaceUserRole role = null;
for (SpaceUserRole r : SPACE_USER_AUTH_CONFIG.getRoles()) {
if (r.getKey().equals(spaceUserRole)) {
role = r;
break;
}
}
类加载器和获取资源文件路径
在Java中,类加载器的主要作用是根据**类路径(Classpath)**加载类文件以及其他资源文件。
启动类加载器(Bootstrap ClassLoader):加载 Java 运行时环境的核心类库,包括 Java 的标准库和 JVM 必须的类,比如 java.lang.* 包中的类。
扩展类加载器:加载 Java 扩展目录中的类库,这些库通常是 ext 目录中的 JAR 文件(例如,$JAVA_HOME/jre/lib/ext/)。
系统类加载器:加载应用程序的类路径(classpath)下的类和资源文件,通常用于加载项目中的类和 JAR 包。
自定义类加载器:这个类加载器是由开发者自定义实现的,用于加载非标准的类或动态加载类。它是 Java 提供的类加载机制的扩展,允许你根据特定需求来自定义类加载的行为。
双亲委派机制的基本原理:
双亲委派机制的核心思想是:一个类加载器在加载类时,首先将请求委托给它的父类加载器,只有当父类加载器无法加载该类时,当前加载器才会自己去加载。
原因:
1.避免类的重复加载
2.保证核心类的安全性,如 java.lang.Object 类等是 Java 核心类库的一部分,必须由启动类加载器(Bootstrap ClassLoader)加载。
类路径是JVM在运行时用来查找类文件和资源文件的一组目录或JAR包。在许多项目(例如Maven或Gradle项目)中,src/main/resources目录下的内容在编译时会被复制到输出目录(如target/classes),src/main/java 下编译后的 class 文件也会放到这里。
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
// 获取 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;
this.getClass():获取当前对象(即调用该代码的对象)的 Class 对象。.getClassLoader():获取该 Class 对象的类加载器(ClassLoader)。.getResource("emp.xml"):从类路径中获取名为 "emp.xml" 的资源,并返回一个 URL 对象,该 URL 对象指向 "emp.xml" 文件的位置。.getFile():从 URL 对象中获取文件路径部分,即获取 "emp.xml" 文件的绝对路径字符串。
**类路径(Classpath)**是 Java 虚拟机(JVM)用于查找类文件和其他资源文件的一组路径。
是的,类加载器的主要作用之一确实是从类路径中加载类文件(.class 文件)以及其他资源(如图片、配置文件等)。在 Java 项目启动时,类加载器不仅会加载类文件,还会把这些类文件转换为 Java 程序可以使用的 Class 对象,并将它们放入 运行时数据区,即 方法区(Method Area) 和 堆区(Heap)。
反射
反射技术的关键之一是能够在 运行时动态加载 类的字节码并将其转换为 Class 对象,并以编程的方法解刨出类中的各个成分(成员变量、方法、构造器等)。
.class 是静态的文件,加载到内存并解析后,就会创建一个对应的 java.lang.Class 实例即 Class对象,是动态的。

反射技术例子:IDEA通过反射技术就可以获取到类中有哪些方法,并且把方法的名称以提示框的形式显示出来,所以你能看到这些提示了。
获取类的字节码(Class对象)
有三种方法:
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("edu.whut.pojo.Student"); //全类名
System.out.println(c1 == c2); //true
Student s = new Student();
Class c3 = s.getClass();
System.out.println(c2 == c3); //true
}
}
类的 Class 对象(字节码对象)是类的模具,不会直接存放数据。
类的 实例对象obj是用 new 出来的,它代表着 实际的运行时对象,会在堆(Heap)里开辟内存,存放实例字段的数据。每次 new 一下,就会有一个独立的实例,它们共享同一个 Class,但各自的数据独立。
1.获取类的元信息
- 类名:
getName()、getSimpleName()、getPackage() - 父类和接口:
getSuperclass()、getInterfaces() - 修饰符:
getModifiers()(配合Modifier工具类解析 public、private、abstract 等) - 注解:
getAnnotation()/getAnnotations()获取类上的注解!!!
2.获取类的构造器
定义类
public class Cat{
private String name;
private int age;
public Cat(){}
private Cat(String name, int age){
}
}
获取构造器列表
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() 用于获取该构造器的参数个数。
获取某个构造器:指定参数类型!
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(实参列表) 就可以执行构造器,完成对象的初始化了。
Constructor 本身就是用来创建对象实例的,它的职责是生成实例,而不是操作某个已经存在的实例。
3.获取类的成员变量
获取类的成员变量
| 方法 | 说明 |
|---|---|
public Field[] getFields() |
获取类的全部成员变量(只能获取 public 修饰的) |
public Field[] getDeclaredFields() |
获取类的全部成员变量(只要存在就能拿到) |
public Field getField(String name) |
获取类的某个成员变量(只能获取 public 修饰的) |
public Field getDeclaredField(String name) |
获取类的某个成员变量(只要存在就能拿到) |
设置与获取字段值
| 方法 | 说明 |
|---|---|
void set(Object obj, Object value) |
设置字段值 |
Object get(Object obj) |
获取字段值 |
public void setAccessible(boolean flag) |
设置为 true,表示禁止检查访问控制(暴力反射) |
不管是设置值还是获取值,都需要:
-
获取
Field对象 —— 先通过Class对象拿到目标字段的Field实例。 -
指定目标实例 —— 操作字段时必须传入具体的对象实例,告诉 JVM 要修改或读取哪一个对象的该字段。
-
处理访问权限 —— 如果字段是私有的,需要调用
setAccessible(true)来关闭 Java 的访问检查(俗称“暴力反射”)。
import java.lang.reflect.Field;
public class ReflectionExample {
// 示例类
public static class MyClass {
public String publicField = "Public Field";
private String privateField = "Private Field";
}
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
// 创建 MyClass 的实例
MyClass obj = new MyClass();
// 获取 MyClass 的 Class 对象
Class<?> clazz = obj.getClass();
// 获取 public 字段
Field publicField = clazz.getField("publicField");
System.out.println("Public Field: " + publicField.get(obj)); // 获取并输出 publicField 的值
// 获取 private 字段(使用 getDeclaredField)
Field privateField = clazz.getDeclaredField("privateField");
// 设置私有字段为可访问(通过 setAccessible)
privateField.setAccessible(true);
System.out.println("Private Field: " + privateField.get(obj)); // 获取并输出 privateField 的值
// 修改 private 字段的值
privateField.set(obj, "New Private Value");
System.out.println("Updated Private Field: " + privateField.get(obj)); // 获取修改后的值
}
}
4.获取类的成员方法

获取单个指定的成员方法:第一个参数填方法名、第二个参数填方法中的参数类型

执行:第一个参数传入一个对象实例,然后是若干方法参数(无参可不写)...

示例:Cat 类与测试类
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!");
}
}
public class FieldReflectionTest {
@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识别进行语法检查。
使用注解
元注解
是修饰注解的注解。
@Retention(RetentionPolicy.SOURCE) //只在源码阶段保留,编译后 `.class` 文件中不会有这个注解信息。
@Retention(RetentionPolicy.RUNTIME) //指定注解的生命周期,即在运行时有效,可用于反射等用途。
@Target(ElementType.TYPE) //类上的注解(包含类、接口、枚举等类型)
@Target(ElementType.METHOD) //方法上的注解
@Target(ElementType.FIELD) //字段上的注解
注意,若想在运行时通过反射读取,只能设置@Retention(RetentionPolicy.RUNTIME)
定义注解
使用 @interface 定义注解
// 定义注解
@Retention(RetentionPolicy.RUNTIME) // 生命周期:运行时保留,可反射获取
@Target(ElementType.METHOD) // 目标:作用于方法
public @interface MyAnnotation {
String description() default "This is a default description";
int value() default 0;
}
用法:
// 1. 只传 value,可省略属性名
@MyAnnotation(5)
public void someMethod() {}
// 2. 多属性赋值必须指明名称
@MyAnnotation(value = 5, description = "Specific description")
public void anotherMethod() {}
// 3. 使用默认值
@MyAnnotation
public void defaultMethod() {}
解析注解
在 Java 中,注解本质上是类的元数据,要在运行时获取注解信息,必须依赖反射 API 来读取。
下面示例展示了如何通过反射获取方法上的自定义注解 @MyAnnotation。
1.定义示例类与注解
// 自定义注解
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可反射获取
@Target(ElementType.METHOD) // 仅可作用于方法
public @interface MyAnnotation {
String value();
}
// 使用注解
public class MyClass {
@MyAnnotation(value = "specific value")
public void myMethod() {
// 方法实现
}
}
2.通过反射获取注解
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());
}
}
}
// 反射调用链:
// Class → 通过类加载器获取类的运行时描述对象;
// Method → 从 Class 对象中获取方法的反射对象;
// getAnnotation() → 从方法反射对象中获取注解实例。
3.快速判断注解是否存在
if (method.isAnnotationPresent(MyAnnotation.class)) {
// 如果存在MyAnnotation注解,则执行相应逻辑
}
Junit 单元测试
步骤
1.导入依赖 将 JUnit 框架的 jar 包添加到项目中(注意:IntelliJ IDEA 默认集成了 JUnit,无需手动导入)。
2.编写测试类
- 为待测业务方法创建对应的测试类。
- 测试类中定义测试方法,要求方法必须为
public且返回类型为void。
3.添加测试注解
在测试方法上添加 @Test 注解,确保 JUnit 能自动识别并执行该方法。
4.运行测试 在测试方法上右键选择“JUnit运行”。
- 测试通过显示绿色标志;
- 测试失败显示红色标志。
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 的各种功能,例如自动装配、依赖注入、配置加载等
@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注解,那么该测试函数就可以直接运行!若一个测试类中写了多个测试方法,可以全部执行!

原理可能是:
//自定义注解
@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 instance = new AnnotationTest4();
// 1. 获取 Class 对象
Class<?> clazz = AnnotationTest4.class;
// 2. 获取类中声明的所有方法
Method[] methods = clazz.getDeclaredMethods();
// 3. 遍历方法,执行带 @MyTest 的方法
for (Method method : methods) {
if (method.isAnnotationPresent(MyTest.class)) {
method.invoke(instance); // 反射调用方法
}
}
}
}
在Springboot中,如何快速生成单元测试?
选中类名,右键:

JAVA笔试题总结
1)构造函数用于初始化当前类的对象,不能被继承。子类会调用父类的构造函数(通过 super),但这不是继承。
2)try-finally
public static int func() {
try {
return 1; // 这个返回值被"吞噬"
} finally {
return 0; // 这个返回值覆盖了try中的值
}
}
// 结果: 返回 0
因此极不推荐在 finally 中使用 return
3)关于 Java 的包和部署,下列说法正确的是(单选题)?
A. Java 虚拟机在使用 JAR 的 class 之前必须先将其解压缩
B. 编译包中的类时,完整名称必须和目录结构一致
C. 同一源文件中的不同类可以放到不同的包中
D. JAR 是保存 .class 文件的标准目录
解析:
- A 错误:JVM 可以直接读取 JAR 中的 .class 文件,无需解压(JAR 本质是 ZIP 格式,JVM 能直接加载)。
- B 正确:Java 要求包名必须与文件目录结构完全匹配(例如
com.example.Test需放在com/example/Test.java中),否则编译失败。 - C 错误:同一 .java 源文件中的多个类默认属于同一个包,且只能有一个 public 类,不能拆分到不同包中。
- D 错误:JAR 是压缩文件(归档格式),不是标准目录;标准目录是文件系统本身的文件夹结构。