泛型&反射&枚举&lambda表达式

泛型&反射&枚举&lambda表达式

一、泛型

泛型(Generic)是 Java 5 新增的特性,核心作用是在编译阶段约束数据类型,避免类型转换异常,提高代码的通用性和安全性(本质是「类型参数化」,将具体类型作为参数传递)。

核心说明

  1. 泛型只在编译阶段有效,运行阶段会被擦除(类型擦除),最终编译为 Object 类型。
  2. 泛型的类型参数只能是引用类型,不能是基本数据类型(需使用对应的包装类)。
  3. 泛型可以用于类、方法、接口,分别称为「泛型类」「泛型方法」「泛型接口」。
  4. 通配符 ? 用于解决泛型的类型兼容问题,支持上届限定(? extends 父类)和下届限定(? super 子类)。

泛型类、泛型方法、泛型上届

import java.util.Comparator;

// 泛型类:类名后添加 <T>,T 为类型参数(可自定义命名,常用 T/E/K/V 等)
class MyArray<T> {

    // 底层用 Object 数组存储数据(类型擦除后的结果)
    Object[] arr = new Object[5];

    // 泛型方法(类中的方法使用泛型类型参数)
    public void setArr(int pos, T val) {
        this.arr[pos] = val;
    }

    // 泛型方法:返回值为 T 类型,需要强制类型转换(类型擦除导致)
    public T getArr(int pos) {
        return (T) arr[pos];
    }

}

// 泛型类继承:继承时需指定具体的泛型类型(或继续保留泛型参数)
class StringMyArray extends MyArray<String> {
    // 重写父类方法:参数类型为 String(已指定泛型类型)
    // 由于父类泛型擦除后参数为 Object,编译器会自动生成桥接方法保证兼容性
    @Override
    public void setArr(int pos, String val) {
        super.setArr(pos, val);
    }
}

// 泛型上届:使用 extends 限定泛型类型的上届(E 必须实现 Comparator 接口)
// 表示 E 只能是 Comparator 接口的实现类,限制泛型的取值范围
class MyArray2<E extends Comparator<E>> {
}

// 工具类:包含静态泛型方法
class util {
    // 静态泛型方法:返回类型前必须添加 <T>,声明这是一个泛型方法
    public static <T> void swap(T[] arr, int i, int j) {
        T tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

public class GenericClass {
    public static void main(String[] args) {
    
        // 泛型类实例化:指定具体类型为 Integer(必须是引用类型)
        MyArray<Integer> myArray = new MyArray<>(); // 右侧可省略泛型类型(菱形语法)
        myArray.setArr(1, 2);
        int ret = myArray.getArr(1); // 无需手动类型转换,编译阶段已约束
        System.out.println(ret);

        // 静态泛型方法调用:传入包装类型数组(不能传入 int[] 基本类型数组)
        util.swap(new Integer[]{1, 2, 3}, 1, 2);
    }
}

泛型通配符(上届限定)

// 定义继承关系,用于演示泛型通配符
class Food {}
class Fruit extends Food{}
class Apple extends Fruit {}
class Banana extends Fruit {}

// 泛型容器类
class Plate<T> {
    private T data ;
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
}

// 泛型通配符演示
class TestDemo {
    public static void main(String[] args) {
        Plate<Apple> plate = new Plate<>();
        plate.setData(new Apple());
        fun(plate); // 可传入 Plate<Apple>(Fruit 子类)
        
        Plate<Banana> plate2 = new Plate<>();
        plate2.setData(new Banana());
        fun(plate2); // 可传入 Plate<Banana>(Fruit 子类)
    }

    // 泛型通配符上届:? extends Fruit
    // 表示只能接收 Plate<Fruit> 或 Plate<Fruit 子类> 的对象
    public static void fun(Plate<? extends Fruit> temp){
        // 注意:上届通配符只能「读取」,不能「写入」
        // 原因:无法确定 temp 具体是 Plate<Apple> 还是 Plate<Banana>,添加元素会存在类型安全问题
        // temp.setData(new Banana()); // 编译报错
        // temp.setData(new Apple()); // 编译报错
        System.out.println(temp.getData()); // 读取安全,返回值为 Fruit 类型
    }
}

二、反射

反射(Reflection)是 Java 的核心特性之一,允许程序在运行时获取类的完整信息(类名、构造方法、字段、方法等),并能动态操作类的对象、字段和方法,无需在编译阶段知晓具体类名。

核心说明

  1. 反射的核心是 java.lang.Class 类:每个类在 JVM 中只有一个 Class 对象,作为该类的「元数据」载体。
  2. 反射打破了类的封装性(可通过 setAccessible(true) 访问私有成员),灵活性高,但效率较低,且存在安全风险。
  3. 反射广泛应用于框架(Spring、MyBatis 等),实现动态创建对象、依赖注入等功能。

准备工作:Student 实体类

public class Student {

    private String name = "meat";
    private int age = 18;

    // 无参公共构造方法
    public Student() {
        System.out.println("Student()");
    }

    // 私有有参构造方法
    private Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 公共无参方法
    public void eat() {
        System.out.println("meat eat");
    }

    // 公共无参方法
    public void sleep() {
        System.out.println("meat sleep");
    }

    // 公共有参方法
    public void func(String str) {
        System.out.println(str);
    }

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

获取 Class 对象的 3 种方式

Class 对象是反射的入口,获取方式有 3 种,且 JVM 中同一个类的 Class 对象唯一。

方式 1:Class.forName("全类名")(最常用)

import java.lang.Class;

class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        // 传入「包名.类名」,获取 Class 对象
        // 需抛出 ClassNotFoundException 异常(类不存在时抛出)
        Class<?> stu1 = Class.forName("Student");
    }
}
  • 特点:无需实例化对象,仅通过类名即可获取,适合框架动态加载类。
  • 返回值:Class<?>(通配符,表示未知类型的 Class 对象)。

方式 2:类名.class

class Test {
    public static void main(String[] args) {
        // 直接通过类名.class 获取,编译阶段检查类是否存在
        Class<?> stu2 = Student.class;
    }
}
  • 特点:无需实例化对象,无需处理异常,效率较高。
  • 适用场景:已知具体类,用于静态代码块、工具类等。

方式 3:对象.getClass()

class Test {
    public static void main(String[] args) {
        // 先实例化对象,再通过对象的 getClass() 方法获取
        Student student = new Student();
        Class<?> stu3 = student.getClass();
    }
}
  • 特点:需要先创建对象,适用于已持有对象实例的场景。
  • 注意:无论通过哪种方式获取,stu1 == stu2 == stu3 结果为 true(同一个类的 Class 对象唯一)。

反射的核心操作

操作 1:通过 Class 对象实例化类(无参构造)

import java.lang.Class;

class Test {
    public static void main(String[] args) {
        Class<?> stu;
        try {
            stu = Class.forName("Student");
            // 调用无参构造方法,实例化对象(已过时,推荐使用 Constructor 方式)
            Student student = (Student) stu.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);
        }
    }
}
  • 注意:要求类必须有公共无参构造方法,否则会抛出 InstantiationException
  • 缺点:无法调用有参构造方法,灵活性低,已被 Constructor.newInstance() 替代。

操作 2:获取构造方法并实例化类(有参构造)

import java.lang.Class;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

class Test {
    public static void main(String[] args) {
        Class<?> stu;
        try {
            stu = Class.forName("Student");
            // 获取指定参数类型的构造方法(私有构造方法也可获取)
            Constructor<?> constructor = stu.getDeclaredConstructor(String.class, int.class);
            // 打破封装性:设置可访问私有成员(忽略访问权限检查)
            constructor.setAccessible(true);
            // 调用有参构造方法,实例化对象
            Student student = (Student) constructor.newInstance("肉肉", 1);
            System.out.println(student);
        } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 核心方法:getDeclaredConstructor(参数类型列表)(获取所有构造方法,包括私有)、getConstructor(参数类型列表)(仅获取公共构造方法)。
  • 关键步骤:私有构造方法必须调用 setAccessible(true),否则会抛出 IllegalAccessException

操作 3:修改对象的字段(私有字段)

import java.lang.Class;
import java.lang.reflect.Field;

class Test {
    public static void main(String[] args) {
        Class<?> stu;
        try {
            stu = Class.forName("Student");
            // 获取指定名称的字段(私有字段也可获取)
            Field field = stu.getDeclaredField("name");
            // 打破封装性:设置可访问私有字段
            field.setAccessible(true);
            // 实例化对象
            Student student = (Student) stu.newInstance();
            // 修改字段值:field.set(对象实例, 新值)
            field.set(student, "肥肉肉");
            System.out.println(student);
        } catch (ClassNotFoundException | NoSuchFieldException | InstantiationException | IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 核心方法:getDeclaredField(字段名)(获取所有字段,包括私有)、getField(字段名)(仅获取公共字段)。
  • 关键步骤:私有字段必须调用 setAccessible(true),否则无法修改。

操作 4:调用对象的方法(私有方法)

import java.lang.Class;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class Test {
    public static void main(String[] args) {
        Class<?> stu;
        try {
            stu = Class.forName("Student");
            // 获取指定名称和参数类型的方法(私有方法也可获取)
            Method method = stu.getDeclaredMethod("func", String.class);
            // 实例化对象
            Student student = (Student) stu.newInstance();
            // 打破封装性:设置可访问私有方法
            method.setAccessible(true);
            // 调用方法:method.invoke(对象实例, 方法参数)
            method.invoke(student, "吃肉肉");
        } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}
  • 核心方法:getDeclaredMethod(方法名, 参数类型列表)(获取所有方法,包括私有)、getMethod(方法名, 参数类型列表)(仅获取公共方法)。
  • 关键步骤:私有方法必须调用 setAccessible(true),否则无法调用。
  • 返回值:invoke() 方法返回方法的执行结果(无返回值则返回 null)。

三、枚举

枚举(Enum)是 Java 5 新增的特性,用于表示一组固定的、有限的常量集合(如颜色、季节、星期等),所有枚举类都默认继承自 java.lang.Enum 类(无法手动继承其他类)。

核心说明

  1. 枚举类的构造方法默认是私有的,无法通过 new 关键字实例化对象。
  2. 枚举常量是枚举类的静态最终实例,按定义顺序排列,拥有唯一的索引(从 0 开始)。
  3. 枚举支持 switch 语句,且无需手动转换类型。
  4. 反射无法创建枚举实例(JVM 禁止),保证枚举的单例性。

基础枚举定义与使用

// 基础枚举定义:使用 enum 关键字,常量用大写字母表示,逗号分隔,末尾可加分号
public enum Color {
    RED, YELLOW, BLUE;
}
// 枚举的 switch 语句使用
public enum Color {
    RED, YELLOW, BLUE;

    public static void main(String[] args) {
        Color color = RED;
        switch (color) { // switch 直接接收枚举常量,无需引号
            case RED:
                System.out.println(RED);
                break;
            case BLUE:
                System.out.println(BLUE);
                break;
            case YELLOW:
                System.out.println(YELLOW);
                break;
            default:
                System.out.println("null");
                break;
        }
    }
}

枚举的常用方法(继承自 Enum 类)

public enum Color {
    RED, YELLOW, BLUE;

    public static void main(String[] args) {
        // 1. values():以数组形式返回枚举的所有常量(常用)
        Color[] colors = Color.values();
        for (int i = 0; i < colors.length; i++) {
            // 2. ordinal():获取枚举常量的索引位置(从 0 开始)
            System.out.println(colors[i] + "-->" + colors[i].ordinal());
        }
        
        // 3. valueOf(String name):将字符串转换为枚举实例(字符串必须与枚举常量名一致,否则抛出异常)
        Color tmp = Color.valueOf("RED");
        System.out.println(tmp);
        
        // 4. compareTo():比较两个枚举常量的索引差(当前常量索引 - 目标常量索引)
        System.out.println(RED.compareTo(YELLOW)); // 0 - 1 = -1
    }
}

带属性和构造方法的枚举(增强枚举)

// 带属性、构造方法的枚举
public enum Color{
    // 枚举常量:必须放在枚举类的第一行,相当于调用构造方法创建实例
    RED("红色", 1), 
    YELLOW("黄色", 2), 
    BLUE("蓝色", 3);

    // 枚举的成员变量(可私有,提供 getter 方法)
    private String name;
    private int key;

    // 私有构造方法(默认私有,可省略 private 关键字)
    Color(String name, int key) {
        this.name = name;
        this.key = key;
    }
}
// 反射无法创建枚举实例(运行报错)
class main {
    public static void main(String[] args) {
        Class<?> col;
        try {
            col = Class.forName("Color");
            // 尝试获取枚举的构造方法(实际 Enum 类的构造方法为私有,且 JVM 禁止调用)
            Constructor<?> constructor = col.getDeclaredConstructor(String.class, int.class, String.class, int.class);
            constructor.setAccessible(true);
            // 运行时抛出 InstantiationException:无法实例化枚举类
            Color color = (Color) constructor.newInstance("棕色", 4, "黑色", 4);
            System.out.println(color);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

四、Lambda 表达式

Lambda 表达式是 Java 8 新增的特性,是匿名内部类的简化写法,仅适用于「函数式接口」,核心作用是简化代码,提高可读性,支持函数式编程。

核心说明

  1. 函数式接口:只有一个抽象方法的接口(可包含默认方法、静态方法),可使用 @FunctionalInterface 注解校验。
  2. Lambda 表达式的本质:是一个「函数式接口的实例」,无需手动实现接口,直接编写方法体。
  3. Lambda 表达式不依赖于类,无法访问非 final 的局部变量(变量捕获特性)。

函数式接口定义

// @FunctionalInterface 注解:校验该接口是否为函数式接口(非必须,但推荐添加)
@FunctionalInterface
public interface Test {
    // 唯一的抽象方法
    void test();
}

Lambda 表达式的语法与简写

基础语法

(参数列表) -> { 方法体 }
  • ():包裹方法的参数列表,参数类型可省略(编译器自动推断)。
  • ->:箭头操作符,用于分隔参数列表和方法体。
  • {}:包裹方法体,若方法体只有一句代码,可省略 {}

简写规则

  1. 参数类型可省略(编译器自动推断)。
  2. 若只有一个参数,可省略 ()
  3. 若方法体只有一句代码,可省略 {}
  4. 若方法体只有一句 return 语句,可省略 {}return

示例对比(匿名内部类 vs Lambda 表达式):

// 匿名内部类
Test test1 = new Test() {
    @Override
    public void test() {
        System.out.println("匿名内部类实现");
    }
};

// Lambda 表达式(完整写法)
Test test2 = () -> {
    System.out.println("Lambda 完整写法");
};

// Lambda 表达式(简写写法,方法体只有一句代码)
Test test3 = () -> System.out.println("Lambda 简写写法");

变量捕获

Lambda 表达式(及匿名内部类)只能访问「最终变量」或「事实上的最终变量」(即变量值从未被修改)。

@FunctionalInterface
public interface lambda {
    void test();
}

public class Test {
    public static void main(String[] args) {
        int a = 100; // 事实上的最终变量(值未被修改)
        lambda lambda = () -> {
            // a = 10; // 编译报错:无法修改局部变量 a 的值(变量捕获特性)
            System.out.println(a);
        };
        lambda.test();
    }
}

Lambda 表达式的实际应用

应用 1:遍历集合(Consumer 接口)

import java.util.ArrayList;
import java.util.function.Consumer;

public class Test {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("meat");
        list.add("OK!");
        
        // 匿名内部类写法
        /*list.forEach(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });*/
        
        // Lambda 表达式简写
        list.forEach(s -> System.out.println(s));
    }
}

应用 2:集合排序(Comparator 接口)

import java.util.ArrayList;
import java.util.Comparator;

public class Test {
    public static void main(String[] args) {
        ArrayList<String> list = new ArrayList<>();
        list.add("Hello");
        list.add("meat");
        list.add("OK!");
        
        // 匿名内部类写法
        /*list.sort(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });*/
        
        // Lambda 表达式简写
        list.sort((o1, o2) -> o1.compareTo(o2));
    }
}
异常 2025-07-20
顺序表 2025-07-26

评论区