Skip to content

环境安装 ​

java环境

java 的 jdk 类似于前端的浏览器或 node 运行环境

1.https://www.oracle.com/cn/java/technologies/downloads/ 官网下载 jdk

2.配置环境变量

1.新建环境变量 变量名:JAVA_HOME 变量值:D:\JDK 2.添加 path 环境变量 变量值:%JAVA_HOME%\bin

验证

bash
java -version

Maven安装

maven 是用来管理 java 项目依赖的,类似前端的 npm 包管理工具。

1.https://maven.apache.org/download.cgi 官网下载(如:apache-maven-3.9.16-bin.zip)

2.配置环境变量

1.新建环境变量 变量名:MAVEN_HOME 变量值:D:\sw\install\java\apache-maven-3.9.7 2.添加 path 环境变量 变量值:%MAVEN_HOME%\bin

验证

bash
mvn -v

设置镜像 ​

为了解决下载依赖慢的问题,需要设置为国内镜像。

在 /conf/ 目录内 settings.xml (maven 解压位置)配置文件中添加如下代码:

xml
<!-- 镜像所有仓库 -->
<!-- 在 settings.xml 的 <mirrors> 中添加,如果没有 <mirrors> 就新建一个 -->
<mirror>
    <id>aliyunmaven</id>
    <mirrorOf>*</mirrorOf>
    <name>aliyunmaven</name>
    <url>https://maven.aliyun.com/repository/public</url>
</mirror>
xml
<!-- 只镜像 central 仓库 -->
<mirror>
    <id>aliyun-central</id>
    <!-- mirrorof 写 central:只替代中央仓库,不影响你公司私服 -->
    <!-- 如果你还有公司私服,就不要写成*,避免把私服也镜像掉 -->
    <mirrorOf>central</mirrorOf>
    <url>https://maven.aliyun.com/repository/public</url>
</mirror>

默认情况下,安装的依赖目录为 win用户目录/.m2/repository,相当于是本地仓库一样

1.本地仓库

就是 settings.xml 中配置的目录,使用过的依赖会存储在这里,后续复用。

2.中央仓库

是 maven 社区提供的仓库,内置了中央仓库的地址,其中包含了流行的开源 java 构件

3.公共仓库(重点)

依赖包下载

4.私服

公司私有的共享仓库,不对外开放,可以通过 Nexus 来创建,管理

VScode配置 ​

安装插件 ​

搜索并安装Extension Pack for Java插件。

为了能够在vscode中方便的使用java语言,Microsoft特意推出了java插件包

搜索并安装 Spring Boot Extension Pack

这个扩展包含了多个与 Spring Boot 相关的扩展

创建项目 ​

创建普通项目 ​

1.在 vscode 资源管理器区域,右键,创建 java 项目

2.在弹出的选项中,选择 No build tools,然后选择目录,在输入项目名称即可

创建的项目结构如下

tex
bin
lib   // jar 包放置位置
src
 -App.java   // 入口main方法
README.md

创建springBoot项目 ​

1.在vscode资源管理器区域,右键,创建java项目,或直接点击创建java项目

2.在弹出的选项中,选择Spring Boot,然后选择Maven Project,然后选择 boot 版本,再选择 java

3.输入项目组id,一般为倒写域名,例如com.example,接着输入项目名称,例如demo

4.然后弹窗中选择jar,接着选择jdk版本,然后选需要的依赖,可以勾选 Spring Web、Spring Boot DevTools、Lombok ,勾选后接下来选择保存目录。

5.等待 VScode 下载依赖完成后即可。

创建的项目结构如下

tex
.mvn
.vscode
src
 -main
 --java  // 项目代码目录
 --resources
 ---application.properties   // 项目boot配置,例如启动端口号
 -test
target  // 打包后输出文件夹
HELP.md
mvnw
mvnw.cmd
pom.xml   // 项目依赖列表

编写测试代码

properties
# 在 src\main\resources\application.properties 文件添加服务端口配置
spring.application.name=demo
# 添加服务端口
server.port=8080
java
// 在 src\main\java\com\example\demo 下创建 controller 文件再在其下创建 Hello.java 文件
// 写入如下代码
package com.example.demo.controller; // 声明当前文件为一个包
import org.springframework.web.bind.annotation.*; // 引入模块

@RestController
public class Hello {
    @RequestMapping("/hello") // 注解
    public String handel(@RequestParam String name) { // 接口处理
        return "Hello " + name + "!";
    }

    @PostMapping("/hello2")
    public String handel2(@RequestParam String name) {
        return "Hello22 " + name + "!";
    }
}

然后在 VScode 代码区右上角点击 Run Java 运行项目。

最后打开浏览器输入 127.0.0.1:8080/hello?name=zs 访问即可。

当然也可以直接使用命令行运行(以下均为 Windows 系统)

bash
# 在 spring-boot 项目根目录打开 cmd
# 使用 Maven 运行 
mvnw.cmd spring-boot:run

# 使用 Gradle 运行
gradlew.bat bootRun

构建 ​

bash
# 这个命令会清理之前的构建结果,然后编译并打包你的应用。打包后的 jar 文件通常位于 target 目录下,文件名类似于 your-application-0.0.1-SNAPSHOT.jar。
# 打包为 jar 打包后会输出到 target 文件夹
mvn clean package
# 进入 target 文件夹后运行打包后的 jar 包
java -jar demo.jar

基础 ​

运行 ​

新建 code 文件夹,再新建 HelloWorld.java 文件

java
// 这里必须要和文件夹名称一致
package code;
// 类名称必须与文件名一致
public class HelloWorld {
    // 入口函数
    public static void main() {
        System.out.println("执行");
    }
}

运行

bash
javac HelloWorld.java

之后会在同文件夹下生成一个 HelloWorld.class

bash
# 在 code 文件夹之外执行如下命令,即可执行上述代码
java code.HelloWorld

变量 ​

变量要先声明才能使用

变量是什么类型,就必须装什么类型的数据

变量是从定义开始到 "}" 截止的范围内有效

同一个范围内,变量的名称不能一样

变量定义的时候可以不赋初始值,但在使用时,变量里必须有值

java
// Java:必须先声明类型
String name = "张三";        // 字符串
final int age = 18;          // final 类似 JS 的 const
boolean flag = true;
double num = 3.14;

// Java 10+ 也支持类型推断(但仍是静态类型)
var city = "北京";  // 编译器自动推断为 String

