如何在Java中从一个构造函数调用另一个构造函数?

2663

在同一个类中,是否可能从一个构造函数中调用另一个构造函数?如果可以,怎么做?还有,如何最好地调用另一个构造函数(如果有多种方法)?


2
我认为你的问题前提是错误的。不要在构造函数中调用另一个构造函数,而应该使用工厂模式。静态工厂方法首先创建所有较低级别的对象。然后它构造高级别的对象,并从工厂调用中返回。这种技术可以减少模型的复杂性,有助于维护、清晰度和测试。 - David Medinets
1
我通常会转向使用私有构造函数和工厂方法,因为由于其限制,构造函数违反了开闭原则。我认为这个评论应该是正确的答案,其他任何东西都会让你的团队成员感到困惑。 - Richard
很抱歉,如果您想要这样做,过度充电构造函数并不是一个好的实践。如果您想要包装内容,那么可以做到,但是目的不同,不是构造函数。public class Foo { private int x;public Foo() { } public Foo(int x) { this.x = x; } public Foo(int x, int y) { this.x = x; this.y = y } - Augusto
1
在Java中从另一个构造函数调用构造函数主要是为了为应该实际构造对象的一个构造函数提供默认值,然后在构造函数体中分配值就足够了。如果您的对象需要复杂的构建,则这是一种代码异味,表明您的类缺乏凝聚力。如果构造函数对您来说不够用,那么您设计类时可能做得很差,这将在未来进行更改时发现。 - Stuporman
我们为什么不能使用“new”代替“this”呢?例如,在无参构造函数中调用“new Foo(5);”是否正确?如果不是,为什么?@peter - abc123
22个回答

3383

是的,这是可能的:

public class Foo {
    private int x;

    public Foo() {
        this(1);
    }

    public Foo(int x) {
        this.x = x;
    }
}

使用super而不是this来链接到特定超类构造函数,注意您只能链接到一个构造函数它必须是构造函数主体中的第一条语句

另请参见此相关问题,该问题涉及C#,但适用相同原则。


40
我认为不可能在同一个构造函数中既调用超类的构造函数又调用本类的另一个构造函数,因为两者都需要放在第一行。 - gsgx
39
确实如此。你只能链接到一个其他构造函数。 - Jon Skeet
53
这段内容需要出现在第一行,但您可以在调用构造函数之前进行计算:您可以在第一行的this()参数中使用静态方法,并封装任何必须在调用其他构造函数之前执行的计算在该静态方法中。(我已将其添加为单独的答案。) - Christian Fries
10
@gsingh2011 我知道现在有点晚了,但是你可以使用 this(...) 来调用重载构造函数,然后在那个重载构造函数中,你可以使用 super(...) 调用基类的构造函数。 - Ali
4
这确实是“在一个构造函数中调用另一个构造函数”,但这并不会实现 OP 想要的功能,即通过多个构造函数对单个对象进行初始化,其中一个构造函数链接到另一个构造函数。只需在一个构造函数调用中创建另一个对象就不能实现相同的效果。 - Jon Skeet
显示剩余15条评论

299

使用 this(args)。首选模式是从最小的构造函数开始逐步构建。

public class Cons {

    public Cons() {
        // A no arguments constructor that sends default values to the largest
        this(madeUpArg1Value,madeUpArg2Value,madeUpArg3Value);
    }

    public Cons(int arg1, int arg2) {
       // An example of a partial constructor that uses the passed in arguments
        // and sends a hidden default value to the largest
        this(arg1,arg2, madeUpArg3Value);
    }

    // Largest constructor that does the work
    public Cons(int arg1, int arg2, int arg3) {
        this.arg1 = arg1;
        this.arg2 = arg2;
        this.arg3 = arg3;
    }
}

您还可以使用更近期提倡的valueOf或只是"of"方法:

public class Cons {
    public static Cons newCons(int arg1,...) {
        // This function is commonly called valueOf, like Integer.valueOf(..)
        // More recently called "of", like EnumSet.of(..)
        Cons c = new Cons(...);
        c.setArg1(....);
        return c;
    }
} 

要调用父类,请使用super(someValue)。在构造函数中,调用super必须是第一个调用,否则会出现编译错误。


33
如果使用了许多构造器参数,请考虑使用建造者模式。请参阅Joshua Bloch的《Effective Java》中的第2条建议。 - koppor
6
使用工厂方法newCons实现上一个方法的问题在于,使用setArg1(...)来更改对象的状态,而该对象的字段最好设置为final。由于我们尽可能地使对象不可变,如果不是完全不可变的话,建造者模式将更正确地解决这个问题。 - YoYo
2
你宁愿这样做吗 :: public Cons() { this(madeUpArg1Value,madeUpArg2Value); } - LordHieros
3
在Java中,较低元数的构造函数通常会调用更高元数的构造函数,并且不做其他操作。例如,如果一个类K有两个final字段a、b,那么“一般构造函数”将是 K(A a, B b) { this.a = a; this.b = b; }。接着,如果b有一个合理的默认值,可以有一个单参数构造函数 K(A a) { this(a, DEFAULT_B); },如果也有一个默认的a,则会有一个默认构造函数:K() { this(DEFAULT_A); }。这是Java中相当常见的惯例。 - Joshua Taylor
如果您有一个final字段(因此必须设置它),那么默认构造函数必须将其设置。如果您的高阶构造函数调用默认构造函数(这必须在任何其他操作之前完成),则高阶构造函数永远没有任何选项来设置这些字段中的任何一个。 - Joshua Taylor
这个可以在Android Studio中使用,而被接受的答案则不行。不确定为什么。 - Hong

240

[注意:我想补充一个方面,其他答案中没有看到的内容:如何克服必须将this()放在第一行的要求限制。]

在Java中,可以通过this()从构造函数调用同一类的另一个构造函数。但请注意,this必须放在第一行。

public class MyClass {

  public MyClass(double argument1, double argument2) {
    this(argument1, argument2, 0.0);
  }

  public MyClass(double argument1, double argument2, double argument3) {
    this.argument1 = argument1;
    this.argument2 = argument2;
    this.argument3 = argument3;
  }
}

this 必须出现在第一行似乎是一个很大的限制,但你可以通过静态方法构造其他构造函数的参数。例如:

public class MyClass {

  public MyClass(double argument1, double argument2) {
    this(argument1, argument2, getDefaultArg3(argument1, argument2));
  }

  public MyClass(double argument1, double argument2, double argument3) {
    this.argument1 = argument1;
    this.argument2 = argument2;
    this.argument3 = argument3;
  }

  private static double getDefaultArg3(double argument1, double argument2) {
    double argument3 = 0;

    // Calculate argument3 here if you like.

    return argument3;

  }

}

16
确实可以以这种方式调用静态方法,以便对参数值执行复杂计算,这是可以的。但是,如果觉得需要在构造函数委托之前编写代码(this(...)), 那么可以合理地假设某处出现了可怕的错误,而设计可能需要重新考虑一下。 - Software Engineer
16
我同意,一个非常复杂的转换可能意味着设计上存在问题。但是1)有些简单的变换可能也会有用 - 并不是所有构造函数都只是对其他函数的线性投影;2)在支持历史代码等情况下,这些信息可能会变得很重要。(尽管我同意你的结论,但我不明白为什么它会导致贬值)。 - Christian Fries
2
@RodneyP.Barbati: 我看到通过你描述的方式有几个问题:a)通过那种方式,不可能演示在构造函数中使用静态方法(这是示例的目的);-)并且 b) 如果按照你的方式做,字段无法是final(最终字段只能被初始化一次)。 - Christian Fries
1
@RodneyP.Barbati:另外还有两个方面: c)我认为你应该总是在一个单一的点进行对象初始化,这个点必须是最通用的构造函数。如果对象初始化需要复杂的任务(对象初始化不是惰性的)或者检查或获取一些资源(比如文件),那么你只需要做一次即可。 d)如果添加另一个参数(比如参数4),其初始化取决于参数1到参数3的值,那么在你的情况下,你将不得不更改所有构造函数,而在这里,你只需要添加一个构造函数,让3个参数调用4个参数的构造函数即可。 - Christian Fries
要了解克服“必须是构造函数中的第一个语句”的限制的更一般方法,请参见此答案。它适用于super()this()调用。 - John McClane

