2. Java 基础该怎么学,先抓业务开发真正用得上的部分

摘要:前端转 Java 后端时,最容易陷入一个误区:从变量、循环、数组、继承、多态一路背到 JVM,把 Java 基础学成一份语法清单。我的阶段目标不是成为 Java 语言专家,而是先能看懂业务代码、写出稳定接口、理解项目里的类、接口、集合、异常、泛型、注解和 Stream。本文记录一下我准备怎么学 Java 基础,以及哪些 Java 特性和真实业务开发最相关。

Start

上一篇记录了我为什么想从前端往后端走,以及大概的学习路线。

这篇就从第一阶段开始:Java 基础。

一开始看 Java 基础资料,很容易被内容量劝退。变量、数据类型、运算符、流程控制、数组、字符串、面向对象、接口、抽象类、集合、泛型、异常、IO、线程、JVM、反射、注解、Stream、枚举、日期时间......每个点都能展开一堆。

但如果目标是"前端转 Java 后端",我觉得第一阶段不应该把 Java 基础学成百科全书。

更现实的目标应该是:

  • 能看懂公司后端项目里的业务代码
  • 能理解 Controller、Service、Mapper 里为什么这样写
  • 能写出类型清晰、异常明确、结构稳定的接口代码
  • 能知道哪些 Java 特性是业务开发中高频出现的
  • 能先跑通一个完整 CRUD,而不是在语法细节里耗太久

所以这篇不会堆一份 Java 基础知识清单,而是按业务开发视角,整理我认为最先要掌握的 Java 基础。

一、Java 基础不是从语法开始,而是从类型意识开始

前端写 JavaScript 久了,最明显的习惯是:很多事情可以先跑起来再说。

js 复制代码
const user = {
  id: 1,
  name: "Tom"
};

console.log(user.age);

这段代码不会在写的时候拦住你,最多运行时发现 ageundefined

但 Java 不一样。

java 复制代码
public class User {
    private Long id;
    private String name;
}

在 Java 里,一个对象有哪些字段、字段是什么类型、方法返回什么、参数传什么,都会被提前写清楚。

这就是 Java 对前端思维的第一处改造:你要更早地把数据边界想明白。

后端业务代码里,类型不是形式主义。它会影响:

  • 接口参数能不能被正确接收
  • 数据库字段能不能正确映射
  • 返回给前端的数据结构是否稳定
  • 空值会不会引发运行时异常
  • 业务状态是否可以被明确表达

比如用户状态,如果前端随手写字符串:

js 复制代码
status: "enabled"

后面可能又出现:

js 复制代码
status: "ENABLE"
status: "enable"
status: 1

Java 里更推荐用明确的类型收住它:

java 复制代码
public enum UserStatus {
    ENABLED,
    DISABLED
}

这不是为了写得复杂,而是为了减少"大家各写各的"。

二、类和对象:业务模型的第一层表达

前端也有 class,但在很多前端业务里,我们更常用对象字面量、接口类型、函数组合。

Java 后端不一样,类是非常核心的组织单位。

比如一个用户:

java 复制代码
public class User {
    private Long id;
    private String username;
    private String email;
    private UserStatus status;
}

它不只是一个数据袋子。放到后端项目里,它可能会对应数据库表,也可能会作为接口返回值,也可能只是业务逻辑中的一个中间对象。

真实项目里经常会看到不同类型的类:

text 复制代码
Entity:数据库实体,通常和表结构接近
DTO:请求参数,前端传给后端
VO:响应对象,后端返回给前端
Service:业务服务
Controller:接口入口
Mapper / Repository:数据访问

刚开始学 Java 类的时候,不要只停留在"类有属性和方法"。

更重要的是理解:一个类在业务项目里承担什么角色。

比如创建用户时,前端传来的参数可能是:

java 复制代码
public class CreateUserDTO {
    private String username;
    private String password;
    private String email;
}

数据库里的用户实体可能是:

