Skip to content
YK Blog
Go back

Java 转 Python(一):语言特性

本篇用代码对比讲清六个核心差异。


1. 基础语法

Python 不需要声明类型,直接赋值即创建变量。

# Python — 直接赋值
now = time.time()
name = "CES"
score = 95

// Java — 必须声明类型
double now = System.currentTimeMillis();
String name = "CES";
int score = 95;

类型注解:可选 vs 必须

# Python — 两种都合法
def greet(name: str) -> str:
    return f"Hello {name}"

def greet(name):                # 无注解也能跑
    return f"Hello {name}"

// Java — 类型必须写
public String greet(String name) {
    return "Hello " + name;
}

FastAPI 特例:路由参数的类型注解不只是文档,FastAPI 用它做参数验证和 OpenAPI 生成。

命名规范

概念JavaPython
私有private 关键字_ 前缀(约定,非强制)
常量static final全大写 MAX_SIZE = 100
类名PascalCasePascalCase(相同)
方法名camelCasesnake_case
包/模块com.example.pkgapp.services.xxx

字符串格式化

# Python f-string
name, score = "CES", 95
msg = f"展会 {name} 评分 {score} 分"

// Java
String msg = String.format("展会 %s 评分 %d 分", name, score);
// Java 21+
String msg = STR."展会 \{name} 评分 \{score} 分";

2. 数据结构

list = ArrayList

# 创建
items = [1, 2, 3]                    # Java: List.of(1, 2, 3)
items = []                           # Java: new ArrayList<>()

# 增删查
items.append(4)                      # Java: items.add(4)
items.pop()                          # 删最后一个
items[0]                             # Java: items.get(0)
items[-1]                            # 最后一个!Java 没有
3 in items                           # Java: items.contains(3)
len(items)                           # Java: items.size()

# 切片(Python 独有)
items[1:3]                           # 取索引 1~2
items[:3]                            # 前 3 个
items[-3:]                           # 最后 3 个
items[::-1]                          # 反转

dict = HashMap

# 创建
d = {"name": "CES", "score": 95}    # Java: Map.of(...)

# 增改查
d["location"] = "上海"               # Java: d.put("location", "上海")
d["name"]                            # KeyError if missing!
d.get("name")                        # None if missing
d.get("name", "默认值")              # Java: d.getOrDefault(...)
"name" in d                          # Java: d.containsKey("name")

# 遍历
for key, value in d.items():         # Java: for (Map.Entry e : d.entrySet())

列表推导式

# 基本形式
names = [e.name for e in exhibitions]

# 带条件
high = [e for e in exhibitions if e.score > 80]

# dict 推导式
pairs = {e.id: e.name for e in exhibitions}

// Java Stream 等价
exhibitions.stream()
    .filter(e -> e.getScore() > 80)
    .collect(Collectors.toList());

** 解包

data = {"name": "张三", "age": 25}

# 以下两行完全等价
user = CreateUser(**data)
user = CreateUser(name="张三", age=25)

# 项目实际:数据库行 → 对象
rows = await cur.fetchall()
return [Exhibition(**r) for r in rows]

** 把 dict 展开成关键字参数,Java 没有等价语法。

tuple 多返回值

# Python 函数可以返回多个值
def find_paginated(...) -> tuple[list[Exhibition], int]:
    return exhibitions, total

# 调用方拆包
exhibitions, total = await repo.find_paginated(page=1)

// Java 做不到,需要包装类
// public class PageResult<T> { List<T> items; int total; }

3. 装饰器 vs 注解

外观像,本质完全不同:Java 注解是元数据标签,Python 装饰器是函数调用。

// Java 注解 — 只是标签,Spring 扫描后才生效
@Transactional
@RequestMapping("/api/users")
public class UserService { ... }

# Python 装饰器 — 就是函数调用,定义时立即执行
@router.get("/api/users")
@staticmethod
async def get_users(): ...
Java 注解Python 装饰器
本质元数据(贴标签)函数(包装函数)
何时生效运行时靠框架扫描定义时立即执行
需要框架必须(Spring 等)不需要,纯 Python 语法
能修改行为不能,只提供信息能,直接包裹/替换函数

装饰器 = 套娃

@router.get("/health")
async def health():
    return {"status": "ok"}

# 等价于:
async def health():
    return {"status": "ok"}
health = router.get("/health")(health)  # 装饰器就是函数调用!

项目中常见的 4 种

@router.get("/health")           # 1. 路由注册
async def health(): ...

class ArticleService:
    @staticmethod                # 2. 静态方法,不需要 self
    def _to_item(art): ...

@asynccontextmanager             # 3. 上下文管理器(事务)
async def session_scope():
    async with async_session_factory() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise

class User:
    @property                    # 4. 方法伪装成属性
    def full_name(self):
        return f"{self.first} {self.last}"
# user.full_name  不用加括号,Java 要 getFullName()

自己写一个(= Java AOP @Around)

# Python — 5 行搞定
import functools

def log_method(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__}")
        result = await func(*args, **kwargs)
        print(f"{func.__name__} 完成")
        return result
    return wrapper

