Skip to content

一、复习

1、面向对象的三个基本特征:

  • 封装:

    • 属性私有化
    • 目的:隐藏实现细节,保护内部成员更安全,便于代码的维护和使用
  • 继承

    • extends
    • 目的:代码的复用、扩展、表示事物之间 is-a 的关系,提高代码的复用性、扩展性性
  • 多态

    • 方法的重载(编译时多态)、方法的重写(运行时多态)

    • 前提条件:继承、重写、多态引用(父类或父接口的变量指向子类的对象)

      • 多态引用后,调用子类可能重写的方法,那么遵循一个原则:编译时看左边(父类),运行时看右边(子类)。如果子类重写了,就一定执行重写的代码,如果子类没有重写,仍然执行父类找到的方法。
    • 好处:让代码编写更灵活,更方便维护

2、向上转型和向下转型

  • 向上转型:
    • 多态引用时,把子类的对象赋值给父类的变量,就会发生向上转型
    • 当然也可以强制向上转型 ( (父类类型)子类对象)
  • 向下转型
    • 当多态引用后,又需要调用子类扩展的成员(即父类无子类有的成员)时,就需要向下转型
    • 向下转型必须强制进行 (子类) 父类的变量
    • 向下转型有风险,可能发生 ClassCastException,需要通过 instanceof 进行判断, if( 变量 instanceof 类型), 当前你要向下转型为什么类型,instanceof 后面就写什么类型,当条件成立时,向下转型就是安全的

3、面试题的情况

(1)多态引用时,直接访问成员变量:直接看编译时类型。

(2)多态引用时,调用虚方法

  • 编译时看左边(父类)找到实参(实参的类型也按编译时类型)与形参匹配的方法

    • 先找最匹配:实参的类型与形参完全相同
    • 再找兼容:如果没有最匹配的,才找兼容。 实参的类型 < 形参的类型
  • 运行时看右边(子类),看是否有对刚刚匹配的方法进行重写,如果有,就执行重写的,如果没有就仍然执行刚刚匹配的方法

4、内部类

四种内部类形式:

  • 局部内部类:方法内定义的

    • 匿名内部类,本质上属于局部内部类,但没有名字

    • 有名字的局部内部类

  • 成员内部类:方法外定义

    • 静态内部类
    • 非静态内部类

匿名内部类的语法格式:

java
new 父类(){
    //方法
}

new 父类(实参列表){
    //方法
}

new 父接口(){
    //方法
}

局部内部类的语法格式:

java
【修饰符】 class 外部类{
    【修饰符】 返回值类型 方法名(形参列表){
        class 局部内部类名{
            //方法等成员
        }
    }
}

成员内部类的语法格式:

java
【修饰符】 class 外部类{
    【修饰符】 【staticclass 成员内部类{

    }
}

所有内部类都可以直接使用外部类的私有的成员。但是静态的不可以直接使用非静态的。

外部类要使用内部类:调用内部类的静态成员,就直接 内部类名.静态成员; 调用内部类的非静态成员,就先创建内部类的对象,在用对象.非静态成员。

成员内部类该不该加 static,取决于在成员内部类中是否要用到外部类的其他非静态的成员:如果要用,内部类就只能是非静态;如果不用,建议用静态。

5、代码块

语法格式:

java
【修饰符】 class 类名{
    //静态代码块
    static{
        语句;
    }

    //非静态代码块
    {
        语句;
    }
}
静态代码块非静态代码块
作用为静态变量初始化和构造器一起为实例变量初始化
执行的特点只执行一次,在类加载和初始化时执行每次 new 的时候执行
父子关系先父后子。但是如果父类已经初始化过了,多个子类不会重复初始化父类。每次都先父后子。非静态代码块比构造器早执行。

二、拓展(了解)

1、类初始化

底层会有一个<clinit>的方法来完成。 cl 代表 class,init 代表初始化,initialize。

<clinit>方法由编译器根据类的静态变量的显式赋值和静态代码块中的代码根据编写顺序组装而成。

image-20250711091952941

image-20250711092008527

java
package com.atguigu.difficult;

public class Demo {

    static {
        System.out.println("a = " + Demo.a);
        a = 2;
        System.out.println("a = " + Demo.a);
    }
    private static int a = 1; //a=1称为静态变量的显式赋值
    public static void main(String[] args) {
        System.out.println("a = " + a);
    }
}

2、实例初始化

底层会有一个个的<init>的方法来完成。init 代表初始化,initialize。

<init>方法由编译器根据类的:

  1. super()super(实参列表)
    • 之前说代表父类的无参或有参构造
    • 现在说代表父类的__INLINE__0__()__INLINE__1__(参数)
  2. 实例变量的显式赋值
  3. 非静态代码块
  4. 构造器中除了 super()super(实参列表) 之外的代码

其中实例变量的显式赋值和非静态代码块的执行顺序与编写顺序有关。

java
package com.atguigu.difficult;

public class Example {

    {
        System.out.println("非静态代码块a ="  + this.a);
    }
    private int a = 1;