44

当我需要在代码中调用另一个构造函数(不是第一行),我通常会使用像这样的帮助方法:

class MyClass {
   int field;


   MyClass() {
      init(0);
   } 
   MyClass(int value) {
      if (value<0) {
          init(0);
      } 
      else { 
          init(value);
      }
   }
   void init(int x) {
      field = x;
   }
}

但更常见的做法是,我尝试通过在第一行从简单构造函数调用更复杂的构造函数来实现, 尽可能地这样做。对于上面的例子

class MyClass {
   int field;

   MyClass(int value) {
      if (value<0)
         field = 0;
      else
         field = value;
   }
   MyClass() {
      this(0);
   }
}

32
在构造函数中,您可以使用“this”关键字来调用同一类中的另一个构造函数。这样做被称为“显式构造函数调用”。
这是另一个Rectangle类,其实现与对象部分中的不同。
public class Rectangle {
    private int x, y;
    private int width, height;

    public Rectangle() {
        this(1, 1);
    }
    public Rectangle(int width, int height) {
        this( 0,0,width, height);
    }
    public Rectangle(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }

}

这个类包含一组构造函数。每个构造函数都会初始化矩形的某些或所有成员变量。


1
为什么你不在Rectangle()中调用第二个构造函数Rectangle(int width, int height),而是调用了Rectangle(int x, int y, int width, int height) - ANjaNA
1
@RodneyP.Barbati 在这种情况下我不能同意。该模式不允许使用最终字段。 - Wes

