java基础-知识点(二)
1.反射
Java 反射(Reflection)是运行时动态获取类信息、创建对象、调用方法 / 访问字段的机制,核心入口是java.lang.Class,操作 API 在java.lang.reflect包。
每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
1.1核心概念
- 定义:反射让程序在运行时 “自省”—— 查看类的结构(构造器、方法、字段、注解)、动态造对象、调用任意方法 / 读写任意字段(含
private)。 - 本质:类加载后,JVM 在方法区生成唯一
Class对象,反射通过该对象逆向操作类的元数据。 - 核心类:
Class<?>:类元数据入口Constructor:构造器Method:方法Field:字段Modifier:修饰符工具
1.2获取 Class 对象(3 种)
// 1. 类名.class(编译期安全,常用)
Class<String> cls1 = String.class;
// 2. 对象.getClass()(实例获取)
String str = "hello";
Class<? extends String> cls2 = str.getClass();
// 3. Class.forName("全限定名")(运行时动态加载,框架常用)
Class<?> cls3 = Class.forName("java.lang.String");
1.3常用反射操作
1.创建对象实例
class User {
public User() {}
public User(String name) {}
private User(int age) {}
}
// 无参构造
Class<User> userClass = User.class;
User u1 = userClass.newInstance(); // 过时,推荐下面
// 显式无参构造
User u2 = userClass.getDeclaredConstructor().newInstance();
// 有参构造
User u3 = userClass.getConstructor(String.class).newInstance("Tom");
// 私有构造(需开访问权限)
Constructor<User> privateCtor = userClass.getDeclaredConstructor(int.class);
privateCtor.setAccessible(true); // 突破private
User u4 = privateCtor.newInstance(18);
2.访问 / 修改字段(Field)
class User {
public String name;
private int age;
}
User user = new User();
Class<User> cls = User.class;
// 公有字段
Field nameField = cls.getField("name");
nameField.set(user, "Alice"); // 写
System.out.println(nameField.get(user)); // 读:Alice
// 私有字段
Field ageField = cls.getDeclaredField("age");
ageField.setAccessible(true); // 必加
ageField.set(user, 20);
System.out.println(ageField.get(user)); // 20
3.调用方法(Method)
class User {
public void sayHello() { ... }
private String getMsg(String s) { return s; }
}
User user = new User();
Class<User> cls = User.class;
// 公有无参方法
Method sayHello = cls.getMethod("sayHello");
sayHello.invoke(user); // 调用
// 私有有参方法
Method getMsg = cls.getDeclaredMethod("getMsg", String.class);
getMsg.setAccessible(true);
String res = (String) getMsg.invoke(user, "Hi");
System.out.println(res); // Hi
4. 获取类的完整信息
Class<?> cls = User.class;
// 类名
System.out.println(cls.getName()); // 全限定名
System.out.println(cls.getSimpleName()); // 简单名
// 构造器
Constructor<?>[] ctors = cls.getDeclaredConstructors();
// 方法(含私有)
Method[] methods = cls.getDeclaredMethods();
// 字段(含私有)
Field[] fields = cls.getDeclaredFields();
// 父类/接口
Class<?> superCls = cls.getSuperclass();
Class<?>[] interfaces = cls.getInterfaces();
1.4 setAccessible (true) 关键作用
- 作用:关闭 Java 语言访问检查,突破
private/protected/ 包私有限制,访问私有成员。 - 风险:破坏封装、安全管理器可拦截、模块系统(Java 9+)会限制。
1.5 优缺点
优点
- 动态性:运行时确定类型,无需编译期硬编码。
- 框架基石:Spring IoC、AOP、MyBatis、JUnit 等底层依赖反射。
- 通用工具:序列化、JSON 解析、动态代理等。
缺点
- 性能差:反射绕开编译期优化,比直接调用慢 5~10 倍。
- 安全风险:可访问私有成员,破坏封装。
- 维护难:字符串硬编码(如方法名),编译期不报错,运行时才发现。
- 兼容性:Java 9 + 模块系统对反射有严格限制。
1.6 总结
反射是 Java 动态性的核心,运行时可操作类的所有成员,是框架和工具的底层必备技术。但因性能、安全、维护问题,业务代码应尽量少用,优先用接口 / 工厂模式;框架层合理使用,配合setAccessible(true)突破访问限制
2.异常
Java 异常是程序运行时出现的错误或意外情况(如空指针、数组越界、文件找不到),Java 用面向对象的方式统一处理异常,让程序更健壮、易维护。
2.1 异常体系结构(核心)
所有异常的根父类:java.lang.Throwable
它有两个直接子类:
1. Error(错误)
- JVM 系统级错误,程序无法处理
- 例如:OOM 内存溢出、栈溢出、虚拟机崩溃
- 我们不需要捕获,也捕获不住
2. Exception(异常)
- 程序级错误,可以捕获、处理、修复
- 分两大类:
- 运行时异常(RuntimeException)
- 编译时异常(非 RuntimeException)
2.2 两大异常分类
1. 运行时异常(RuntimeException)
- 不强制捕获,编译不报错,运行时才抛出
- 大多是代码逻辑错误导致
- 最常见的:
NullPointerException空指针IndexOutOfBoundsException数组越界IllegalArgumentException参数不合法ClassCastException类型转换异常
2. 编译时异常(Checked Exception)
- 必须捕获或声明抛出,否则编译不通过
- 不是代码逻辑问题,是外部环境问题
- 常见:
IOException文件 / IO 异常SQLException数据库异常ClassNotFoundException类找不到
2.3 异常处理 5 大关键字
try 监控区域
catch 捕获异常
finally 最终必须执行(常用于关流、关连接)
throw 手动抛出一个异常
throws 方法上声明可能抛出的异常
2.4 最标准的异常处理写法
1. 基础捕获
try {
// 可能出异常的代码
int res = 10 / 0;
} catch (ArithmeticException e) {
// 异常处理
System.out.println("除数不能为0");
e.printStackTrace(); // 打印异常堆栈(调试用)
} finally {
// 无论是否异常,都会执行
System.out.println("finally 一定执行");
}
2.多异常捕获
try {
// ...
} catch (NullPointerException e) {
// 处理空指针
} catch (IndexOutOfBoundsException e) {
// 处理越界
} catch (Exception e) {
// 捕获其他所有异常(兜底)
}
3.手动抛异常(throw)
public void checkAge(int age) {
if (age < 0) {
// 手动抛出运行时异常
throw new IllegalArgumentException("年龄不能为负数");
}
}
4.方法声明异常(throws)
// 告诉调用者:我这里可能抛出IO异常
public void readFile() throws IOException {
// 读文件代码
}
2.5 总结
- Throwable:异常顶层父类
- Error:JVM 错误,管不了
- Exception:程序异常,能处理
- RuntimeException:运行时异常,不强制捕获
- CheckedException:编译时异常,必须捕获
- try-catch-finally:捕获处理
- throw/throws:抛出异常
- 自定义异常:继承 RuntimeException 最常用
3.泛型
Java 泛型是 JDK 5 引入的特性,核心作用是:在编译时检查类型安全,避免运行时类型转换异常,同时让代码更通用、可复用。
简单说:泛型 = 参数化类型,把类型当作参数传递,让类 / 方法 / 接口可以适配多种数据类型
3.1 为什么需要泛型?
先看没有泛型的问题:
List list = new ArrayList();
list.add("Hello");
list.add(100); // 可以存任意类型,编译不报错
// 取出时必须强转,很容易出错
String str = (String) list.get(1); // 运行时报错:ClassCastException
问题:
- 编译期无法发现类型错误,运行时才崩溃
- 频繁强制类型转换,代码臃肿、易出错
- 集合能存任意对象,类型不安全
使用泛型后:
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(100); 编译直接报错,提前发现问题
String str = list.get(0); // 无需强转
3.2 泛型的核心概念
1. 泛型标识(常用字母)
约定俗成的写法,方便阅读:
T:Type(任意类型)E:Element(集合元素类型)K:Key(键)V:Value(值)N:Number(数字类型)
2. 泛型的使用场景
- 泛型类
- 泛型接口
- 泛型方法
- 通配符
3.3 泛型的三种使用方式
1. 泛型类
把类型定义在类上,整个类都可以使用这个类型。
// 泛型类:T 是类型参数
public class Box<T> {
private T data;
public void set(T data) {
this.data = data;
}
public T get() {
return data;
}
}
使用:
Box<String> box1 = new Box<>();
box1.set("字符串");
String s = box1.get();
Box<Integer> box2 = new Box<>();
box2.set(100);
int i = box2.get();
2. 泛型接口
和泛型类用法一致,定义在接口上。
public interface Generator<T> {
T generate();
}
实现类:
// 实现时指定类型
public class StringGenerator implements Generator<String> {
@Override
public String generate() {
return "生成字符串";
}
}
3. 泛型方法
方法自己独立定义泛型,不依赖类的泛型。
public class Utils {
// 泛型方法:<T> 声明泛型,T 是返回值/参数类型
public static <T> T getFirstElement(T[] array) {
if (array == null || array.length == 0) {
return null;
}
return array[0];
}
}
使用:
String[] strArr = {"a", "b"};
String first = Utils.getFirstElement(strArr);
Integer[] intArr = {1, 2, 3};
Integer firstInt = Utils.getFirstElement(intArr);
3.4 泛型通配符(?)
通配符 ? 表示任意类型,主要用于灵活接收泛型对象。
1. 无界通配符 ?
// 可以接收任意泛型类型
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
2. 上界通配符 ? extends 类型
只接收 该类型 及其 子类(读安全,不能写)
// 只能接收 Number 及其子类(Integer、Double等)
public static void printNumbers(List<? extends Number> list) { }
3. 下界通配符 ? super 类型
只接收 该类型 及其 父类(写安全)
// 只能接收 Integer 及其父类(Number、Object)
public static void addIntegers(List<? super Integer> list) {
list.add(100);
}
3.5 泛型的重要规则
- 泛型只在编译期有效,运行时会被类型擦除(编译后变成原始类型,如
List) - 不能使用基本类型(必须用包装类
Integer/Double) - 静态方法 / 变量不能使用类的泛型,只能用自己的泛型
- 不能使用
instanceof判断泛型类型 - 不能创建泛型数组:
new T[10]错误
3.6 总结
- 泛型 = 参数化类型,让代码通用且类型安全
- 三大使用方式:泛型类、泛型接口、泛型方法
- 通配符:
?(任意)、? extends(上限)、? super(下限) - 核心价值:编译期检查 + 避免强转 + 代码复用
4.注解
Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。
4.1 内置三大基础注解(常用)
1. @Override
重写校验,编译检查
- 必须重写父类 / 接口方法
- 方法名、参数、返回值错了直接编译报错
@Override
public String toString() {
return "xxx";
}
2. @Deprecated
标记过时、废弃方法 / 类 / 字段
- 不推荐使用,编译器标删除线警告
3. @SuppressWarnings
压制编译器警告
@SuppressWarnings("all") // 忽略所有警告
@SuppressWarnings("unchecked")// 忽略泛型未检查强转警告
4.2 元注解(用来「修饰注解」的注解)
写自定义注解必须掌握,共 5 个核心元注解:
1. @Target
指定注解能加在哪些位置
// 常用取值
ElementType.TYPE // 类、接口、枚举
ElementType.FIELD // 成员变量
ElementType.METHOD // 方法
ElementType.PARAMETER // 方法参数
ElementType.CONSTRUCTOR// 构造器
ElementType.LOCAL_VARIABLE // 局部变量
2. @Retention
指定注解保留时机(关键)
RetentionPolicy.SOURCE:仅源码阶段,编译后丢弃(如 @Override)CLASS:保留到 class 文件,运行时丢失(默认)RUNTIME:保留到运行时,可通过反射读取(框架核心)
3. @Documented
生成 javadoc 时,保留该注解文档
4. @Inherited
子类自动继承父类上的该注解
5. @Repeatable
JDK8+,同一位置可重复加多个相同注解
4.3 自定义注解
1. 定义格式
// 元注解 + @interface 关键字
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldDesc {
// 注解属性:格式 类型 名() default 默认值;
String value() default "";
int sort() default 1;
}
注解属性支持类型:基本类型、String、枚举、注解、以上类型数组
2. 使用自定义注解
public class User {
@FieldDesc(value = "用户名", sort = 2)
private String username;
@FieldDesc(value = "年龄")
private Integer age;
}
更多推荐




所有评论(0)