开发者

Why can a constructor be called only once per instance?

开发者 https://www.devze.com 2022-12-16 13:53 出处:网络
Why are constructor calls in Java allowed only once per instance? If woul开发者_运维知识库d be useful to set multiple instance variables in one call rather than calling several setters.If you want to

Why are constructor calls in Java allowed only once per instance? If woul开发者_运维知识库d be useful to set multiple instance variables in one call rather than calling several setters.


If you want to set several variables at once just define a function:

public class Person {
  public Person(String firstName, String lastName, Date dateOfBirth) {
    set(firstName, lastName, dateOfBirth);
  }

  public void set(String firstName, String lastName, String dateOfBirth) {
    ...
  }
}

By definition an object is only constructed once, hence the constructor is only called once.

One thing worth noting: it's more common to favour immutability, so:

public class Person {
  private final String firstName;
  private final String lastName;
  private final Date dateOfBirth;

  public Person(String firstName, String lastName, Date dateOfBirth) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.dateOfBirth = dateOfBirth == null ? null new Date(dateOfBirth.getTime());
  }

  public String getFirstName() { return firstName; }
  public String getLastName() { return lastName; }
  public Date getDateOfBirth() { return dateOfBirth == null ? null : new Date(dateOfBirth.getTime()); }

  public Person withFirstName(String firstName) {
    return new Person(firstName, lastName, dateOfBirth);
  }

  public Person withLastName(String lastName) {
    return new Person(firstName, lastName, dateOfBirth);
  }

  public Person withDateOfBirth(Date dateOfBirth) {
    return new Person(firstName, lastName, dateOfBirth);
  }
}

because a lot of concurrency issues simply go away when you do this. Not just concurrency issues too. String, BigDecimal, BigInteger and some other standard classes are immutable (immutable means once instantiated its state can never change). Date isn't. You see in the above code I have to constantly defensively copy the date of birth? That's because Date is not immutable. If you didn't a caller could do this:

public class Person {
  private final Date dateOfBirth;

  ...

  public Date getDateOfBirth() { return dateOfBirth; }
}

Person person = new Person(...);
Date date1 = person.getDateOfBirth();
date1.setTime(1000000000L);
System.out.println(person.getDateOfBirth());

The date of birth will have changed. That problem is caused solely by Date being mutable. That's another reason to favour immutability.


Your mistake is assuming that you should be writing default constructors and setting all the values using setters. An object should be 100% ready to go when it's created, so you should be writing one constructor that initializes its entire state.

It's not the Java "gods" that are mistaken, it's you.


There a deeper point here: Each method can run any number of times, zero to many, per object. The fact that a constructor can run only once is an advantage as it allows you to write code that will be executed exactly once per object. For instance: registration, acquisition of external resources (files, sockets, DB connections), data manipulation (if you want your input to be sorted you make the sorting inside the constructor and the language ensures that sorting will take place).


Another alternative to the constructor and other constructor patterns described here is the Builder pattern. There are plenty of pages describing how it works, but can be summeraised with the following concise example:

import java.awt.Color; import java.util.LinkedList; import java.util.List;

public class Badger {
private int legs;
private boolean nose;
private List<Color> colors;

private Badger() {
    colors = new LinkedList<Color>();
}

public static class BadgerBuilder {
    private Badger badger;

    private BadgerBuilder(){}

    public static BadgerBuilder buildBadger() {
        BadgerBuilder b = new BadgerBuilder();
        b.badger = new Badger();
        return b;
    }

    public BadgerBuilder legs(int legs) {
        badger.legs = legs;
        return this;
    }

    public BadgerBuilder hasNose(boolean nose) {
        badger.nose = nose;
        return this;
    }

    public BadgerBuilder addColor(Color c) {
        badger.colors.add(c);
        return this;
    }

    public Badger build() {
        return badger;
    }
}

public static void main(String [] args) {
    Badger b = BadgerBuilder.buildBadger()
                                .legs(4)
                                .hasNose(true)
                                .addColor(Color.black)
                                .addColor(Color.white)
                                .build();
}


Nothing prevents you from writing another method that sets all the desired fields at once. It can even be called from the constructor in order to prevent code duplication.


You can call one constructor from another.

For example:

class MyClass {

  public MyClass() {
     // initialize what you want
  }

  public MyClass(String custom) {
    this();
    // custom initialization
  }
}


As many have mentioned, the constructor doesn't actually construct but sets the initial values on the object. This is important in that it is the only place to set final fields that are otherwise uninitialized. This should be a clue that the constructor isn't the same as any other function. Also, having a good and complete constructor can help define the invariants.

However, there are some tradeoffs and potential pitfalls here. It is not always correct that objects are "completely finished" when the constructor is complete. The constructors are invoked bottom up inheritance-wise but completed top down. Thus even when your constructor is finished, subclasses may still have work to do. So if you pass out a reference to yourself (an escape) during construction and that other class expect the caller to be in a complete/stable state, then problems could occur because the subclasses have not had their opportunity complete. This is particularly risky in highly concurrent code. In this case it becomes safer or even mandatory to use a construct/init pattern. Thus it may not be possible to get the object into a safe enough state to pass to other objects in the constructor. The are many cases where objects have circular dependencies and it becomes necessary to do basic construction on all then init them all.

IRC Josh Bloch (Effective Java) and Brian Goetz (Java Concurrency in Practice) both have some good discussions of these topics. Factory construction with private constructors if often a good alternative and has many benefits and is covered by Bloch.

0

精彩评论

暂无评论...
验证码 换一张
取 消