繁体   English   中英

在Java中,我们可以在一个类中创建多少个构造函数?

[英]In Java how many constructor can we create in one class?

在Java中,我们可以在单个类中创建多少个构造函数。

严格地说,JVM类文件格式将类的方法(包括所有构造函数)的数量限制为小于65536.根据Tom Hawtin,有效限制为65527.每个方法签名占用常量池中的一个插槽。 由于某些65535个池条目(不可避免地)被其他东西占用,因此格式良好的类文件不可能使用所有可能的方法/构造函数ID。

参考 - JVMS 4.1 ClassFile结构

但是,如果您以正常方式编写合理的Java代码,则不会遇到该限制。

应该有多少? 这取决于类用例。 拥有多个“方便”构造函数重载通常很好,并使用this(...)实现它们以链接到“主”构造函数。 (但是,你可以超越顶部。有N个不同参数的N!个可能组合(重载)。)

如果你发现你正在编写一个过多的(主观!)数量的构造函数,你应该看看构建器模式等替代方案。

lambda的最大数量嵌套方法调用 的最大数量一样,我们必须区分正式的Java语言规范和技术限制,这可能是由于正式指定的类文件格式或由于编译器限制或错误。

通常,语言规范没有对构造函数的数量进行任何限制。 因此,只有实际的限制是类声明必须以字节代码格式表示。

构造函数被编译为特殊方法(名为<init> ),因此在类文件中,它们使用普通方法共享一个表,该方法限制为65535个条目。 我们可以通过不宣布任何普通方法来最大化这一点。 此外,由于每个构造函数必须具有不同的签名,因此每个构造函数在常量池中需要其自己的类型签名字符串,其自身限制为65534个条目。

常量池还有其他用途,比如保存这个类的声明,超类和Code属性的名称,这在构造函数时是必需的,以及超类构造函数的链接信息,我们必须调用,这是类文件方面的限制因素。

因此,所需的最小常量池条目是

  1. 超类名称(修改后的UTF8条目)
  2. 超类(类型Class,指代1.)
  3. 此类名称(修改后的UTF8条目)
  4. 这个类(类型Class,指的是3.)
  5. 构造函数的“方法”名称<init> (修改后的UTF8条目)
  6. 引用5的名称和类型条目以及超级构造函数签名(可以与我们的构造函数签名共享)
  7. 方法条目引用2.和6.(对于超级构造函数调用)
  8. 属性名称Code (修改后的UTF8条目)

给定这些必需的条目和65534个条目的限制(大小加一个存储为无符号的两个字节数量),我们得到65526个构造函数的类文件限制,实际上,我可以使用带有该数字的ASM库生成一个有效的类文件建设者而不是更多。

实际上,如果您将类命名为java.lang.Object ,则可以获得更多,因为在特殊情况下,没有要声明的超类,也没有要调用的超级构造函数。 决定你自己,你想要调用最大数量的实际限制......

如上所述,存在第三个限制,即编译器实现。 使用Java编译器时,必须确保它不会生成调试信息(如果是javac ,请使用-g:none ),并且不能生成可能占用常量池条目的其他可选属性。 但是使用JDK11的javac ,当你开始定义很多构造函数时,性能会显着下降。 我得到了以下编译时间:

 1000 constructors:   1 second
 2000 constructors:   2 seconds
 5000 constructors:  10 seconds
10000 constructors:   1 minute
15000 constructors:   2 minutes
20000 constructors:   4 minutes
30000 constructors:  10 minutes
40000 constructors:  20 minutes
50000 constructors:  between 25 minutes and ½ hour
65526 constructors:  between 45 minutes and 1 hour

所以javac最终设法最大化了类文件限制,但我们甚至可以考虑实际限制。

Eclipse编译器似乎更好地处理了这样的源文件,但是,最大化构造函数的数量使得IDE几乎无法使用。 关闭调试符号并且有点耐心,我设法用Eclipse编译了一个带有65526构造函数的类。 声明65528构造函数生成了关于太多常量池条目的错误消息,并且声明65527构造函数在Eclipse中发现了一个错误,产生了一个声明零常量池条目的损坏的类文件(如前所述,该数字存储为count加1 ,因此编译器供应商必须记住,限制不是65535而是65534)。

