基础语法
面向对象三大特性:封装、继承、多态
- 封装(Encapsulation)(保护数据,隐藏细节 → 让代码安全)
- 核心理解:把属性和方法打包成一个类,对外隐藏内部细节,只开放安全的访问入口。
- 核心作用:保护数据(防随意篡改)、简化代码(专注外部接口)、解耦维护(高内聚、改内部不影响外部功能)
- 实现方式:private、public 修饰,getter、setter。数据校验
- 继承 (Inheritance)(复用代码,建立层级 → 让代码简洁)
- 简单理解:子类「继承」父类的属性和方法,不用重复写代码,还能扩展自己的功能。
- 核心作用:代码复用、层级清晰(is a 的关系)
- 实现方式:extends 单继承,super,调用父类非私有方法,重写方法,新增方法属性
- 多态(Polymorphism)(统一接口,灵活扩展 → 让代码通用)
- 通俗理解:同一个行为,不同对象表现出不同的形态。
- 核心作用:代码灵活(用父类 / 接口接收所有子类对象)、易于扩展(新增子类,不用修改原有代码)
- 三要素:必须有继承 / 实现关系、必须方法重写、父类引用指向子类对象
- 形式:
- 运行时多态(重点):方法重写 → 程序运行时才确定调用哪个子类方法(实习最常用)
- 编译时多态:方法重载 → 同一个类中,方法名相同、参数不同(编译时就确定)
异常处理
概念:异常就是程序运行时出现的意外 / 错误情况。比如:空指针、数组越界、文件找不到、数据库连不上、传入非法参数。
不能让程序直接崩溃,而是提供一套异常机制:发现异常 → 抛出异常 → 捕获异常 → 处理异常,保证程序健壮、不闪退。

