背景
The Java® Virtual Machine Specification 有对主版本号(major version)、次版本号(minor version)的描述(在 4.1. The ClassFile Structure 这一小节)

我想到可以借助 trae 来生成一个 Python 脚本,这个 Python 提供图形化界面,可以让用户选择 class 文件,然后展示这个 class 文件的主版本号和次版本号。
要点
1. class 文件格式
class 文件的格式如下表所示 ⬇️ (原始内容来自 4.1. The ClassFile Structure)
| 类型 | |
|---|---|
| u4 | magic |
| u2 | minor_version (次版本号) |
| u2 | major_version (主版本号) |
| u2 | constant_pool_count |
| cp_info | constant_poolconstant_pool_count-1 |
| u2 | access_flags |
| u2 | this_class |
| u2 | super_class |
| u2 | interfaces_count |
| u2 | interfacesinterfaces_count |
| u2 | fields_count |
| field_info | fieldsfields_count |
| u2 | methods_count |
| method_info | methodsmethods_count |
| u2 | attributes_count |
| attribute_info | attributesattributes_count |
本文只关心主版本号和次版本号,上表中将 主版本号 和 次版本号 用粗体展示了。
2. 如何借助 javap 命令查看主版本号和次版本号
如果我们想查看 Main.class 的 主版本号 和 次版本号,可以执行下方的命令 ⬇️
bash
javap -v Main.class
正文
生成代码的过程
需求很明确,生成 Python 代码的过程如下 ⬇️

trae 最初生成的 Python 代码中未能支持 Java 26 以及 Java 27。4.1. The ClassFile Structure 中提到了 一个表格


最终生成的 Python 代码如下
python
import struct
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
# 主版本号到 Java 版本的映射
JAVA_VERSION_MAP = {
45: "Java 1.0/1.1",
46: "Java 1.2",
47: "Java 1.3",
48: "Java 1.4",
49: "Java 5",
50: "Java 6",
51: "Java 7",
52: "Java 8",
53: "Java 9",
54: "Java 10",
55: "Java 11",
56: "Java 12",
57: "Java 13",
58: "Java 14",
59: "Java 15",
60: "Java 16",
61: "Java 17",
62: "Java 18",
63: "Java 19",
64: "Java 20",
65: "Java 21",
66: "Java 22",
67: "Java 23",
68: "Java 24",
69: "Java 25",
70: "Java 26",
71: "Java 27",
}
# class 文件魔数: 0xCAFEBABE
MAGIC_NUMBER = 0xCAFEBABE
def parse_class_version(file_path):
"""读取 class 文件,返回 (次版本号, 主版本号)。"""
with open(file_path, "rb") as f:
header = f.read(8)
if len(header) < 8:
raise ValueError("文件太小,不是有效的 class 文件")
magic, minor, major = struct.unpack(">IHH", header)
if magic != MAGIC_NUMBER:
raise ValueError(f"魔数不匹配,期望 0xCAFEBABE,实际为 0x{magic:08X}")
return minor, major
class ClassVersionViewer:
def __init__(self, root):
self.root = root
self.root.title("Class 文件版本查看器")
self.root.geometry("520x260")
self.root.resizable(False, False)
self.file_path = tk.StringVar(value="尚未选择文件")
self.minor_version = tk.StringVar(value="-")
self.major_version = tk.StringVar(value="-")
self.java_version = tk.StringVar(value="-")
self._build_ui()
def _build_ui(self):
pad = {"padx": 12, "pady": 8}
# 文件选择行
file_frame = ttk.Frame(self.root)
file_frame.pack(fill="x", **pad)
ttk.Button(file_frame, text="选择 class 文件", command=self._choose_file).pack(side="left")
ttk.Label(file_frame, textvariable=self.file_path, foreground="gray", wraplength=380).pack(side="left", padx=(8, 0))
# 结果展示区
result_frame = ttk.LabelFrame(self.root, text="版本信息")
result_frame.pack(fill="both", expand=True, **pad)
rows = [
("次版本号 (minor version):", self.minor_version),
("主版本号 (major version):", self.major_version),
("对应 Java 版本:", self.java_version),
]
for i, (label_text, value_var) in enumerate(rows):
ttk.Label(result_frame, text=label_text).grid(row=i, column=0, sticky="w", padx=8, pady=6)
ttk.Label(result_frame, textvariable=value_var, font=("Menlo", 12, "bold")).grid(row=i, column=1, sticky="w", padx=8, pady=6)
result_frame.columnconfigure(0, weight=1)
result_frame.columnconfigure(1, weight=2)
def _choose_file(self):
path = filedialog.askopenfilename(
title="选择 class 文件",
filetypes=[("Java class 文件", "*.class"), ("所有文件", "*.*")],
)
if not path:
return
self.file_path.set(path)
try:
minor, major = parse_class_version(path)
except (ValueError, OSError) as e:
messagebox.showerror("错误", str(e))
self._reset_results()
return
self.minor_version.set(str(minor))
self.major_version.set(str(major))
self.java_version.set(JAVA_VERSION_MAP.get(major, "未知 (可能是较新的版本)"))
def _reset_results(self):
for var in (self.minor_version, self.major_version, self.java_version):
var.set("-")
def main():
root = tk.Tk()
ClassVersionViewer(root)
root.mainloop()
if __name__ == "__main__":
main()
运行效果
运行效果如下 ⬇️

选择了一个 class 文件后,处理结果如下 ⬇️

补充说明
如果只是想查看 class 文件的主版本号和次版本号的话,本文的做法可谓小题大做,常规的做法是直接使用 javap 命令。示例如下(假设我们已经有一个名为 Main.class 的文件了)
bash
javap -v Main.class
运行结果如下图所示 ⬇️
