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



专栏:Java 数据结构秘籍
个人主页:手握风云
目录
一、反射
1.1. 定义
1.2. 反射相关的类
1. Class 类(反射机制的起源)
2. 反射示例
1.3. 反射的优缺点
二、枚举的使用
2.1. 引入枚举的原因
2.2. 枚举的使用
1. switch 语句
2. 常用方法
2.3. 枚举的优缺点
三、Lambda 表达式
3.1. 背景
1. Lambda 表达式的语法
2. 函数式接口
3.2. Lambda 表达式的基本使用
3.3. 变量捕获
1. 匿名内部类
2. 匿名内部类的变量捕获
3.4. Lambda 在集合中的使用
1. Collection 接口
2. List 接口
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 对象的三种方式
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");}@Overridepublic 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();}
}
- 反射的使用
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. 反射的优缺点
- 优点:对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。增加程序的灵活性和扩展性,降低耦合性,提高自适应能力。
- 缺点:使用反射会有效率问题,会导致程序效率降低。反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。
二、枚举的使用
2.1. 引入枚举的原因
在 JDK 1.5 之前,开发者通常通过整数 / 字符串静态常量表示一组相关值(如颜色、状态),但这种方式存在 3 个关键问题,而枚举正是为解决这些问题而生:类型不安全:常量易被误解或滥用;取值范围不可控,无法限制非法值。可读性差,常量值无语义,调试困难。
枚举不光解决传统痛点外,枚举还提供了传统常量不具备的特性,进一步简化开发:统一组织常量,逻辑更清晰;内置丰富方法,简化操作;支持自定义属性与方法,适配复杂场景。
2.2. 枚举的使用
1. switch 语句
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() | 比较两个枚举成员在定义时的顺序 |
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当中枚举实际上就是⼀个类。所以我们在定义枚举的时候,还可以这样定义和使用枚举。注意:枚举的构造⽅法默认是私有的。
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. 枚举的优缺点
优点:
- 枚举常量更简单安全;
- 枚举具有内置方法,代码更优雅。
缺点:
- 不可继承,无法扩展。
三、Lambda 表达式
3.1. 背景
Lambda 表达式是 Java SE 8 中一个重要的新特性,其核心作用是允许通过表达式来代替功能接口,简化代码实现。它的形式类似方法,包含正常的参数列表和使用这些参数的主体(主体可是一个表达式或一个代码块);该特性基于数学中的 λ 演算得名,也可称为闭包。
1. Lambda 表达式的语法
Lambda 表达式的核心语法有两种形式,适用于不同的方法体场景:
(parameters) -> expression // 方法体为单个表达式(无需大括号,表达式结果即为返回值)
(parameters) -> { statements; } // 方法体为代码块(需大括号包裹,语句需分号结尾,返回值需显式写return)
Lambda 表达式由 3 个关键部分构成,各部分的规则与作用如下:
-
parameters(参数列表)
- 本质是函数式接口中抽象方法的参数列表(Lambda 需与函数式接口绑定,函数式接口指仅含一个抽象方法的接口)。
- 参数类型可显式声明(如
(int x, int y)),也可省略声明(由 JVM 根据上下文隐含推断,如(x, y))。 - 若仅含一个参数且类型省略,可进一步省略参数的小括号(如
x -> 2 * x,无需写成(x) -> 2 * x)。
-
->(箭头操作符)
- 连接参数列表与方法体,可理解为 “将参数用于执行后续方法体” 的逻辑关联,是 Lambda 表达式的语法标志。
-
方法体(body)
分为两种形式:- 代码块:需用大括号
{}包裹,语句需以分号;结尾;若有返回值,需显式写return语句。例如(x, y) -> { int sum = x + y; return sum; }。 - 单个表达式:无需大括号包裹,表达式的结果会自动作为返回值(若有返回值),无需写
return。例如(x, y) -> x + y(返回 x 与 y 的和)。
- 代码块:需用大括号
// 不需要参数,返回值为2
() -> 2;// 接收一个参数,返回原来的2倍
x -> 2 * x;// 接收两个参数,返回它们的和
(x, y) -> x + y;// 接收两个 int 型参数,返回它们的乘积
(int x, int y) -> x * y;
2. 函数式接口
函数式接口是有且只有一个抽象方法的接口,它是 Lambda 表达式的 “目标类型”——Lambda 表达式本质是函数式接口抽象方法的简洁实现,二者需一一对应(Lambda 的参数 / 返回值需与接口抽象方法匹配)。
@FunctionalInterface 注解是可选但推荐添加的 “标记注解”,用于让编译器强制校验接口是否符合函数式接口规则。
@FunctionalInterface
interface NoParameterNoReturn {// 唯一抽象方法void test1();// 默认方法(有实现,不影响函数式接口定义)default void test2() {System.out.println("JDK1.8新特性,default默认方法可以有具体的实现");}
}
3.2. Lambda 表达式的基本使用
// 无返回值无参数
@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 句代码时,大括号可省略;
-
方法体仅 1 句 return 语句时,大括号 + return 关键字可同时省略。
3.3. 变量捕获
Lambda 表达式中存在变量捕获,了解了变量捕获之后,我们才能更好的理解 Lambda 表达式的作用域。Java 当中的匿名类中,会存在变量捕获。
1. 匿名内部类
匿名内部类是没有名字的内部类,无需单独定义类名,在使用时直接创建其实例并实现接口 / 继承类、重写方法。
// 定义外部类
class Test {public void func() {System.out.println("func()");}
}public class Demo2 {public static void main(String[] args) {// 匿名内部类:无类名,直接new Test()并重写func()new Test() {@Overridepublic void func() {System.out.println("我是内部类,且重写了func这个方法!");}};}
}
2. 匿名内部类的变量捕获
public class Test {public static void main(String[] args) {int a = 100; // 未修改的外部变量new Test() {@Overridepublic 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 接口
default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}
}
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 接口
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);}
}
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 接口
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);}
}
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));}
}