01_Java基础速通

一、基础概念与环境安装

1.1 编译型 vs 解释型

我们写前端,浏览器直接解释 JS 运行;但是 Java 多了一道编译关卡:

.java 源码 --(javac 编译)--> .class 字节码 --(JVM 执行)--> 运行

  • .java 文件 = Java 源码,类似我们的 .vue / .ts 文件(人类可读)。
  • .class 文件 = JDK 把 .java 编译后的产物,可由 JDK 或 JRE 运行(与平台无关的中间码)。

1.2 JDK / JRE / JVM

名词 全称 能编译? 能运行? 类比前端
JDK Java Development Kit 装了 vite 依赖 + node 环境 的开发机
JRE Java Runtime Environment 只装了 node 的运行环境
JVM Java Virtual Machine --- 真正执行字节码 浏览器里的 JS 引擎(JVM 和 JS 引擎内部都含有 JIT)

​**WORA(Write Once, Run Anywhere)**​:字节码不绑定操作系统/CPU,任何装了 JVM 的机器都能跑。JVM 内部做「解释执行 + JIT 即时编译」,兼顾跨平台与性能。

1.3 安装

注:​**IDEA 会在我们使用的时候自动弹出 JDK 缺失的弹窗,我们点击后就可以在 IDEA 内部自动下载,这里的 JDK 安装仅供此方法不生效的同学(下图展示了详细的安装步骤)**​。

1.4 第一个程序

TypeScript 复制代码
// HelloWorld.java
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("你好,后端世界");
    }
}
Python 复制代码
javac HelloWorld.java   # 生成 HelloWorld.class
java HelloWorld         # JVM 运行,输出一句话

这里运行有两种方法:

  • 第一种方法就是上述你们看到的,通过命令行工具 javac 进行编译,java 进行程序运行。(讲解命令行运行的主要原因是想让大家感受到,平时 AI 帮我们运行 Java 代码或者启动 Java 项目,其实都是通过各种各样的命令行命令来运行的)
  • 第二种方法就是通过可视化的界面,我们鼠标直接点击这个按钮就可以直接运行该文件(IDEA 内部经过了编译和运行的步骤)。

跑完后去目录里看一眼 HelloWorld.class------那就是 javac 的编译产物。

二、八大基本数据类型

2.1 为什么类型这么多?

TS 里 let n: number 底层永远是 ​64 位双精度浮点​,不论我们存 1 还是 10 亿。

Java 把数字按占用内存大小拆成多种------后端常处理海量数据,每个字段省几个字节,总量天壤之别。

这里埋一个彩蛋 🥚,后续会有相关面试题专门考察这里的坑。

类型 字节 位数 取值范围 类成员默认值 说明
byte 1 8 -128 ~ 127 0 最小整型,省内存
short 2 16 -32768 ~ 32767 0 短整数,极少用
int 4 32 -2³¹ ~ 2³¹-1 0 日常最常用整数
long 8 64 -2⁶³ ~ 2⁶³-1 0L 超大数字,字面量末位加 L
float 4 32 约 ±3.4E38(精度 6~7 位) 0.0f 单精度,末位加 F
double 8 64 约 ±1.8E308(精度 15~16 位) 0.0d 默认浮点类型
char 2 16 0 ~ 65535(Unicode) \u0000 单字符,可存中文
boolean 1 位 --- true / false FALSE 不能与数字互转

2.2 必须记住的语法细节

  • 整数默认是 int;写 long 字面量末位加 L(如 10000000000L)。
  • 浮点默认是 double;写 float 末位加 F
  • ""字符串'' 是**单字符(char)**------和 JS 里 ''/"" 混用完全不同。
  • **String、数组、类的实例都不是基本类型,而是「引用类型」**:变量里存的是地址,不是值本身(这点后面讲堆/栈会展开)。
Java 复制代码
int a = 10;
long b = 100L;          // 必须加 L
double c = 3.14;        // 默认 double
float d = 3.14F;        // 必须加 F
char e = '中';          // 单引号
boolean f = true;
String s = "hello";     // 引用类型,不是基本类型

三、包(package)与导入(import)

包 = 前端用来划分模块的目录 ​,区别是每个 Java 类都必须用 package 声明自己属于哪个包,且有个统一规范:​基础包名用公司域名倒写​。

Java 复制代码
package com.example.demo;          // 域名倒写:com.example

import java.util.List;            // 引入指定类
import java.util.*;               // 通配符,引入该包下所有类

和前端 import 目的一样------引入别的模块。语法差异:import 包名.类名;

