前端转战后端:JavaScript 与 Java 对照学习指南(第五篇 —— 面向对象:类、接口与多态)

前言

在前几篇中,我们从 int/String 聊到了 List/Map,解决了数据的"存储"问题。现在,我们要解决代码的"组织"问题。

对于前端开发者,class 并不陌生。但 JavaScript 的类更像是一个灵活的模具 (基于原型,随时可改),而 Java 的类则是一张严格的工程蓝图(编译期确定,不可篡改)。在后端开发中,类不仅仅是数据的集合,更是业务逻辑的载体、安全控制的关卡以及系统解耦的钥匙。

本篇将深入剖析 Java 面向对象(OOP)的核心,带你跨越从"写脚本"到"架构系统"的鸿沟。


1. 类的本质:动态 vs 静态

1.1 字段(Field)的严格定义

在 JS 中,我们习惯在 constructor 里直接挂载属性,甚至在对象实例化后随手添加属性。但在 Java 中,这是绝对禁止的。

JavaScript (自由的灵魂):

JavaScript 复制代码
class User {
  constructor(name) {
    this.name = name; // 直接定义
  }
}
const u = new User('F2B');
u.age = 18; // 随时添加新属性,JS 觉得没问题

Java (严谨的蓝图):

Java 必须在类级别显式声明所有字段。这不仅是为了类型安全,更是为了内存布局的确定性。

Java 复制代码
public class User {
    // 1. 必须先声明,才能使用
    private String name;
    private Integer age;

    public User(String name) {
        this.name = name;
        // this.gender = "Male"; // ❌ 报错!类定义中没有 gender 字段
    }
    
    // ... getter and setter
}

1.2 方法重载 (Overloading) ------ JS 没有的特性

这是前端同学最容易忽略的特性。在 JS 中,如果定义两个同名函数,后面的会覆盖前面的。但在 Java 中,只要参数列表不同(类型或数量不同),方法名可以相同

这在后端非常常用,用于提供"默认参数"的效果(Java 不支持 func(a=1) 这种默认值语法,通常用重载实现)。

Java 复制代码
public class SearchService {
    
    // 场景1:只按名字搜
    public List<User> search(String name) {
        return search(name, 18); // 转发调用给全量方法,默认 age 18
    }

    // 场景2:按名字和年龄搜
    public List<User> search(String name, int minAge) {
        // 执行具体的数据库查询逻辑
        return db.find(name, minAge);
    }
}

核心差异: Java 是根据参数签名 来区分方法的,而 JS 仅根据函数名


2. 封装:不仅仅是 private

在后端开发中,封装(Encapsulation)不是为了"隐藏",而是为了保护数据的一致性

2.1 为什么要写烦人的 Getter/Setter?

前端同学常问:"为什么不直接把变量设为 public?"

看这个例子:

Java 复制代码
public class BankAccount {
    private double balance; // 余额私有化,禁止外部直接修改

    // 只提供存款方法,可以在这里加逻辑
    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("存款金额必须大于0");
        }
        this.balance += amount;
    }

    // 只提供读取,不提供 setBalance,防止外部随意篡改余额
    public double getBalance() {
        return balance;
    }
}

如果 balance 是 public 的,任何代码都能写 account.balance = -9999,这将导致严重的业务事故。Java 的封装强制你通过方法来操作数据,从而在方法里构筑防线。

2.2 开发神器:Lombok

为了解决 Java"样板代码"过多的问题(写一堆 getter/setter 手很累),后端开发标配插件 Lombok

实际开发中的写法:

Java 复制代码
import lombok.Data;

@Data // 一个注解,编译时自动生成 get、set、toString、equals、hashCode
public class UserDTO {
    private String username;
    private String password;
    private Integer age;
}

注:转战后端,请务必尽早学会配置 Lombok,它能让你的 Java 代码像 JS 一样简洁。


3. 继承的高级形态:抽象类 (Abstract Class)

JS 的 extends 大家都懂,但 Java 有一个中间态------抽象类。

它位于"普通类"和"接口"之间。它不能被实例化,可以包含具体实现,也可以包含强制子类实现的"抽象方法"。

场景:电商支付系统

不管是支付宝、微信还是银联,支付流程(校验 -> 扣款 -> 记录日志)是相似的,只有"扣款"动作不同。

Java 复制代码
// 抽象父类:定义模板
public abstract class BasePayment {
    