20

使用此关键字,我们可以在同一类中的一个构造函数中调用另一个构造函数。

例:

 public class Example {
   
      private String name;
   
      public Example() {
          this("Mahesh");
      }

      public Example(String name) {
          this.name = name;
      }

 }

20

正如其他人已经说过的那样,您使用this(…),这被称为显式构造函数调用

然而,请注意,在此类显式构造函数调用语句中,您不能引用:

  • 任何实例变量
  • 任何实例方法
  • 在此类或任何超类中声明的任何内部类,或
  • this
  • super

如JLS(§8.8.7.1)所述。


16

是的,在一个类中可以有任意数量的构造函数,并且它们可以被另一个构造函数使用this()来调用。[请不要将this()构造函数调用与this关键字混淆]。this()this(args) 应该是构造函数中的第一行。

例如:

Class Test {
    Test() {
        this(10); // calls the constructor with integer args, Test(int a)
    }
    Test(int a) {
        this(10.5); // call the constructor with double arg, Test(double a)
    }
    Test(double a) {
        System.out.println("I am a double arg constructor");
    }
}

这被称为构造函数重载。
请注意,对于构造函数,仅适用于重载概念,而不是继承或覆盖。


13

是的,可以从一个构造函数调用另一个构造函数。但是有一条规则需要遵循:如果从一个构造函数调用另一个构造函数,则该新构造函数调用必须是当前构造函数中的第一条语句

public class Product {
     private int productId;
     private String productName;
     private double productPrice;
     private String category;

    public Product(int id, String name) {
        this(id,name,1.0);
    }

    public Product(int id, String name, double price) {
        this(id,name,price,"DEFAULT");
    }

    public Product(int id,String name,double price, String category){
        this.productId=id;
        this.productName=name;
        this.productPrice=price;
        this.category=category;
    }
}

因此,类似以下内容将无法正常工作。

public Product(int id, String name, double price) {
    System.out.println("Calling constructor with price");
    this(id,name,price,"DEFAULT");
}

此外,在继承的情况下,当子类对象被创建时,首先调用超类构造函数。

public class SuperClass {
    public SuperClass() {
       System.out.println("Inside super class constructor");
    }
}
public class SubClass extends SuperClass {
    public SubClass () {
       //Even if we do not add, Java adds the call to super class's constructor like 
       // super();
       System.out.println("Inside sub class constructor");
    }
}

因此,在这种情况下,还有另一个构造函数调用在任何其他语句之前被声明。


9

我会告诉你一个简单的方法

两种类型的构造函数:

  1. 默认构造函数
  2. 参数化构造函数

我将通过一个示例来解释。

class ConstructorDemo 
{
      ConstructorDemo()//Default Constructor
      {
         System.out.println("D.constructor ");
      }

      ConstructorDemo(int k)//Parameterized constructor
      {
         this();//-------------(1)
         System.out.println("P.Constructor ="+k);       
      }

      public static void main(String[] args) 
      {
         //this(); error because "must be first statement in constructor
         new ConstructorDemo();//-------(2)
         ConstructorDemo g=new ConstructorDemo(3);---(3)    
       }
   }                  

在上面的示例中,我展示了3种调用方式:
  1. 在构造函数中,this()调用必须是第一条语句。
  2. 这是一个无名对象。this会自动调用默认构造函数。
  3. 这将调用参数化构造函数。

注意:this必须是构造函数中的第一条语句。


4
在主方法中有以下代码:“//this(); error because "must be first statement in constructor”(注释:此处错误,因为“必须成为构造函数中的第一个语句”)。这段代码并不是很清晰。如果你的意思是说_this()_不能从_main_方法内调用,那么是的,因为_main_是静态的,并且没有引用_this()_。 - S R Chaitanya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接