[Java] 一个方法最多可以有多少个入参?

41 阅读15分钟

背景

根据 The Java® Virtual Machine Specification4.3.3. Method Descriptors 的描述 ⬇️

image.png

A method descriptor is valid only if it represents method parameters with a total length of 255 or less, where that length includes the contribution for this in the case of instance or interface method invocations. The total length is calculated by summing the contributions of the individual parameters, where a parameter of type long or double contributes two units to the length and a parameter of any other type contributes one unit.

一个方法的方法描述符(descriptor\text{descriptor})最多可以表示 255255 个入参的信息,所以入参的数量不能超过 255255

如此说来,一个 Java\text{Java} 方法中的入参数量的上限应该是 255\text{255}。我们用程序来验证一下。

结论

大白话版的描述

在一个 Java\text{Java} 方法中,最多可以有 255255 个入参

  • 如果是实例方法,那么 this\text{this} 对入参数量的贡献是 11
    • 对实例方法而言,最多可以有 2551=254255-1=254 个显式入参
  • 如果有 long\text{long} 类型的参数,每个这样的参数对入参数量的贡献是 22
    • 对静态方法而言,如果所有入参的类型都是 long\text{long},那么最多可以有 2552=127\lfloor \frac{255}{2} \rfloor=127 个入参
    • 对实例方法而言,如果所有(显式)入参的类型都是 long\text{long},那么最多可以有 25512=127\lfloor \frac{255-1}{2} \rfloor=127 个(显式的)入参
  • 如果有 double\text{double} 类型的参数,每个这样的参数对入参数量的贡献是 22
    • 对静态方法而言,如果所有入参的类型都是 double\text{double},那么最多可以有 2552=127\lfloor \frac{255}{2} \rfloor=127 个入参
    • 对实例方法而言,如果所有(显式)入参的类型都是 double\text{double},那么最多可以有 25512=127\lfloor \frac{255-1}{2} \rfloor=127 个(显式的)入参

严谨的描述

请参考 The Java® Virtual Machine Specification 中的 4.3.3. Method Descriptors

正文

如何通过对比来验证

为了验证该结论,我们需要生成两个类 ⬇️

  • C1C_1: 包含一个入参数量为 255255 的静态方法 mm (它的所有参数既不是 long\text{long} 类型也不是 double\text{double} 类型)
  • C2C_2: 包含一个入参数量为 256256 的静态方法 mm (它的所有参数既不是 long\text{long} 类型也不是 double\text{double} 类型)

如果 C1C_1 可以编译成功,而 C2C_2 编译失败,那么就可以证明我们的猜测(即,一个方法最多有 255255 个入参)是正确的。

如何用 Python 代码来生成简单的 Java 代码

为了减少重复劳动,我用 豆包 生成了 Python3\text{Python3} 的代码 ⬇️

import argparse
import os

def main():
    parser = argparse.ArgumentParser(description="生成带大量参数的Java类")
    parser.add_argument("-c", "--count", type=int, required=True, help="总参数个数,正整数")
    parser.add_argument("-o", "--output", type=str, required=True, help="输出Java文件名称,例如 C1.java")
    parser.add_argument("--static-method", action="store_true", help="开启则生成static静态方法,关闭则为实例方法")
    parser.add_argument("--param-type", type=str, default="int", help="参数类型,默认 int,例如 long / String")
    args = parser.parse_args()

    TOTAL_PARAMS = args.count
    OUTPUT_FILE = args.output
    PARAMS_PER_LINE = 8
    use_static = args.static_method
    param_type = args.param_type.strip()

    if TOTAL_PARAMS <= 0:
        raise ValueError("总参数个数必须是大于0的整数")
    if not param_type:
        raise ValueError("参数类型不能为空")

    # 提取类名
    basename = os.path.basename(OUTPUT_FILE)
    if basename.lower().endswith(".java"):
        class_name = basename[:-5]
    else:
        class_name = basename

    if not class_name.strip():
        raise ValueError("无法从输出文件名解析出有效类名")

    static_modifier = "static " if use_static else ""

    lines = []
    lines.append(f"public class {class_name} {{")
    lines.append(f"    {static_modifier}void m(")

    # 使用传入的参数类型
    param_parts = [f"{param_type} p{i}" for i in range(1, TOTAL_PARAMS + 1)]

    groups = []
    for start in range(0, len(param_parts), PARAMS_PER_LINE):
        groups.append(param_parts[start:start + PARAMS_PER_LINE])

    for idx, group in enumerate(groups):
        line_text = "        " + ", ".join(group)
        if idx != len(groups) - 1:
            line_text += ","
        lines.append(line_text)

    lines.append("    ) {")
    lines.append("    }")
    lines.append("}")

    output_content = "\n".join(lines)
    print(output_content)

    with open(OUTPUT_FILE, "w", encoding="utf-8") as f:
        f.write(output_content)

    method_type = "静态方法" if use_static else "实例方法"
    print(f"\n类名:{class_name},方法类型:{method_type},参数类型:{param_type},已写入文件: {OUTPUT_FILE}")


