Java 数据结构第二十八期:反射、枚举以及 lambda 表达式

专栏:Java 数据结构秘籍

个人主页:手握风云

目录

一、反射

[1.1. 定义](#1.1. 定义)

[1.2. 反射相关的类](#1.2. 反射相关的类)

[1. Class 类(反射机制的起源)](#1. Class 类(反射机制的起源))

[2. 反射示例](#2. 反射示例)

[1.3. 反射的优缺点](#1.3. 反射的优缺点)

二、枚举的使用

[2.1. 引入枚举的原因](#2.1. 引入枚举的原因)

[2.2. 枚举的使用](#2.2. 枚举的使用)

[1. switch 语句](#1. switch 语句)

[2. 常用方法](#2. 常用方法)

[2.3. 枚举的优缺点](#2.3. 枚举的优缺点)

[三、Lambda 表达式](#三、Lambda 表达式)

[3.1. 背景](#3.1. 背景)

[1. Lambda 表达式的语法](#1. Lambda 表达式的语法)

[2. 函数式接口](#2. 函数式接口)

[3.2. Lambda 表达式的基本使用](#3.2. Lambda 表达式的基本使用)

[3.3. 变量捕获](#3.3. 变量捕获)

[1. 匿名内部类](#1. 匿名内部类)

[2. 匿名内部类的变量捕获](#2. 匿名内部类的变量捕获)

[3.4. Lambda 在集合中的使用](#3.4. Lambda 在集合中的使用)

[1. Collection 接口](#1. Collection 接口)

[2. List 接口](#2. List 接口)

[3. Map 接口](#3. Map 接口)


一、反射

1.1. 定义

Java 反射(Reflection)是一种在程序运行时动态获取类信息、操作类成员(属性、方法、构造器等)的机制 。其核心特点是:无需在编译期明确类的结构 ,而是在运行时通过 Class 对象获取类的完整信息(如名称、属性、方法、注解等),并动态创建对象、调用方法、修改属性,甚至突破访问权限(如私有成员)。通俗地说,反射是在程序运行时 "临时解析类",像 "解剖" 类一样,动态获取未知类的结构并操作(例如通过类名字符串)。

1.2. 反射相关的类

|-------------|---------------|
| 类名 | 用途 |
| Class | 代表类的实体(反射的起点) |
| Field | 代表类的属性(成员变量) |
| Method | 代表类的方法 |
| Constructor | 代表类的构造方法 |

1. Class 类(反射机制的起源)

Java 文件被编译后,生成了.class文件,JVM此时就要去解读 .class 文件,被编译后的 Java 文件 .class 也被 JVM 解析为一个对象,这个对象就是 java.1ang.class ,这样当程序在运行时,每个 java 文件就最终变成了 Class 类对象的个实例。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类。

  • 常用获得类相关的方法

|---------------------------|----------------------------------|
| 方法 | 用途 |
| getClassLoader() | 获得类的加载器 |
| getDeclaredClasses() | 返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的) |
| forName(String className) | 根据类名返回类的对象 |
| newInstance() | 创建类的实例 |
| getName() | 获得类的完整路径名字 |

  • 常用获得类中属性相关的方法

|-------------------------------|-------------|
| 方法 | 用途 |
| getField(String name) | 获得某个公有的属性对象 |
| getFields() | 获得所有公有的属性对象 |
| getDeclaredField(String name) | 获得某个属性对象 |
| getDeclaredFields() | 获得某个公有的属性对象 |

  • 获得类中构造器相关的方法

|------------------------------------------------|---------------------|
| 方法 | 用途 |
| getConstructor(Class...parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
| getConstructors() | 获得该类的所有公有构造方法 |
| getDeclaredConstructor(Class...parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
| getDeclaredConstructors() | 获得该类所有构造方法 |

  • 获得类中方法相关的方法

|--------------------------------------------------------|-------------|
| 方法 | 用途 |
| getMethod(String name, Class...parameterTypes) | 获得该类某个公有的方法 |
| getMethods() | 获得该类所有公有的方法 |
| getDeclaredMethod(String name,Class... parameterTypes) | 获得该类某个方法 |
| getDeclaredMethods() | 获得该类所有方法 |

2. 反射示例

  • 获得 Class 对象的三种方式
java 复制代码
package reflection.demo;

class Student {
    private String name = "yang";
    public int age = 18;

    public Student() {
        System.out.println("Student()");
    }

    private Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Student(name, age)");
    }

    private void eat() {
        System.out.println("I'm eating");
    }

    public void sleep() {
        System.out.println("I'm sleeping");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

public class Test {
    public static void main(String[] args) {
        Class<?> c1 = null;
        try {
            c1 = Class.forName("reflection.demo.Student");
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }

        Class<?> c2 = Student.class;

        Student student = new Student();
        Class<?> c3 = student.getClass();
    }
}
  • 反射的使用
java 复制代码
package reflection.demo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionClassDemo {
    public void reflectNewInstance() {
        Class<?> c1 = null;
        try {
            c1 = Class.forName("reflection.demo.ReflectionClass");
            Student student = (Student) c1.newInstance();
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    public static void reflectPrivateConstructor() {
        Class<?> c1 = null;
        try {
            c1 = Class.forName("reflection.demo.ReflectionClass");
            Constructor<?> constructor = c1.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true);
            Student student = (Student) constructor.newInstance("yang", 18);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static void reflectPrivateField() {
        try {
            Class<?> classStudent = Class.forName("reflection.demo.ReflectionClass");
            Field field = classStudent.getDeclaredField("name");
            field.setAccessible(true);
            Object objectStudent = classStudent.newInstance();
            Student student = (Student) objectStudent;
            field.set(student, "小明");
            String name = (String) field.get(student);
            System.out.println("反射属性修改了name: " + name);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void reflectPrivateMethod() {
        try {
            Class<?> classStudent = Class.forName("demo.Student");
            Method methodStudent = classStudent.getDeclaredMethod("function",String.class);

            //私有的一般都要加
            methodStudent.setAccessible(true);

            Object objectStudent = classStudent.newInstance();
            Student student = (Student) objectStudent;

            methodStudent.invoke(student,"我是给私有的function函数传的参数");

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        reflectPrivateConstructor();
        reflectPrivateMethod();
        reflectPrivateField();
    }
}

1.3. 反射的优缺点

  1. 优点:对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。增加程序的灵活性和扩展性,降低耦合性,提高自适应能力。
  2. 缺点:使用反射会有效率问题,会导致程序效率降低。反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。

二、枚举的使用

2.1. 引入枚举的原因

在 JDK 1.5 之前,开发者通常通过整数 / 字符串静态常量表示一组相关值(如颜色、状态),但这种方式存在 3 个关键问题,而枚举正是为解决这些问题而生:类型不安全:常量易被误解或滥用;取值范围不可控,无法限制非法值。可读性差,常量值无语义,调试困难。

枚举不光解决传统痛点外,枚举还提供了传统常量不具备的特性,进一步简化开发:统一组织常量,逻辑更清晰;内置丰富方法,简化操作;支持自定义属性与方法,适配复杂场景。

2.2. 枚举的使用

1. switch 语句

java 复制代码
public enum TestEnum {
    RED, BLACK, GREEN;

    public static void main(String[] args) {
        TestEnum testEnum = TestEnum.RED;
        switch (testEnum) {
            case RED:
                System.out.println("red");
                break;
            case BLACK:
                System.out.println("black");
                break;
            case GREEN:
                System.out.println("green");
                break;
        }
    }
}

2. 常用方法

|-------------|------------------|
| 方法名称 | 描述 |
| values() | 以数组形式返回枚举类型的所有成员 |
| ordinal() | 获取枚举成员的索引位置 |
| valueOf() | 将普通字符串转换为枚举实例 |
| compareTo() | 比较两个枚举成员在定义时的顺序 |

java 复制代码
public enum TestEnum {
    BLACK, RED, GREEN;

    public static void main(String[] args) {
        TestEnum[] testEnums = TestEnum.values();
        for (int i = 0; i < testEnums.length; i++) {
            System.out.println(testEnums[i] + " " + testEnums[i].ordinal());
        }
        System.out.println("=====");
        try {
            // 通过valueOf方法将字符串"RED"转换为TestEnum枚举实例
            TestEnum testEnum = TestEnum.valueOf("RED");
            // 输出转换后的枚举实例
            System.out.println(testEnum);
        } catch (IllegalArgumentException e) {
            // 捕获并处理当传入的字符串与任何枚举常量名称不匹配时抛出的异常
            e.printStackTrace();
        }

        // 此方法实现的自然顺序是声明常量的顺序
        int to = RED.compareTo(BLACK);
        System.out.println(to);
    }
}

在Java当中枚举实际上就是⼀个类。所以我们在定义枚举的时候,还可以这样定义和使用枚举。注意:枚举的构造⽅法默认是私有的

java 复制代码
public enum TestEnum {
    BLACK(1, "黑色"), RED(2, "红色"), GREEN(3, "绿色");

    private int ordinal;
    private String color;

    TestEnum(int ordinal, String color) {
        this.ordinal = ordinal;
        this.color = color;
    }
}

2.3. 枚举的优缺点

优点:

  1. 枚举常量更简单安全;
  2. 枚举具有内置方法,代码更优雅。

缺点:

  1. 不可继承,无法扩展。

三、Lambda 表达式

3.1. 背景

Lambda 表达式是 Java SE 8 中一个重要的新特性,其核心作用是允许通过表达式来代替功能接口,简化代码实现。它的形式类似方法,包含正常的参数列表和使用这些参数的主体(主体可是一个表达式或一个代码块);该特性基于数学中的 λ 演算得名,也可称为闭包。

1. Lambda 表达式的语法

Lambda 表达式的核心语法有两种形式,适用于不同的方法体场景:

java 复制代码
(parameters) -> expression  // 方法体为单个表达式(无需大括号,表达式结果即为返回值)
(parameters) -> { statements; }  // 方法体为代码块(需大括号包裹,语句需分号结尾,返回值需显式写return)

Lambda 表达式由 3 个关键部分构成,各部分的规则与作用如下:

  1. parameters(参数列表)

    • 本质是函数式接口中抽象方法的参数列表(Lambda 需与函数式接口绑定,函数式接口指仅含一个抽象方法的接口)。
    • 参数类型可显式声明 (如(int x, int y)),也可省略声明 (由 JVM 根据上下文隐含推断,如(x, y))。
    • 若仅含一个参数 且类型省略,可进一步省略参数的小括号(如x -> 2 * x,无需写成(x) -> 2 * x)。
  2. ->(箭头操作符)

    • 连接参数列表与方法体,可理解为 "将参数用于执行后续方法体" 的逻辑关联,是 Lambda 表达式的语法标志。
  3. 方法体(body)

    分为两种形式:

    • 代码块 :需用大括号{}包裹,语句需以分号;结尾;若有返回值,需显式写return语句。例如(x, y) -> { int sum = x + y; return sum; }
    • 单个表达式 :无需大括号包裹,表达式的结果会自动作为返回值(若有返回值),无需写return。例如(x, y) -> x + y(返回 x 与 y 的和)。
java 复制代码
// 不需要参数,返回值为2
() -> 2;

// 接收一个参数,返回原来的2倍
x -> 2 * x;

// 接收两个参数,返回它们的和
(x, y) -> x + y;

// 接收两个 int 型参数,返回它们的乘积
(int x, int y) -> x * y;

2. 函数式接口

函数式接口是有且只有一个抽象方法的接口,它是 Lambda 表达式的 "目标类型"------Lambda 表达式本质是函数式接口抽象方法的简洁实现,二者需一一对应(Lambda 的参数 / 返回值需与接口抽象方法匹配)。

@FunctionalInterface 注解是可选但推荐添加的 "标记注解",用于让编译器强制校验接口是否符合函数式接口规则。

java 复制代码
@FunctionalInterface
interface NoParameterNoReturn {
    // 唯一抽象方法
    void test1();
    // 默认方法(有实现,不影响函数式接口定义)
    default void test2() {
        System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");
    }
}

3.2. Lambda 表达式的基本使用

java 复制代码
// 无返回值无参数
@FunctionalInterface
interface NoParameterNoReturn {
    void test();
}

// 无返回值一个参数
@FunctionalInterface
interface OneParameterNoReturn {
    void test(int a);
}

// 无返回值多个参数
@FunctionalInterface
interface MoreParameterNoReturn {
    void test(int a, int b);
}

// 有返回值无参数
@FunctionalInterface
interface NoParameterReturn {
    int test();
}

// 有返回值一个参数
@FunctionalInterface
interface OneParameterReturn {
    int test(int a);
}

// 有返回值多参数
@FunctionalInterface
interface MoreParameterReturn {
    int test(int a, int b);
}

public class Demo1 {
    public static void main(String[] args) {
        NoParameterNoReturn noParameterNoReturn = () -> System.out.println("无参数无返回值");
        noParameterNoReturn.test();

        OneParameterNoReturn oneParameterNoReturn = (int a) -> System.out.println("一个参数无返回值");
        oneParameterNoReturn.test(1);

        MoreParameterNoReturn moreParameterNoReturn = (int a, int b) -> System.out.println("多个参数无返回值");
        moreParameterNoReturn.test(1, 2);

        NoParameterReturn noParameterReturn = () -> 10;
        System.out.println(noParameterReturn.test());

        OneParameterReturn oneParameterReturn = (int x) -> x * 2;
        System.out.println(oneParameterReturn.test(10));

        MoreParameterReturn moreParameterReturn = (int a, int b) -> a + b;
        System.out.println(moreParameterReturn.test(1, 2));
    }
}

语法精简:

  1. 参数类型可省略,所有参数的类型必须同时省略,不能部分省略;

  2. 单个参数时,小括号可省略;

  3. 方法体仅 1 句代码时,大括号可省略;

  4. 方法体仅 1 句 return 语句时,大括号 + return 关键字可同时省略。

3.3. 变量捕获

Lambda 表达式中存在变量捕获,了解了变量捕获之后,我们才能更好的理解 Lambda 表达式的作用域。Java 当中的匿名类中,会存在变量捕获。

1. 匿名内部类

匿名内部类是没有名字的内部类,无需单独定义类名,在使用时直接创建其实例并实现接口 / 继承类、重写方法。

java 复制代码
// 定义外部类
class Test {
    public void func() {
        System.out.println("func()");
    }
}

public class Demo2 {
    public static void main(String[] args) {
        // 匿名内部类:无类名,直接new Test()并重写func()
        new Test() {
            @Override
            public void func() {
                System.out.println("我是内部类,且重写了func这个方法!");
            }
        };
    }
}

2. 匿名内部类的变量捕获

java 复制代码
public class Test {
    public static void main(String[] args) {
        int a = 100; // 未修改的外部变量
        new Test() {
            @Override
            public void func() {
                System.out.println("我是内部类,且重写了func这个方法!");
                System.out.println("捕获到变量 a == " + a + "(未被修改,合法)");
            }
        };
    }
}

3.4. Lambda 在集合中的使用

|------------|-------------------------------------------------------------------------------------------------------------------------------|
| 对应的接口 | 新增的方法 |
| Collection | removeIf() spliterator() stream() parallelStream() forEach() |
| List | replaceAll() sort() |
| Collection | getOrDefault() forEach() replaceAll() putIfAbsent() remove() replace() computeIfAbsent() computeIfPresent() compute() merge() |

1. Collection 接口

java 复制代码
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}
java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Demo2 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("Java");
        list.add("lambda");

        list.forEach(s -> {
            System.out.println(s);
        });
    }
}

2. List 接口

java 复制代码
default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}
java 复制代码
import java.util.ArrayList;
import java.util.List;

public class Demo3 {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("hello");
        list.add("Java");
        list.add("lambda");

        list.sort((str1, str2) -> str1.length() - str2.length());
        System.out.println(list);
    }
}

3. Map 接口

java 复制代码
default void forEach (BiConsumer < ? super K, ?super V > action){
    Objects.requireNonNull(action);
    for (Map.Entry<K, V> entry : entrySet()) {
        K k;
        V v;
        try {
            k = entry.getKey();
            v = entry.getValue();
        } catch (IllegalStateException ise) {
            // this usually means the entry is no longer in the map.
            throw new ConcurrentModificationException(ise);
        }
        action.accept(k, v);
    }
}
java 复制代码
import java.util.HashMap;
import java.util.Map;

public class Demo4 {
    public static void main(String[] args) {
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "hello");
        map.put(2, "Java");
        map.put(3, "lambda");

        map.forEach((k, v) -> System.out.println(k + "=" + v));
    }
}
相关推荐
ᐇ9593 小时前
Java Vector集合全面解析:线程安全的动态数组
java·开发语言
毕设源码-朱学姐3 小时前
【开题答辩全过程】以 广州网红点打卡介绍网站为例,包含答辩的问题和答案
java·eclipse
程序定小飞3 小时前
基于springboot的web的音乐网站开发与设计
java·前端·数据库·vue.js·spring boot·后端·spring
Hello_WOAIAI3 小时前
2.4 python装饰器在 Web 框架和测试中的实战应用
开发语言·前端·python
搬山.摧城4 小时前
线程池和单例模式
开发语言·单例模式
百锦再4 小时前
第1章 Rust语言概述
java·开发语言·人工智能·python·rust·go·1024程序员节
武昌库里写JAVA4 小时前
element-ui 2.x 及 vxe-table 2.x 使用 css 定制主题
java·vue.js·spring boot·sql·学习
一叶之秋14124 小时前
QT背景介绍与环境搭建
开发语言·qt
java1234_小锋4 小时前
PyTorch2 Python深度学习 - 模型保存与加载
开发语言·python·深度学习·pytorch2