    public Example() {
        super();
        System.out.println("无参构造");
    }

    public Example(int a) {
        super();//代表Object类的 <init>()
        this.a = a;
    }

    public static void main(String[] args) {
        Example e1 = new Example();
        Example e2 = new Example(2);
    }
}
java
package com.atguigu.difficult;

public class Rectangle {
    private double length;
    private double width;
    private double area = length * width;//错误的

    public Rectangle() {
    }

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public String toString() {
        return "Rectangle{" +
                "length=" + length +
                ", width=" + width +
                ", area=" + area +
                '}';
    }
}
java
package com.atguigu.difficult;

public class TestRectangle {
    public static void main(String[] args) {
        Rectangle r = new Rectangle();
        System.out.println(r);

        Rectangle r2 = new Rectangle(8,5);
        System.out.println(r2);
    }
}

三、枚举(掌握)

3.1 什么是枚举类型

所谓枚举类型,就是把它所有可能的对象都列出来,除此之外,它没有别的对象了,即它的对象是固定的有限的几个。例如:星期类只有 7 个对象。

3.2 如何声明枚举类型

3.2.1 JDK5 之前

java
package com.atguigu.meiju;

public class XingQi {
    //(2)在类初始化时提前创建好这个类的固定的有限的几个对象
    //以下对象一般都是比较固定的,"建议"它们都加final
    public static final XingQi MONDAY = new XingQi("星期一");
    public static final XingQi TUESDAY = new XingQi();
    public static final XingQi WEDNESDAY = new XingQi();
    public static final XingQi THURSDAY = new XingQi();
    public static final XingQi FRIDAY = new XingQi();
    public static final XingQi SATURDAY = new XingQi();
    public static final XingQi SUNDAY = new XingQi();
    //转大小写快捷键:Ctrl + Shift + U

    //对于上面的常量对象,它们的属性一般也"建议"用final
    private final String description;

    //(1)构造器私有化
    private XingQi(){
        description = "xxx";
    }

    private XingQi(String description) {
        this.description = description;
    }

    public String getDescription() {
        return description;
    }

    @Override
    public String toString() {
        return "XingQi{" +
                "description='" + description + '\'' +
                '}';
    }
}
java
package com.atguigu.meiju;

public class TestXingQi {
    public static void main(String[] args) {
        XingQi x1 = XingQi.MONDAY;
        System.out.println(x1);

        XingQi x2 = XingQi.TUESDAY;
        System.out.println(x2);
    }
}

3.2.2 JDK5(含)之后

语法格式:

java
【修饰符】 enum  枚举类名{
    常量对象列表;

    其他成员列表
}

image-20250711093602278

  • enum 定义的枚举类,构造器默认就是 private,不能换成其他修饰符
  • enum 定义的枚举类,常量对象列表必须写在枚举类的首行,即要先列出这个类所有的对象,再写其他成员
  • enum 定义的枚举类,它的直接父类是 java.lang.Enum 类,在 Enum 类中重写了 Object 的 toString、hashCode、equals 等方法。它不能继承别人了。
  • enum 定义的枚举类,也不能有子类,虽然它不是 final 的,因为它的构造器私有化,子类无法调用。
java
package com.atguigu.meiju;

public enum Week {
    //省略了 public static final  Week
    MONDAY("星期一"), //调用有参构造创建的对象
    TUESDAY(), //空()可以省略,表示调用无参构造创建的对象
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY;

    private final String description;

    Week() {
        description = "xx";
    }

    Week(String description) {
        this.description = description;
    }

    @Override
    public String toString() {
        return super.toString() +"=" + description;
    }
}
java
package com.atguigu.meiju;

public class TestWeek {
    public static void main(String[] args) {
        Week w1 = Week.MONDAY;
        System.out.println(w1);
    }
}

3.2.3 枚举类的方法

它的方法来自于:

  1. 从 Object 继承,Enum 没有重写
  2. 从 Object 继承,Enum 重写过的
  3. Enum 定义
    • String name():返回枚举常量对象的名称
    • int oridinal():返回枚举常量对象的下标索引,从 0 开始
  4. 编译器自动添加的
    • static 枚举类型[] values()
    • static 枚举类型 valueOf(枚举对象的字符串名称)
  5. 我们自己在枚举类中写的
java
package com.atguigu.meiju;

public class TestWeekMethod {
    public static void main(String[] args) {
        Week[] values = Week.values();
        for (Week w : values) {
            System.out.println(w);
        }

        Week monday = Week.valueOf("MONDAY");
        System.out.println("monday = " + monday);

        Week friday = Week.FRIDAY;
        String name = friday.name();
        System.out.println("name = " + name);

        int ordinal = friday.ordinal();
        System.out.println("ordinal = " + ordinal);
    }
}

3.2.4 switch 与枚举

回忆:

  • switch(表达式)的类型只支持:byte、short、int、char,及其它们的包装类、字符串 String、枚举。
java
package com.atguigu.meiju;

import java.util.Scanner;