java 复制代码
public class User {
    private Long id;
    private String username;
    private String passwordHash;
    private String email;
    private UserStatus status;
    private LocalDateTime createdAt;
}

返回给前端时,又不应该把密码相关字段返回:

java 复制代码
public class UserVO {
    private Long id;
    private String username;
    private String email;
    private UserStatus status;
}

这就是后端里很常见的对象拆分。

前端可能习惯一个 User 类型到处用,但后端更强调边界:请求对象、数据库对象、响应对象不一定是同一个东西。

三、封装:不是 private 加 getter/setter 这么简单

学 Java 基础时,封装通常会被解释成:

java 复制代码
private String name;

public String getName() {
    return name;
}

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

这当然是封装的一部分,但业务里更重要的是:不要让外部随便破坏对象的状态。

比如订单状态:

java 复制代码
public class Order {
    private OrderStatus status;

    public void pay() {
        if (status != OrderStatus.CREATED) {
            throw new IllegalStateException("只有待支付订单可以支付");
        }
        this.status = OrderStatus.PAID;
    }
}

这里的 pay() 比一个简单的 setStatus() 更有业务意义。

如果到处都能这样写:

java 复制代码
order.setStatus(OrderStatus.PAID);

那谁都可以绕过业务规则直接改状态。

所以封装不是"把字段 private 掉再暴露 setter",而是把业务规则收进对象或服务里,让状态变化有入口、有校验、有语义。

这点对前端转后端很重要。后端系统最终要守住规则,而不是只把数据改成功。

四、接口:业务能力的抽象

前面聊过 extendsimplements 的区别。真正写后端时,接口非常常见。

比如支付:

java 复制代码
public interface PaymentService {
    void pay(Long orderId);
}

支付宝实现:

java 复制代码
public class AlipayPaymentService implements PaymentService {
    @Override
    public void pay(Long orderId) {
        // 调用支付宝支付
    }
}

微信实现:

java 复制代码
public class WechatPaymentService implements PaymentService {
    @Override
    public void pay(Long orderId) {
        // 调用微信支付
    }
}

接口的价值不是让代码多一层,而是让调用方依赖"能力",不是依赖某个具体类。

业务代码只关心:

text 复制代码
我需要一个能支付的东西

而不是:

text 复制代码
我必须绑定支付宝支付这个类

在 Spring 项目里,你会经常看到:

java 复制代码
private final UserService userService;

而不是:

java 复制代码
private final UserServiceImpl userService;

这就是接口思维:面向能力编程,降低调用方和实现方的耦合。

五、集合框架:业务代码每天都在用

Java 集合是必须优先掌握的部分,因为业务开发几乎每天都在处理列表、映射、去重、分组。

最常见的几个:

text 复制代码
List:有序列表,类似数组
Set:不重复集合
Map:键值映射,类似对象 / 字典

比如查询用户列表:

java 复制代码
List<User> users = userMapper.selectList();

根据用户 ID 快速查用户:

java 复制代码
Map<Long, User> userMap = users.stream()
        .collect(Collectors.toMap(User::getId, user -> user));

去重:

java 复制代码
Set<Long> userIds = new HashSet<>();

前端也会处理数组,但 Java 集合的类型更明确。

java 复制代码
List<UserVO>

这表示列表里只能放 UserVO

这种约束在后端很有价值,因为接口返回、数据库查询、中间转换都依赖稳定结构。

学集合时,我不会一上来死记所有实现类,而是先掌握几个业务里高频的选择:

  • ArrayList:最常用的列表
  • HashSet:常用去重
  • HashMap:常用键值映射
  • LinkedHashMap:需要保留插入顺序时使用
  • Collections / List.of:常用工具方法

等业务写多了,再补并发集合、队列、树结构这些。

六、泛型:让类型约束贯穿业务链路

泛型对前端同学不陌生,TypeScript 里也有:

ts 复制代码
type ApiResponse<T> = {
  code: number;
  message: string;
  data: T;
};

Java 里也会这么写:

