文章目录
- 一、反射(Reflection):动态操作类
-
- [1. 反射是什么?](#1. 反射是什么?)
- [2. 反射的用途](#2. 反射的用途)
- [3. 反射的核心类](#3. 反射的核心类)
- [4. 如何使用反射?](#4. 如何使用反射?)
-
- [4.1 第一步:获取`Class`对象](#4.1 第一步:获取
Class对象) - [4.2 核心操作:反射类的成员](#4.2 核心操作:反射类的成员)
- [4.1 第一步:获取`Class`对象](#4.1 第一步:获取
- [5. 反射的优缺点](#5. 反射的优缺点)
- 二、枚举(Enum):常量管理方案
-
- [1. 枚举是什么?](#1. 枚举是什么?)
- [2. 枚举的核心用途](#2. 枚举的核心用途)
- [3. 枚举的基本用法](#3. 枚举的基本用法)
-
- [3.1 简单枚举定义与使用](#3.1 简单枚举定义与使用)
- [3.2 枚举的常用方法](#3.2 枚举的常用方法)
- [3.3 带属性和方法的枚举](#3.3 带属性和方法的枚举)
- [4. 枚举与反射的关系](#4. 枚举与反射的关系)
- [5. 枚举的优缺点](#5. 枚举的优缺点)
- 三、Lambda表达式
-
- [1. Lambda是什么?](#1. Lambda是什么?)
- [2. 函数式接口:Lambda的前提](#2. 函数式接口:Lambda的前提)
- [3. Lambda的语法格式](#3. Lambda的语法格式)
- [4. Lambda的核心用法](#4. Lambda的核心用法)
-
- [4.1 替代匿名内部类](#4.1 替代匿名内部类)
- [4.2 集合中的应用](#4.2 集合中的应用)
- [4.3 变量捕获](#4.3 变量捕获)
- [5. Lambda的优缺点](#5. Lambda的优缺点)
- 四、三大特性对比与应用场景总结
一、反射(Reflection):动态操作类
1. 反射是什么?
Java的反射机制是指程序在运行状态中,对于任意一个类,都能获取其所有属性和方法;对于任意一个对象,都能调用其任意方法和属性(包括私有),并可修改部分类型信息。这种动态获取信息和调用方法的能力,让Java具备了极强的灵活性和扩展性。
正常编程是"正射"------先知道类,再创建对象调用方法;而反射是"反向"------先有对象或类名,再去探索其内部结构并操作。
2. 反射的用途
- 突破访问限制:获取类的私有成员(变量、方法、构造器),常用于第三方框架开发中访问隐藏API。
- 开发通用框架:Spring的IOC容器、MyBatis的SQL映射等框架,就是通过反射读取配置文件(XML/注解),动态创建类实例并注入依赖。
- 动态适配:根据运行时条件动态调用不同方法或创建不同类,减少硬编码。
3. 反射的核心类
Java反射的功能主要通过java.lang.reflect包下的4个核心类实现,它们对应类的不同组成部分:
| 类名 | 用途 |
|---|---|
Class |
代表类的实体,是反射的"入口",封装了类的所有信息 |
Field |
代表类的成员变量(属性),可获取/修改属性值 |
Method |
代表类的方法,可动态调用方法 |
Constructor |
代表类的构造器,可创建类实例(包括私有构造器) |
其中,Class类是反射的基础------ .Java文件通过编译得到.class文件(字节码文件),再在JVM上被解析为唯一的Class对象,反射的所有操作都围绕这个对象展开。
4. 如何使用反射?
4.1 第一步:获取Class对象
获取Class对象有三种方式,适用于不同场景:
Class.forName("类的全路径名"):最常用,支持动态加载(编译时无需知道类名),需处理ClassNotFoundException。
java
Class<?> clazz = Class.forName("com.example.Student"); // 需带包名
类名.class:编译时已知类,安全可靠,性能最高(无异常抛出)。
java
Class<?> clazz = Student.class;
对象.getClass():已有对象实例时使用,通过对象反向获取Class。
java
Student student = new Student();
Class<?> clazz = student.getClass();
注意: 一个类在JVM中只有一个Class对象,上述三种方式获取的clazz本质是同一个(equals()比较返回true)。
4.2 核心操作:反射类的成员
以Student类为例,演示反射的用法:
java
class Student {
private String name = "Daisy"; // 私有属性
public int age = 18; // 公有属性
public Student() {} // 公有无参构造
private Student(String name, int age) { // 私有有参构造
this.name = name;
this.age = age;
}
private void eat() { System.out.println("i am eat"); } // 私有方法
public void sleep() { System.out.println("i am pig"); } // 公有方法
private void function(String str) { System.out.println(str); } // 私有带参方法
}
(1)反射创建对象
- 调用无参构造(公有):
java
Class<?> clazz = Class.forName("Student");
Student student = (Student) clazz.newInstance();
- 调用私有有参构造:
java
// 获取私有构造器(参数类型为String.class和int.class)
Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true); // 突破私有访问限制
Student student = (Student) constructor.newInstance("小明", 20); // 创建实例
(2)反射获取/修改属性
- 获取私有属性并修改:
java
Field nameField = clazz.getDeclaredField("name"); // 获取私有属性name
nameField.setAccessible(true); // 允许访问私有属性
nameField.set(student, "小红"); // 修改属性值
String newName = (String) nameField.get(student); // 获取修改后的值
System.out.println(newName); // 输出:小红
- 获取公有属性:
java
Field ageField = clazz.getField("age"); // 直接获取公有属性
int age = (int) ageField.get(student);
(3)反射调用方法
- 调用私有带参方法:
java
Method functionMethod = clazz.getDeclaredMethod("function", String.class);
functionMethod.setAccessible(true); // 突破私有限制
functionMethod.invoke(student, "反射调用私有方法成功!"); // 调用方法并传参
- 调用公有方法:
java
Method sleepMethod = clazz.getMethod("sleep"); // 获取公有方法
sleepMethod.invoke(student); // 输出:i am pig
5. 反射的优缺点
| 优点 | 缺点 |
|---|---|
| 动态获取类信息,增强灵活性和扩展性 | 性能开销:反射操作绕开编译优化,比直接调用慢 |
| 突破访问限制,适配框架开发 | 代码可读性差:反射逻辑较复杂,调试困难 |
| 降低代码耦合,支持通用框架开发 | 安全风险:可能破坏封装性,恶意反射可修改私有成员 |
反射适合框架开发或动态场景,普通业务代码尽量避免使用。
二、枚举(Enum):常量管理方案
1. 枚举是什么?
枚举是JDK1.5引入的特性,用于统一管理一组相关常量 。在此之前,常量通常用public static final定义,但存在类型不安全、易混淆等问题。
枚举本质是java.lang.Enum的子类(无需显式继承),其构造器默认是私有的,确保不能通过new创建实例,常量实例在枚举类加载时唯一初始化。
2. 枚举的核心用途
- 定义固定常量集:如颜色(红、绿、蓝)、状态(成功、失败、 pending)、错误码等。
- 实现单例模式:枚举天然是线程安全的单例,且能避免反射和序列化破坏单例。
- 简化
switch语句:枚举类型作为switch参数,比int更安全、可读性更高。
3. 枚举的基本用法
3.1 简单枚举定义与使用
java
// 定义枚举(常量集)
enum ColorEnum {
RED, GREEN, BLUE; // 枚举常量,默认调用无参构造
}
// 使用枚举
public class Test {
public static void main(String[] args) {
ColorEnum color = ColorEnum.RED;
// switch中使用
switch (color) {
case RED:
System.out.println("红色");
break;
case GREEN:
System.out.println("绿色");
break;
case BLUE:
System.out.println("蓝色");
break;
}
}
}
3.2 枚举的常用方法
Enum类提供了内置方法,简化枚举操作:
| 方法名 | 描述 |
|---|---|
values() |
以数组形式返回所有枚举常量 |
ordinal() |
获取枚举常量的索引(从0开始) |
valueOf(String name) |
将字符串转换为枚举实例(需与常量名完全一致) |
compareTo(E o) |
比较两个枚举常量的定义顺序(返回索引差值) |
示例:
java
public class EnumDemo {
public static void main(String[] args) {
// values():遍历所有常量
ColorEnum[] colors = ColorEnum.values();
for (ColorEnum c : colors) {
System.out.println(c + " -> 索引:" + c.ordinal());
}
// 输出:RED -> 0,GREEN -> 1,BLUE -> 2
// valueOf():字符串转枚举
ColorEnum green = ColorEnum.valueOf("GREEN");
System.out.println(green); // 输出:GREEN
// compareTo():比较顺序
System.out.println(ColorEnum.GREEN.compareTo(ColorEnum.RED)); // 输出:1(1-0)
}
}
3.3 带属性和方法的枚举
枚举本质是类,可添加属性、构造器和自定义方法(构造器必须私有):
java
enum StatusEnum {
SUCCESS("成功", 200),
FAIL("失败", 500),
PENDING("处理中", 100);
// 自定义属性
private String desc;
private int code;
// 私有构造器(必须私有,默认也为私有)
private StatusEnum(String desc, int code) {
this.desc = desc;
this.code = code;
}
// 自定义方法:根据code获取枚举
public static StatusEnum getByCode(int code) {
for (StatusEnum status : values()) {
if (status.code == code) {
return status;
}
}
return null;
}
// getter方法
public String getDesc() { return desc; }
public int getCode() { return code; }
}
// 使用
public class Test {
public static void main(String[] args) {
StatusEnum success = StatusEnum.SUCCESS;
System.out.println(success.getCode() + ":" + success.getDesc()); // 输出:200:成功
StatusEnum pending = StatusEnum.getByCode(100);
System.out.println(pending); // 输出:PENDING
}
}
4. 枚举与反射的关系
枚举的构造器是私有的,但无法通过反射创建枚举实例 !这是因为JVM在Constructor.newInstance()方法中明确禁止反射创建枚举对象,源码如下:
java
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
这也是枚举实现单例模式安全的核心原因------避免了反射破坏单例。
5. 枚举的优缺点
| 优点 | 缺点 |
|---|---|
类型安全:避免常量值混淆(如ColorEnum.RED不会被误读) |
不可继承:枚举类不能被继承,无法扩展 |
内置方法:提供values()、valueOf()等实用方法,代码优雅 |
灵活性不足:常量集固定,运行时无法动态添加 |
| 线程安全:枚举实例在类加载时唯一初始化,天然支持单例 |
三、Lambda表达式
1. Lambda是什么?
Lambda表达式是Java 8引入的核心特性,本质是匿名函数------没有名称,但有参数列表、方法体和返回值。它的核心作用是简化函数式接口的实现,让代码更简洁、更聚焦逻辑。
Lambda表达式的设计灵感来自数学中的λ演算,也可称为"闭包",能捕获外部变量并在表达式中使用。
2. 函数式接口:Lambda的前提
Lambda表达式只能用于函数式接口 ------即有且只有一个抽象方法 的接口(可包含默认方法default或静态方法)。
Java 8提供了@FunctionalInterface注解,用于显式声明函数式接口(编译器会校验是否符合规范):
java
// 函数式接口(只有一个抽象方法)
@FunctionalInterface
interface Calculator {
int add(int a, int b); // 抽象方法
default void print() { // 默认方法(可带实现)
System.out.println("计算完成");
}
}
常见的内置函数式接口:Runnable(无参无返回)、Consumer(消费型)、Supplier(供给型)、Predicate(断言型)等。
3. Lambda的语法格式
Lambda表达式的语法可简化为:(参数列表) -> 方法体,具体规则如下:
- 参数列表:可省略参数类型(JVM自动推断),单个参数可省略括号。
- ->:分隔符,读作"被用于"。
- 方法体 :单个语句可省略大括号和
return;多个语句需用大括号包裹,且需显式return。
语法示例:
java
// 1. 无参无返回
() -> System.out.println("无参无返回");
// 2. 单个参数(省略类型和括号)
a -> System.out.println("单个参数:" + a);
// 3. 多个参数(省略类型)
(a, b) -> a + b;
// 4. 多个语句(需大括号和return)
(a, b) -> {
int sum = a + b;
System.out.println("和为:" + sum);
return sum;
};
4. Lambda的核心用法
4.1 替代匿名内部类
传统匿名内部类实现函数式接口的代码冗长,Lambda可大幅简化:
java
// 传统匿名内部类
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类实现Runnable");
}
};
// Lambda简化(无参无返回)
Runnable runnable2 = () -> System.out.println("Lambda实现Runnable");
// 线程中使用
new Thread(runnable2).start();
4.2 集合中的应用
Java 8为集合新增了forEach()、sort()等方法,结合Lambda可极大简化集合操作:
(1)遍历集合(forEach())
java
List<String> list = Arrays.asList("Hello", "Lambda", "Java");
// 传统for循环
for (String s : list) {
System.out.println(s);
}
// Lambda简化
list.forEach(s -> System.out.println(s));
// 进一步简化(方法引用)
list.forEach(System.out::println);
(2)集合排序(sort())
java
List<String> list = Arrays.asList("Hello", "bit", "lambda");
// 传统Comparator匿名内部类
list.sort(new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length(); // 按长度排序
}
});
// Lambda简化
list.sort((s1, s2) -> s1.length() - s2.length());
System.out.println(list); // 输出:[bit, Hello, lambda]
(3)Map遍历(forEach())
java
Map<Integer, String> map = new HashMap<>();
map.put(1, "Java");
map.put(2, "Lambda");
map.put(3, "反射");
// Lambda遍历Map
map.forEach((k, v) -> System.out.println(k + ":" + v));
4.3 变量捕获
Lambda可捕获外部变量,但需满足:变量必须是final或"effectively final"(即声明后未修改):
java
int num = 10; // effectively final(未修改)
Consumer<Integer> consumer = (a) -> System.out.println(a + num); // 捕获num
consumer.accept(5); // 输出:15
// 错误示例:捕获后修改变量
int num2 = 20;
num2 = 30; // 破坏effectively final
Consumer<Integer> consumer2 = (a) -> System.out.println(a + num2); // 编译报错
5. Lambda的优缺点
| 优点 | 缺点 |
|---|---|
| 代码简洁:大幅减少模板代码(如匿名内部类) | 可读性差:复杂逻辑用Lambda会显得晦涩 |
| 函数式编程:支持传递行为(如将排序逻辑作为参数) | 调试困难:无法直接打断点调试Lambda表达式 |
并行计算:易于结合Stream实现并行处理 |
性能:非并行场景下,性能可能略低于传统for循环 |
| 改善集合操作:简化遍历、排序等操作 |
四、三大特性对比与应用场景总结
| 特性 | 核心优势 | 典型应用场景 | 注意事项 |
|---|---|---|---|
| 反射 | 动态性、突破访问限制 | 框架开发(Spring IOC)、动态适配 | 慎用!性能开销大,破坏封装 |
| 枚举 | 类型安全、常量管理、单例 | 状态码、错误类型、固定常量集 | 不可继承,常量集固定 |
| Lambda | 简洁、函数式编程、集合优化 | 集合操作、多线程、回调函数 | 复杂逻辑慎用,影响可读性 |