[Java] 借助GUI展示class文件的版本号

背景

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

我想到可以借助 trae 来生成一个 Python\text{Python} Python 脚本,这个 Python\text{Python} Python 提供图形化界面,可以让用户选择 class\text{class} class 文件,然后展示这个 class\text{class} class 文件的主版本号和次版本号。

要点

1. class 文件格式

class\text{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\text{Main.class} Main.class 的 主版本号 和 次版本号,可以执行下方的命令 ⬇️

bash 复制代码
javap -v Main.class

正文

生成代码的过程

需求很明确,生成 Python\text{Python} Python 代码的过程如下 ⬇️

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

于是,我又让 trae 借助 这个表格 调整代码 ⬇️

最终生成的 Python\text{Python} 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\text{class} class 文件后,处理结果如下 ⬇️

补充说明

如果只是想查看 class\text{class} class 文件的主版本号和次版本号的话,本文的做法可谓小题大做,常规的做法是直接使用 javap\text{javap} javap 命令。示例如下(假设我们已经有一个名为 Main.class\text{Main.class} Main.class 的文件了)

bash 复制代码
javap -v Main.class

运行结果如下图所示 ⬇️

参考资料

相关推荐
勿信日志1 小时前
Playwright 元素定位:一个 width>50 过滤器把 42px 的输入框删掉了
python
larance1 小时前
[菜鸟教程] 机器学习教程十课-Python 机器学习应用
人工智能·python·机器学习
栖凤1 小时前
Java 的 try-catch 在 Agent 里失效了,我用了 5 个模式才兜住
java·前端·python
粥里有勺糖1 小时前
视野修炼第136期 | 前端小恐龙"Deno"被收购
前端·github·ai编程
六维空间1 小时前
筛了 40 多个信源,最后只留 29 个:一份「AI 日报」信源选型笔记
ai编程
Yyyyyy~1 小时前
【python】函数
python
Thneonl1 小时前
全集群钟差 300 毫秒会发生什么:证书悄悄过期,日志倒流
运维·后端
Solara1 小时前
我重算了 11 天,抓出自己抄错的 1 天:AI 会把"公式"记成"数列"
人工智能·程序员·ai编程