【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 简洁、函数式编程、集合优化 集合操作、多线程、回调函数 复杂逻辑慎用,影响可读性
相关推荐
翊谦2 小时前
Java Agent开发 Milvus 向量数据库安装
java·数据库·milvus
晓晓hh2 小时前
JavaSE学习——迭代器
java·开发语言·学习
Laurence2 小时前
C++ 引入第三方库(一):直接引入源文件
开发语言·c++·第三方库·添加·添加库·添加包·源文件
查古穆3 小时前
栈-有效的括号
java·数据结构·算法
kyriewen113 小时前
你点的“刷新”是假刷新?前端路由的瞒天过海术
开发语言·前端·javascript·ecmascript·html5
Java面试题总结3 小时前
Spring - Bean 生命周期
java·spring·rpc
硅基诗人3 小时前
每日一道面试题 10:synchronized 与 ReentrantLock 的核心区别及生产环境如何选型?
java
014-code3 小时前
String.intern() 到底干了什么
java·开发语言·面试
421!3 小时前
GPIO工作原理以及核心
开发语言·单片机·嵌入式硬件·学习
摇滚侠4 小时前
JAVA 项目教程《苍穹外卖-12》,微信小程序项目,前后端分离,从开发到部署
java·开发语言·vue.js·node.js