🏠 返回首页
Java → Python · Syntax Crosswalk

给 Java 工程师的
Python 语法对照手册

不讲“什么是变量”,只讲“你熟悉的 Java 写法,在 Python 里应该怎么写、为什么不同、哪里会踩坑”。全篇 16 章,左右分栏逐条对照。

Java 8 / 11 / 17 → Python 3.10+ 16 章 · 120+ 段对照代码 含避坑清单与速查表
01

执行模型与运行环境

这是所有差异的根源。Java 是「编译成字节码 → JVM 执行」,Python 也是「编译成字节码 → 虚拟机执行」,看起来很像,但两者在类型检查时机代码组织方式上完全不同。

维度Java说明
源文件Foo.java,文件名必须等于 public 类名Python 是 foo.py,文件名即模块名,一个文件可放任意多个类/函数
编译产物.class 字节码Python 生成 __pycache__/*.pyc,可删,会自动重建
执行引擎JVM(解释 + JIT 即时编译)CPython 逐字节码解释,几乎不做 JIT(PyPy 才有)
类型检查编译期静态检查,类型错误跑不起来运行期动态检查,x = 1 之后还能 x = "abc"
入口方法public static void main(String[] args)没有入口函数,从文件第一行往下执行;惯用 if __name__ == "__main__": 区分被导入还是直接运行
编译/运行命令javac Foo.javajava Foopython foo.py 一步到位
依赖隔离Maven/Gradle 管理,全局仓库venv 每个项目一个虚拟环境,pip 装包
并发模型真·多线程(可跑满多核)受 GIL 限制,CPU 密集要用 multiprocessing
一句话记住

Java 的编译器帮你挡住了大量错误,Python 把这些错误推迟到了运行时。所以写 Python 时要靠 严格的自律 + 类型注解 + 单元测试 + 静态检查(ruff / mypy)来补上编译器的那部分工作。

最简单的程序

Java · Hello.java
package com.demo;

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, World");
    }
}
Python · hello.py
# 不需要包声明、不需要类、不需要 main

print("Hello, World")


# 想区分"被导入"和"直接运行"时:
if __name__ == "__main__":
    print("只有在直接执行本文件时才会打印")
02

变量与类型系统

Python 变量是「贴在对象上的名字」,不是「装值的盒子」。所以既不需要写类型,也不能给变量单独声明类型——你标注的类型是给对象和阅读者看的。

Java
int age = 25;
long money = 10_0000_0000L;
double price = 19.9;
boolean flag = true;
char grade = 'A';
String name = "Alice";
final int MAX = 100;       // 常量

// 必须显式声明类型,且类型不可变
// 泛型集合
List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();

// 局部变量可推断(Java 10+)
var count = 10;
Python
age = 25
money = 10000000000      # 整数无上限,不需要 long
price = 19.9
flag = True              # 注意首字母大写
grade = "A"              # 没有 char,用长度 1 的字符串
name = "Alice"
MAX = 100                # 全大写只是"约定",语法上仍可修改

# 类型是"动"的,随时可换
x = 1
x = "现在是字符串了"      # 合法

# 容器不需要泛型声明,天然可异构
lst = []
mp = {}

# 类型注解是可选的,运行时不强制
age: int = 25
name: str = "Alice"

def greet(n: str) -> str:   # 返回类型注解
    return "Hi " + n
变量重新赋值不会被拦截

Java 里 int x = "abc"; 编译直接报错;Python 里 x: int = "abc" 完全合法。想找回编译期检查,需要在 IDE 里接入 mypy / pyright,并在 CI 里跑一次类型检查。

类型对照表

Java 类型Python 对应关键差异
byte / short / int / longintPython 只有一种整数,任意精度,不会溢出,不需要 Long.MAX_VALUE
float / doublefloat只有双精度一种,没有 float/double 之分,同样不精确
booleanboolTrue / False 首字母大写;它其实是 int 的子类,True + True == 2
charstr(长度 1)没有字符类型,"a"[0] 仍是字符串
Stringstr都是不可变对象
BigDecimaldecimal.Decimal金额计算用,注意别用 float 构造
nullNone唯一的空值,没有 NullPointerException,取而代之是 AttributeError / TypeError
void无 / -> None函数不写 return 就返回 None
Objectobject所有类的根基类
包装类 Integer不需要没有自动装箱/拆箱概念,一切皆对象引用

变量命名习惯

Java 习惯Python 习惯(PEP 8)示例
camelCase 变量/方法snake_caseuserNameuser_name
PascalCase 类名相同,PascalCaseUserService
UPPER_SNAKE 常量相同MAX_RETRY
private 关键字_name 单下划线(内部使用)_cache
protected同上,靠约定
避免命名冲突__name 双下划线触发名称改写近似 private,但并非访问控制
getXxx()优先直接属性访问,需要计算时用 @propertyobj.area
心智转变
  • Java:变量有类型,类型决定它能装什么。
  • Python:对象有类型,变量只是指向对象的标签,标签可以随时改指向。
  • 所以 Python 里 a = b 永远是「让 a 也指向 b 那个对象」,不是复制。这一点在第 7、15 章会反复出现。
03

字符串

Python 的字符串是日常用得最多的类型,也是从 Java 过来最爽的地方:格式化、切片、多行文本都比 Java 顺手得多。

Java
String name = "Alice";
int age = 25;

// 格式化
String msg = String.format("我是 %s,今年 %d 岁", name, age);

// 多行文本(Java 15+)
String multi = """
    第一行
    第二行
    """;

String s = "hello";
s.length();            // 5  —— 方法
s.toUpperCase();       // "HELLO"
s.substring(1, 3);     // "el"  左闭右开
s.charAt(0);           // 'h'
s.contains("ell");
s.split(",");
s.trim();
s.isEmpty();
"a" + "b";             // 拼接
String.join("-", list);
Python
name = "Alice"
age = 25

# f-string(3.6+):推荐,最直观
msg = f"我是 {name},今年 {age} 岁"
msg2 = f"明年 {age + 1} 岁"        # 可以直接写表达式

# 多行文本:三个引号
multi = """第一行
第二行"""

s = "hello"
len(s)                 # 5   —— 函数,不是方法!
s.upper()              # "HELLO"
s[1:3]                 # "el"  切片,左闭右开
s[0]                   # "h"
"ell" in s             # 用 in 做包含判断
s.split(",")
s.strip()              # 等价 trim
not s                  # 空字符串为假
"a" + "b"
"-".join(["a", "b"])   # "a-b"

# 单双引号完全等价,省去转义
a = 'He said "hi"'

切片:Python 独有、用了就回不去的语法

s = "Python"
s[0]        # "P"
s[-1]       # "n"      负数从右边数
s[0:3]      # "Pyt"    左闭右开,等价 s[:3]
s[3:]       # "hon"    省略终点表示到末尾
s[:]        # "Python" 整个字符串(浅拷贝)
s[::2]      # "Pto"    步长为 2
s[::-1]     # "nohtyP" 反转!Java 要 StringBuilder.reverse()

nums = [1, 2, 3, 4, 5]
nums[1:4]   # [2, 3, 4]  —— 切片对 list 一样适用
nums[-2:]   # [4, 5]

字符串格式化三种写法

name, score = "Alice", 95.678

# 1. f-string  —— 首选
f"{name} 得分 {score:.2f}"          # Alice 得分 95.68
f"{score:>10.2f}"                    # 右对齐宽度10
f"{1234567:,}"                       # 1,234,567
f"{0.856:.1%}"                       # 85.6%
f"{name=}"                           # name='Alice'  调试神器

# 2. str.format()  —— 老写法
"{} 得分 {:.2f}".format(name, score)
"{n} 得分 {s}".format(n=name, s=score)

# 3. % 格式化  —— 类 C 风格,新代码不建议
"%s 得分 %.2f" % (name, score)
拼接字符串别用 +

Python 字符串不可变,循环里 s += item 会不断新建对象。大量拼接请用 "".join(parts)——这比 Java 的 StringBuilder 还简洁。另外一定要明确指定编码:open(path, encoding="utf-8"),否则 Windows 上默认 GBK 会读中文乱码。

04

运算符

运算符差异不大,但有三个地方会直接咬人:除法语义逻辑运算符的写法、以及 ==is 的区别。

Java
int a = 5 / 2;              // 2     整数除法截断
double b = 5 / 2.0;        // 2.5   要写一个浮点字面量
double c = 5 % 2;          // 1.0
double p = Math.pow(2, 10); // 1024.0
int q = Math.floorDiv(-7, 2); // -4
int r = Math.floorMod(-7, 2); // 1

if (x > 0 && x < 10) { }
if (a == 0 || b == 0) { }
if (!done) { }

// 三元
String s = age >= 18 ? "成年" : "未成年";

int i = 0;
i++;  i--;  ++i;          // 自增自减

// 字符串比较必须用 equals
if (name.equals(other)) { }
if (null == obj) { }
Python
a = 5 // 2       # 2    整除(向下取整)
b = 5 / 2        # 2.5  真除法,永远是 float
c = 5 % 2        # 1
p = 2 ** 10      # 1024 幂运算,不用 Math.pow
q = -7 // 2      # -4   向下取整(Java 的 / 是向零取整)
r = -7 % 2       # 1    余数符号跟随除数

if 0 < x < 10:      # 链式比较,比 Java 优雅
    ...

if a == 0 or b == 0:
    ...

if not done:
    ...

# 三元(条件表达式)
s = "成年" if age >= 18 else "未成年"

i = 0
i += 1  # 没有 ++ / --,一行只能写一个语句

# 字符串比较就用 ==(比较值)
if name == other:
    ...

# is 是"身份"比较,等价于 Java 的 == 引用比较
if obj is None:
    ...

逻辑与比较的坑

Java 写法Python 写法说明
&& || !and or not没有 &&/||,写错了不报错但语义不同(& 是按位与)
a.equals(b)a == bPython 的 == 就是值比较;is 才是引用比较
x == nullx is None惯用法,因为 None 全局唯一
!list.isEmpty()not lst空容器、0、空串、None 都是「假值」
a & b(位与)a & b位运算一致,但 Python 还需要 a ^ b(异或)、~a(取反)
>>> 无符号右移没有 >>>因为整数无固定位宽,只保留 >>
a ? b : cb if a else c顺序是「值 if 条件 else 值」,别写反
y := expr(海象运算符)3.8+,赋值并同时作为表达式使用
强制转换不做隐式转换

Java 里 "age: " + 25 会自动把 int 转成 String。Python 的 "age: " + 25 会直接抛 TypeError。要么用 f-string,要么 "age: " + str(25)。相反方向的转换也一样:int("10")float("1.5")str(10)

05

流程控制

逻辑完全一样,但 Python 用缩进代替花括号for 是「遍历可迭代对象」而不是「三段式循环」,这是最需要重新训练肌肉记忆的地方。

Java
if (score >= 90) {
    grade = "A";
} else if (score >= 60) {
    grade = "B";
} else {
    grade = "C";
}

// 三段式 for
for (int i = 0; i < 5; i++) {
    System.out.println(i);
}

// 增强 for
for (String s : list) {
    System.out.println(s);
}

while (cond) { }

do { } while (cond);

// switch
switch (day) {
    case 1 -> "周一";
    case 2, 3 -> "工作日";
    default -> "未知";
}

break;  continue;
Python
if score >= 90:      # 冒号 + 缩进,没有 ()
    grade = "A"
elif score >= 60:    # 是 elif,不是 else if
    grade = "B"
else:
    grade = "C"

for i in range(5):   # 0,1,2,3,4   左闭右开
    print(i)

for s in lst:        # 直接遍历元素
    print(s)

for i, s in enumerate(lst):     # 需要下标时
    print(i, s)

for a, b in zip(xs, ys):        # 并行遍历
    print(a, b)

while cond:
    ...

# 没有 do-while,用 while True + break 模拟

# 3.10+ 的 match(类似 switch,但语义更强)
match day:
    case 1:
        r = "周一"
    case 2 | 3:              # 或
        r = "工作日"
    case _:                  # 默认分支
        r = "未知"

break
continue
pass            # 空语句占位,相当于 Java 的 {}

range 与常用循环套路

range(5)            # 0 1 2 3 4
range(2, 5)         # 2 3 4
range(0, 10, 2)     # 0 2 4 6 8
range(5, 0, -1)     # 5 4 3 2 1  倒序

# 倒序遍历
for i in reversed(lst): ...

# 带下标倒序
for i in range(len(lst) - 1, -1, -1): ...

# 同时取索引和值(比 Java 的 for i 干净)
for idx, val in enumerate(lst, start=1):
    print(idx, val)

# 字典遍历
for k in d: ...                 # 键
for k, v in d.items(): ...      # 键值对
for v in d.values(): ...        # 值

# for-else / while-else:循环"正常结束"(没被 break)时执行
for n in nums:
    if n < 0:
        break
else:
    print("全都是非负数")   # 没 break 才打印
缩进就是语法
  • 统一用 4 个空格,绝对不要把 Tab 和空格混用,否则报 TabError
  • Java 里 if (x) y = 1; 不带花括号合法,Python 里冒号后必须有缩进块,否则 IndentationError
  • Python 没有块级作用域for / if 里定义的变量,出了块照样能访问。这跟 Java 完全相反,会导致"变量泄漏"式 bug。
06

函数

Python 函数是「一等公民」:可以赋给变量、当参数传、被返回。同时它用默认参数 + 关键字参数 + *args/**kwargs 取代了 Java 的方法重载。

Java
public static int add(int a, int b) {
    return a + b;
}

// 方法重载:同名不同参
public static int add(int a, int b, int c) {
    return a + b + c;
}

// 可变参数
public static int total(int... nums) { }

// 默认值只能靠重载实现
public static String greet(String name) {
    return greet(name, "你好");
}
public static String greet(String name, String g) {
    return g + ", " + name;
}

// 多返回值要包对象
record DivResult(int q, int r) { }
public static DivResult divmod(int a, int b) {
    return new DivResult(a / b, a % b);
}
Python
def add(a, b):
    return a + b

# 默认参数:取代了大量重载
def greet(name, greeting="你好"):
    return f"{greeting}, {name}"

# 关键字参数:调用时任意顺序,可读性极强
greet("Tom")
greet("Tom", "Hi")
greet(greeting="Hi", name="Tom")

# 可变参数:*args 收位置参数为元组
def total(*nums):
    return sum(nums)
total(1, 2, 3)

# **kwargs 收关键字参数为字典
def config(**kwargs):
    print(kwargs)
config(debug=True, port=8080)

# 顺序:普通参数 → 默认参数 → *args → **kwargs
def f(a, b=1, *args, **kwargs):
    pass

# 多返回值:本质是元组,自动解包
def divmod_(a, b):
    return a // b, a % b

q, r = divmod_(7, 2)   # q=3, r=1

# 类型注解(可选,运行时不检查)
def add(a: int, b: int) -> int:
    return a + b

函数即对象

def double(x):
    return x * 2

f = double              # 函数可以赋值给变量
f(3)                    # 6

def apply(func, value):  # 可以当参数传
    return func(value)
apply(double, 5)        # 10

# 匿名函数:只能写单个表达式
square = lambda x: x * x
sorted(words, key=lambda w: len(w))     # 最常见的用法

# 排序多关键字
users.sort(key=lambda u: (-u.score, u.name))

# 偏函数
from functools import partial
inc = partial(add, 1)
inc(5)                  # 6
默认参数只在定义时求值一次
def append_to(item, target=[]):     # 危险!
    target.append(item)
    return target

append_to(1)    # [1]
append_to(2)    # [1, 2]   —— 你以为会是 [2]

# 正确写法:用 None 做哨兵
def append_to(item, target=None):
    if target is None:
        target = []
    target.append(item)
    return target

这是 Java 工程师最容易中招的坑之一,因为 Java 没有默认参数这个概念。原因是默认值在函数定义时就创建好了,所有调用共享同一个可变对象。

07

数据结构

Java 的集合框架要选类型(ArrayList 还是 LinkedList?HashMap 还是 TreeMap?),Python 只有四种内置容器,覆盖了 95% 的场景。

list

有序、可变、可重复。最常用,相当于 ArrayList,也能当栈和队列用。

tuple

有序、不可变。适合表示固定结构的记录,可作 dict 的键。

dict

键值对,3.7+ 保证插入顺序。相当于 LinkedHashMap + HashMap 的合体。

set

无序、去重。相当于 HashSet,支持交并差运算。

Java
// 数组:定长
int[] arr = {1, 2, 3};
arr.length;

// List
List<String> list = new ArrayList<>();
list.add("a");
list.add(0, "b");           // 指定位置插入
list.get(0);
list.set(0, "c");
list.remove(0);
list.size();
list.contains("a");
list.isEmpty();
Collections.sort(list);
list.subList(0, 2);

// Map
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.get("a");               // 没有返回 null
map.getOrDefault("b", 0);
map.containsKey("a");
map.remove("a");
map.keySet();

// Set
Set<String> set = new HashSet<>();
set.add("x");
set.contains("x");

// 元组:Java 14+ record
record Point(int x, int y) {}
Point p = new Point(1, 2);
p.x();
Python
# list:什么都能装
lst = [1, 2, 3]
lst[0]                      # 索引
lst.append("a")             # 尾部追加
lst.insert(0, "b")
lst[0] = "c"
lst.pop()                   # 弹出末尾
lst.remove("b")             # 按值删除
len(lst)
"a" in lst
lst.sort()                  # 原地排序
lst[::-1]                   # 反转(返回新列表)
lst[0:2]                    # 切片

# 栈:直接用 list
stack = []
stack.append(1)
stack.pop()

# 队列:用 deque,别用 list(头部操作是 O(n))
from collections import deque
q = deque()
q.append(1)                 # 右进
q.popleft()                 # 左出

# dict
d = {"a": 1}
d["a"]                      # 取不到抛 KeyError(不是返回 null)
d.get("b")                  # 安全取值,返回 None
d.get("b", 0)               # 带默认值
d["c"] = 3                  # 新增/覆盖
"a" in d
d.keys(); d.values(); d.items()
del d["a"]

# set
s = {"x"}
s.add("y")
"x" in s
{1, 2} | {2, 3}             # 并集
{1, 2} & {2, 3}             # 交集
{1, 2} - {2, 3}             # 差集

# tuple:不可变,可解包
t = (1, 2)
x, y = t                    # 解包
# t[0] = 9                  # TypeError

容器对照表

JavaPython备注
int[] / T[]listPython 没有定长数组语法,科学计算用 numpy.ndarray
ArrayListlistlist 即可,尾部操作都是 O(1)
LinkedListcollections.deque需要两端高效操作时用 deque
HashMap / LinkedHashMapdictdict 自带插入顺序(3.7+)
TreeMap无内置排序用 sorted(d.items()),或 sortedcontainers
HashSetset
TreeSet无内置同上
ArrayDequecollections.deque
PriorityQueueheapq基于 list 的小顶堆,需自己取负做大顶堆
record / 元组tuple / @dataclasstuple 可哈希,能当字典键
Collections.unmodifiableListtuple真正的不可变
Arrays.asList直接写 [...]
Collections.emptyList()[]
赋值不是复制
a = [1, 2, 3]
b = a               # b 和 a 指向同一个 list!
b.append(4)
print(a)            # [1, 2, 3, 4]  —— a 也被改了

c = a[:]            # 浅拷贝(一层)
d = a.copy()        # 同上
import copy
e = copy.deepcopy(a)  # 深拷贝,嵌套结构也能断开

Java 里 List b = a; 也是引用共享,但你习惯了之后 Python 里的"改一个动一片"更隐蔽——因为容器还能当函数默认参数、还能在函数里被原地修改。这条在第 15 章还会再强调一次。

08

推导式与函数式写法

Java 用 Stream 链式处理集合,Python 用推导式——一行搞定过滤 + 映射,可读性反而更高。如果你习惯 stream().filter().map().collect(),把它替换成推导式即可。

Java
List<Integer> nums = List.of(1, 2, 3, 4, 5);

// 过滤 + 映射
List<Integer> squares = nums.stream()
        .filter(n -> n % 2 == 0)
        .map(n -> n * n)
        .collect(Collectors.toList());

int sum = nums.stream()
        .mapToInt(Integer::intValue)
        .sum();

// 转 Map
Map<Integer, String> m = nums.stream()
        .collect(Collectors.toMap(
            n -> n, n -> "v" + n));

// 分组
Map<Integer, List<Integer>> g = nums.stream()
        .collect(Collectors.groupingBy(n -> n % 2));

// 任意匹配
boolean anyEven = nums.stream()
        .anyMatch(n -> n % 2 == 0);

// 排序取前 3
nums.stream().sorted().limit(3).toList();
Python
nums = [1, 2, 3, 4, 5]

# 列表推导式:过滤 + 映射一步到位
squares = [n * n for n in nums if n % 2 == 0]

# 还能嵌套、能写条件表达式
matrix = [[i * j for j in range(3)] for i in range(3)]

# 字典推导式
m = {n: f"v{n}" for n in nums}

# 集合推导式(自动去重)
s = {n % 3 for n in nums}

# 生成器表达式:用 (),惰性求值、不占内存
total = sum(n * n for n in nums)

# 分组用 defaultdict
from collections import defaultdict
g = defaultdict(list)
for n in nums:
    g[n % 2].append(n)

# 任意匹配
any_even = any(n % 2 == 0 for n in nums)   # ≈ anyMatch
all_pos = all(n > 0 for n in nums)         # ≈ allMatch

# 排序取前 3
sorted(nums)[:3]

# 求和 / 最大 / 最小
sum(nums); max(nums); min(nums)

# map / filter(能写推导式就别用它们)
list(map(lambda n: n * n, filter(lambda n: n % 2 == 0, nums)))

常用内置函数对照

Java Stream / 集合操作Python 写法说明
filter()[x for x in xs if cond]优先推导式
map()[f(x) for x in xs]优先推导式
sum() / count()sum(xs)配生成器表达式还能做加权求和
anyMatch / allMatchany() / all()接收可迭代对象
findFirstnext((x for x in xs if cond), None)找到第一个匹配,默认值兜底
sorted().limit(n)heapq.nlargest(n, xs)大数据量比 sorted 更优
Collectors.groupingBydefaultdict(list) / itertools.groupby
flatMap[y for x in xs for y in f(x)]双层推导式
IntStream.range()range()
reduce()functools.reduce()能用 sum / max 就别用 reduce
distinct()set(xs) / dict.fromkeys(xs)后者保序
join()"".join(xs)
09

类与面向对象

Python 的 OOP 比 Java「轻」:没有访问修饰符关键字,没有方法重载,没有接口,但支持多继承和鸭子类型。self 必须显式写出来,这是最需要适应的点。

Java
public class Animal {
    private final String name;      // 字段必须声明
    static int count = 0;           // 类变量

    public Animal(String name) {
        this.name = name;
        count++;
    }

    public void speak() {
        System.out.println(name + " 发出声音");
    }

    public String getName() { return name; }

    @Override
    public String toString() {
        return "Animal(" + name + ")";
    }
}

public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println("汪");
    }
}

// 接口 + 抽象类
interface Flyable {
    void fly();
}
Python
class Animal:
    count = 0                       # 类属性

    def __init__(self, name):       # 构造方法,self ≈ this
        self.name = name            # 实例属性随用随建
        Animal.count += 1

    def speak(self):                # 第一个参数必须是 self
        print(f"{self.name} 发出声音")

    @property                       # ≈ getter,但调用时不加 ()
    def display_name(self):
        return self.name.upper()

    def __str__(self):              # ≈ toString()
        return f"Animal({self.name})"

    def __repr__(self):             # 调试用表示
        return f"Animal({self.name!r})"


class Dog(Animal):                  # 继承用 ()
    def __init__(self, name):
        super().__init__(name)      # 必须显式调用

    def speak(self):                # 直接覆盖,无 @Override
        print("汪")


d = Dog("旺财")
d.speak()
print(d.name)
print(d.display_name)   # 属性式访问
isinstance(d, Animal)   # True,≈ instanceof
issubclass(Dog, Animal)

Java → Python 的 OOP 概念映射

Java 特性Python 等价物要点
thisself必须作为第一个参数显式声明,调用时不用传
构造方法__init__(self, ...)不是构造函数而是初始化方法;创建对象用 Animal("x")
private / protected_name / __name纯约定,不是语法限制(__x 只是改名,仍可访问)
static 方法@staticmethod不接收 self
类方法(如工厂)@classmethod第一个参数是 cls,常用于替代构造重载
重载构造 / 工厂方法@classmethod 或默认参数Python 不支持同名重载(后者会覆盖前者)
toString()__str__ / __repr__print(obj)__str__
equals() / hashCode()__eq__ / __hash__要能放进 set / 作 dict 键必须两个都实现
interfaceabc.ABC / Protocol或用鸭子类型(有方法就能用,不检查类型)
abstract classabc.ABC + @abstractmethod
单继承多继承 + MROPython 支持多继承,方法解析按 C3 线性化
record / Lombok @Data@dataclass自动生成 __init__ / __repr__ / __eq__
getter / setter@property / @x.setter外部访问像字段,内部却是方法
final 类/方法没有靠约定,不阻止继承
运算符重载(无)__add__ 等魔法方法Python 可以重载运算符

dataclass:Java record / Lombok 的替代

from dataclasses import dataclass, field

@dataclass
class User:
    name: str
    age: int
    tags: list = field(default_factory=list)   # 可变默认值要用 factory

u = User("Alice", 30)
print(u)            # User(name='Alice', age=30, tags=[])
u2 = User("Alice", 30)
print(u == u2)      # True  —— 自动生成 __eq__

# 不可变版本(可哈希,能当字典键)
@dataclass(frozen=True)
class Point:
    x: int
    y: int

{Point(1, 2): "原点附近"}
10

异常处理

语法骨架和 Java 很像,但有三个关键区别:没有受检异常可以一次捕获多种多了 else 分支。另外 Python 社区倡导 EAFP(先做再捕获)而不是 LBYL(先检查再做)。

Java
try {
    int r = 10 / x;
    process(r);
} catch (ArithmeticException e) {
    logger.error(e.getMessage());
} catch (IOException | SQLException e) {
    // 一次捕获多种
    e.printStackTrace();
} catch (Exception e) {
    // 兜底
} finally {
    // 一定执行,用于释放资源
    cleanup();
}

// 抛出
throw new IllegalArgumentException("x 不能为 0");

// 自定义异常
class BizException extends RuntimeException {
    public BizException(String msg) { super(msg); }
}

// 受检异常必须声明
public void read() throws IOException { }
Python
try:
    r = 10 / x
    process(r)
except ZeroDivisionError as e:
    print(f"除零: {e}")
except (ValueError, TypeError) as e:
    # 一次捕获多种,用元组
    print(e)
except Exception as e:
    # 兜底;永远别写裸 except:
    print(f"未知错误: {e}")
else:
    # 没有异常时执行(Java 没有)
    print("成功")
finally:
    cleanup()      # 一定执行


# 抛出:和 Java 的 throw 一样
raise ValueError("x 不能为 0")

# 重新抛出当前异常
except Exception:
    raise

# 自定义异常
class BizError(Exception):
    def __init__(self, msg, code=500):
        super().__init__(msg)
        self.code = code

# 没有"受检异常",不需要 throws 声明
# 异常链
raise RuntimeError("包装后的错误") from original_error

EAFP vs LBYL:习惯要反过来

Java · LBYL 风格
// 先检查,再操作
if (map.containsKey(key)) {
    value = map.get(key);
} else {
    value = "default";
}

if (file.exists()) {
    content = read(file);
}
Python · EAFP 风格
# 先做,出错再处理(Easier to Ask Forgiveness)
try:
    value = d[key]
except KeyError:
    value = "default"

# 或者直接用 dict 提供的 API
value = d.get(key, "default")

try:
    with open(path) as f:
        content = f.read()
except FileNotFoundError:
    content = ""

常见异常类型对照

Java 异常Python 异常触发场景
NullPointerExceptionAttributeError / TypeErrorNone 取属性或调用
ArithmeticExceptionZeroDivisionError除零
ArrayIndexOutOfBoundsExceptionIndexError下标越界
ClassCastExceptionTypeError类型不匹配的操作
NumberFormatExceptionValueErrorint("abc")
Map.get() 返回 nullKeyErrord["不存在"]
FileNotFoundException / IOExceptionFileNotFoundError / OSError文件与环境相关错误
InterruptedExceptionKeyboardInterruptCtrl+C
RuntimeExceptionRuntimeError通用运行时错误
StackOverflowErrorRecursionError递归过深(默认上限约 1000 层)
不要写裸 except

except: 会连 KeyboardInterruptSystemExit 一起吞掉,导致 Ctrl+C 都停不下来、排查问题毫无线索。至少写 except Exception as e:,并记录日志。另外 except 的顺序是自上而下匹配,子类异常必须写在父类前面。

11

模块、包与依赖管理

Java 的 package/import 和你熟悉的概念一一对应,但 Python 里「一个文件就是一个模块」,「一个目录就是一个包」,粒度更细。

Java
package com.demo.service;

import java.util.List;
import java.util.ArrayList;
import java.util.*;                    // 通配
import static java.lang.Math.max;    // 静态导入

// 项目结构
// src/main/java/com/demo/
//   ├── App.java
//   └── service/UserService.java
//
// 目录结构必须和包名一致

// 依赖管理:pom.xml
// <dependency>
//   <groupId>com.google.guava</groupId>
//   <artifactId>guava</artifactId>
//   <version>33.0.0-jre</version>
// </dependency>
Python
# 导入整个模块,用 模块.名字 访问
import math
math.sqrt(16)

# 别名(社区惯例:numpy 一律 as np)
import numpy as np
import pandas as pd

# 只导入需要的名字
from math import sqrt, pi
from collections import defaultdict

# 通配导入(不推荐,会污染命名空间)
from math import *

# 相对导入(同一个包内部)
from .utils import helper
from ..models import User

# 项目结构
# myproject/
#   ├── main.py
#   └── pkg/                 ← 目录名就是包名
#       ├── __init__.py      ← 标记为包,可为空
#       ├── utils.py         ← 模块
#       └── service.py

__name__ == "__main__" 到底在干什么

# utils.py
def helper():
    return "帮助函数"

print(f"模块 {__name__} 被加载了")

if __name__ == "__main__":
    # 只有"用 python utils.py 直接运行"时才会执行
    # 被别的文件 import 时,__name__ 是 "utils",这里不执行
    print("作为脚本运行")
    helper()

这相当于 Java 的 public static void main 加上「可被 import」的双重身份。Python 的一个文件既是库又是可执行脚本,全靠这一行区分。

依赖管理与工程工具对照

Java 生态Python 生态说明
Maven / Gradlepip + pyproject.toml现代项目用 uv / poetry / pdm 更接近 Maven 体验
pom.xmlpyproject.toml(或 requirements.txt后者只锁依赖列表,不描述构建
本地仓库 ~/.m2全局 site-packages + 项目内 .venv强烈建议每个项目一个 venv
依赖隔离(classpath)虚拟环境 python -m venv .venv激活后 pip 装到项目内
mvn test / JUnitpytest断言直接用 assert,不用 Assert 类
Checkstyle / SpotBugsruff / flake8ruff 同时做 lint 和 format,速度极快
google-java-formatblack / ruff format不提供配置项,强制统一风格
类型检查(编译器)mypy / pyright需要显式跑,不是默认行为
Log4j / SLF4Jlogging标准库内置
Jackson / Gsonjson / pydanticpydantic 做数据校验,接近 Jackson + Bean Validation
Spring BootFastAPI / Django / FlaskFastAPI 对 Java 后端最友好
JDB / IDE 调试pdb / breakpoint()代码里插一行 breakpoint() 即断点
起步命令
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install requests pandas pytest
pip freeze > requirements.txt    # 导出依赖
12

文件与资源管理

Python 的 with 语句等价于 Java 的 try-with-resources,而且更通用——任何实现了上下文管理器协议的对象都能用。

Java
// try-with-resources:自动关闭
try (BufferedReader br = new BufferedReader(
         new FileReader("data.txt", StandardCharsets.UTF_8))) {
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

// 写文件
try (BufferedWriter bw = new BufferedWriter(
         new FileWriter("out.txt", StandardCharsets.UTF_8))) {
    bw.write("hello");
    bw.newLine();
}

// 读取全部内容(Java 11+)
String content = Files.readString(Path.of("data.txt"));
List<String> lines = Files.readAllLines(Path.of("data.txt"));
Python
# with ≈ try-with-resources,自动关闭
with open("data.txt", encoding="utf-8") as f:
    for line in f:                    # 直接迭代,逐行读
        print(line.rstrip())          # 行尾自带 \n

# 一次性读取
with open("data.txt", encoding="utf-8") as f:
    content = f.read()                # 整个字符串
with open("data.txt", encoding="utf-8") as f:
    lines = f.readlines()             # 每行一个元素
with open("data.txt", encoding="utf-8") as f:
    lines = [l.strip() for l in f]

# 写文件:w 覆盖,a 追加
with open("out.txt", "w", encoding="utf-8") as f:
    f.write("hello\n")
    print("world", file=f)            # 也能用 print

# 同时开多个文件
with open("in.txt") as src, open("out.txt", "w") as dst:
    dst.write(src.read())

更现代的路径操作:pathlib

from pathlib import Path

p = Path("data") / "logs" / "app.log"   # 用 / 拼路径,跨平台
p.exists()
p.is_file()
p.suffix              # ".log"
p.stem                # "app"
p.parent              # data/logs

# 读 / 写一行搞定
text = Path("a.txt").read_text(encoding="utf-8")
Path("b.txt").write_text("hi", encoding="utf-8")

# 遍历目录
for f in Path(".").glob("**/*.py"):
    print(f)