数据类型 ​

基础数据类型 ​

java 中基础数据类型分为 4 种:整型、浮点型、字符型、布尔型

表达式的最终结果类型由表达式中的最高类型决定

表达式中 byte、short、char 是直接转换成 int 类型参与运算的

java
// 整型
byte  b = 1;        // 1字节(值范围:-128 到 127)
short s = 2;        // 2字节(值范围:-32768 到 32767)
int   i = 100;      // 4字节,最常用(值范围:-2147483648 到 2147483647(10位数,大概21亿多))
long  l = 100L;     // 8字节,注意加 L,大写小写都行,一般用大写(值范围:19位数)

// 浮点型(小数)
float  f = 1.5F;    // 注意加 F ,大写小写都行,一般用大写
double d = 3.14;    // 最常用的小数类型

// 字符型
char    c = 'A';    // 单个字符,用单引号(值范围:0 到 65535)

// 布尔型
boolean flag = true;

// 类型转换(值范围小的可以赋值给值范围大的类型)
int a = 1;
byte b = 2;
a = b;

// 运算(当几个值范围不一样之间运算,必须使用值范围最大的那个类型接收)
byte b = 1;
short s = 2;
long l = 22L;
// 正确
long i = b + s + l;
// 错误
// int i = b + s + l;

// 正确
double d = b + s + l + 3.15;
// 错误
// float d = b + s + l + 3.15;

// 面试题
byte b = 1;
byte s = 2;
// 正确(表达式中 byte、short、char 是直接转换成 int 类型参与运算的)
int i = b + s;
// 错误
// byte i = b + s;

System.out.println("a" + 1); // 输出字符串的 "a1"
// 输出数字的 98,因为单引号表示 char 类型,加运算时会将其转换为 int 类型计算,小写 a 对应 ASCII 码为97
System.out.println('a' + 1);

BigDecimal ​

用于解决浮点型运算时,出现结果失真的问题

java
// 浮点型运算时,直接 + - * / 可能会出现运算结果失真
System.out.println(0.1 + 0.2); // 0.30000000000000004

BigDecimal a = BigDecimal.valueOf(0.1);
BigDecimal b = BigDecimal.valueOf(0.3);
// 相加
System.out.println(a.add(b)); // 0.4
// 相除 a.divide(b, 保留几位小数, 取整模式) RoundingMode.HALF_UP 表示向上取整
System.out.println(a.divide(b, 2, RoundingMode.HALF_UP)); // 0.33

引用数据类型 ​

java 中的引用数据类型分为**类(Class)、接口(Interface)、数组(Array)**‌三大类,变量存储的是堆内存对象的地址而非直接值。

字符串 ​

尽管 String 使用频率极高且不可变,但其本质仍是‌类‌,非语言内置的基本类型。

java
// Java:String 是一个类,length() 是方法
String s = "hello";
int len = s.length();// 注意是方法,要括号

// 拼接
String str = "你好 " + name;

// Java 15+ 文本块(类似模板字符串的多行)
String json = """
    {
        "name": "张三"
    }
    """;

// 字符串格式化
String msg = String.format("你好 %s,今年 %d 岁", name, age);
一些细节 ​

String 的对象是不可变字符串对象

java
// 每次试图改变字符串对象实际上是新产生了新的字符串对象,变量每次都是指向新的字符串对象,之前字符串对象的内容确实是没有改变的,因此说 String 的对象是不可变的。
// 和 js 类似,在频繁拼接时会有性能问题,对于此场景可使用 StringBuilder 解决
String s = "1";
s += "2";
s += "3";

只要是以 “…” 方式写出的字符串对象,会存储到字符串常量池,且相同内容的字符串只存储一份

java
String s = "1";
String s1 = "1";
System.out.println(s == s1); // true

通过 new 方式创建字符串对象,每 new 一次都会产生一个新的对象放在堆内存中

java
String s = new String("1");
String s1 = new String("1");
System.out.println(s == s1); // false
StringBuilder ​

代表可变字符串对象,相当于是一个容器,它里面装的字符串是可以改变的,就是用来操作字符串的

StringBuilder 比 String 更适合做字符串的修改操作,效率会更高,代码也会更简洁。

对于字符串相关的操作,如频繁的拼接、修改等,建议用 StringBuidler,效率更高

如果操作字符串较少,或者不需要操作,以及定义字符串变量,还是建议用 String

java
// 创建空字符
StringBuilder s = new StringBuilder();
StringBuilder s1 = new StringBuilder("1");
String s3 = "23";

// append 方法可以拼接任意类型,支持链式调用,如:s1.append("1").append("1")
StringBuilder s4 = s1.append(s3);
System.out.println(s4); // "123"

// 将对象的内容反转
System.out.println(s4.reverse()); // "321"

// 返回对象内容长度
System.out.println(s4.length()); // 3

// 通过 toString() 就可以实现把 StringBuilder 转换为 String
System.out.println(s4.toString()); // "321"
StringJoiner ​

跟 StringBuilder 一样,也是用来操作字符串的,也可以看成是一个容器,创建之后里面的内容是可变的

不仅能提高字符串的操作效率,并且在有些场景下使用它操作字符串,代码会更简洁

java
public class DemoApplication {
	public static void main() {
		System.out.println(getArr(new int[] { 1, 2, 3 })); // [1,2,3]
	}

	public static String getArr(int[] arr) {
        // 使用方式:new StringJoiner(间隔符号, 开始符号, 结束符号);
		StringJoiner s = new StringJoiner(",", "[", "]");
		for (int i = 0; i < arr.length; i++) {
            // 类型转换,整数转换成字符串
			s.add(Integer.toString(arr[i]));
		}
		return s.toString();
	}
}

数组 ​

数组就是一种容器,可以存储同种类型的多个值

数组一旦创建,长度就固定了,不能直接追加元素

java
int[] arr = new int[]{1,2,3}; // 完整格式
double[] arr2 = {1.2,3}; // 简易格式
// 打印的不是数组内容而是数组的十六进制内存地址
// @:分隔符	[:当前的空间是数组类型	I:数组中元素的类型	13c6f6d7:十六进制地址
System.out.println(arr); // [I@13c6f6d7

System.out.println(arr[1]); // 按照索引取值
arr[2] = 5; // 按照索引修改值
动态初始化 ​

初始化时只指定数组长度,由系统为数组分配初始值

java
int[] arr = new int[10]; // [] 内可直接指定数组长度
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]); // 0
}
二维数组 ​
java
// 二维数组创建简写方式(完整方式参考普通数组创建方式)
int[][] arr = { { 1, 2 }, { 3, 4 } };
for (int i = 0; i < arr.length; i++) {
    for (int i2 = 0; i2 < arr[i].length; i2++) {
        // 二维数组访问
        System.out.println(arr[i][i2]);
    }
}

类 ​

JS 也有 class,但 Java 的 OOP 更严格、更体系化

java
public class Person {
    // 1. 成员变量(字段)需要声明类型
    private String name;
    private int age;

    // 2. 构造方法(方法名 = 类名,无返回值)
    // 创建对象时执行,方法名与类名相同
    // 如果不定义系统则会补全一个默认的无参数构造方法
    public Person(String name, int age) {
        // this 代表当前类对象的引用(地址),和 js 中类似
        this.name = name;
        this.age = age;
    }
    
    // 构造方法也是可以重载的
    public Person() {}

    // 3. getter / setter(Java 习惯用私有字段 + 公开方法访问)
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    // 4. 普通方法
    public String sayHi() {
        // 局部变量
        int a = 10;
        return "我是 " + name;
    }
}

// 使用
Person p = new Person("张三", 18);
System.out.println(p.sayHi());
成员变量和局部变量 ​
区别成员变量局部变量
类中位置不同函数外函数中
初始化值不同有默认初始化值没有,使用之前需要完成赋值
内存位置不同堆内存栈内存,因为存在函数中
生命周期不同随着对象的创建而存在,随着对象的消失而消失随着函数的调用而存在,随着函数的执行结束而消失
作用域在自己所归属的大括号中在自己所归属的大括号中
this ​

可以调用本类的成员变量和成员方法

当成员变量不存在重名的问题、调用本类方法就可以省略不写

static 修饰的方法不能使用 this 关键字

java
class Fu {
	public Fu(int a) {
		System.out.println("构造方法");
	}
 	// 重载构造方法
	public Fu() {
        // 通过 this 调用构造方法
		this(2);
	}

	public void eat() {
		System.out.println("eat");
	}

	public void print() {
        // 调用本类的方法
		this.eat();
	}
}
封装 ​

是一种思想,将数据(属性)和操作数据的方法捆绑在一起组成一个整体,也就是类。

通过访问修饰符来控制对类中属性和方法的访问权限,从而实现信息隐藏,使用封装设计类的时候,要做到合理隐藏,合理暴露

访问修饰符 ​
修饰符含义
public任何地方都能访问
private只能在本类访问(最常用于字段)
protected本类 + 子类访问,不同包无关类(无父子关系)不可访问
不写同一个包(同一个目录下)内访问 + 同一个类中
static ​

static 是静态的意思,可以修饰成员变量,也可以修饰成员方法

被 static 修饰的变量或方法可以被所有的示例访问并使用。

static 中只能访问同样被 static 修饰的成员(直接访问)。

static 修饰的方法中没有 this 关键字。

java
public class Fu {
    public Fu() {
        // 可直接通过类名访问被 static 修饰的变量或方法
        Hello.fn();
    }
}

public class Hello {
    static void fn() {}
}
继承 ​

让类与类之间产生关系(子父类关系),子类可以直接使用父类中非私有的成员。

当类与类之间,存在相同(共性)的内容,并且产生了 is a 的关系,就可以考虑使用继承,来优化代码。

Java 中的继承只允许单继承,不允许多继承(类 A 同时继承类 B 和类 C),允许多层继承(类 A 可以继承类 B ,类 B 又继承类 C)。

java
// extends 关键字(和 JS 一样)
public class Student extends Person {
    private String school;

    public Student(String name, int age, String school) {
        super(name, age);   // 调用父类构造,类似 JS 的 super()
        this.school = school;
    }

    // @Override 注解表示重写父类方法
    @Override
    public String sayHi() {
        return "我是学生 " + getName();
    }
}
重写 ​

子类重写父类方法,需要保证方法声明完全一致(方法名,参数,返回值类型需要保持一致)

父类中私有方法不能被重写

子类重写父类方法时,访问权限必须大于等于父类

java
public class Fu {
    public void fn() {
        System.out.println("父类方法");
    }
}

public class Zi extends Fu {
    // 重写的标记注解,用于声明当前方法旨在重写(覆盖)父类或接口中的方法,若未真正重写则编译报错
    @Override
    public void fn() {
        super.fn(); // 可通过 super 调用父类方法
        System.out.println("重写");
    }
}
final ​

可以修饰方法(修饰后不能被重写)、类(修饰后不能被继承)、变量(修饰后变成常量,不能被修改)。

java
/*
final 修饰的基础数据类型变量数据值不可变
修饰后变成常量,常量命名规范:
    1.如果是一个单词:所有字母大写
    2.如果是多个单词:所有字母大写,中间使用 _ 分割
*/
final int AGE = 1;
// AGE=2; 报错

// final 修饰的引用数据类型变量地址值不可变
final int[] arr = {1,2};
arr[1]=3; // 允许修改
// arr = new int[]{3,4}; 报错


class Fu {
    // final 修饰成员变量必须在定义的时候赋值
    final int a = 1;
	// final int a; 报错
    
	public Fu() {
        // 如果定义时不赋值,那么必须在构造函数中赋值
		a = 1;
	}
}
抽象类 ​

一种特殊的父类,内部可以编写抽象方法,作为“半成品模板”,让子类复用代码并强制实现某些行为

注意事项:

1.抽象类不能实例化

2.抽象类不能创建对象,但里面还是存在构造方法

3.抽象类中可以编写普通的方法

4.抽象类的子类必须重写所有抽象方法

java
// abstract 关键字声明抽象类
abstract class Animal {
    // 抽象类的构造方法
    public Animal(){};
	public abstract void eat();
    // 抽象类中的普通方法
    public void eat2(){};
}

class Cat extends Animal {
    // 子类必须重写所有抽象方法
    @Override
	public void eat() {}
}
接口 ​

接口:一套规范/契约,只定义"能做什么",但不实现。

和 ts 的 interface 概念类似,但 Java 接口是运行时真实存在的

