Skip to content

基础语法

面向对象三大特性:封装、继承、多态

  • 封装(Encapsulation)(保护数据,隐藏细节 → 让代码安全
    • 核心理解:把属性和方法打包成一个类,对外隐藏内部细节,只开放安全的访问入口。
    • 核心作用:保护数据(防随意篡改)、简化代码(专注外部接口)、解耦维护(高内聚、改内部不影响外部功能)
    • 实现方式:private、public 修饰,getter、setter。数据校验
  • 继承 (Inheritance)(复用代码,建立层级 → 让代码简洁
    • 简单理解:子类「继承」父类的属性方法,不用重复写代码,还能扩展自己的功能。
    • 核心作用:代码复用、层级清晰(is a 的关系)
    • 实现方式:extends 单继承,super,调用父类非私有方法,重写方法,新增方法属性
  • 多态(Polymorphism)(统一接口,灵活扩展 → 让代码通用
    • 通俗理解:同一个行为,不同对象表现出不同的形态。
    • 核心作用:代码灵活(用父类 / 接口接收所有子类对象)、易于扩展(新增子类,不用修改原有代码)
    • 三要素:必须有继承 / 实现关系、必须方法重写父类引用指向子类对象
    • 形式:
      • 运行时多态(重点):方法重写 → 程序运行时才确定调用哪个子类方法(实习最常用)
      • 编译时多态:方法重载 → 同一个类中,方法名相同、参数不同(编译时就确定)
异常处理

概念:异常就是程序运行时出现的意外 / 错误情况。比如:空指针、数组越界、文件找不到、数据库连不上、传入非法参数。

不能让程序直接崩溃,而是提供一套异常机制:发现异常 → 抛出异常 → 捕获异常 → 处理异常,保证程序健壮、不闪退。


image-20260413145920587

  • 体系结构:
    • 所有异常的顶级父类:java.lang.Throwable
      • Error(错误):系统级严重问题,程序员处理不了
        • 比如:OutOfMemoryError(内存溢出)、StackOverflowError(栈溢出)、NoClassDefFoundError
        • 原则:不捕获、不处理,只能优化代码 / JVM
      • Exception(异常):业务 / 代码问题,程序员可以捕获并处理
        • 受检异常 Checked Exception:编译期就报错,必须处理
          • 比如:IOExceptionSQLExceptionFileNotFoundException
          • 场景:IO 操作、网络、数据库、文件读写
        • 非受检异常 Unchecked Exception:编译不报错,运行时才炸 = RuntimeException 及其子类
          • 比如:NullPointerException(NPE)、IndexOutOfBoundsExceptionIllegalArgumentException
          • 原则:尽量通过代码避免,而不是靠 catch 兜底

异常处理 5 个关键字:trycatchfinallythrowthrows

不要在 finally 里写 return!会覆盖 try/catch 里的返回值,甚至吞掉异常

throw:手动抛出一个异常 throws:方法上声明可能抛出的异常


TODO: 自定义业务异常:OK


TODO: 全局异常处理:OK


最佳实践:

  1. 禁止空 catch 块(最恶心的坏代码)
  2. 不要捕获 Throwable / Error
  3. 异常一定要打完整日志:不要只打 e.getMessage(),要打堆栈信息
  4. 早抛出,晚捕获(Early Throw, Late Catch)
    • 底层发现问题立刻抛 上层统一处理(Controller 层全局捕获)
  5. 不要用异常做业务逻辑判断
  6. 资源必须在 finally 关闭 / 用 try-with-resources
  7. 自定义业务异常,统一错误码
  8. 运行时异常尽量避免,不要靠 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代表类的成员方法,用于动态调用方法

  1. 第一步:获取 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;
  2. 第二步:通过反射创建对象

    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, "张三");
  3. 第三步:通过反射操作成员变量(Field)

    java
    User 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
  4. 第四步:通过反射调用成员方法(Method)

    java
    User 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 映射实体类赋值。

getFieldgetDeclaredField 的区别?

答:前者获取公有 + 父类属性,后者获取自身所有属性(含私有)。

注解

注解(Annotation)是Java 代码的元数据,本质是给类、方法、变量等打标记,本身不执行逻辑,需要配合反射解析后生效。

定义:一种特殊的接口,用于为代码添加说明性信息,不直接影响代码执行,需通过反射解析后生效。

作用:替代 XML 配置、简化代码、编译检查、运行时动态处理。

核心特点:注解是标记,反射是解析器


注解的生命周期:通过@Retention指定,决定注解存活阶段:

  1. SOURCE:仅源码阶段,编译后丢弃(如@Override
  2. CLASS:编译到 class 文件,运行时 JVM 不加载(默认)
  3. 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:定义自定义注解

    java
    import 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:通过反射解析注解

    java
    public 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 的注解,底层全是自定义注解 + 反射解析这套逻辑。


并发编程

记录学习,分享技术