public class TestSwitch {
    public static void main(String[] args) {
        //从键盘输入星期的单词,然后从Week枚举类中获取它对应的常量对象,输出它的下标
        Scanner input = new Scanner(System.in);

        System.out.print("请输入星期的单词:");
        String name = input.next();

        //无论输入的是大写还是小写,都给你转为大写
        name = name.toUpperCase();//转为大写,它是String类的方法(提前用一下)

        Week week = Week.valueOf(name);
        System.out.println(week +"的下标:" + week.ordinal());

        /*
        根据week对象的不同,输出不同的话:
        如果是MONDAY对象,输出 星期一是最痛苦的一天
        如果是TUESDAY对象,输出 星期二是最难熬的一天
        ....
         */
        switch (week){
            case MONDAY -> System.out.println("星期一是最痛苦的一天");
            case TUESDAY -> System.out.println("星期二是最难熬的一天");
            //...
        }

        input.close();
    }
}

四、新特性(了解)

4.1 记录类

记录类是一种特殊的类,它的特殊之处在于,

  • 它的实例变量都是 final 的
  • 它只有唯一的全参构造
  • 它使用 record 进行声明
  • 它会自动重写 toString、hashCode、equals 等方法

image-20250711111539309

java
package com.atguigu.record;

//(double a, double b,double c)既是有参构造的参数列表,同时也是Triangle的实例变量列表,且它们都是final的。
//toString,equalst,hashCode会自动重写
public record Triangle(double a, double b,double c) {

    //静态变量也与普通类一样定义即可
    private static String name;

    public static String getName() {
        return name;
    }

    public static void setName(String name) {
        Triangle.name = name;
    }

    //其他方法与普通类一样定义
    public double area(){
        double p = (a+b+c)/2;
        return Math.sqrt(p*(p-b)*(p-c)*(p-a));
    }
}

4.2 密封类

回忆:

  • 普通类:可以随意被继承,子类没有限制
  • final 类:不能被继承,没有子类
  • abstract 抽象类:必须被继承,有子类,子类没有限制

现在密封类是一种特殊类,它特殊之处在于,它的子类是有限制的,子类是固定的有限的几个。

语法格式:

java
【其他修饰符】 sealed class 密封类名 permits 子类名列表{

}

要求:

  • 密封类本身必须通过 permits 指定它有哪些子类

  • 密封类的子类只能是以下 3 种之一

    • 继续是 sealed
    • 最终是 final
    • 恢复普通类 non-sealed
  • 密封类的成员没有任何限制,与普通类一样

image-20250711113018348

java
package com.atguigu.sealed;

//sealed、non-sealed、permits、record这些是上下文关键字,只在特定位置是关键字,在其他地方仍然是普通单词,所以它们可以当包名、变量名等。

//Shape:图形
public sealed class Shape permits Rectangle,Circle,Triangle{
}
java
package com.atguigu.sealed;

public final class Rectangle extends Shape{//矩形
}
java
package com.atguigu.sealed;

public sealed class Triangle extends Shape permits SubTriangle{
}
java
package com.atguigu.sealed;

public final class SubTriangle extends Triangle{
}
java
package com.atguigu.sealed;

//Circle恢复为普通类,Circle就可以被随意继承了
public non-sealed class Circle extends Shape{
}
java
package com.atguigu.sealed;

public class SubCircle1 extends Circle{
}
java
package com.atguigu.sealed;

public class SubCircle2 extends Circle{
}

五、Maven 工具

Maven 是帮我们管理和下载各种 jar,甚至还可以帮助我们构建整个项目(编译、测试、打包、发布等)。

IDEA 已经帮助我们集成了 Maven 插件,我们可以直接使用,不需要单独下载 Maven 工具。

5.1 创建 Maven 工程

Maven 的工程结构与普通的 Java 项目的结构有点不同:

image-20250711115010623

image-20250711115223571

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- xml文件中的注释格式 -->
    <!-- groupId一般是公司域名倒置 -->
    <groupId>com.atguigu</groupId>
    <!--当前项目或模块的唯一标识名-->
    <artifactId>day12_teacher_maven_code</artifactId>
    <!--版本标识-->
    <version>1.0-SNAPSHOT</version>
    <!--打包方式,默认是jar。后面我们学习web项目时,这里会改为war-->
    <!--<packaging>jar</packaging>-->

    <properties>
        <maven.compiler.source>17</maven.compiler.source><!--JDK的版本-->
        <maven.compiler.target>17</maven.compiler.target><!--编译版本-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!--字符编码方式,统一为UTF-8-->
    </properties>



</project>

5.2 gavp 介绍

Maven 工程相对之前的项目,多出一组 gavp 属性,gav 需要我们在创建项目的时候指定,p 有默认值,我们先行了解下这组属性的含义:

Maven 中的 GAVP 是指 GroupId、ArtifactId、Version、Packaging 等四个属性的缩写,其中前三个是必要的,而 Packaging 属性为可选项。这四个属性主要为每个项目在 maven 仓库总做一个标识,类似人的姓-名!有了具体标识,方便后期项目之间相互引用依赖等!