# 创建 / 删除目录
Path("out/nested").mkdir(parents=True, exist_ok=True)

JSON 读写(≈ Jackson 的极简版)

import json

data = {"name": "Alice", "tags": ["a", "b"], "age": 30}

# 对象 → 字符串
s = json.dumps(data, ensure_ascii=False, indent=2)

# 字符串 → 对象
obj = json.loads(s)

# 直接读写文件
with open("d.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

with open("d.json", encoding="utf-8") as f:
    obj = json.load(f)
encoding 一定要写

Python 3 的 open() 默认编码取决于操作系统区域设置。Windows 中文环境下默认 GBK,读 UTF-8 文件会直接抛 UnicodeDecodeError。养成习惯:所有 open() 都显式写 encoding="utf-8"

13

标准库对照表

Java 里需要引第三方库甚至自己写的东西,Python 大量已经内置。下面这张表是日常开发最高频的对照,建议收藏。

Java 中的东西Python 对应典型用法
System.out.printlnprint()print("x", a, sep=",", end="")
Scannerinput()name = input("姓名: ")
System.getenv / System.getPropertyos.environ / os.getenvos.getenv("HOME", "/root")
System.currentTimeMillistime.time()返回秒(float),毫秒要乘 1000
LocalDate / LocalDateTimedatetime.date / datetime.datetimedatetime.now()timedelta(days=1)
SimpleDateFormatstrftime / strptimedt.strftime("%Y-%m-%d %H:%M")
BigDecimaldecimal.DecimalDecimal("0.1") + Decimal("0.2")
Pattern / Matcherrere.findall(r"\d+", s),注意用原始字符串
UUIDuuidstr(uuid.uuid4())
Randomrandomrandom.choice(xs)random.sample(xs, 3)random.shuffle(xs)
Mathmathmath.sqrtmath.floormath.pi;另有 statistics 做统计
Collections.sortsorted() / .sort()sorted(xs, key=..., reverse=True)
Optional无对应直接用 None 判断,或用 dict.get
Stream推导式 / itertoolsitertools.chaingroupbyislice
Files / PathspathlibPath("a").read_text()
Thread.sleeptime.sleeptime.sleep(1.5)
ExecutorService / CompletableFutureconcurrent.futures / asyncioThreadPoolExecutor 用法非常接近 Java
Loggerlogginglogging.basicConfig(level=logging.INFO)
JUnitpytest(第三方,但事实标准)函数名 test_ 开头,用 assert 断言
Jacksonjson / pydanticpydantic 还能做字段校验与类型转换
JSch / HttpClientrequests(第三方,必备)requests.get(url).json()
Apache POI / OpenCSVopenpyxl / csvExcel 与 CSV 处理
javax.mailsmtplib / email发邮件
StringUtils(Commons)多数是 str 自带方法s.strip()s.startswith()s.replace()
Objects.equals / hashCode== / hash()
Arrays.toStringrepr()外部用 __str__,调试用 __repr__
JVM 参数调优sys.setrecursionlimitPython 很少需要调这类参数
14

进阶语法特性

这几个特性是 Python 的「特色菜」,Java 里没有直接对应物。掌握它们,你写的就不再是「用 Python 语法的 Java 代码」。

装饰器:比 Java 注解更强大

Java 的注解(@Transactional@Cacheable)靠 Spring 之类的框架在运行时/编译期用反射处理。Python 的装饰器本身就是一个包装函数的函数,不需要框架介入。

import functools, time, logging

def timer(func):
    @functools.wraps(func)              # 保留原函数的名字和文档
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        logging.info(f"{func.__name__} 耗时 {time.time() - start:.3f}s")
        return result
    return wrapper

@timer                                  # 等价于 work = timer(work)
def work():
    time.sleep(0.1)

# 带参数的装饰器:多一层嵌套
def retry(times=3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(times):
                try:
                    return func(*args, **kwargs)
                except Exception:
                    if i == times - 1:
                        raise
        return wrapper
    return decorator

@retry(times=5)
def fetch():
    ...

# 标准库自带的常用装饰器
@functools.lru_cache(maxsize=128)       # 结果缓存,≈ @Cacheable
@staticmethod / @classmethod / @property

生成器:惰性求值,内存友好

Java 里要处理百万行日志,你可能得写 Iterator + 状态机。Python 用 yield 几行搞定,而且天然懒执行——不占内存。

def read_large_file(path):
    with open(path, encoding="utf-8") as f:
        for line in f:
            yield line.strip()          # 每次只产出一行,不占内存

for line in read_large_file("huge.log"):
    process(line)

# 生成器表达式:列表推导式换成 () 就是惰性的
squares = (n * n for n in range(10_000_000))   # 瞬间返回,不构建列表
next(squares)      # 0
next(squares)      # 1

def fib():
    a, b = 0, 1
    while True:                        # 无限序列也没问题
        yield a
        a, b = b, a + b

from itertools import islice
list(islice(fib(), 10))                # 取前 10 个

上下文管理器:with 背后的协议

# 自己实现一个 with 可用的对象(≈ AutoCloseable)
class DBConnection:
    def __enter__(self):
        self.conn = connect()
        return self.conn
    def __exit__(self, exc_type, exc_val, tb):
        self.conn.close()
        return False        # False 表示不吞异常

with DBConnection() as conn:
    conn.execute("select 1")

# 或者用装饰器写法,更简洁
from contextlib import contextmanager

@contextmanager
def timer(name):
    start = time.time()
    try:
        yield                                  # 分界线:yield 之前是进入,之后是退出
    finally:
        print(f"{name} 用了 {time.time()-start:.3f}s")

with timer("查询"):
    do_something()

解包与魔法方法

# 解包:极其常用
a, b = 1, 2
a, *rest = [1, 2, 3, 4]        # a=1, rest=[2,3,4]
*init, last = [1, 2, 3, 4]     # init=[1,2,3], last=4
first, *mid, last = range(5)
d = {"x": 1, "y": 2}
f(**d)                          # 字典解包成关键字参数

# 交换变量(Java 要临时变量)
a, b = b, a

# 魔法方法(dunder):让自定义类像内置类型一样工作
class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __add__(self, o):        # 支持 v1 + v2
        return Vector(self.x + o.x, self.y + o.y)
    def __len__(self):
        return 2
    def __eq__(self, o):         # 支持 ==
        return (self.x, self.y) == (o.x, o.y)
    def __hash__(self):
        return hash((self.x, self.y))
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

asyncio:Python 的异步

import asyncio

async def fetch(url):
    await asyncio.sleep(1)          # 模拟 IO
    return f"data from {url}"

async def main():
    # 并发执行,总耗时约 1 秒而不是 3 秒
    results = await asyncio.gather(
        fetch("a"), fetch("b"), fetch("c")
    )
    print(results)

asyncio.run(main())

如果你用过 Java 的 CompletableFuture 或 Kotlin 协程,这套模型是同一类思想:单线程事件循环 + await 让出控制权,适合 IO 密集型任务。

15

Java 开发者避坑清单

这 12 条是 Java 背景开发者最常踩的坑,按「踩坑频率 × 排查难度」排序。建议写代码前先扫一遍。

01 · 可变默认参数

def f(x, lst=[]) 的默认列表是所有调用共享的。必须用 None 做哨兵再初始化。Java 没有默认参数,所以这条你毫无防备。

02 · 没有块级作用域

for/if 里定义的变量出了块还能用,会"泄漏"到函数级。Java 的 {} 是作用域,Python 的缩进不是

03 · == 与 is 的区别

== 比值,is 比身份。判空必须写 x is None。小整数和短字符串有缓存,会出现 is 恰好为 True 的假象。

04 · 赋值不是复制

b = a 只是多一个名字指向同一对象。要复制用 a[:] / a.copy(),嵌套结构要 copy.deepcopy()

05 · 整数不会溢出

Python 整数是任意精度,2**100 合法,Long.MAX_VALUE 不存在。依赖溢出/截断的逻辑(哈希、位运算)必须重写。

06 · 除法语义不同

5/22.5(不是 2),整除要写 //。负数整除向下取整:-7//2 == -4,而 Java 是 -3。

07 · 类型注解不强制

x: int = "abc" 不报错,注解只是给人和工具看的。真正的类型检查要跑 mypypyright

08 · 忘写 self

实例方法第一个参数必须是 self,定义时写、调用时不传。这是新手报错率第一的语法错误。

09 · 循环中改列表

边遍历 listremove 会跳过元素。改成遍历副本 for x in lst[:],或用列表推导式重建。

10 · 闭包延迟绑定

循环里创建 lambda 时,变量在调用时才取值,全都指向最后一个值。用 lambda i=i: ... 固定当前值。

11 · 浮点与金额

0.1 + 0.2 != 0.3。金额一律用 Decimal,且务必用字符串构造:Decimal("0.1") 而不是 Decimal(0.1)

12 · 没有方法重载 & GIL

同名函数后定义的会直接覆盖前者,重载要用默认参数或 @classmethod。并发上:IO 密集用 asyncio/线程,CPU 密集必须用 multiprocessing

几个容易忽略的细节

  • 排序方法返回值不同list.sort() 原地排序返回 None,要新列表用 sorted(xs)。写 xs = xs.sort() 会得到 None
  • dict 缺键抛异常d["k"] 取不到是 KeyError,不是返回 null。习惯用 d.get("k", default)collections.defaultdict
  • 模块导入即执行import foo 会把 foo.py 的顶层代码全跑一遍。把副作用代码放进 if __name__ == "__main__": 或函数里。
  • 可变对象不能当 dict 键list 不行,tuple 可以。自定义类要同时实现 __eq____hash__
  • Python 没有 ++i++ 是语法错误,写 i += 1。而且 a = b = 0; a += 1 这种"连等"含义和 Java 也不同。
  • 字符串是常量池式共享:小整数(-5~256)和部分字符串会被 intern,所以 is 的结果不可预测,别依赖。
16

速查表与学习路线

把最常用的操作压成一张表,用的时候直接查。后面是给 Java 工程师的四周上手路线。

一行速查

要做什么Java 写法Python 写法
打印System.out.println(x)print(x)
字符串长度s.length()len(s)
格式化String.format("%s-%d", a, b)f"{a}-{b}"
列表长度list.size()len(lst)
追加元素list.add(x)lst.append(x)
取值兜底map.getOrDefault(k, d)d.get(k, d)
遍历键值for (var e : map.entrySet())for k, v in d.items():
判断包含list.contains(x)x in lst
排序Collections.sort(list)sorted(lst) / lst.sort()
自定义排序list.sort(Comparator.comparing(...))sorted(lst, key=lambda x: x.age)
去重new HashSet<>(list)set(lst) / list(dict.fromkeys(lst))
求和list.stream().mapToInt(..).sum()sum(lst)
最大值Collections.max(list)max(lst)
拼接字符串String.join("-", list)"-".join(lst)
转大写s.toUpperCase()s.upper()
分割字符串s.split(",")s.split(",")
类型判断obj instanceof Fooisinstance(obj, Foo)
空判断x == nullx is None
转字符串String.valueOf(x)str(x)
转整数Integer.parseInt(s)int(s)
当前时间LocalDateTime.now()datetime.now()
随机整数rnd.nextInt(100)random.randint(0, 99)
读写 JSONobjectMapper.writeValueAsString(o)json.dumps(o)
断言assertEquals(a, b)assert a == b

四周上手路线

Week 01

语法破冰

缩进、动态类型、四种容器、切片、f-string、推导式、函数参数。做法:把以前写过的 Java 小工具(字符串处理、文件批处理)用 Python 重写三五个。

Week 02

面向对象与工程化

class/self、@dataclass@property、异常、模块与包。做法:建立 venv,配好 ruff + pytest,把重写的工具加上单元测试。

Week 03

标准库与三件套

pathlibjsonredatetimelogging,然后上 requestspandaspytest做法:写一个调用第三方 API 并落盘 CSV 的小脚本。

Week 04

项目实战

选一个方向:FastAPI 写个 REST 接口(最贴近你的后端经验)、数据采集、或数据分析。做法:完整做一遍,跑通 CI 里的 lint + test。

工具链与配置建议

uv / poetry

依赖与虚拟环境管理。uv 速度极快,最接近 Maven 体验。

ruff

同时做 lint 和格式化,替代 flake8 + isort + black 三件套。

mypy / pyright

静态类型检查,把"编译器帮你查的那部分"补回来。

pytest

事实标准的测试框架,比 unittest 简洁得多。

pydantic

数据校验与序列化,写接口时必用,接近 Jackson + 校验注解。

fastapi

对 Java 后端最友好的 Python Web 框架,注解风格、自动生成 OpenAPI 文档。

推荐读物
  • 《流畅的 Python》(Fluent Python) —— 从 Java 转到 Python 最值得读的一本,专治"用 Java 思维写 Python"。
  • 官方教程 docs.python.org/zh-cn/3/tutorial/ 与 PEP 8 风格指南。
  • 在解释器里敲一次 import this,这就是「Python 之禅」。
最后一句

从 Java 到 Python,语法可能三天就能看熟,真正的门槛是思维方式的切换:从"编译器替我把关"变成"我用约定和测试替自己把关",从"类型驱动设计"变成"接口与鸭子类型驱动设计"。别急着把 Java 的设计模式照搬过来 —— 很多在 Python 里一行就写完了。