- 体系结构:
- 所有异常的顶级父类:
java.lang.Throwable- Error(错误):系统级严重问题,程序员处理不了
- 比如:
OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出)、NoClassDefFoundError - 原则:不捕获、不处理,只能优化代码 / JVM
- 比如:
- Exception(异常):业务 / 代码问题,程序员可以捕获并处理
- 受检异常 Checked Exception:编译期就报错,必须处理
- 比如:
IOException、SQLException、FileNotFoundException - 场景:IO 操作、网络、数据库、文件读写
- 比如:
- 非受检异常 Unchecked Exception:编译不报错,运行时才炸 =
RuntimeException及其子类- 比如:
NullPointerException(NPE)、IndexOutOfBoundsException、IllegalArgumentException - 原则:尽量通过代码避免,而不是靠 catch 兜底
- 比如:
- 受检异常 Checked Exception:编译期就报错,必须处理
- Error(错误):系统级严重问题,程序员处理不了
- 所有异常的顶级父类:
异常处理 5 个关键字:try、catch、finally、throw、throws
不要在 finally 里写 return!会覆盖 try/catch 里的返回值,甚至吞掉异常
throw:手动抛出一个异常 throws:方法上声明可能抛出的异常
TODO: 自定义业务异常:OK
TODO: 全局异常处理:OK
最佳实践:
- 禁止空 catch 块(最恶心的坏代码)
- 不要捕获 Throwable / Error
- 异常一定要打完整日志:不要只打
e.getMessage(),要打堆栈信息 - 早抛出,晚捕获(Early Throw, Late Catch)
- 底层发现问题立刻抛 上层统一处理(Controller 层全局捕获)
- 不要用异常做业务逻辑判断
- 资源必须在 finally 关闭 / 用 try-with-resources
- 自定义业务异常,统一错误码
- 运行时异常尽量避免,不要靠 catch 兜底
泛型
泛型的作用?
答:编译期类型安全,避免类型转换异常;消除强制类型转换;实现代码复用。
什么是类型擦除?
答:Java 编译后会删除所有泛型信息,字节码中只有原始类型,目的是兼容老版本 JDK。
? extends T和? super T的区别?
答:遵循 PECS 原则,extends是上界(只读,生产者),super是下界(只写,消费者)。
为什么泛型不能用基本类型?
答:类型擦除后泛型会变成Object,基本类型无法继承Object,必须用包装类。
为什么不能new T()?
答:类型擦除后不知道 T 的具体类型,JVM 无法实例化对象。
(1)泛型类 / 泛型接口(最常用:统一返回体、通用 DAO) 实战示例:后端统一返回结果类Result<T>
(2)泛型方法(独立泛型:静态方法专用) 示例:通用工具方法
(3)泛型通配符(?):处理未知类型
? extends T上界通配符(T 及子类)生产者 → 只读读取数据
? super T下界通配符(T 及父类)消费者 → 只写写入数据
反射
字节码对象,全类名
反射(Reflection):在程序运行时,动态获取类的完整信息(类名、父类、接口、构造方法、成员变量、普通方法),并动态创建对象、调用方法、操作属性的机制。
反射核心 API:反射的所有操作,都围绕 Class 字节码对象 展开,核心 4 个类
| 类名 | 作用 |
|---|---|
java.lang.Class | 代表一个类的字节码对象,反射的入口 |
java.lang.reflect.Constructor | 代表类的构造方法,用于创建对象 |
java.lang.reflect.Field | 代表类的成员变量,用于获取 / 修改属性值 |
java.lang.reflect.Method | 代表类的成员方法,用于动态调用方法 |
第一步:获取 Class 对象(3 种方式,面试必考)
Class对象是一个类在 JVM 中的唯一字节码表示,一个类只有一个 Class 对象。java// 方式1:Class.forName("全类名") → 最常用(框架底层用这个) Class<?> clazz1 = Class.forName("com.example.User"); // 方式2:对象.getClass() → 已有对象时使用 User user = new User(); Class<?> clazz2 = user.getClass(); // 方式3:类名.class → 编译期确定,最安全 Class<?> clazz3 = User.class;第二步:通过反射创建对象
java// 1. 获取Class对象 Class<User> clazz = User.class; // 2. 通过无参构造创建对象(最常用,Spring IOC就是这么做的) User user1 = clazz.newInstance(); // 旧写法 User user2 = clazz.getDeclaredConstructor().newInstance(); // 新写法 // 3. 通过有参构造创建对象 Constructor<User> constructor = clazz.getConstructor(Long.class, String.class); User user3 = constructor.newInstance(1L, "张三");第三步:通过反射操作成员变量(Field)
javaUser user = new User(); Class<?> clazz = user.getClass(); // 获取公有属性 Field usernameField = clazz.getField("username"); usernameField.set(user, "李四"); // 设置值 System.out.println(user.getUsername()); // 李四 // 获取私有属性(必须开启暴力访问) Field idField = clazz.getDeclaredField("id"); idField.setAccessible(true); // 关闭访问检查,突破封装 idField.set(user, 100L); System.out.println(user.getId()); // 100第四步:通过反射调用成员方法(Method)
javaUser user = new User(); Class<?> clazz = user.getClass(); // 调用公有方法 Method publicMethod = clazz.getMethod("publicMethod", String.class); publicMethod.invoke(user, "反射调用成功"); // 输出:公共方法:反射调用成功 // 调用私有方法 Method privateMethod = clazz.getDeclaredMethod("privateMethod"); privateMethod.setAccessible(true); privateMethod.invoke(user); // 输出:私有方法被调用
反射的运用场景:
Spring IOC 容器创建 Bean
Spring 读取
application.yml/@Component中的类全限定名 →通过
Class.forName()获取Class对象 →通过反射调用无参构造
newInstance()→创建 Bean 放入容器。
MyBatis 映射数据库结果集
MyBatis 查询数据库得到结果集 →
通过反射获取实体类的字段 →
自动调用
setter方法给对象赋值 →返回封装好的实体对象。
通用工具类:Bean 拷贝
Spring BeanUtils.copyProperties()、Apache BeanUtils底层都是反射:遍历源对象字段 → 反射获取值 → 反射给目标对象赋值。
动态代理 & AOP Spring AOP 底层的 JDK 动态代理,通过反射获取目标方法,在方法前后执行增强逻辑。
优点:
- 动态性强:运行时加载类,解耦代码
- 通用性高:编写通用工具、框架必备
- 突破封装:可访问私有成员
缺点:
- 性能差:反射是动态解析,比直接调用慢很多
- 破坏封装:可访问私有属性 / 方法,违背面向对象封装性
- 安全风险:可绕过权限控制,操作敏感属性
什么是反射?有什么作用?
答:运行时动态获取类信息、操作对象的机制;用于框架底层、通用工具、动态调用。
获取 Class 对象的三种方式?
答:Class.forName()、对象.getClass()、类名.class。
反射的优缺点?
答:优点动态解耦、通用;缺点性能差、破坏封装。
Spring/MyBatis 哪里用到了反射?
答:Spring IoC 创建 Bean;MyBatis 映射实体类赋值。
getField 和 getDeclaredField 的区别?
答:前者获取公有 + 父类属性,后者获取自身所有属性(含私有)。
注解
注解(Annotation)是Java 代码的元数据,本质是给类、方法、变量等打标记,本身不执行逻辑,需要配合反射解析后生效。
定义:一种特殊的接口,用于为代码添加说明性信息,不直接影响代码执行,需通过反射解析后生效。
作用:替代 XML 配置、简化代码、编译检查、运行时动态处理。
核心特点:注解是标记,反射是解析器。
注解的生命周期:通过@Retention指定,决定注解存活阶段:
- SOURCE:仅源码阶段,编译后丢弃(如
@Override) - CLASS:编译到 class 文件,运行时 JVM 不加载(默认)
- RUNTIME:运行时保留,可通过反射解析(框架自定义注解几乎都用这个)
注解的作用目标(ElementType):通过@Target指定,注解能标记的位置:
TYPE:类、接口、枚举FIELD:成员变量METHOD:方法PARAMETER:方法参数CONSTRUCTOR:构造方法LOCAL_VARIABLE:局部变量
Java 内置原生注解:
| 注解 | 作用 |
|---|---|
@Override | 标记方法重写,编译期校验是否正确重写父类方法 |
@Deprecated | 标记方法 / 类已过时,不推荐使用 |
@SuppressWarnings | 压制编译器警告(如未使用变量、泛型警告) |
@FunctionalInterface | 标记函数式接口,只能有一个抽象方法(Lambda 用) |
元注解是 JDK 提供的、用来定义其他注解的注解,共 4 个:
@Retention:指定注解生命周期,框架自定义注解必写
RUNTIME。java@Retention(RetentionPolicy.RUNTIME)@Target:指定注解能标记的位置(类 / 方法 / 字段等)。
java@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})@Documented:标记注解会被生成到 JavaDoc 文档中。
@Inherited:标记注解可被子类继承(父类加了该注解,子类自动继承)。 运用场景:抽象接口定义,然后实现类自动继承注解,就不用再手写一次了。
自定义注解 + 反射解析:
步骤 1:定义自定义注解
javaimport java.lang.annotation.*; // 元注解:运行时生效 + 可标记类和字段 @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.FIELD}) public @interface MyAnnotation { // 注解属性(类似方法) String value() default ""; int age() default 18; }步骤 2:用注解标记类 / 字段
java@MyAnnotation(value = "用户类", age = 20) public class User { @MyAnnotation(value = "用户ID") private Long id; private String username; }步骤 3:通过反射解析注解
javapublic class AnnotationParse { public static void main(String[] args) { Class<User> clazz = User.class; // 1. 解析类上的注解 if (clazz.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation anno = clazz.getAnnotation(MyAnnotation.class); System.out.println("类注解value:" + anno.value()); // 用户类 System.out.println("类注解age:" + anno.age()); // 20 } // 2. 解析字段上的注解 try { java.lang.reflect.Field idField = clazz.getDeclaredField("id"); if (idField.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation fieldAnno = idField.getAnnotation(MyAnnotation.class); System.out.println("字段注解value:" + fieldAnno.value()); // 用户ID } } catch (NoSuchFieldException e) { e.printStackTrace(); } } }
总结:Spring、MyBatis 的注解,底层全是自定义注解 + 反射解析这套逻辑。