java 复制代码
public class ApiResponse<T> {
    private Integer code;
    private String message;
    private T data;
}

这在后端接口里非常常见。

用户详情接口可以返回:

java 复制代码
ApiResponse<UserVO>

用户列表接口可以返回:

java 复制代码
ApiResponse<List<UserVO>>

分页接口可以返回:

java 复制代码
ApiResponse<PageResult<UserVO>>

泛型的价值是:一套结构可以复用,但里面的数据类型仍然明确。

这比到处写 Object 稳很多。

java 复制代码
private Object data;

如果什么都用 Object,编译器帮不上忙,调用方也不知道里面到底是什么。

所以泛型不是语法炫技,而是业务代码里非常实用的类型工具。

七、异常:后端不能只写正常流程

前端写业务时,也会处理异常,比如接口失败弹 toast、表单校验失败展示错误信息。

但后端的异常更靠近系统边界。

一个接口失败,可能是:

  • 参数错误
  • 用户未登录
  • 用户无权限
  • 数据不存在
  • 当前状态不允许操作
  • 数据库异常
  • 第三方服务失败

如果后端随便抛异常,前端就只能拿到一个模糊的 500。

更好的方式是定义业务异常:

java 复制代码
public class BusinessException extends RuntimeException {
    private final String code;

    public BusinessException(String code, String message) {
        super(message);
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

业务里明确抛出:

java 复制代码
if (user == null) {
    throw new BusinessException("USER_NOT_FOUND", "用户不存在");
}

再通过统一异常处理返回给前端。

学 Java 异常时,我觉得重点不是背 checked exceptionunchecked exception 的所有区别,而是先理解:

  • 哪些错误是业务可预期的
  • 哪些错误应该转换成明确错误码
  • 哪些错误应该记录日志
  • 哪些错误不能直接暴露内部细节

后端代码不能只写 happy path。

异常处理,是业务稳定性的一部分。

八、枚举:让业务状态更可控

枚举是 Java 里非常适合表达业务状态的特性。

比如订单状态:

java 复制代码
public enum OrderStatus {
    CREATED,
    PAID,
    SHIPPED,
    FINISHED,
    CANCELLED
}

比起到处写字符串:

java 复制代码
"created"
"paid"
"cancelled"

枚举更稳定,也更容易被 IDE 和编译器检查。

实际项目里,枚举还可以带字段:

java 复制代码
public enum OrderStatus {
    CREATED("待支付"),
    PAID("已支付"),
    CANCELLED("已取消");

    private final String description;

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

    public String getDescription() {
        return description;
    }
}

它适合表达那些固定范围的业务值:

  • 用户状态
  • 订单状态
  • 支付渠道
  • 审批状态
  • 消息类型
  • 任务执行状态

对前端来说,枚举也能减少联调时的口径不一致。

状态有哪些、每个状态叫什么、什么时候能流转,最好从后端模型里就清楚。

九、Lambda 和 Stream:业务数据转换高频出现

Java 8 之后,Lambda 和 Stream 是非常著名、也非常常用的特性。

前端同学可以把 Stream 先粗略理解成 Java 里的链式数组处理。

JavaScript:

js 复制代码
const names = users
  .filter(user => user.status === "ENABLED")
  .map(user => user.name);

Java:

java 复制代码
List<String> names = users.stream()
        .filter(user -> user.getStatus() == UserStatus.ENABLED)
        .map(User::getName)
        .toList();

业务里常见场景很多:

筛选:

java 复制代码
List<User> enabledUsers = users.stream()
        .filter(user -> user.getStatus() == UserStatus.ENABLED)
        .toList();

转换:

java 复制代码
List<UserVO> userVOList = users.stream()
        .map(this::toUserVO)
        .toList();

提取 ID:

java 复制代码
List<Long> userIds = users.stream()
        .map(User::getId)
        .toList();

转 Map:

java 复制代码
Map<Long, User> userMap = users.stream()
        .collect(Collectors.toMap(User::getId, user -> user));

分组:

java 复制代码
Map<UserStatus, List<User>> groupedUsers = users.stream()
        .collect(Collectors.groupingBy(User::getStatus));

Stream 很好用,但也不要滥用。

如果业务逻辑很复杂,强行写成一长串链式调用,反而不如普通循环清楚。

我的原则是:简单筛选、转换、分组可以用 Stream;复杂业务判断就老老实实拆方法。

十、注解:Java 后端项目里无处不在

学 Spring Boot 时,注解是绕不开的。

比如:

java 复制代码
@RestController
@RequestMapping("/users")
public class UserController {
}
java 复制代码
@Service
public class UserServiceImpl implements UserService {
}
java 复制代码
@Autowired
private UserService userService;

或者构造器注入:

java 复制代码
private final UserService userService;

public UserController(UserService userService) {
    this.userService = userService;
}

注解的作用可以先粗略理解成:给类、方法、字段加元信息,让框架知道它应该怎么处理。

比如:

  • @RestController:这是一个接口控制器
  • @RequestMapping:接口路径
  • @Service:这是一个业务服务
  • @Transactional:这个方法需要事务
  • @Valid:需要参数校验
  • @Override:重写父类或接口方法

注解是 Java 后端的一个著名特性,尤其在 Spring 体系里非常关键。

但入门时不要只背注解名字,要理解它背后的含义:

text 复制代码
框架为什么能找到这个类?
为什么请求能进入这个方法?
为什么事务能自动生效?
为什么参数校验能自动触发?

这些问题后面会牵涉到反射、代理、IoC、AOP。

第一阶段不需要全吃透,但至少要知道:注解不是魔法,它是框架读取元信息后做了额外处理。

十一、反射:先知道它为什么重要

反射这个词一开始会有点抽象。

简单说,反射就是程序在运行时也能知道一个类有哪些字段、方法、注解,并且可以动态操作它们。

很多框架能力都离不开反射。

比如 Spring 能扫描到:

java 复制代码
@Service
public class UserServiceImpl {
}

然后创建对象、管理对象、注入依赖。

再比如 JSON 转对象:

json 复制代码
{
  "username": "Tom",
  "email": "tom@example.com"
}

框架可以把它转换成:

java 复制代码
public class CreateUserDTO {
    private String username;
    private String email;
}

这里背后也会用到类似反射的能力。

第一阶段我不会深挖反射 API,但会先建立一个认知:

text 复制代码
Java 很多框架看起来自动完成的事情,背后都和注解、反射、代理有关。

这个认知对后面理解 Spring Boot 很重要。

十二、日期时间:业务里很容易出问题

日期时间看起来是小知识,但业务里非常高频。

用户注册时间、订单创建时间、支付时间、过期时间、统计时间范围、定时任务,都绕不开时间。

Java 里现在更推荐使用:

java 复制代码
LocalDate
LocalTime
LocalDateTime
Instant
Duration

比如:

java 复制代码
LocalDateTime now = LocalDateTime.now();

订单 30 分钟后过期:

java 复制代码
LocalDateTime expireAt = now.plusMinutes(30);

判断是否过期:

java 复制代码
if (LocalDateTime.now().isAfter(expireAt)) {
    throw new BusinessException("ORDER_EXPIRED", "订单已过期");
}

前端转后端时,要尤其注意:

  • 时间格式怎么传
  • 时区怎么处理
  • 数据库存什么时间
  • 返回给前端是否需要格式化
  • 查询时间范围时边界是否包含

很多线上问题都不是算法问题,而是时间边界没处理好。

十三、JVM 和 GC:第一阶段先了解,不急着深挖

Java 的另一个著名特性是运行在 JVM 上。

这也是 Java 和 JavaScript 很不一样的地方。Java 代码会编译成字节码,在 JVM 上运行。JVM 负责内存管理、类加载、垃圾回收等工作。

第一阶段我不会一上来深挖 JVM 参数、内存模型、GC 算法。

但至少要知道几个基础概念:

  • Java 不是直接运行源码,而是运行编译后的字节码
  • 对象创建后会占用内存
  • 不再使用的对象会被 GC 回收
  • 内存泄漏在 Java 里也会发生
  • 线上服务的性能问题可能和内存、线程、GC 有关

为什么要先知道这些?

因为以后看到下面这些问题时,不至于完全没概念:

  • 服务为什么突然变慢
  • 为什么日志里出现 OutOfMemoryError
  • 为什么频繁创建大对象会有风险
  • 为什么连接、流、线程池要正确关闭

JVM 是进阶重点,但不是第一阶段的主战场。

第一阶段先做到:知道它重要,知道问题大概往哪里查。

十四、我准备怎么安排 Java 基础

为了避免学散,我会按业务相关程度排序。

第一优先级:

  • 基本类型和引用类型
  • 类和对象
  • 封装
  • 接口
  • 集合框架
  • 泛型
  • 异常处理
  • 枚举
  • Lambda 和 Stream
  • 日期时间

第二优先级:

  • 注解
  • 反射基础概念
  • IO 基础
  • JVM 基础概念
  • 常用工具类

暂时不急着深挖:

  • 多线程高级细节
  • JVM 调优
  • 复杂设计模式
  • NIO 底层实现
  • 并发容器源码
  • 类加载机制细节

不是它们不重要,而是对"先写出业务接口"这个阶段来说,优先级可以后移。

十五、学习 Java 基础的正确打开方式

我现在更倾向于用业务场景驱动 Java 基础。

比如不要单独背枚举,而是用订单状态学枚举。

不要单独背泛型,而是用统一接口返回学泛型。

不要单独背异常,而是用"用户不存在""订单已支付不能取消"这种业务错误学异常。

不要单独背接口,而是用支付、通知、文件存储这种可替换实现学接口。

这样学出来的 Java 基础不会悬空。

因为后端代码最终不是写给考试看的,而是写给业务系统跑的。

结语

前端转 Java 后端,第一阶段学基础时最重要的不是"我看完了多少语法",而是"我能不能用 Java 的方式表达业务"。

Java 的强类型、面向对象、接口、泛型、异常、集合、枚举、注解、Stream,这些特性并不是孤立知识点。它们最终都会落到真实业务里:

  • 用类型稳定接口结构
  • 用类表达业务对象
  • 用接口抽象可替换能力
  • 用集合处理批量数据
  • 用泛型复用通用结构
  • 用异常表达失败原因
  • 用枚举收住业务状态
  • 用注解接入框架能力
  • 用 Stream 做清晰的数据转换

所以这一步我不会把 Java 基础学成语法词典,而是先抓业务开发最常见、最有价值的部分。

下一步,就是基于这些基础去跑一个 Spring Boot 项目,把一个请求从 Controller 打到 Service,再从数据库查出来,最后返回给前端。

先把链路跑通,很多概念才会真正长到脑子里。

相关推荐
AI老猿博士44 分钟前
SpringBoot自动配置揭秘
后端
程序员爱钓鱼1 小时前
Go for 循环详解
后端·面试·go
程序员爱钓鱼1 小时前
Rust impl详解:为Struct定义方法与关联函数
前端·后端·rust
weixin_431600441 小时前
NestJS 入门(9):连上数据库,SQL 写在哪?
数据库·后端·sql·学习·nest.js
qq_22589174661 小时前
基于Flask的城市地铁客流量数据预测系统设计与实现
后端·python·flask
IT_陈寒1 小时前
搞不定JavaScript的数组去重?你可能漏了这两个坑
前端·人工智能·后端
AINative软件工程2 小时前
AI Agent 证据仲裁工程实践:别让检索结果按自信程度撒谎
人工智能·后端·ai编程
卷无止境2 小时前
FastAPI 权限管理实战:从 ACL 到 RBAC 的那些门道
后端·python·fastapi
GreenTea10 小时前
深度解读 Anthropic 多智能体报告:更强的模型 ≠ 更好的协调
前端·后端·算法