Java支持构造函数重载(当java类包含多个构造函数时,它被称为构造函数被重载)。一个类可以有多个构造函数,只要它们的签名(参数)不一样。所以你可以根据需要定义许多构造函数。没有限制。 这是一个例子: -

class Demo {
    private String name;
    private String city;
    private Double salary;

    public Demo() {
    }

    public Demo(String name) {
        this.name = name;
    }
    public Demo(Double salary) {
        this.city = city;
    }
    public Demo(String name,String city) {
        this.name = name;
        this.city = city;
    }
}

一个类中可以有65535个构造函数(根据Oracle文档)。 但重要的是要记住这一点。 我们只通过CONSTRUCTOR OVERLOADING( https://beginnersbook.com/2013/05/constructor-overloading/ )实现这一目标。 您可以创建许多构造函数但具有不同的签名。

你可以在一个类中拥有尽可能多的构造函数.JAVA不会对类可以拥有的构造函数的数量施加任何限制。只需构造函数可以参数化或默认。

默认构造函数:默认构造函数没有参数,用于初始化具有相同数据的每个对象。

参数化构造函数:参数化构造函数具有一个或多个参数,用于使用不同的数据初始化每个对象。

TL;博士

对于具有合理功能的类,您将首先遇到其他问题,包括技术问题和非技术问题。 无用类的类文件格式的常量池所施加的技术限制是:

  • 65526用于具有您选择名称的班级。
  • 65527用于名为Code的类(每个类加载器一个)。
  • 65530表示名为java.lang.Object的类(每个VM一个)。

细节

在我发布答案之前,问题已经结束。 所以这里有@Holger已经涵盖的内容。

JVM规范的相关部分是4.1。 Java SE 11版中的ClassFile结构

u2             constant_pool_count;
cp_info        constant_pool[constant_pool_count-1];

注意-1 条目编号为1. 0用于表示:

  • 没有超类的类(有趣!)
  • 一个没有名字的形式参数
  • 没有版本信息的模块
  • 模块依赖,没有版本信息

如果该类被称为Code则该字符串常量将使用所需的Code属性名称进行重复(请参阅@ Holger的答案)。 如果要从默认包外部访问它,您将需要一个非常旧版本的javac

如果在字节代码中写入不是作弊,则可以通过抛出null (不是字节码中的常量)而不是调用super()或类似来删除一些条目。 我不记得构造函数的字节码验证的确切细节 - 如果不调用this()super()它们肯定无法正常终止。

javac运行越来越慢(O(n ^ 2)ish?)是优雅降级的一个很好的例子。 :)

游戏时间

通过编译具有不同数量的构造函数的小类,您可以非常轻松地看到这一点。

public class Min1 {
   public Min1() {
   }
   /* Followed by (int a), (byte a), (int a, byte b), etc. */
}

编译没有调试信息(人们是否仍然意外地使用调试信息分发类文件?)。

javac -g:none Min1.java

用好的旧javap列出内容。

javap -verbose Min1

应该给你一些类似的东西

Classfile /Users/tackline/code/scratch/minimal_class/Min1.class
  Last modified Dec 5, 2018; size 119 bytes
  MD5 checksum c1a6b7c31c286165e01cc4ff240e7718
public class Min1
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#7          // java/lang/Object."<init>":()V
   #2 = Class              #8             // Min1
   #3 = Class              #9             // java/lang/Object
   #4 = Utf8               <init>
   #5 = Utf8               ()V
   #6 = Utf8               Code
   #7 = NameAndType        #4:#5          // "<init>":()V
   #8 = Utf8               Min1
   #9 = Utf8               java/lang/Object
{
  public Min1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
}

您可以在一个类中创建65535个构造函数

更多信息: Oracle Docs

一个类可以拥有的构造函数数量没有限制。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM