Appearance
环境安装
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 -versionMaven安装
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 版本,再选择 java3.输入项目组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=8080java
// 在 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); // falseStringBuilder
代表可变字符串对象,相当于是一个容器,它里面装的字符串是可以改变的,就是用来操作字符串的
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.允许重复、允许
null3.中间插入/删除需要移动元素,较慢
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.允许重复、允许
null4.可以当队列、栈、双端队列用
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()); // DHashSet
基于
HashMap实现,元素作为 key,适合:去重、判断元素是否存在,不关心顺序特点:
1.不允许重复
2.不保证迭代顺序
3.允许一个
null4.增删查平均 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.允许一个
null4.性能略低于
HashSet5.非线程安全
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会NullPointerException5.非线程安全
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 | 性能特点 | 常用场景 |
|---|---|---|---|---|---|---|---|
ArrayList | List | 动态数组 | 插入顺序 | 允许 | 允许 | 随机访问快,中间增删慢 | 查询多、遍历多 |
LinkedList | List、Deque | 双向链表 | 插入顺序 | 允许 | 允许 | 头尾增删快,随机访问慢 | 队列、栈、频繁头尾操作 |
HashSet | Set | HashMap | 不保证顺序 | 不允许 | 允许一个 | 增删查平均 O(1) | 去重、快速查找 |
LinkedHashSet | Set | LinkedHashMap | 插入顺序 | 不允许 | 允许一个 | 略慢于 HashSet | 去重且保持插入顺序 |
TreeSet | SortedSet、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,或者创建时传入Comparator5.通常不允许
null键,值可以为null6.非线程安全
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) | 快速增删查,不关心顺序 |
LinkedHashMap | HashMap + 双向链表 | 插入顺序或访问顺序 | 允许 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 install | npm install |
| mvn package | npm run build |
| mvn test | npm 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