    // 1. 公共逻辑:所有支付方式都一样的校验逻辑
    public boolean validate(double amount) {
        return amount > 0;
    }

    // 2. 抽象方法:留给子类必须去实现的(JS没有这个强制约束)
    protected abstract void doDeduct(double amount);

    // 3. 核心流程(模板方法模式)
    public void pay(double amount) {
        if (validate(amount)) {
            doDeduct(amount); // 调用子类的具体实现
            System.out.println("支付日志已记录");
        }
    }
}

// 具体子类
public class AliPay extends BasePayment {
    @Override
    protected void doDeduct(double amount) {
        System.out.println("调用支付宝API扣款: " + amount);
    }
}

后端思维: 抽象类用于复用代码。如果你发现几个类有大量重复逻辑,就应该提取一个抽象父类。


4. 接口 (Interface):后端的"契约精神"

这是 Java 最重要的概念,也是 TypeScript 极力模仿的对象。

在 Java 后端(特别是 Spring 框架)中,我们遵循**"面向接口编程"**。

4.1 接口 vs 抽象类

  • 抽象类:是 "is-a" 关系(它是某种东西)。如:猫是动物。
  • 接口:是 "can-do" 关系(它具备某种能力)。如:猫能跑(Runnable),车也能跑(Runnable)。

4.2 实际业务场景:多态与解耦

假设你需要把用户信息存起来,现在用 MySQL,以后可能换 Redis。

定义接口(契约):

Java 复制代码
public interface UserRepository {
    void save(User user);
    User findById(String id);
}

实现 A(MySQL版本):

Java 复制代码
public class MySQLUserRepository implements UserRepository {
    public void save(User user) {
        System.out.println("写入 MySQL 表...");
    }
    // ...
}

业务层调用(关键点):

Java 复制代码
public class UserService {
    // 这里声明的是接口类型,而不是具体的 MySQLUserRepository
    private UserRepository userRepo; 

    // 构造函数注入实现类
    public UserService(UserRepository repo) {
        this.userRepo = repo;
    }

    public void register(User user) {
        // 业务层完全不知道底层是 MySQL 还是 Redis,只知道 userRepo 能 save
        userRepo.save(user); 
    }
}

好处: 如果明天老板说换 MongoDB,你只需要写一个 MongoUserRepository 实现接口,然后在启动配置里改一下注入,UserService 的代码一行都不用动 。这就是解耦


5. 静态(Static):属于类的领地

在 JS 中,函数是一等公民,我们可以直接 export 一个函数。

但在 Java 中,一切皆对象。如果你想提供一个"工具函数"(不依赖具体对象状态),必须使用 static。

Java 复制代码
public class DateUtils {
    // 静态常量
    public static final String FORMAT = "yyyy-MM-dd";

    // 静态方法:通过 DateUtils.now() 直接调用,不需要 new DateUtils()
    public static String now() {
        return LocalDate.now().toString();
    }
}

内存区别:

  • 实例变量(非Static): new 一个对象,堆内存就分配一份。100个对象有100份 name
  • 静态变量(Static): 全局只有一份,属于类,所有对象共享。慎用可变静态变量,会引发线程安全问题!

6. 总结与对照表

特性 JavaScript (ES6+) Java 后端应用场景
属性定义 动态,构造函数中赋值 静态,类体内显式声明 DTO、Entity 数据模型
方法重载 不支持 (后覆盖前) 支持 (看参数签名) 提供多种参数的查询接口
访问修饰 #私有 (新), _约定 private/protected/public 保护核心业务数据不被污染
抽象类 无 (普通类模拟) abstract class 提取公共业务逻辑 (模板模式)
接口 无 (TS 有 interface) interface 核心! Service层解耦,依赖倒置
多态 鸭子类型 (也就是动态类型) 严格基于继承/接口 插件化架构,策略模式
相关推荐
用户15630681035127 分钟前
Day01 | Java 基础(Java SE)
java
Pedantic28 分钟前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘44 分钟前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆1 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师2 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
行者全栈架构师2 小时前
Maven dependency:tree 的 8 个高级用法
java·后端
雨季mo浅忆2 小时前
VSCode自动格式化三要素
前端
Chenyiax2 小时前
从一次请求看懂 OkHttp:架构、调度与连接管理
后端
爱勇宝3 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员