GAV 遵循一下规则:

  • G (GroupId): 通常表示项目的组织或公司域名的倒序,比如 com.alibaba 或 org.springframework 或 com.mysql。

    • GroupID 格式:com.{公司/BU }.业务线.[子业务线],最多 4 级。

    • 例如:com.taobao.tddl 或 com.alibaba.sourcing.multilang

  • A (ArtifactId): 表示项目内部的具体模块或组件名称。比如 druid 或 spring-web 或 mysql-connector-j

  • V (Version): 版本号,标识了模块的具体版本,格式:主版本号.次版本号.修订号,如 1.0.0。

    • 主版本号:当做了不兼容的 API 修改,或者增加了能改变产品方向的新功能。

    • 次版本号:当做了向下兼容的功能性新增(新增类、接口等)。

      • 修订号:修复 bug,没有修改方法签名的功能加强,保持 API 兼容性。

      • 例如: 初始 →1.0.0 修改 bug → 1.0.1 功能调整 → 1.1.1 增加新模块 -> 2.0.0 等

  • P (Packaging): 包装类型,默认情况下可以省略,常见的值包括 jar, war, pom 等,表示最终构建产物的类型。

    • 指示将项目打包为什么类型的文件,idea 根据 packaging 值,识别 maven 项目类型!

      • packaging 属性为 jar(默认值),代表普通的 Java 工程,打包以后是.jar 结尾的文件。

      • packaging 属性为 war,代表 Java 的 web 工程,打包以后.war 结尾的文件。

      • packaging 属性为 pom,代表不会打包,用来做继承的父工程。

image-20240919110554964

有时候在 jar 包或软件版本后面会看到一些后缀,例如:

image-20250408185940889

5.3 利用 Maven 帮我们管理 jar

https://mvnrepository.com/路径找jar包的gav等id

image-20250711115806257

image-20250711115908384

image-20250711120012789

image-20250711120130780

5.3 Maven 设置

image-20240919112154550

我们需要修改maven3/conf/settings.xml配置文件,来修改 maven 的一些默认配置。我们主要修改的有三个配置:

​ 1.依赖本地缓存位置(本地仓库位置)

​ 2.maven 下载镜像

​ 3.maven 选用编译项目的 jdk 版本!

  1. 配置本地仓库地址

    如果我们没有配置过 maven 的本地仓库,那么它默认在 c://用户//用户名//.m2//repository,这会占用很多 C 盘空间,所以通常我们会将其改到其他路径下,例如:D:\ProgramFiles\Maven\RepMaven 路径下。

    image-20250711140935129

    xml
      <!-- localRepository
       | The path to the local repository maven will use to store artifacts.
       |
       | Default: ${user.home}/.m2/repository
      <localRepository>/path/to/local/repo</localRepository>
      -->
     <!-- Maven软件/conf/settings.xml 55行,具体的路径自己填写 -->
     <localRepository>D:\ProgramFiles\Maven\RepMaven</localRepository>
  2. 配置国内阿里镜像

    xml
    <!--在mirrors节点(标签)下添加中央仓库镜像 160行附近-->
    <mirror>
        <id>alimaven</id>
        <name>aliyun maven</name>
        <url>https://maven.aliyun.com/nexus/content/groups/public/</url>
        <mirrorOf>central</mirrorOf>
    </mirror>
  3. 配置 jdk17 版本项目构建

    xml
    <!--在profiles节点(标签)下添加jdk编译版本 260行附近-->
    <profile>
        <id>jdk-17</id>
        <activation>
          <activeByDefault>true</activeByDefault>
          <jdk>17</jdk>
        </activation>
        <properties>
          <maven.compiler.source>17</maven.compiler.source>
          <maven.compiler.target>17</maven.compiler.target>
          <maven.compiler.compilerVersion>17</maven.compiler.compilerVersion>
        </properties>
    </profile>
  4. 检查 IDEA 中 maven 的设置

image-20250711141624711

image-20250711141738992

image-20250711141946551

六、JUnit 单元测试

JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing framework),供 Java 开发人员编写单元测试之用。多数 Java 的开发环境都已经集成了 JUnit 作为单元测试的工具。JUnit 测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。

要使用 JUnit,必须在项目的编译路径中必须引入 JUnit 的库,即相关的.class 文件组成的 jar 包。

6.1 下载 JUnit 的依赖

image-20250711151636367

把上面的 gav 标识负责到项目的 pom.xml 文件中

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- xml文件中的注释格式 -->
    <!-- groupId一般是公司域名倒置 -->
    <groupId>com.atguigu</groupId>
    <!--当前项目或模块的唯一标识名-->
    <artifactId>day12_teacher_maven_code</artifactId>
    <!--版本标识-->
    <version>1.0-SNAPSHOT</version>
    <!--打包方式,默认是jar。后面我们学习web项目时,这里会改为war-->
    <!--<packaging>jar</packaging>-->

    <properties>
        <maven.compiler.source>17</maven.compiler.source><!--JDK的版本-->
        <maven.compiler.target>17</maven.compiler.target><!--编译版本-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!--字符编码方式,统一为UTF-8-->
    </properties>

    <dependencies><!--所有的依赖-->
        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.38</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.12.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>


