It might be worth considering using a static factory method instead of a constructor.
I say instead, but obviously you cannot replace the constructor. However, you can hide the constructor behind the static factory method. Thus, we publish the static factory method as part of the class API, but at the same time we hide the constructor making it a private or closed package.
This is a fairly simple solution, especially compared to the Builder pattern (as seen from Joshua Bloch Effective Java 2nd Edition), be careful the Gang of Four Design Patterns define a completely different design pattern with the same name, so this can be a bit confusing). which means creating a nested class, a builder, etc.
This approach adds an extra layer of abstraction between you and your client, simplifies encapsulation, and makes it easier to make changes to the road. It also gives you instance control β since objects are created inside the class, you, not the client, decide when and how these objects are created.
Finally, it simplifies testing - providing a dumb constructor that simply assigns values ββto the fields without performing any logic or verification allows you to enter an invalid state in your system to check how it behaves and reacts to it, you wonβt be able to do it, if you are checking the data in the constructor.
You can read a lot more about this in the (already mentioned) Joshua Bloch Effective Java 2nd Edition - this is an important tool in all developer toolkits, and it is not surprising that this is the topic of the first chapter of the book .; -)
Following your example:
public class Book { private static final String DEFAULT_TITLE = "The Importance of Being Ernest"; private final String title; private final String isbn; private Book(String title, String isbn) { this.title = title; this.isbn = isbn; } public static Book createBook(String title, String isbn) { return new Book(title, isbn); } public static Book createBookWithDefaultTitle(String isbn) { return new Book(DEFAULT_TITLE, isbn); } ...
}
Whatever method you choose, it is good practice to have a main constructor that simply blindly assigns all values, even if it is just used by other constructors.