if __name__ == "__main__":
    main()

请将以上代码保存为 gen.py\text{gen.py}。使用 gen.py\text{gen.py} 时,我们可以指定以下选项

  • -c/--count: 用于指定(显式的)参数的数量(例如 255255
  • -o/--output: 用于指定输出文件的名称(例如 C1.java\text{C1.java}
  • --static-method: 用于控制生成的 java\text{java} 代码里使用静态方法还是实例方法
  • --param-type: 用于指定(显式)参数的类型,默认值是 int\text{int}

Python 代码来生成 C1.java

用如下命令可以生成 C1.java\text{C1.java}

python3 gen.py -c 255 -o C1.java --static-method --param-type int

运行成功后,当前目录会生成一个名为 C1.java\text{C1.java} 的文件,其内容如下 ⬇️

public class C1 {
    static void m(
        int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8,
        int p9, int p10, int p11, int p12, int p13, int p14, int p15, int p16,
        int p17, int p18, int p19, int p20, int p21, int p22, int p23, int p24,
        int p25, int p26, int p27, int p28, int p29, int p30, int p31, int p32,
        int p33, int p34, int p35, int p36, int p37, int p38, int p39, int p40,
        int p41, int p42, int p43, int p44, int p45, int p46, int p47, int p48,
        int p49, int p50, int p51, int p52, int p53, int p54, int p55, int p56,
        int p57, int p58, int p59, int p60, int p61, int p62, int p63, int p64,
        int p65, int p66, int p67, int p68, int p69, int p70, int p71, int p72,
        int p73, int p74, int p75, int p76, int p77, int p78, int p79, int p80,
        int p81, int p82, int p83, int p84, int p85, int p86, int p87, int p88,
        int p89, int p90, int p91, int p92, int p93, int p94, int p95, int p96,
        int p97, int p98, int p99, int p100, int p101, int p102, int p103, int p104,
        int p105, int p106, int p107, int p108, int p109, int p110, int p111, int p112,
        int p113, int p114, int p115, int p116, int p117, int p118, int p119, int p120,
        int p121, int p122, int p123, int p124, int p125, int p126, int p127, int p128,
        int p129, int p130, int p131, int p132, int p133, int p134, int p135, int p136,
        int p137, int p138, int p139, int p140, int p141, int p142, int p143, int p144,
        int p145, int p146, int p147, int p148, int p149, int p150, int p151, int p152,
        int p153, int p154, int p155, int p156, int p157, int p158, int p159, int p160,
        int p161, int p162, int p163, int p164, int p165, int p166, int p167, int p168,
        int p169, int p170, int p171, int p172, int p173, int p174, int p175, int p176,
        int p177, int p178, int p179, int p180, int p181, int p182, int p183, int p184,
        int p185, int p186, int p187, int p188, int p189, int p190, int p191, int p192,
        int p193, int p194, int p195, int p196, int p197, int p198, int p199, int p200,
        int p201, int p202, int p203, int p204, int p205, int p206, int p207, int p208,
        int p209, int p210, int p211, int p212, int p213, int p214, int p215, int p216,
        int p217, int p218, int p219, int p220, int p221, int p222, int p223, int p224,
        int p225, int p226, int p227, int p228, int p229, int p230, int p231, int p232,
        int p233, int p234, int p235, int p236, int p237, int p238, int p239, int p240,
        int p241, int p242, int p243, int p244, int p245, int p246, int p247, int p248,
        int p249, int p250, int p251, int p252, int p253, int p254, int p255
    ) {
    }
}

我们用如下命令可以编译 C1.java\text{C1.java}

javac C1.java

当前目录多了一个名为 C1.class\text{C1.class} 的文件,说明编译成功了。

Python 代码来生成 C2.java

用如下命令可以生成 C2.java\text{C2.java}

python3 gen.py -c 256 -o C2.java --static-method --param-type int

运行成功后,当前目录会生成一个名为 C2.java\text{C2.java} 的文件,其内容如下 ⬇️

public class C2 {
    static void m(
        int p1, int p2, int p3, int p4, int p5, int p6, int p7, int p8,
        int p9, int p10, int p11, int p12, int p13, int p14, int p15, int p16,
        int p17, int p18, int p19, int p20, int p21, int p22, int p23, int p24,
        int p25, int p26, int p27, int p28, int p29, int p30, int p31, int p32,
        int p33, int p34, int p35, int p36, int p37, int p38, int p39, int p40,
        int p41, int p42, int p43, int p44, int p45, int p46, int p47, int p48,
        int p49, int p50, int p51, int p52, int p53, int p54, int p55, int p56,
        int p57, int p58, int p59, int p60, int p61, int p62, int p63, int p64,
        int p65, int p66, int p67, int p68, int p69, int p70, int p71, int p72,
        int p73, int p74, int p75, int p76, int p77, int p78, int p79, int p80,
        int p81, int p82, int p83, int p84, int p85, int p86, int p87, int p88,
        int p89, int p90, int p91, int p92, int p93, int p94, int p95, int p96,
        int p97, int p98, int p99, int p100, int p101, int p102, int p103, int p104,
        int p105, int p106, int p107, int p108, int p109, int p110, int p111, int p112,
        int p113, int p114, int p115, int p116, int p117, int p118, int p119, int p120,
        int p121, int p122, int p123, int p124, int p125, int p126, int p127, int p128,
        int p129, int p130, int p131, int p132, int p133, int p134, int p135, int p136,
        int p137, int p138, int p139, int p140, int p141, int p142, int p143, int p144,
        int p145, int p146, int p147, int p148, int p149, int p150, int p151, int p152,
        int p153, int p154, int p155, int p156, int p157, int p158, int p159, int p160,
        int p161, int p162, int p163, int p164, int p165, int p166, int p167, int p168,
        int p169, int p170, int p171, int p172, int p173, int p174, int p175, int p176,
        int p177, int p178, int p179, int p180, int p181, int p182, int p183, int p184,
        int p185, int p186, int p187, int p188, int p189, int p190, int p191, int p192,
        int p193, int p194, int p195, int p196, int p197, int p198, int p199, int p200,
        int p201, int p202, int p203, int p204, int p205, int p206, int p207, int p208,
        int p209, int p210, int p211, int p212, int p213, int p214, int p215, int p216,
        int p217, int p218, int p219, int p220, int p221, int p222, int p223, int p224,
        int p225, int p226, int p227, int p228, int p229, int p230, int p231, int p232,
        int p233, int p234, int p235, int p236, int p237, int p238, int p239, int p240,
        int p241, int p242, int p243, int p244, int p245, int p246, int p247, int p248,
        int p249, int p250, int p251, int p252, int p253, int p254, int p255, int p256
    ) {
    }
}

我们用如下命令可以编译 C2.java\text{C2.java} ⬇️

javac C2.java

编译失败了,说明 256256 个参数确实超过了虚拟机的限制。

image.png

补充说明

  • 对实例方法而言,考虑到 this\text{this} 是一个隐式的参数,它对入参数量的贡献是 11,所以显式出现的入参的数量的上限会是 2551=254255-1=254
  • 如果有 long\text{long} 类型的参数,每个这样的参数对入参数量的贡献是 22
    • 举个例子,对一个静态方法而言,如果它的所有入参都是 long\text{long} 类型,那么它最多只能有 2552=127\lfloor \frac{255}{2} \rfloor=127 个(long\text{long} 类型的)参数
  • 如果有 double\text{double} 类型的参数,每个这样的参数对入参数量的贡献是 22
    • 举个例子,对一个静态方法而言,如果它的所有入参都是 double\text{double} 类型,那么它最多只能有 2552=127\lfloor \frac{255}{2} \rfloor=127 个(double\text{double} 类型的)参数

这三个补充点都来自 The Java® Virtual Machine Specification4.3.3. Method Descriptors 的描述 ⬇️ (我用绿色箭头和绿色框标出来了)

image.png

验证补充点 1

我们需要验证实例方法中隐式的 this\text{this} 参数对入参个数的影响,可以这样做 ⬇️

python3 gen.py -c 254 -o C3.java --param-type int
python3 gen.py -c 255 -o C4.java --param-type int

执行上述 bash\text{bash} 命令后,我们会得到

  • C3.java\text{C3.java}: 其中包含了入参个数=254254 的实例方法
  • C4.java\text{C4.java}: 其中包含了入参个数=255255 的实例方法

我们再用以下命令编译它们

javac C3.java
javac C4.java

可以看到,C3.java\text{C3.java} 编译成功,C4.java\text{C4.java} 编译失败,所以实例方法中的隐式 this\text{this} 参数,对入参个数的贡献的确是 11

image.png

验证补充点 2

我们需要验证 long\text{long} 类型的参数对入参个数的影响,可以这样做 ⬇️

python3 gen.py -c 127 -o C5.java --static-method --param-type long
python3 gen.py -c 128 -o C6.java --static-method --param-type long
python3 gen.py -c 127 -o C7.java --param-type long
python3 gen.py -c 128 -o C8.java --param-type long

执行上述 bash\text{bash} 命令后,我们会得到

  • C5.java\text{C5.java}: 其中包含了入参个数=127127 的静态方法(所有参数的类型都是 long\text{long}
  • C6.java\text{C6.java}: 其中包含了入参个数=128128 的静态方法(所有参数的类型都是 long\text{long}
  • C7.java\text{C7.java}: 其中包含了入参个数=127127 的实例方法(所有参数的类型都是 long\text{long}
  • C8.java\text{C8.java}: 其中包含了入参个数=128128 的实例方法(所有参数的类型都是 long\text{long}

我们再用以下命令编译它们

javac C5.java
javac C6.java
javac C7.java
javac C8.java
image.png

编译的情况如下 ⬇️

  • C5.java\text{C5.java} 编译成功 ✅
  • C6.java\text{C6.java} 编译失败 ❌
  • C7.java\text{C7.java} 编译成功 ✅
  • C8.java\text{C8.java} 编译失败 ❌

由此可见,每一个 long\text{long} 类型的参数,对参数个数的贡献的确是 22

验证补充点 3

我们需要验证 double\text{double} 类型的参数对入参个数的影响,可以这样做 ⬇️

python3 gen.py -c 127 -o C9.java --static-method --param-type double
python3 gen.py -c 128 -o C10.java --static-method --param-type double
python3 gen.py -c 127 -o C11.java --param-type double
python3 gen.py -c 128 -o C12.java --param-type double

执行上述 bash\text{bash} 命令后,我们会得到

  • C9.java\text{C9.java}: 其中包含了入参个数=127127 的静态方法(所有参数的类型都是 double\text{double}
  • C10.java\text{C10.java}: 其中包含了入参个数=128128 的静态方法(所有参数的类型都是 double\text{double}
  • C11.java\text{C11.java}: 其中包含了入参个数=127127 的实例方法(所有参数的类型都是 double\text{double}
  • C12.java\text{C12.java}: 其中包含了入参个数=128128 的实例方法(所有参数的类型都是 double\text{double}

我们再用以下命令编译它们

javac C9.java
javac C10.java
javac C11.java
javac C12.java
image.png

编译的情况如下 ⬇️

  • C9.java\text{C9.java} 编译成功 ✅
  • C10.java\text{C10.java} 编译失败 ❌
  • C11.java\text{C11.java} 编译成功 ✅
  • C12.java\text{C12.java} 编译失败 ❌

由此可见,每一个 double\text{double} 类型的参数,对参数个数的贡献的确是 22

参考资料