</project>

刷新 maven 下载依赖

6.2 使用 JUnit

注意两个版本包名变化

  • JUnit 4: org.junit.*
  • JUnit 5: org.junit.jupiter.api.*(Jupiter API)

JUnit 4 vs JUnit 5 注解对比:

功能JUnit 4 注解JUnit 5 注解说明
测试方法@Test@Test标记一个方法为测试方法。JUnit 5 的 @Test 不再支持 expectedtimeout,需用 assertThrows 等新特性替代。
前置初始化@Before@BeforeEach每个测试方法执行。JUnit 5 更名以增强语义化。
后置清理@After@AfterEach每个测试方法执行。
静态前置初始化@BeforeClass@BeforeAll所有测试方法前执行一次(方法需为 static)。JUnit 5 允许非静态方法但需配合 @TestInstance(Lifecycle.PER_CLASS)
静态后置清理@AfterClass@AfterAll所有测试方法后执行一次(方法需为 static)。JUnit 5 允许非静态方法但需配合 @TestInstance(Lifecycle.PER_CLASS)
  • 使用@Test 标记的方法:必须是非 private、无参、返回值类型是 void、非 static。且@Test 标记的方法所在的类只能有 1 个无参构造。
  • 使用@BeforeAll 和@AfterAll 标记的方法必须是 static,因为它们是随着类的加载而加载,卸载而卸载,只执行 1 次。

image-20250711152023914

java
package com.atguigu.test;

import org.junit.jupiter.api.*;

public class TestJUnit {
    private TestJUnit() {
    }

    @BeforeAll
    public static void init(){//初始化
        System.out.println("初始化");
    }

    @BeforeEach
    public void beforeEveryone(){
        System.out.println("start");
    }

    @Test
    public void test1(){
        System.out.println("hello");
    }

    @Test
    public void test2(){
        System.out.println("world");
    }

/*    @Test
    public void add(int a, int b){
        System.out.println(a+ b);
    }

    @Test
    public int m1(){
        return 1;
    }

    @Test
    public static void m2(){
        System.out.println("m2");
    }*/

    @Test
    protected void m3(){
        System.out.println("m3");
    }

    @AfterEach
    public void afterEveryone(){
        System.out.println("end");
    }

    @AfterAll
    public static void destory(){//销毁
        System.out.println("销毁");
    }

}

6.3 JUnit 不支持键盘输入问题

image-20250711154746792

image-20250711154820384

加上如下这句设置:

txt
-Deditable.java.test.console=true

image-20250711154919243

6.4 自定义快捷模板

image-20250711155422624

6.5 关键字:assert(了解)

assert 用于断言某个条件是否成立,如果成立测试通过,如果不成立,测试报错。

语法格式:

java
assert 条件表达式;

assert 条件表达式 : "错误提示信息" ;
java
package com.atguigu.test;

import org.junit.jupiter.api.Test;

public class TestMethod {
    @Test
    public void test1() {
        //assert断言,预测某个表达式应该成功,如果不成功就报错
       assert  max(5,7) == 7 : "找最大值错误";
    }

    //希望max方法可以求两个整数的最大值
    public int max(int a, int b) {
        return a < b ? a : b;
    }
}

如果在低版本的 JUnit 或在 main 方法中,默认断言功能没有打开,需要手动加-ea

image-20250711162044852

image-20250711162129702

image-20250711162206804

6.6 scope 问题

image-20250711193531545

七、lombok 小辣椒

Lombok 是一个 Java 工具库,旨在通过注解的方式自动生成样板代码,从而减少开发者的重复劳动,使代码更加简洁。它能在编译时动态生成代码(如 getter/setter、构造方法、日志变量等),而不会影响运行时性能。

原来的代码:

java
package com.atguigu.bean;

import java.util.Objects;

public class Student {
    private String name;
    private int score;
    private char gender;

    public Student() {
    }

    public Student(String name, int score, char gender) {
        this.name = name;
        this.score = score;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getScore() {
        return score;
    }

    public void setScore(int score) {
        this.score = score;
    }

    public char getGender() {
        return gender;
    }

    public void setGender(char gender) {
        this.gender = gender;
    }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return score == student.score && gender == student.gender && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, score, gender);
    }
}

7.1 下载 lombok 依赖

image-20250711162545242

xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- xml文件中的注释格式 -->
    <!-- groupId一般是公司域名倒置 -->
    <groupId>com.atguigu</groupId>
    <!--当前项目或模块的唯一标识名-->
    <artifactId>day12_teacher_maven_code</artifactId>
    <!--版本标识-->
    <version>1.0-SNAPSHOT</version>
    <!--打包方式,默认是jar。后面我们学习web项目时,这里会改为war-->
    <!--<packaging>jar</packaging>-->

    <properties>
        <maven.compiler.source>17</maven.compiler.source><!--JDK的版本-->
        <maven.compiler.target>17</maven.compiler.target><!--编译版本-->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><!--字符编码方式,统一为UTF-8-->
    </properties>

    <dependencies><!--所有的依赖-->
        <!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.38</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.12.2</version>
            <scope>test</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.38</version>
        </dependency>
    </dependencies>