四、类、对象与实体类

4.1 类与实例(面向对象的第一直觉)

  • :和 TS 中的类一个意思,提供属性方法
  • 实例 :通过 new 关键字构建出来的对象,叫实例。

5.1 实体类(和表结构一一对应)

后端最常用的一类类(突然叠词 😂):​实体类(Entity)和数据库表结构一一对应​。一笔订单、一个用户,就是一张表对应一个类。

5.2 构造方法

用来​初始化实例 ​,在 new 时自动调用。分无参构造、有参构造。

5.3 getter / setter(封装的核心)

Java 约定:​**类属性默认 private(私有)**​,再通过 getter/setter 暴露读写。

从这里就可以看出强类型语言和我们 JS 这种弱类型语言的明显差异。

5.4 equals / hashCode / toString

这三个方法都在 Object 基类里,所以​每个 Java 对象都自带​:

  • toString():把对象转成字符串(默认打印出来是一串看不懂的地址,所以要重写)。
  • equals(Object o):判断两个对象「逻辑上是否相等」。
  • hashCode():对象的哈希值,默认按存储地址算。

两个对象 equals 为 true,hashCode 必须相等 ​------所以重写 equals 时必须同时重写 hashCode

TypeScript 复制代码
package com.demo;

public class User {
    private Long id;
    private String name;

    public User() {}                         // 无参构造
    public User(Long id, String name) {     // 有参构造
        this.id = id; this.name = name;
    }

    // getter / setter
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    @Override 
    public boolean equals(Object o) { // @Override 有这个东西就是重写
        if (this == o) return true;
        if (!(o instanceof User)) return false;
        User u = (User) o;
        return id != null && id.equals(u.id);   // 按业务主键判等
    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;  // 与 equals 保持一致
    }

    @Override
    public String toString() {
        return "User{id=" + id + ", name='" + name + "'}";
    }
}

六、继承与 final

  • 继承A extends B,表示 A 继承 B。B 里的 protectedpublic 属性和方法会被 A 继承下来。
  • 不可继承final class 不能被 extends
Java 复制代码
public class Animal {
    protected String name;
    public void eat() { System.out.println(name + " 在吃东西"); }
}

public class Cat extends Animal {       // Cat 继承了 name 和 eat()
    public void meow() { System.out.println("喵"); }
}

public final class Constants {          // 加了 final,谁都不能继承它
    public static final int MAX = 100;
}

七、抽象类、重写、重载、注解

7.1 抽象类(abstract)

abstract 标记类或方法。​抽象方法强制子类必须实现 ​,否则子类也得标成 abstract

7.2 重写(Override)vs 重载(Overload)

重写 Override 重载 Overload
场景 子类覆盖父类方法 同一类里同名不同参
要求 方法签名相同 只认参数列表不同
Java 复制代码
abstract class Shape {
    abstract double area();              // 抽象方法,子类必须实现
}
class Circle extends Shape {
    double r;
    @Override 
    double area() { return Math.PI * r * r; }  // 重写
}

class Calculator {
    int add(int a, int b) { return a + b; }          // 重载 1
    double add(double a, double b) { return a + b; } // 重载 2(参数不同)
    // void add(int a, int b) { }  // ❌ 编译错误:只改返回类型不算重载
}

7.3 注解(Annotation)

标记在类或方法上,对类/方法做描述或处理:

  • @Override:声明这是重写方法(编译器会帮我们校验父类是否真有该方法)。
  • @Deprecated:标记方法/类已过期,不建议使用。

TS 语言对照:这里的注解类似于我们使用 NestJS 中的装饰器。

八、枚举与接口

8.1 枚举(enum)

「变异的类」,用来定义一组固定的复合型常量,比满地 public static final 更优雅、类型更安全。

Java 复制代码
enum OrderStatus {
    PENDING, PAID, CANCELLED, REFUNDED
}
OrderStatus s = OrderStatus.PAID;

8.2 接口(interface)

用来​定义规范/契约 ​,不关心实现。implements 一个接口,就必须实现里面的方法。

Java 复制代码
interface Payable {
    void pay();                  // 抽象方法,默认 public abstract
}
class Order implements Payable {
    @Override
    public void pay() { System.out.println("支付成功"); }
}

思考灌输,超前学习:接口是后端「解耦」的核心武器。Controller 依赖 Service 接口而非具体实现,将来换实现(比如把 MySQL 换成 ES)上层代码一行都不用改。这是 Spring 的依赖倒置(DIP)。