java
// 定义接口
public interface Animal {
    public static final int NUM = 1;
    void eat();
    // 用 default 修饰后表示该方法不强制重写,一般用于维护老代码添加新功能
    public default void go(){
        // 调用接口中的私有方法
        go2();
    };
    // 可以定义静态方法,通过 Animal.go1 调用
    public static void go1(){};
    // JDK9 中可以用 private 修饰方法
    private void go2() {};
}

public interface Animal2 {
    void sleep();
}

// 实现接口用 implements,多继承用逗号分隔
public class Dog implements Animal,Animal2 {
    @Override
    public void eat() {}
    @Override
    public void sleep() {
        // 调用接口上新增的方法
        Animal.super.go();
    }
}

// 接口之间也可以继承
interface Animal3 extends Animal,Animal2 {
	void go();
}
接口和抽象类的区别 ​

抽象类表示“你是什么”,接口表示“你能做什么”

对比点抽象类接口
继承/实现类只能extends一个抽象类类可以implements多个接口
成员变量可以有普通实例变量、静态变量、常量字段默认是 public static final 常量,不能有实例变量
构造器有构造器,供子类初始化没有构造器
方法可以有抽象方法,也可以有普通方法Java 8 前全是抽象方法;Java 8+可有static、default。Java 9+ 可有private
访问修饰符方法可以是protected、public、private 等抽象方法默认 public,不能是 protected
多继承类只能单继承抽象类接口可以多实现,接口也能多继承接口
设计意图代码复用、模板设计、共享状态定义行为规范、契约、解耦、能力扩展
使用场景多个类有共同属性和公共代码多个类只需要具备某种能力
多态 ​

多态就是同一个方法调用,在不同对象上表现出不同的行为

多态前提:继承或实现关系、方法重写、父类引用指向子类对象

作用:统一调用方式、提高扩展性、降低耦合、便于替换实现、减少大量 if-else

java
Fu fu = new Zi();
System.out.println(fu.num); // 10
fu.show(); // 这时调用的是子类(Zi)的 show 方法

class Fu {
	int num = 10;
	public void show() {};
}

class Zi extends Fu {
	int num = 20;
	public void show() {};
}
java
// 最常见的形式:父类引用指向子类对象。
public class DemoApplication {
	public static void main() {
		useAnim(new Cat()); // 这里调用的是 Cat 类的 eat 方法
		useAnim(new Dog()); // 这里调用的是 Dog 类的 eat 方法
	}
	// 接收父类 Anim 的形参
	public static void useAnim(Anim anim) {
        // 通过父类 Anim 的形参直接调用子类方法
		anim.eat();
	}
}

abstract class Anim {
	abstract public void eat();
}

class Cat extends Anim {
	public void eat() {};
}

class Dog extends Anim {
	public void eat() {};
}
转型 ​
java
public class DemoApplication {
	public static void main() {
		useAnim(new Cat());
		useAnim(new Dog());
	}

	public static void useAnim(Anim anim) {
        // 判断传入的类类型调用不同方法
        // instanceof 用于判断左边是否是右边的实例
		if (anim instanceof Cat) {
            // 向下转型
			((Cat) anim).catgo();
		} else if (anim instanceof Dog) {
            // 向下转型
			((Dog) anim).doggo();
		}
	}
}

abstract class Anim {
	abstract public void eat();
}

class Cat extends Anim {
	public void eat() {};
	public void catgo() {};
}

class Dog extends Anim {
	public void eat() {};
	public void doggo() {};
}
equals 方法 ​

用于判断两个对象是否“逻辑相等",而不只是判断它们是不是同一个对象

和 == 的区别:== 判断引用地址是否相同,equals 默认也是比较地址,但子类可以重写它,改成比较内容

String、Integer、List 等类都重写了 equals,所以它们比较的是内容

java
String s1 = new String("abc");
String s2 = new String("abc");

System.out.println(s1 == s2);      // false,不是同一个对象
System.out.println(s1.equals(s2)); // true,内容相同
java
class Person {
	private String name;
	private int age;

	@Override
	// 自定义类重写 equals 方法,添加自己的对比逻辑
	public boolean equals(Object o) {
		if (!(o instanceof Person)) return false;
		// 向下转型
		Person p = (Person) o;
		return this.age == p.age && this.name.equals(o);
	}
}
内部类 ​

是定义在另一个类、方法或代码块里面的类

内部类的核心价值是更好的封装、组织和访问控制,同时让回调、迭代器、辅助实现等代码更紧凑

类型定义位置特点
成员内部类类中、方法外非静态,隐式持有外部类实例,可访问外部类所有成员
静态嵌套类类中、方法外,带 static不持有外部类实例,类似普通类,只是命名空间嵌套
局部内部类方法或代码块中只在局部作用域可用
匿名内部类(最常用)表达式里没有类名,常用于一次性实现接口/抽象类
java
public class Outer {
    private int x = 10;
    
    void fn() {
        // 外部类访问内部类属性和方法
		Inner in = new Inner();
		System.out.println(in.i);
	}

    // 成员内部类
    class Inner {
        int i = 1;
        void print() {
            // 直接访问外部类私有字段和方法
            // 如果外部类属性名和内部类一致时需要使用 Outer.this.x 来访问
            System.out.println(x); 
        }
    }

    // 静态嵌套类
    static class Nested {
        /*
            调用方式
            Outer.Nested nested = new Outer.Nested();
        */
        void print() {
            System.out.println("nested");
        }
    }

    // 匿名内部类,本质就是一个特殊的局部内部类
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println(x);
        }
    };

    void method() {
        int y = 20; // 事实 final
        // 局部内部类
        class Local {
            void print() {
                System.out.println(x + y);
            }
        }
        new Local().print();
    }
}

// 使用
Outer outer = new Outer();

Outer.Inner inner = outer.new Inner();      // 非静态内部类需要外部实例
Outer.Nested nested = new Outer.Nested();   // 静态嵌套类直接 new
匿名内部类 ​
java
public class DemoApplication {
	public static void main() {
        // 匿名内部类,本质就是一个特殊的局部内部类
        // 这里就相当于创建了匿名类并继承 Fu 类
		new Fu() {
			@Override
            // 重写 Fu 类的方法
			public void method() {}
            // 直接调用重写后的方法
		}.method();
        
        // 这里就相当于实现了 UseInter 接口并重写方法
        new UseInter() {
			@Override
			public void show() {
				System.out.println("UseInter");
			}
		}.show();
	}

}

class Fu {
	public void method() {}
}

interface UseInter {
	void show();
}
lambda 表达式 ​