</project>

7.2 使用 lombok

java
package com.atguigu.bean;

import lombok.*;

@Data //自动生成get/set,toString,equals和hashCode等方法
@NoArgsConstructor  //无参构造
@AllArgsConstructor //全参构造
@RequiredArgsConstructor //必填参数的构造器
public class Student {
    @NonNull  //非空
    private String name;
    @NonNull  //非空
    private int score;
    private char gender;
}
java
package com.atguigu.test;

import com.atguigu.bean.Student;
import org.junit.jupiter.api.Test;

public class TestStudent {
    @Test
    public void test1(){
        Student s1 = new Student();
        s1.setName("李四");
        s1.setGender('女');
        s1.setScore(100);
        System.out.println(s1);

        Student s2 = new Student("张三",99, '男');
        System.out.println(s2);

        System.out.println(s2.getName());
    }

    @Test
    public void test2(){
        Student s3 = new Student("王五",56);
        System.out.println(s3);
    }
}

7.3 启动注解处理流程的设置

image-20250711163014990

image-20250711163131483

7.4 IDEA 提示不区分大小写设置

image-20250711162717856

7.5 有父子类继承时使用 lombok

以下是 Lombok 常用注解的表格总结,包含功能说明和典型使用场景:

注解作用示例
@Getter / @Setter自动生成字段的 getter 和 setter 方法(可指定访问级别)。@Getter @Setter private String name;
@ToString自动生成 toString() 方法(可排除字段)。@ToString(exclude = "password")
@EqualsAndHashCode生成 equals()hashCode() 方法(基于字段值)。@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor生成无参构造方法。@NoArgsConstructor
@AllArgsConstructor生成全参构造方法(包含所有字段)。@AllArgsConstructor
@RequiredArgsConstructor生成包含 final@NonNull 字段的构造方法。@RequiredArgsConstructor
@Data复合注解:包含 @Getter@Setter@ToString@EqualsAndHashCode@RequiredArgsConstructor@Data public class User { ... }
@NonNull自动生成空值检查(若字段为 null,抛出 NullPointerException)。public void setName(@NonNull String name) { ... }
@Builder提供建造者模式(链式调用)。User user = User.builder().name("Alice").age(25).build();
@SuperBuilder提供建造者模式(链式调用)。父子类继承时使用

7.5.1 建造者模式

java
package com.atguigu.bean;

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class Rectangle {
    private double length;
    private double width;
}
java
package com.atguigu.test;

import com.atguigu.bean.Rectangle;
import org.junit.jupiter.api.Test;

public class TestRectangle {
    @Test
    public void test(){
        Rectangle r = Rectangle.builder().length(8).width(6).build();
        System.out.println(r);
    }
}

7.5.2 父子类建造者模式

  • 父子类上面要用 @SuperBuilder 代替 @NoArgsConstructor、@AllArgsConstructor、@RequiredArgsConstructor
  • 子类中的 toString,equals 和 hashCode 中需要调用父类的属性等,或需要调用父类的 toString、equals 和 hashCode,不用@Data,改用
    • @ToString(callSuper = true)
    • @EqualsAndHashCode(callSuper = true)
java
package com.atguigu.bean;

import lombok.Data;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Data
public class Person {
    private String name;
    private int age;
}
java
package com.atguigu.bean;

import lombok.*;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Employee extends Person{
    private double salary;
}
java
package com.atguigu.test;

import com.atguigu.bean.Employee;
import org.junit.jupiter.api.Test;

public class TestEmployee {
    @Test
    public void test1(){
      /*  Employee e = new Employee();
        System.out.println(e);

        Employee e2 = new Employee(15000);
        System.out.println(e2);*/

        Employee e1 = Employee.builder().name("张三").age(23).salary(15000).build();
        System.out.println(e1);

        Employee e2 = Employee.builder().build();
        System.out.println(e2);


        Employee e3 = Employee.builder().name("张三").build();
        System.out.println(e3);
    }
}

目前我们创建一个类的对象有 2 种方式:

(1)直接 new + 构造器()

(2)建造者模式:类名.builder(). 属性名(属性值). 属性名(属性值).....build()

八、注解

8.1 什么样的元素是注解

在 Java 中以@开头的都是注解。

我们第一个接触的注解是@Override,用于标记重写的方法。

8.2 注解有什么用