九、常见数据结构

结构 特点 类比
数组 Array 查询快(下标直取),插入慢(要挪位) 一排带编号的储物柜
链表 Linked List 查询慢(挨个走),插入快(改指针) 一节节火车,加车厢只动接头
栈 Stack 后进先出 LIFO 炮弹,后塞的先打
队列 Queue 先进先出 FIFO 子弹夹,先进先出
树 Tree 有父子关系 文件夹/文件、后台系统的菜单

十、集合(Collection / Map)与泛型

​**Java 的集合就是对上面那些数据结构的「封装 + 工程化」**​,我们不用自己手写链表,直接用现成的。

我们终于学到了真正的集合,而不是像在 JS 中那样强行用数组模拟各种复杂数据结构。

10.1 两大体系

Java 复制代码
Collection(存单个元素)
  ├── List   有序可重复   → ArrayList(数组实现,查快)、LinkedList(链表,插快)
  └── Set    无序不重复   → HashSet(哈希实现)
Map(存键值对 key-value)
  └── HashMap(最常用)

10.2 泛型(<>

泛型​限制集合里元素的类型​,编译期就能拦住「往装 String 的 list 里塞 Integer」这种错。

TypeScript 复制代码
List<String> names = new ArrayList<>();      // 只能放 String
names.add("Tom");
// names.add(123);  编译报错,类型不安全被拦下

Map<String, Integer> score = new HashMap<>();
score.put("Tom", 100);

// 泛型也能用在类上
class Box<T> {
    private T value;
    public void set(T v) { this.value = v; }
    public T get() { return value; }
}
Box<Integer> intBox = new Box<>();
intBox.set(10);

前端对照:泛型 ≈ TypeScript 的 <T>。我们写 Array<string>Promise<T> 就是一回事。

十一、字符串拼接与日期

11.1 字符串拼接的坑(性能)

Java 里用 + 拼字符串会​产生临时字符串对象​,大量拼接时开销爆炸。消除方式:

  • StringBuilder非线程安全,拼接与线程无关时用它(绝大多数场景)。
  • StringBuffer线程安全,拼接涉及多线程时用它。
Java 复制代码
// ❌ 一万次循环会产生上万个临时 String
String s = "";
for (int i = 0; i < 10000; i++) s += i;

// ✅ 用 StringBuilder,只产生一个对象
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.append(i);
String result = sb.toString();

11.2 日期

  • Date:日期对象,接收一个日期数据时用它接。
  • Calendar:类似前端的 dayjs,用来做日期计算。

注:Date / Calendar 是​老 API ​,设计有缺陷。工作中我们更常用的是 Java 8 引入的 java.time 包:LocalDate(日期)、LocalDateTime(日期时间)、DateTimeFormatter(格式化)。

Java 复制代码
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

LocalDate today = LocalDate.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String s = today.format(fmt);

十二、异常处理

Java 稳定性高,很大程度来自​完善的异常机制​。

12.1 异常体系

TypeScript 复制代码
Throwable
 ├── Error         (如 OutOfMemoryError,严重错误,不该捕获)
 └── Exception
      ├── RuntimeException        (运行时的异常,不会受到Java编译器的检查,可不处理,如空指针)
      └── 其他 Exception 如 IOException(受到Java编译器的检查,必须 try-catch 或 throws 声明)

12.2 基本写法

Java 复制代码
try {
    int x = 1 / 0;
} catch (ArithmeticException e) {
    e.printStackTrace();          // 生产环境请打日志,别直接 print
} finally {
    System.out.println("无论成败都会执行");  // 常用于关资源
}

12.3 自定义异常

两点就够:① 继承 Exception(受检)或 RuntimeException(非受检);② 构造方法里 super(message)

Java 复制代码
// 业务异常,继承 RuntimeException 不用强制 try-catch
public class BizException extends RuntimeException {
    // 自定义错误码
    private Integer code;
    
    public BizException(Integer code, String message) {
        super(message);          // 把错误信息传给父类
        this.code = code;
    }
    
    public void setCode(Integer code) {
        this.code = code;
    }
    public Integer getCode() {
        return code;
    }
}
// 使用:throw new BizException("余额不足");

真实后端几乎都定义一套业务异常(如 BizException / AuthException),配合全局异常处理器(@ControllerAdvice)统一返回错误码和提示,前端拿到的是结构化 JSON,而不是一堆堆栈。

十三、线程入门

线程 = 一条独立的执行路径。一个程序(进程)里可以跑多个线程「同时」干活。

JS 是单线程语言,而 Java 是多线程语言,这里需要额外的精力去理解使用。

方式一:继承 Thread,重写 run 方法

TypeScript 复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程执行:" + Thread.currentThread().getName());
    }
}
// 启动
new MyThread().start();      // 注意调 start(),不是 run()