可以简化函数式接口的匿名内部类的代码写法

其他补充:

参数类型可以省略不写

如果只有一个参数,参数类型可以省略,同时 () 也可以省略

如果 Lambda 表达式的方法体代码只有一行代码,可以省略大括号不写,同时要省略末尾分号(;),此时,如果这行代码是 return 语句,必须省略 return 不写,同时也必须省略末尾分号(;)不写

java
public class DemoApplication {
	public static void main() {
        // 必须是函数式接口才能简写
		fn((int i) -> {
			System.out.println("UseInter");
            return i;
		});
        /*
        	上方写法等同于下方代码
            new UseInter() {
                @Override
                public int show(int i) {
                    System.out.println("UseInter");
                    return i;
                }
            }.show(1);
        */

	}
	public static void fn(UseInter inter) {
		inter.show(1);
	}
}

// 函数式接口:仅有一个抽象方法的形式
// 该注解表示该接口必须满足函数式接口
@FunctionalInterface
interface UseInter {
	int show(int i);
}

代码块 ​

java
/*
下方代码执行结果:
    同步代码块
    静态代码块
    构造代码块
    无参构造函数
    构造代码块
    有参构造函数
*/
public class DemoApplication {
	public static void main() {
        System.out.println("同步代码块");
		Person p = new Person();
		Person p2 = new Person("张三");
	}
}

class Person {
	{
        // 构造代码块,创建对象时执行,无论调用哪个构造函数都会在构造函数之前执行
        // 可以将多个构造函数一样的代码抽离到这里执行
		System.out.println("构造代码块");
	}
    
    static {
        // 在构造代码块之前执行,只会执行一次,可用于进行一些初始化操作
		System.out.println("静态代码块");
	}

	public Person() {
        // 局部代码块 方法中的一对 {}
		System.out.println("无参构造函数");
	}

	public Person(String s) {
		System.out.println("有参构造函数");
	}
}

运算符 ​

把多个条件放在一起运算,最终返回布尔类型的值:true、false

符号叫法例子运算逻辑
&逻辑与2>1&3>2多个条件必须都是true,结果才是true;有一个是false,结果就是false
|逻辑或2>1|3<5多个条件中只要有一个是true,结果就是true
!逻辑非!(2>1)就是取反:你真我假,你假我真
^逻辑异或2>1^3>1前后条件的结果相同,就直接返回false,前后条件的结果不同,返回true
&&短路与2 >10 &&3>2判断结果与 & 一样,过程不同:左边为fase,右边则不执行。
||短路或2>1||3<5判断结果与 | 一样,过程不同:左边为true,右边则不执行。

其他 ​

自增自减(如 ++a)、三元运算符、运算符优先级(括号内优先级最高,&& 优先级比 || 优先级高)等和 js 基本一致

控制流 ​

和 js 中基本一样

java
// if-else 一致
if (age >= 18) {
    System.out.println("成年");
} else {
    System.out.println("未成年");
}

// for 循环一致
// 循环中 () 中定义的变量,在整个循环结束后,都会从内存中释放
for (int i = 0; i < 10; i++) {
    // 循环中 {} 中定文的变量,在每一轮循环结束后,都会从内存中释放
    int a = 10;
    System.out.println(i);
}

// while 循环(先判断后执行)
int i = 1;
while (i <= 5) {
    i++;
    System.out.println("执行");
}

// do while 循环(先执行后判断)
int i = 1;
do {
    i++;
    System.out.println("执行");
} while (i <= 5);

// 增强 for(类似 JS 的 for...of)
int[] arr = {1, 2, 3};
for (int item : arr) {
    System.out.println(item);
}

// 注意这里的 i 只支持 byte、short、int、char 类型,JDK5开始支持枚举,JDK7开始支持 String
int i = 1;
switch (i) {
    // case 给出的值不允许重复,且只能是字面量,不能是变量
    case 1:
        System.out.println("等于1");
        // 正常使用 switch 的时候,不要忘记写 break,否则会出现穿透现象
        break;
    case 2:
        System.out.println("等于2");
        break;
    default:
        System.out.println("等于10");
}

// switch(Java 14+ 新写法很好用)
String result = switch (age) {
    case 18 -> "成年了";
    case 1, 2, 3 -> "小孩";
    default -> "其他";
};

// break 终止循环体内容的执行,也就是说结束当前的整个循环
// continue 跳过某次循环体内容的执行,继续下一次的执行

函数 ​

Java 中方法必须写在类里,且要声明返回类型和参数类型

注意事项:

1.函数在类中的位置放前放后无所谓,但一个函数不能定义在另一个函数里面。

2.函数的返回值类型写 void(无返回申明)时,函数内不能使用 return 返回数据,如果函数的返回值类型写了具体类型,函数内部则必须使用 return 返回对应类型的数据。

3.return 语句的下面,不能编写代码,属于无效的代码,执行不到这儿

4.函数不调用就不会执行,调用函数时,传给函数的数据,必须严格匹配函数的参数情况。

5.调用有返回值的函数,有3种方式:可以定义变量接收结果、或者直接输出调用、甚至直接调用。

6.调用无返回值的函数,只有1种方式:1、只能直接调用,因为它无返回值所以不能接收其返回值。

java
public int add(int a, int b) {
    return a + b;
}

// 无返回值用 void
public void printName(String name) {
    System.out.println(name);
}

// 修饰符说明:
// public  = 访问权限(公开)
// int     = 返回值类型
// add     = 方法名

函数执行原理 ​

函数没有被调用的时候,在函数区中的字节码文件中存放。

函数被调用的时候,是进入到栈(先进后出)内存中运行。这么做有两个原因:

1.确保函数调用其他函数后可以回来(在一个函数 A 中调用函数 B,B 执行完后可以返回 A 函数继续执行 A 函数的剩余代码)。

2.确保函数执行完毕后可以释放栈内存

参数传递机制 ​

参数传递机制是值传递:指的是在传输实参给方法的形参的时候传输的是实参变量中存储的值的副本。

java
// 这点和 js 基本一致
public static void main() {
    int a = 10;
    fun(a);
    System.out.println(a); // 10
}

static void fun(int c) {
    c = c + 1;
    System.out.println("函数内:" + c); // 11
}

重载 ​

函数重载必须在同一个类中。

函数名必须一致。

参数列表必须不同(个数、类型、顺序)。

