【Java SE】反射、枚举与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对象有三种方式,适用于不同场景:

  1. Class.forName("类的全路径名") :最常用,支持动态加载(编译时无需知道类名),需处理ClassNotFoundException
java 复制代码
   Class<?> clazz = Class.forName("com.example.Student"); // 需带包名
  1. 类名.class:编译时已知类,安全可靠,性能最高(无异常抛出)。
java 复制代码
   Class<?> clazz = Student.class;
  1. 对象.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 简洁、函数式编程、集合优化 集合操作、多线程、回调函数 复杂逻辑慎用,影响可读性
相关推荐
weixin_436525072 小时前
若依多租户版 - @ApiEncrypt, api接口加密
java·开发语言
superman超哥2 小时前
序列化格式的灵活切换:Serde 生态的统一抽象力量
开发语言·rust·编程语言·rust serde·序列化格式·rust序列化格式
Hello.Reader2 小时前
Flink Java 版本兼容性与 JDK 模块化(Jigsaw)踩坑11 / 17 / 21 怎么选、怎么配、怎么稳
java·大数据·flink
TechPioneer_lp2 小时前
小红书后端实习一面|1小时高强度技术追问实录
java·后端·面试·个人开发
TH_12 小时前
37、SQL的Explain
java·数据库·sql
康王有点困2 小时前
Flink部署模式
java·大数据·flink
EndingCoder2 小时前
属性和参数装饰器
java·linux·前端·ubuntu·typescript
芒克芒克3 小时前
LeetCode 134. 加油站(O(n)时间+O(1)空间最优解)
java·算法·leetcode·职场和发展
HellowAmy3 小时前
我的C++规范 - 随机时间点
开发语言·c++·代码规范