方式二:实现 Runnable(可以避免单继承限制)

TypeScript 复制代码
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable 执行");
    }
}
// 启动
new Thread(new MyRunnable()).start();

我们写 JS 用 Promise / async-await 处理异步,那是「单线程事件循环」。Java 是真·多线程并行,能利用多核 CPU------这是后端高并发的根本,但多线程也带来了「并发安全问题」(下一节)。

十四、并发问题与解决方案

14.1 问题:竞态条件

多个线程同时改同一个共享变量,执行顺序不确定,结果就错。

Java 复制代码
class Counter {
    private int count = 0;
    public void inc() { count++; }   // 看似一行,实际是 读-改-写 三步,在多线程会丢更新
}

14.2 解决方案(从浅到深)

方案 说明 适用
synchronized JVM 内置锁,同一时刻只一个线程进临界区 简单同步
ReentrantLock 显示锁,功能更丰富(可超时、可公平) 复杂控制
AtomicInteger 等 基于 CAS 无锁,性能更高 计数器类简单场景
volatile 保证可见性(一改全见),不保证原子性 状态标志位
Java 复制代码
class SafeCounter {
    private int count = 0;
    public synchronized void inc() { count++; }   // 加锁即可修复
}

真实后端​不会手搓 synchronized ​。我们会用:① 线程池 ExecutorService 管理线程(而不是 new Thread);② 并发集合 ConcurrentHashMap;③ 各种锁和原子类。

并发是后端最难的深水区之一,这一节先建立「共享变量 + 多线程 = 要加锁」的直觉即可。

这里解决竞态条件的锁和前端解决竞态条件的状态锁类似,本质都是对业务原子化。

十五、IO 流与反射

15.1 流(Stream)

从方向分​输入流 / 输出流 ​,从数据分​字节流 / 字符流​:

维度 含义
输入流 以程序为准,把外部(如文件)读进程序
输出流 以程序为准,把程序数据写到外部(如生成新文件)
字节流 最小存储单位,音频/视频/图片等二进制都用它
字符流 按「字符」处理,是对字节流的封装,纯文本(txt)用它
Java 复制代码
// 读文本文件(字符流 + 缓冲,自动关资源 try-with-resources)
try (BufferedReader br = new BufferedReader(new FileReader("a.txt"))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
}

// 写文本文件
try (BufferedWriter bw = new BufferedWriter(new FileWriter("b.txt"))) {
    bw.write("你好");
}

15.2 反射(Reflection)

Java 提供的一种在运行时动态创建实例、调用方法的机制。

Java 复制代码
// 1. 根据类名拿到 Class 对象
Class<?> clazz = Class.forName("com.demo.User");
// 2. 动态创建实例(调无参构造)
Object obj = clazz.getDeclaredConstructor().newInstance();
// 3. 拿到方法并调用
Method m = clazz.getMethod("setName", String.class);
m.invoke(obj, "Tom");
System.out.println(obj);

反射是 Spring 的地基。Spring 启动时干的事就是------扫描我们打了 @Component / @Service 注解的类,用反射 new 出实例,塞进自己的容器(IOC 容器)统一管理。所以我们现在理解的「注解 + 反射 + 容器」,就是后面学 Spring 要捅破的那层窗户纸。

相关推荐
前端炒粉1 小时前
DOM 常用 API 速查表
开发语言·javascript·ecmascript
lemon_sjdk1 小时前
javafx-Image图像(2)
java·javafx
小七在进步2 小时前
C++入门(2)
java·jvm·c++
To0R2 小时前
Agent 上线前必须过的五道门(附 Spring Boot 实现)
java
vipxieliang2 小时前
固定电话验证详解:区号、号码、分机号的完整验证
java·spring boot
步行cgn2 小时前
Spring Boot 主入口类上的 @Enable 和 @Scan 注解详解
java·spring boot·后端
lhldsg2 小时前
全民健身解决方案软件开发实战:从架构设计到部署指南
java·前端·数据库·小程序
两点王爷2 小时前
Java 与前端加载 MVT 数据:从服务端切片到浏览器渲染
java·前端·状态模式
en.en..2 小时前
C语言核心解析:#define与typedef本质区别
开发语言·c++·算法