@log_method
async def analyze(article_id): ...

// Java 同样效果需要:
// 1. 定义 @LogMethod 注解
// 2. 写 @Aspect 切面类
// 3. 配置 AOP 扫描
// 4. 确保 Bean 被 Spring 管理

面试一句话:Java 注解是元数据,本身不执行逻辑,需要框架在运行时读取并处理。Python 装饰器是函数,定义时就直接包裹原函数,不依赖任何框架。


4. 生成器 / yield

return 一次性返回全部数据;yield 逐个产出,调用方按需消费,内存友好。

# return — 一次性返回
def get_all():
    return db.query("SELECT * FROM articles")  # 全部加载到内存

# yield — 逐个产出,等价 Iterator 语法糖
def get_all():
    for row in db.stream("SELECT * FROM articles"):
        yield Article(**row)

for i in countdown(5): print(i)   # for 循环自动调用 __next__()

// Java 需要实现 Iterator 接口,hasNext() + next()

# 生成器表达式 — (x**2 for x in range(1_000_000)) 惰性求值
# 列表推导     — [x**2 for x in range(1_000_000)] 立即占内存

实际场景:SSE 推送

PayReach 抓取进度用 SSE 长连接,生成器逐条推送事件:

@router.get("/crawl/events")
async def crawl_events():
    q = crawler.subscribe()

    async def event_stream():
        try:
            yield f"event: state\ndata: {json.dumps(crawler.crawl_state)}\n\n"
            while True:
                try:
                    msg = await asyncio.wait_for(q.get(), timeout=30)
                    yield f"event: {msg['event']}\ndata: {json.dumps(msg)}\n\n"
                except asyncio.TimeoutError:
                    yield ": keepalive\n\n"    # 心跳保活
        finally:
            crawler.unsubscribe(q)

    return StreamingResponse(event_stream(), media_type="text/event-stream")
returnyield
返回时机函数结束,一次性每次 yield 暂停并产出
内存全部数据在内存按需,一条一条
适用小数据集大文件、流式 API、SSE

5. async/await

Java 一个请求占一个线程;Python 一个请求是一个协程,单线程管数千并发。

// Java — 一个请求 = 一个线程(Tomcat 默认 200 线程)
@GetMapping("/articles")
public List<Article> getArticles() {
    return articleMapper.selectList(null);  // 线程卡住等 DB
}

# Python — 一个请求 = 一个协程
@router.get("/articles")
async def get_articles():
    articles = await article_repo.find_list()  # 让出 CPU,DB 返回后继续
    return articles
Java 多线程Python async
等 DB 时线程阻塞,啥也不干协程让出,处理别的请求
并发模型200 线程 = 200 并发1 线程 + 数千协程
内存开销每线程 ~1MB每协程 ~几 KB
CPU 密集多核并行单线程,会卡住

await = 发请求 → 让出 CPU 处理别的协程 → 结果返回后继续。asyncio.gather 并行多个 await:

# Python — 5 个查询并行执行
articles, total, tags, global_total, new_count = await asyncio.gather(
    article_repo.find_list(...),
    article_repo.count(...),
    article_repo.get_keyword_tags(...),
    article_repo.count(),
    article_repo.count_new(),
)

// Java 等价 — CompletableFuture
CompletableFuture.allOf(
    CompletableFuture.supplyAsync(() -> articleMapper.selectList(...)),
    CompletableFuture.supplyAsync(() -> articleMapper.count(...)),
    // ...
);

I/O 密集用 async/await;CPU 密集用 multiprocessing;简单脚本直接写同步函数。


6. GIL 全局解释器锁

CPython 进程里有一把全局锁,同一时刻只有一个线程能执行 Python 字节码。

// Java — 4 个线程真正同时跑在 4 个核心上
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> compute(1));  // 核心 1
pool.submit(() -> compute(2));  // 核心 2
pool.submit(() -> compute(3));  // 核心 3
pool.submit(() -> compute(4));  // 核心 4

# Python — 4 个线程,但同一时间只有 1 个执行
t1 = threading.Thread(target=compute, args=(1,))
t2 = threading.Thread(target=compute, args=(2,))
# 实际在 1 个核心上轮流跑,时间 ≈ 4 倍

Python 用引用计数管理内存,多线程同时改计数器会数据竞争,GIL 保证同一时刻只有一个线程执行字节码。Java 用 GC,无此问题。

影响与解决方案

场景GIL 影响解决方案
CPU 密集(计算、加密)致命,多线程没用multiprocessing 多进程
I/O 密集(网络、DB)没影响,等 I/O 时释放 GILasyncio 或多线程都行
# 多进程 — 每个进程独立 GIL
from multiprocessing import Pool
with Pool(4) as pool: results = pool.map(compute, [1,2,3,4])

# 多 Worker — uvicorn app.main:app --workers 4
# Python 3.13 — 实验性 --disable-gil

下一篇

语言特性打底之后,下一篇进入实战层:Java 转 Python(二):Web 框架与分层架构 — FastAPI 路由、依赖注入、SQLModel 数据库、Pydantic 数据模型。



Previous Post
Java 转 Python(二):框架实践