执行模型与运行环境
这是所有差异的根源。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.java → java Foo | python foo.py 一步到位 |
| 依赖隔离 | Maven/Gradle 管理,全局仓库 | venv 每个项目一个虚拟环境,pip 装包 |
| 并发模型 | 真·多线程(可跑满多核) | 受 GIL 限制,CPU 密集要用 multiprocessing |
Java 的编译器帮你挡住了大量错误,Python 把这些错误推迟到了运行时。所以写 Python 时要靠 严格的自律 + 类型注解 + 单元测试 + 静态检查(ruff / mypy)来补上编译器的那部分工作。
最简单的程序
package com.demo;
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, World");
}
}
# 不需要包声明、不需要类、不需要 main
print("Hello, World")
# 想区分"被导入"和"直接运行"时:
if __name__ == "__main__":
print("只有在直接执行本文件时才会打印")
变量与类型系统
Python 变量是「贴在对象上的名字」,不是「装值的盒子」。所以既不需要写类型,也不能给变量单独声明类型——你标注的类型是给对象和阅读者看的。
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;
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 / long | int | Python 只有一种整数,任意精度,不会溢出,不需要 Long.MAX_VALUE |
float / double | float | 只有双精度一种,没有 float/double 之分,同样不精确 |
boolean | bool | True / False 首字母大写;它其实是 int 的子类,True + True == 2 |
char | str(长度 1) | 没有字符类型,"a"[0] 仍是字符串 |
String | str | 都是不可变对象 |
BigDecimal | decimal.Decimal | 金额计算用,注意别用 float 构造 |
null | None | 唯一的空值,没有 NullPointerException,取而代之是 AttributeError / TypeError |
void | 无 / -> None | 函数不写 return 就返回 None |
Object | object | 所有类的根基类 |
包装类 Integer 等 | 不需要 | 没有自动装箱/拆箱概念,一切皆对象引用 |
变量命名习惯
| Java 习惯 | Python 习惯(PEP 8) | 示例 |
|---|---|---|
camelCase 变量/方法 | snake_case | userName → user_name |
PascalCase 类名 | 相同,PascalCase | UserService |
UPPER_SNAKE 常量 | 相同 | MAX_RETRY |
private 关键字 | _name 单下划线(内部使用) | _cache |
protected | 同上,靠约定 | — |
| 避免命名冲突 | __name 双下划线触发名称改写 | 近似 private,但并非访问控制 |
getXxx() | 优先直接属性访问,需要计算时用 @property | obj.area |
- Java:变量有类型,类型决定它能装什么。
- Python:对象有类型,变量只是指向对象的标签,标签可以随时改指向。
- 所以 Python 里
a = b永远是「让 a 也指向 b 那个对象」,不是复制。这一点在第 7、15 章会反复出现。
字符串
Python 的字符串是日常用得最多的类型,也是从 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);
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 会读中文乱码。
运算符
运算符差异不大,但有三个地方会直接咬人:除法语义、逻辑运算符的写法、以及 == 与 is 的区别。
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) { }
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 == b | Python 的 == 就是值比较;is 才是引用比较 |
x == null | x is None | 惯用法,因为 None 全局唯一 |
!、list.isEmpty() | not lst | 空容器、0、空串、None 都是「假值」 |
a & b(位与) | a & b | 位运算一致,但 Python 还需要 a ^ b(异或)、~a(取反) |
>>> 无符号右移 | 没有 >>> | 因为整数无固定位宽,只保留 >> |
a ? b : c | b 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)。
流程控制
逻辑完全一样,但 Python 用缩进代替花括号,for 是「遍历可迭代对象」而不是「三段式循环」,这是最需要重新训练肌肉记忆的地方。
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;
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。
函数
Python 函数是「一等公民」:可以赋给变量、当参数传、被返回。同时它用默认参数 + 关键字参数 + *args/**kwargs 取代了 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);
}
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 没有默认参数这个概念。原因是默认值在函数定义时就创建好了,所有调用共享同一个可变对象。
数据结构
Java 的集合框架要选类型(ArrayList 还是 LinkedList?HashMap 还是 TreeMap?),Python 只有四种内置容器,覆盖了 95% 的场景。
有序、可变、可重复。最常用,相当于 ArrayList,也能当栈和队列用。
有序、不可变。适合表示固定结构的记录,可作 dict 的键。
键值对,3.7+ 保证插入顺序。相当于 LinkedHashMap + HashMap 的合体。
无序、去重。相当于 HashSet,支持交并差运算。
// 数组:定长
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();
# 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
容器对照表
| Java | Python | 备注 |
|---|---|---|
int[] / T[] | list | Python 没有定长数组语法,科学计算用 numpy.ndarray |
ArrayList | list | 用 list 即可,尾部操作都是 O(1) |
LinkedList | collections.deque | 需要两端高效操作时用 deque |
HashMap / LinkedHashMap | dict | dict 自带插入顺序(3.7+) |
TreeMap | 无内置 | 排序用 sorted(d.items()),或 sortedcontainers 库 |
HashSet | set | — |
TreeSet | 无内置 | 同上 |
ArrayDeque | collections.deque | — |
PriorityQueue | heapq | 基于 list 的小顶堆,需自己取负做大顶堆 |
record / 元组 | tuple / @dataclass | tuple 可哈希,能当字典键 |
Collections.unmodifiableList | tuple | 真正的不可变 |
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 章还会再强调一次。
推导式与函数式写法
Java 用 Stream 链式处理集合,Python 用推导式——一行搞定过滤 + 映射,可读性反而更高。如果你习惯 stream().filter().map().collect(),把它替换成推导式即可。
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();
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 / allMatch | any() / all() | 接收可迭代对象 |
findFirst | next((x for x in xs if cond), None) | 找到第一个匹配,默认值兜底 |
sorted().limit(n) | heapq.nlargest(n, xs) | 大数据量比 sorted 更优 |
Collectors.groupingBy | defaultdict(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) | — |
类与面向对象
Python 的 OOP 比 Java「轻」:没有访问修饰符关键字,没有方法重载,没有接口,但支持多继承和鸭子类型。self 必须显式写出来,这是最需要适应的点。
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();
}
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 等价物 | 要点 |
|---|---|---|
this | self | 必须作为第一个参数显式声明,调用时不用传 |
| 构造方法 | __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 键必须两个都实现 |
interface | abc.ABC / Protocol | 或用鸭子类型(有方法就能用,不检查类型) |
abstract class | abc.ABC + @abstractmethod | — |
| 单继承 | 多继承 + MRO | Python 支持多继承,方法解析按 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): "原点附近"}
异常处理
语法骨架和 Java 很像,但有三个关键区别:没有受检异常、可以一次捕获多种、多了 else 分支。另外 Python 社区倡导 EAFP(先做再捕获)而不是 LBYL(先检查再做)。
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 { }
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:习惯要反过来
// 先检查,再操作
if (map.containsKey(key)) {
value = map.get(key);
} else {
value = "default";
}
if (file.exists()) {
content = read(file);
}
# 先做,出错再处理(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 异常 | 触发场景 |
|---|---|---|
NullPointerException | AttributeError / TypeError | 对 None 取属性或调用 |
ArithmeticException | ZeroDivisionError | 除零 |
ArrayIndexOutOfBoundsException | IndexError | 下标越界 |
ClassCastException | TypeError | 类型不匹配的操作 |
NumberFormatException | ValueError | int("abc") |
Map.get() 返回 null | KeyError | d["不存在"] |
FileNotFoundException / IOException | FileNotFoundError / OSError | 文件与环境相关错误 |
InterruptedException | KeyboardInterrupt | Ctrl+C |
RuntimeException | RuntimeError | 通用运行时错误 |
StackOverflowError | RecursionError | 递归过深(默认上限约 1000 层) |
except: 会连 KeyboardInterrupt 和 SystemExit 一起吞掉,导致 Ctrl+C 都停不下来、排查问题毫无线索。至少写 except Exception as e:,并记录日志。另外 except 的顺序是自上而下匹配,子类异常必须写在父类前面。
模块、包与依赖管理
Java 的 package/import 和你熟悉的概念一一对应,但 Python 里「一个文件就是一个模块」,「一个目录就是一个包」,粒度更细。
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>
# 导入整个模块,用 模块.名字 访问
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 / Gradle | pip + pyproject.toml | 现代项目用 uv / poetry / pdm 更接近 Maven 体验 |
pom.xml | pyproject.toml(或 requirements.txt) | 后者只锁依赖列表,不描述构建 |
本地仓库 ~/.m2 | 全局 site-packages + 项目内 .venv | 强烈建议每个项目一个 venv |
| 依赖隔离(classpath) | 虚拟环境 python -m venv .venv | 激活后 pip 装到项目内 |
mvn test / JUnit | pytest | 断言直接用 assert,不用 Assert 类 |
| Checkstyle / SpotBugs | ruff / flake8 | ruff 同时做 lint 和 format,速度极快 |
| google-java-format | black / ruff format | 不提供配置项,强制统一风格 |
| 类型检查(编译器) | mypy / pyright | 需要显式跑,不是默认行为 |
| Log4j / SLF4J | logging | 标准库内置 |
| Jackson / Gson | json / pydantic | pydantic 做数据校验,接近 Jackson + Bean Validation |
| Spring Boot | FastAPI / Django / Flask | FastAPI 对 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 # 导出依赖
文件与资源管理
Python 的 with 语句等价于 Java 的 try-with-resources,而且更通用——任何实现了上下文管理器协议的对象都能用。
// 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"));
# 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)
Python 3 的 open() 默认编码取决于操作系统区域设置。Windows 中文环境下默认 GBK,读 UTF-8 文件会直接抛 UnicodeDecodeError。养成习惯:所有 open() 都显式写 encoding="utf-8"。
标准库对照表
Java 里需要引第三方库甚至自己写的东西,Python 大量已经内置。下面这张表是日常开发最高频的对照,建议收藏。
| Java 中的东西 | Python 对应 | 典型用法 |
|---|---|---|
System.out.println | print() | print("x", a, sep=",", end="") |
Scanner | input() | name = input("姓名: ") |
System.getenv / System.getProperty | os.environ / os.getenv | os.getenv("HOME", "/root") |
System.currentTimeMillis | time.time() | 返回秒(float),毫秒要乘 1000 |
LocalDate / LocalDateTime | datetime.date / datetime.datetime | datetime.now()、timedelta(days=1) |
SimpleDateFormat | strftime / strptime | dt.strftime("%Y-%m-%d %H:%M") |
BigDecimal | decimal.Decimal | Decimal("0.1") + Decimal("0.2") |
Pattern / Matcher | re | re.findall(r"\d+", s),注意用原始字符串 |
UUID | uuid | str(uuid.uuid4()) |
Random | random | random.choice(xs)、random.sample(xs, 3)、random.shuffle(xs) |
Math | math | math.sqrt、math.floor、math.pi;另有 statistics 做统计 |
Collections.sort | sorted() / .sort() | sorted(xs, key=..., reverse=True) |
Optional | 无对应 | 直接用 None 判断,或用 dict.get |
Stream | 推导式 / itertools | itertools.chain、groupby、islice |
Files / Paths | pathlib | Path("a").read_text() |
Thread.sleep | time.sleep | time.sleep(1.5) |
ExecutorService / CompletableFuture | concurrent.futures / asyncio | ThreadPoolExecutor 用法非常接近 Java |
Logger | logging | logging.basicConfig(level=logging.INFO) |
| JUnit | pytest(第三方,但事实标准) | 函数名 test_ 开头,用 assert 断言 |
| Jackson | json / pydantic | pydantic 还能做字段校验与类型转换 |
| JSch / HttpClient | requests(第三方,必备) | requests.get(url).json() |
| Apache POI / OpenCSV | openpyxl / csv | Excel 与 CSV 处理 |
| javax.mail | smtplib / email | 发邮件 |
StringUtils(Commons) | 多数是 str 自带方法 | s.strip()、s.startswith()、s.replace() |
Objects.equals / hashCode | == / hash() | — |
Arrays.toString | repr() | 外部用 __str__,调试用 __repr__ |
| JVM 参数调优 | sys.setrecursionlimit 等 | Python 很少需要调这类参数 |
进阶语法特性
这几个特性是 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 密集型任务。
Java 开发者避坑清单
这 12 条是 Java 背景开发者最常踩的坑,按「踩坑频率 × 排查难度」排序。建议写代码前先扫一遍。
def f(x, lst=[]) 的默认列表是所有调用共享的。必须用 None 做哨兵再初始化。Java 没有默认参数,所以这条你毫无防备。
for/if 里定义的变量出了块还能用,会"泄漏"到函数级。Java 的 {} 是作用域,Python 的缩进不是。
== 比值,is 比身份。判空必须写 x is None。小整数和短字符串有缓存,会出现 is 恰好为 True 的假象。
b = a 只是多一个名字指向同一对象。要复制用 a[:] / a.copy(),嵌套结构要 copy.deepcopy()。
Python 整数是任意精度,2**100 合法,Long.MAX_VALUE 不存在。依赖溢出/截断的逻辑(哈希、位运算)必须重写。
5/2 得 2.5(不是 2),整除要写 //。负数整除向下取整:-7//2 == -4,而 Java 是 -3。
x: int = "abc" 不报错,注解只是给人和工具看的。真正的类型检查要跑 mypy 或 pyright。
实例方法第一个参数必须是 self,定义时写、调用时不传。这是新手报错率第一的语法错误。
边遍历 list 边 remove 会跳过元素。改成遍历副本 for x in lst[:],或用列表推导式重建。
循环里创建 lambda 时,变量在调用时才取值,全都指向最后一个值。用 lambda i=i: ... 固定当前值。
0.1 + 0.2 != 0.3。金额一律用 Decimal,且务必用字符串构造:Decimal("0.1") 而不是 Decimal(0.1)。
同名函数后定义的会直接覆盖前者,重载要用默认参数或 @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的结果不可预测,别依赖。
速查表与学习路线
把最常用的操作压成一张表,用的时候直接查。后面是给 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 Foo | isinstance(obj, Foo) |
| 空判断 | x == null | x is None |
| 转字符串 | String.valueOf(x) | str(x) |
| 转整数 | Integer.parseInt(s) | int(s) |
| 当前时间 | LocalDateTime.now() | datetime.now() |
| 随机整数 | rnd.nextInt(100) | random.randint(0, 99) |
| 读写 JSON | objectMapper.writeValueAsString(o) | json.dumps(o) |
| 断言 | assertEquals(a, b) | assert a == b |
四周上手路线
语法破冰
缩进、动态类型、四种容器、切片、f-string、推导式、函数参数。做法:把以前写过的 Java 小工具(字符串处理、文件批处理)用 Python 重写三五个。
面向对象与工程化
class/self、@dataclass、@property、异常、模块与包。做法:建立 venv,配好 ruff + pytest,把重写的工具加上单元测试。
标准库与三件套
pathlib、json、re、datetime、logging,然后上 requests、pandas、pytest。做法:写一个调用第三方 API 并落盘 CSV 的小脚本。
项目实战
选一个方向:FastAPI 写个 REST 接口(最贴近你的后端经验)、数据采集、或数据分析。做法:完整做一遍,跑通 CI 里的 lint + test。
工具链与配置建议
依赖与虚拟环境管理。uv 速度极快,最接近 Maven 体验。
同时做 lint 和格式化,替代 flake8 + isort + black 三件套。
静态类型检查,把"编译器帮你查的那部分"补回来。
事实标准的测试框架,比 unittest 简洁得多。
数据校验与序列化,写接口时必用,接近 Jackson + 校验注解。
对 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 里一行就写完了。