返回值、修饰符(public、private 等)不同不算重载。

java
public static void main(String[] args) {
    fn(); // 无参数 fn
    fn(2); // 有参数 fn2
}

static void fn() {
    System.out.println("无参数 fn");
}

static void fn(int i) {
    System.out.println("有参数 fn" + i);
}

泛型 ​

类型参数化,让代码复用且类型安全

java
// 1. 泛型方法
public <T> T identity(T arg) {
    return arg;
}

// 2. 泛型在集合中(最常见)
List<String> names = new ArrayList<>();   // 只能放 String

// 3. 自定义泛型类
public class Box<T> {
    private T content;

    public void set(T content) {
        this.content = content;
    }
    public T get() {
        return content;
    }
}

// 使用
Box<String> box = new Box<>();
box.set("hello");
String s = box.get();

泛型通配符 ​

泛型通配符 ? 表示未知类型,主要用于让方法或变量接受更广的泛型类型

java
// 无界通配符,表示类型未知,常用于只读、不关心具体类型
// 元素类型未知的 List 可以读取为 Object,但不能添加除 null 外的元素
List<?> list;

// 上界通配符 ? extends T,表示类型是 T 或 T 的子类。适合“读取”
// 表示 Number 或其子类的 List,例如 List<Integer>、List<Double>。
// 适合读取:可以读成 Number。
// 不适合写入:不能 add(1),因为具体类型可能是 List<Double>。
List<? extends Number> list;

// 下界通配符 ? super T,表示类型是 T 或 T 的父类。适合“写入”
// 表示 Integer 或其父类的 List,例如 List<Integer>、List<Number>、List<Object>。
// 适合写入:可以 add(Integer)。
// 读取只能读成 Object。
List<? super Integer> list;

集合 ​

单列集合 ​

单列集合:每个元素只有一个数据(数组),继承自 Collection 接口

ArrayList、LinkedList、TreeSet、HashSet、LinkedHashSet 等都属于单列集合

ArrayList、LinkedList 特点:存取有序、有索引、可以存储重复元素

TreeSet、HashSet、LinkedHashSet 特点:无序、无索引、不可重复

数组定义完成并启动后,长度就固定了

集合长度可变, 开发中用的更多

java
// List:有序可重复,类似 JS 数组
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
String first = list.get(0);
int size = list.size();

// Set:不重复集合
Set<String> set = new HashSet<>();
set.add("a");
set.add("a");  // 重复无效

公共方法 ​

Collection 接口上的公共方法

java
// 因为单列集合继承自 Collection 接口,所以可以使用一些公共的方法,如:add(添加元素)、clear(清空集合所有元素)、remove(删除指定元素)、contains(判断集合中是否包含指定对象)、isEmpty(非空判断)、size(集合长度)
// 借助多态调用单列集合上的一些公共方法
Collection<String> c = new ArrayList<>();
c.add("1");
c.add("2");
c.add("3");
System.out.println(c); // [1, 2]

// 使用迭代器遍历集合
Iterator<String> iterator = c.iterator();
// hasNext 表示是否还存在下一项
while (iterator.hasNext()) {
    // next 获取下一项的值
    System.out.println(iterator.next()); // 依次打印 1、2、3
}

// 可以使用 for 循环来简化遍历
for (String c2 : c) {
    System.out.println(c2); // 依次打印 1、2、3
}

// 还可以使用 forEach 遍历
c.forEach(new Consumer<String>() {
    @Override
    public void accept(String t) {
        System.out.println(t);
    }
});

// 上述 forEach 循环可简写为如下代码
c.forEach(t -> System.out.println(t));

ArrayList ​

基于动态数组,最常用的 List。适合:频繁按索引访问、遍历,少量中间插入删除。

特点:

1.有索引,get(i) 很快

2.允许重复、允许 null

3.中间插入/删除需要移动元素,较慢

4.非线程安全

java
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("A");
// 一次添加多个元素
Collections.addAll(list, "a", "b", "c");

System.out.println(list.get(0)); // A
list.set(0, "C");                // 修改索引 0
list.remove("A");                // 删除第一个 "A"
list.remove(0);                  // 删除索引 0
System.out.println(list.contains("B")); // true
System.out.println(list.size());

LinkedList ​

基于双向链表,同时实现了 List 和 Deque,适合:频繁在头部或尾部插入删除,比如队列、栈

特点:

1.有索引,但 get(i) 需要从头或尾遍历,慢

2.头尾插入/删除很快

3.允许重复、允许 null

4.可以当队列、栈、双端队列用

5.非线程安全

java
LinkedList<String> list = new LinkedList<>();
list.add("B");
list.addFirst("A");
list.addLast("C");

System.out.println(list);        // [A, B, C]
System.out.println(list.getFirst()); // A
System.out.println(list.getLast());  // C

list.removeFirst();
list.removeLast();

// 当队列用
list.offer("D");
System.out.println(list.poll()); // D

HashSet ​

基于 HashMap 实现,元素作为 key,适合:去重、判断元素是否存在,不关心顺序

特点:

1.不允许重复

2.不保证迭代顺序

3.允许一个 null

4.增删查平均 O(1)

5.非线程安全

6.依赖 hashCode(),对象的哈希值,决定元素存放的位置 和 equals(),判断两个对象是否逻辑相等,用于去重

java
Set<String> set = new HashSet<>();
set.add("A");
set.add("B");
set.add("A"); // 重复,加入失败

System.out.println(set.contains("A")); // true
System.out.println(set.size());        // 2
set.remove("B");

LinkedHashSet ​

继承 HashSet,内部用 LinkedHashMap,额外维护插入顺序,适合:去重,同时想保持插入顺序

特点:

1.不允许重复

2.迭代顺序是插入顺序

3.允许一个 null

4.性能略低于 HashSet

5.非线程安全

java
Set<String> set = new LinkedHashSet<>();
set.add("C");
set.add("A");
set.add("B");
set.add("A"); // 重复,不加入

System.out.println(set); // [C, A, B]

TreeSet ​

基于红黑树,自动排序,适合:需要自动排序、范围查询、找最大最小元素

特点:

1.不允许重复

2.元素按自然顺序或 Comparator 排序

3.增删查 O(log n)

4.通常不允许 null,自然排序时添加 null 会 NullPointerException

5.非线程安全

6.元素必须实现 Comparable(用于指定集合如何排序,有唯一默认顺序,且能改元素类,即自定义类),或者创建时传入 Comparator(用于指定集合如何排序,多种排序、不能改元素类、临时规则)

