一、泛型
泛型(Generic)是 Java 5 新增的特性,核心作用是在编译阶段约束数据类型,避免类型转换异常,提高代码的通用性和安全性(本质是「类型参数化」,将具体类型作为参数传递)。
核心说明
- 泛型只在编译阶段有效,运行阶段会被擦除(类型擦除),最终编译为 Object 类型。
- 泛型的类型参数只能是引用类型,不能是基本数据类型(需使用对应的包装类)。
- 泛型可以用于类、方法、接口,分别称为「泛型类」「泛型方法」「泛型接口」。
- 通配符
?用于解决泛型的类型兼容问题,支持上届限定(? 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 的核心特性之一,允许程序在运行时获取类的完整信息(类名、构造方法、字段、方法等),并能动态操作类的对象、字段和方法,无需在编译阶段知晓具体类名。
核心说明
- 反射的核心是
java.lang.Class类:每个类在 JVM 中只有一个Class对象,作为该类的「元数据」载体。 - 反射打破了类的封装性(可通过
setAccessible(true)访问私有成员),灵活性高,但效率较低,且存在安全风险。 - 反射广泛应用于框架(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 类(无法手动继承其他类)。
核心说明
- 枚举类的构造方法默认是私有的,无法通过
new关键字实例化对象。 - 枚举常量是枚举类的静态最终实例,按定义顺序排列,拥有唯一的索引(从 0 开始)。
- 枚举支持
switch语句,且无需手动转换类型。 - 反射无法创建枚举实例(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 新增的特性,是匿名内部类的简化写法,仅适用于「函数式接口」,核心作用是简化代码,提高可读性,支持函数式编程。
核心说明
- 函数式接口:只有一个抽象方法的接口(可包含默认方法、静态方法),可使用
@FunctionalInterface注解校验。 - Lambda 表达式的本质:是一个「函数式接口的实例」,无需手动实现接口,直接编写方法体。
- Lambda 表达式不依赖于类,无法访问非 final 的局部变量(变量捕获特性)。
函数式接口定义
// @FunctionalInterface 注解:校验该接口是否为函数式接口(非必须,但推荐添加)
@FunctionalInterface
public interface Test {
// 唯一的抽象方法
void test();
}
Lambda 表达式的语法与简写
基础语法
(参数列表) -> { 方法体 }
():包裹方法的参数列表,参数类型可省略(编译器自动推断)。->:箭头操作符,用于分隔参数列表和方法体。{}:包裹方法体,若方法体只有一句代码,可省略{}。
简写规则
- 参数类型可省略(编译器自动推断)。
- 若只有一个参数,可省略
()。 - 若方法体只有一句代码,可省略
{}。 - 若方法体只有一句
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));
}
}