注解是一种特殊的注释。普通的注释(例如,单行注释 //,多行注释/* */ )是给人看的。注解不仅仅是给人看,它更是给另一段代码来看,即有一段代码来读取注解,根据注解的不同执行对应的代码。

注解本身并不会影响原有代码的业务功能,例如:加不加@Override,对于重写方法的方法体功能不会产生影响,只会额外的做一次格式校验而已。即注解可以在不影响原有代码功能的基础上,可以额外获得一下其他的功能,例如:@Test 注解,会使得当前方法具备程序入口的运行功能。

一个完整的注解,应该包含 3 个部分:

  • 声明 :@interface

    • 例如:

      java
      @Target(ElementType.METHOD)   //@Target代表这个注解可以使用的目标位置,  METHOD代表用在方法上
      @Retention(RetentionPolicy.SOURCE)  //SOURCE代表源代码,凡是生命周期是SOURCE,都是由编译器来读取
      public @interface Override { //声明一个新数组
      }
  • 使用:

    • 例如:

      java
      public class Dog extends Animal{
          @Override  //使用注解
          public void eat() {
              System.out.println("吃骨头");
          }
      }
  • 读取

    • 一个注解的生命周期有 3 种可能:

      • RetentionPolicy.SOURCE:编译器读取
      • RetentionPolicy.CLASS:类加载器读取
      • RetentionPolicy.RUNTIME:反射代码读取
    • 以@Override 为例,编译器中专门有一段代码来检查 每一个方法上是不是有 @Override,如果有就执行一段 检查重写方法格式的代码。如果方法上面没有@Override,就不会执行那段特定的检查重写方法格式的代码。

8.3 基础阶段的注解哪些

基础阶段的注解有如下几个需要了解:

  • @Override:建议大家凡是重写的方法都加它

  • @Deprected:用于标记某个方法、类、构造器、变量等已过时,不推荐程序员使用了,这些已过时的方法等通常是有缺陷或者很难用等问题。

    • 问题:已过时为啥不删除?这里是逻辑删除,不采用物理删除。物理删除是直接干掉。逻辑删除,只是标记为不可用。
    • 因为有些旧代码还在用,如果物理删除会导致这些代码就报错了。
  • @SuppressWarnings:抑制警告,能避免的避免,不能避免的就抑制,这样让代码看起来更清爽。

  • @FunctionalInterface:函数式接口的标记,用它标记的接口中有且只有 1 个必须被重写的抽象方法(在 Lambda 表达式部分讲解)

九、和数学相关的工具类

9.1 Math 类

Math 类中有很多静态方法,用于各种各样的数学计算。Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数。

  • public static double abs(double a) :返回 double 值的绝对值。

  • public static double ceil(double a) :返回大于等于参数的最小的整数。

  • public static double floor(double a) :返回小于等于参数最大的整数。

  • public static long round(double a) :返回最接近参数的 long。(相当于四舍五入方法)

  • public static double pow(double a,double b):返回 a 的 b 幂次方法

  • public static double sqrt(double a):返回 a 的平方根

  • public static double random():返回[0,1)的随机值

  • public static final double PI:返回圆周率

  • public static double max(double x, double y):返回 x,y 中的最大值

  • public static double min(double x, double y):返回 x,y 中的最小值

java
package com.atguigu.math;

import org.junit.jupiter.api.Test;

public class TestMath {
    @Test
    public void test(){
        //求一个数的绝对值
        System.out.println(Math.abs(5));
        System.out.println(Math.abs(-5));
    }
    @Test
    public void test2(){
        System.out.println(Math.ceil(2.6));//3.0  ceil天花板,向上取整
        System.out.println(Math.floor(2.6));//2.0  floor地板,向下取整
        System.out.println(Math.round(2.6));//3    round周围,四舍五入
    }
    @Test
    public void test3(){
        System.out.println(Math.ceil(2.4));//3.0  ceil天花板,向上取整
        System.out.println(Math.floor(2.4));//2.0  floor地板,向下取整
        System.out.println(Math.round(2.4));//2    round周围,四舍五入
    }

    @Test
    public void test4(){
        System.out.println(Math.ceil(2.5));//3.0  ceil天花板,向上取整
        System.out.println(Math.floor(2.5));//2.0  floor地板,向下取整
        System.out.println(Math.round(2.5));//3    round周围,四舍五入
    }

    @Test
    public void test5(){
        System.out.println(Math.ceil(-2.5));//-2.0  ceil天花板,向上取整
        System.out.println(Math.floor(-2.5));//-3.0  floor地板,向下取整
        System.out.println(Math.round(-2.5));//-2    round周围,四舍五入
    }

    @Test
    public void test6(){
        System.out.println(Math.round(-2.5));//-2    round周围,四舍五入
        System.out.println(Math.round(-2.4));//-2    round周围,四舍五入
        System.out.println(Math.round(-2.6));//-3    round周围,四舍五入

//        Math.round(x)   x+0.5 向下取整
        // -2.5 + 0.5 = -2  向下取整 -2
        // -2.4 + 0.5 = -1.9  向下取整 -2
        //-2.6 + 0.5 = -2.1  向下取整 -3
    }

    @Test
    public void test7(){
        System.out.println(Math.pow(2,5));//2的5次   32
        System.out.println(Math.pow(2.1, 3.5));//13.42046400464604

        System.out.println(Math.sqrt(9));//3

        System.out.println(Math.random());//[0,1)小数
        System.out.println("圆周率:" + Math.PI);

        System.out.println("最大值:" + Math.max(5,6));//6
        System.out.println("最小值:" + Math.min(5,6));//5
    }
}

9.2 Random 类

用于产生随机数

  • boolean nextBoolean():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的 boolean 值。

  • void nextBytes(byte[] bytes):生成随机字节并将其置于用户提供的 byte 数组中。

  • double nextDouble():返回下一个伪随机数,它是取自此随机数生成器序列的、在 0.0 和 1.0 之间均匀分布的 double 值。

  • float nextFloat():返回下一个伪随机数,它是取自此随机数生成器序列的、在 0.0 和 1.0 之间均匀分布的 float 值。

  • double nextGaussian():返回下一个伪随机数,它是取自此随机数生成器序列的、呈高斯(“正态”)分布的 double 值,其平均值是 0.0,标准差是 1.0。

  • int nextInt():返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的 int 值。

  • int nextInt(int n):返回一个伪随机数,它是取自此随机数生成器序列的、在 0(包括)和指定值(不包括)之间均匀分布的 int 值。

  • long nextLong():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的 long 值。

java
package com.atguigu.math;

import org.junit.jupiter.api.Test;

import java.util.Random;

public class TestRandom {
    @Test
    public void test1(){
        //Random 专门产生随机数
        Random random = new Random();

        double d = random.nextDouble();
        int i = random.nextInt();
        boolean b = random.nextBoolean();
        System.out.println("d = " + d);//[0,1)
        System.out.println("i = " + i);//int范围的任意值
        System.out.println("b = " + b);//true或false
    }

    @Test
    public void test2(){
        //Random 专门产生随机数
        Random random = new Random();
        int i = random.nextInt(100);//[0,100)
        int j = random.nextInt(100, 150);//[100,150)
        System.out.println("i = " + i);
        System.out.println("j = " + j);
    }
}

9.3 BigInteger 和 BigDecimal 类

1、BigInteger

不可变的任意精度的整数。

  • BigInteger(String val)
  • BigInteger add(BigInteger val)
  • BigInteger subtract(BigInteger val)
  • BigInteger multiply(BigInteger val)
  • BigInteger divide(BigInteger val)
  • BigInteger remainder(BigInteger val)
  • ....

2、BigDecimal

不可变的、任意精度的有符号十进制数。

  • BigDecimal(String val)
  • BigDecimal add(BigDecimal val)
  • BigDecimal subtract(BigDecimal val)
  • BigDecimal multiply(BigDecimal val)
  • BigDecimal divide(BigDecimal val)
  • BigDecimal divide(BigDecimal divisor, int roundingMode)
  • BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
  • BigDecimal remainder(BigDecimal val)
  • ....

RoundingMode 枚举类的部分常量对象:

  • CEILING :向正无限大方向舍入的舍入模式。

  • DOWN :向零方向舍入的舍入模式。

  • FLOOR:向负无限大方向舍入的舍入模式。

  • HALF_DOWN :向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向下舍入。

  • HALF_EVEN:向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向相邻的偶数舍入。

  • HALF_UP:向最接近数字方向舍入的舍入模式,如果与两个相邻数字的距离相等,则向上舍入。

  • UNNECESSARY:用于断言请求的操作具有精确结果的舍入模式,因此不需要舍入。 UP:远离零方向舍入的舍入模式。

java
package com.atguigu.math;

import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;

public class TestBigIntegerAndBigDecimal {
    @Test
    public void test1(){
        /*
        float和double是小数类型,是浮点型,不精确的,误差比较大。
            而且精度范围有限制。
            当精度要求更高时,就不能使用float和double,得用BigDecimal
         */
        BigDecimal a = new BigDecimal("968532.89414521452145217851458621456321");
        BigDecimal b = new BigDecimal("8266.8512365489632145632145214521");

//        System.out.println("和:" + (a+b));//错误,因为它们是对象,不能对象相加
        System.out.println("和:" + a.add(b));
        System.out.println("差:" + a.subtract(b));
        System.out.println("乘积:" + a.multiply(b));
       // System.out.println("商:" + a.divide(b));//除不尽
        //java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

        System.out.println("商:" + a.divide(b,1000, RoundingMode.HALF_UP));
        System.out.println("幂次方:" + a.pow(5));
    }

    @Test
    public void test2(){
        //整数:byte,short,int,long
        System.out.println("Long最大值:" + Long.MAX_VALUE);//9223372036854775807
        //如果有比long最大值更大的整数计算怎么办,必须使用BigInteger
        BigInteger a = new BigInteger("85214528963214563214568521452321456321456");
        BigInteger b = new BigInteger("89632155456541254125321563215232");
        System.out.println("和:" + a.add(b));
        System.out.println("差:" + a.subtract(b));
        System.out.println("乘积:" + a.multiply(b));
        System.out.println("商:" + a.divide(b));//仍然遵循,整数/整数只保留整数部分
        System.out.println("幂次方:" + a.pow(5));
    }
}