java
TreeSet<Integer> set = new TreeSet<>();
set.add(3);
set.add(1);
set.add(2);
set.add(1); // 重复,不加入

System.out.println(set);        // [1, 2, 3]
System.out.println(set.first()); // 1
System.out.println(set.last());  // 3

System.out.println(set.headSet(3)); // [1, 2]
System.out.println(set.tailSet(2)); // [2, 3]
System.out.println(set.higher(2));  // 3
System.out.println(set.lower(2));   // 1

注意事项 ​

1.ArrayList、LinkedList 允许重复和多个 null。

2.HashSet、LinkedHashSet 不允许重复,允许一个 null。

3.TreeSet 不允许重复,通常不允许 null。

4.放进 HashSet、LinkedHashSet 的对象,建议正确重写 hashCode() 和 equals()。

5.放进 TreeSet 的对象,必须能比较:实现 Comparable 或传 Comparator。

6.这些集合都不是线程安全的;多线程环境要用并发集合或 Collections.synchronizedXxx() 包装。

7.遍历时不要直接 list.remove(),否则可能抛 ConcurrentModificationException,应使用 Iterator.remove()。

总结 ​
类接口底层结构顺序重复null性能特点常用场景
ArrayListList动态数组插入顺序允许允许随机访问快,中间增删慢查询多、遍历多
LinkedListList、Deque双向链表插入顺序允许允许头尾增删快,随机访问慢队列、栈、频繁头尾操作
HashSetSetHashMap不保证顺序不允许允许一个增删查平均 O(1)去重、快速查找
LinkedHashSetSetLinkedHashMap插入顺序不允许允许一个略慢于 HashSet去重且保持插入顺序
TreeSetSortedSet、NavigableSet红黑树排序顺序不允许通常不允许增删查 O(log n)去重且排序、范围查询

双列集合 ​

双列集合:每个元素包含两个数据(键值对),继承自 Map 接口

TreeMap、HashMap、LinkedHashMap 等都是双列集合,键不能重复,重复 put 同一个键会覆盖旧值

java
// Map:键值对,类似 JS 对象/Map
Map<String, Integer> map = new HashMap<>();
map.put("张三", 18);
Integer age = map.get("张三");

// 遍历 Map 
// entrySet:获取所有键值对对象
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    System.out.println(entry.getKey() + ":" + entry.getValue());
}

// 第二种遍历方式
Map<String, Integer> map = new HashMap<>();
map.put("1", 1);
map.put("2", 2);
map.put("3", 3);
// 获取所有的键
Set<String> keySet = map.keySet();
for (String key : keySet) {
    System.out.println(key + ":" + map.get(key));
}

// 第三种方式(最常用)
map.forEach(new BiConsumer<String, Integer>() {
    @Override
    public void accept(String key, Integer value) {
        System.out.println(key + ":" + value);
    }
});

HashMap ​

最常用,不保证顺序,适合:快速查找、去重、缓存,不关心顺序

特点:

1.键不能重复

2.不保证迭代顺序

3.允许一个 null 键,多个 null 值

4.依赖 hashCode() 和 equals()

5.非线程安全

java
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("A", 3); // 覆盖旧值

System.out.println(map.get("A")); // 3
System.out.println(map.getOrDefault("C", 0)); // 0

map.putIfAbsent("C", 4);
map.computeIfAbsent("D", k -> 5);
map.merge("A", 10, Integer::sum); // A = 13

System.out.println(map.containsKey("B")); // true
System.out.println(map.keySet());   // 所有 key
System.out.println(map.values());   // 所有 value
System.out.println(map.entrySet()); // 所有键值对

LinkedHashMap ​

继承 HashMap,额外维护一个双向链表,所以能记住顺序,适合:需要保持插入顺序,或者实现 LRU 缓存

特点:

1.键不能重复

2.默认迭代顺序 = 插入顺序

3.accessOrder=true 时,get 或 put 已存在的键会把该键移到末尾

4.允许一个 null 键,多个 null 值

5.非线程安全

java
// 默认是 插入顺序:
Map<String, Integer> map = new LinkedHashMap<>();
map.put("C", 3);
map.put("A", 1);
map.put("B", 2);

System.out.println(map); // {C=3, A=1, B=2}


// 也可以设置成 访问顺序
LinkedHashMap<String, Integer> map = new LinkedHashMap<>(16, 0.75f, true);

map.put("A", 1);
map.put("B", 2);
map.put("C", 3);

map.get("A");
System.out.println(map.keySet()); // [B, C, A]

TreeMap ​

基于红黑树,按键自动排序,适合:需要按键排序、找最大最小、范围查询

特点:

1.键不能重复

2.按键排序,不是插入顺序

3.增删查 O(log n)

4.键必须实现 Comparable,或者创建时传入 Comparator

5.通常不允许 null 键,值可以为 null

6.非线程安全

java
TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "C");
map.put(1, "A");
map.put(2, "B");

System.out.println(map); // {1=A, 2=B, 3=C}

// 常用导航方法:
System.out.println(map.firstKey());  // 1
System.out.println(map.lastKey());   // 3
System.out.println(map.headMap(3));  // {1=A, 2=B}
System.out.println(map.tailMap(2));  // {2=B, 3=C}
System.out.println(map.subMap(1, 3));// {1=A, 2=B}

System.out.println(map.floorKey(2));   // 2,小于等于 2 的最大键
System.out.println(map.ceilingKey(2)); // 2,大于等于 2 的最小键
System.out.println(map.higherKey(2));  // 3,大于 2 的最小键
System.out.println(map.lowerKey(2));   // 1,小于 2 的最大键


// 也可以自定义排序:
TreeMap<Integer, String> desc = new TreeMap<>(Comparator.reverseOrder());

注意事项 ​

1.三个都不是线程安全。多线程用 ConcurrentHashMap 或 Collections.synchronizedMap()。

2.HashMap、LinkedHashMap 的键要正确重写 hashCode() 和 equals()。

3.TreeMap 的键必须能比较,否则会报错。

4.HashMap、LinkedHashMap 允许一个 null 键;TreeMap 通常不允许 null 键。

5.遍历时删除元素,建议用 Iterator.remove() 或 removeIf(),不要直接边遍历边删。

6.LinkedHashMap 默认插入顺序;构造参数 accessOrder = true 才是访问顺序。

总结 ​
类底层结构顺序null 键null 值性能常用场景
HashMap数组 + 链表 + 红黑树无序允许 1 个允许多个平均 O(1)快速增删查,不关心顺序
LinkedHashMapHashMap + 双向链表插入顺序或访问顺序允许 1 个允许多个略慢于 HashMap保持顺序、LRU 缓存
TreeMap红黑树按键排序通常不允许允许O(log n)排序、范围查询、导航

集合工具类 ​

Collections 并不属于集合,是用来操作集合的工具类。

注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口

java
// 常用方法:shuffle(打乱 List 集合元素顺序)、max/min(根据默认的自然排序获取最大、最小值)、sort(将集合中元素按默认规则排序)、addAll(批量给集合添加元素)
ArrayList<String> c = new ArrayList<>();
// 一次添加多个元素
Collections.addAll(c, "a", "b", "c");
System.out.println(c); // [a, b, c]

Starem ​

Java 8 引入的 Stream 用来对集合、数组等数据进行链式、声明式处理,它不存储数据,也不修改原数据,只是对数据流做操作

核心流程:源 => 中间操作 => 终端操作

java
// 集合获取 Stream 流对象
Set<String> set = new HashSet<String>();
set.add("A");
set.add("B");
set.add("C");
// 用 stream 和 lambda 表达式的遍历写法
set.stream().forEach(System.out::println);

Map<String, Integer> map = new HashMap<>();
map.put("1", 1);
map.put("2", 2);
map.put("3", 3);
// 用 stream 和 lambda 表达式的遍历写法
map.entrySet().stream().forEach(System.out::println);


// 数组获取 Stream 流对象
int[] arr = {1,2,3};
Arrays.stream(arr).forEach(System.out::println);


// 零散数据获取 Stream 流对象
Stream.of(1, 2, 3, 4).forEach(System.out::println);

常用方法

java
// filter(对流中数据过滤)、limit(获取前几个元素)、skip(跳过前几个元素)、distinct(祛除流中重复的元素,需要依赖 hashCode 和 equals 方法)、concat(合并两个流为一个)、map(对流中每一个元素进行替换)
ArrayList<String> list = new ArrayList<String>();
list.add("林青霞");
list.add("张曼玉");
list.add("王祖贤");
list.add("柳岩");
list.add("张敏");
list.add("张无忌");

// 需求:将集合中以【张】开头的数据,并且只要长度为3的人名,过滤出来并打印在控制台
list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() == 3).forEach(System.out::println);

// 需求1:取前3个数据在控制台输出
list.stream().limit(3).forEach(System.out::println);

// 需求2:跳过3个元素,把剩下的元素在控制台输出
list.stream().skip(3).forEach(System.out::println);

// 需求3:跳过2个元素,把剩下的元素中前2个在控制台输出
list.stream().skip(2).limit(2).forEach(System.out::println);

// 需求4:取前4个数据组成一个流
Stream<String> limit = list.stream().limit(4);

// 需求5:跳过2个数据组成一个流
Stream<String> skip = list.stream().skip(2);

// 需求6:合并需求4和需求5得到的流,并把结果在控制台输出
Stream.concat(limit, skip).forEach(System.out::println);

// 需求7:合并需求4和需求5得到的流,并把结果在控制台输出,要求字符串元素不能重复
Stream.concat(limit, skip).distinct().forEach(System.out::println);

// 需求8:对流中的数据进行转换,每一个数据都做+10操作,随后遍历打印.
Stream.of(1, 2, 3, 4, 5).map(new Function<Integer, Integer>() {
    @Override
    public Integer apply(Integer i) {
        return i + 10;
    }
}).forEach(System.out::println);

收集操作 ​

Stream 流操作,不会修改数据源

java
ArrayList<Integer> list = new ArrayList<Integer>();
Collections.addAll(list, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10);

// 把元素收集到 List 集合中
// 打印结果: [2, 4, 6, 8, 10, 10, 10]
// 可简写为 list.stream().filter(s -> s % 2 == 0).toList()
System.out.println(list.stream().filter(s -> s % 2 == 0).collect(Collectors.toList())); 

// 把元素收集到 Set 集合中
// 打印结果: [2, 4, 6, 8, 10]
System.out.println(list.stream().filter(s -> s % 2 == 0).collect(Collectors.toSet())); 

// 将元素收集到 Map 集合中
ArrayList<String> list = new ArrayList<String>();
Collections.addAll(list, "张三,23", "李四,24", "王五,25");
Map<String, Object> collect = list.stream().collect(Collectors.toMap(new Function<String, String>() {
    @Override
    // 这里是处理组装键
    public String apply(String s) {
        return s.split(",")[0];
    }
}, new Function<String, String>() {
    @Override
    // 这里是处理组装值
    public String apply(String s) {
        return s.split(",")[1];
    }
}));
System.out.println(collect); // {李四=24, 张三=23, 王五=25}

注意事项 ​

1.流只能消费一次,重复使用会抛 IllegalStateException。

2.中间操作惰性,终端操作才触发执行。

3.Stream 不修改原集合。

4.避免在 lambda 中修改外部状态。

5.基本类型可用 IntStream、LongStream、DoubleStream,避免装箱。

6.peek 多用于调试,不要写业务逻辑。

7.并行流慎用,尤其有顺序、共享状态时。

异常处理 ​

java
// Java 异常是分类型的
try {
    int result = 10 / 0;
} catch (ArithmeticException e) {       // 捕获特定异常
    System.out.println("除零错误:" + e.getMessage());
} catch (Exception e) {                 // 兜底
    System.out.println("其他错误");
} finally {
    System.out.println("结束");
}

// 主动抛异常
throw new RuntimeException("自定义错误");

构建工具 ​

构建工具 Maven(类似 npm)

pom.xml 相当于 package.json

xml
<project>
    <groupId>com.example</groupId>      <!-- 组织名 -->
    <artifactId>demo</artifactId>       <!-- 项目名 -->
    <version>1.0.0</version>

    <!-- 依赖,类似 dependencies -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>
Maven 命令npm 类比
mvn installnpm install
mvn packagenpm run build
mvn testnpm test

Maven 会从中央仓库下载 jar 包到本地 .m2 目录(类似 node_modules,但全局共享)。

配置数据库 ​

application.yml 或者 application.properties

yaml
server:
  port: 8080

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver