What is the singleton design pattern?
With the singleton design pattern, you allow only one instance of a class to be created. If your class Person
is
singleton, then there can be at most one Person
in your entire application. As the singleton design pattern is a quite
straightforward pattern, there isn't much explanation needed. Though there are several ways in which to implement it (
especially in Java).
All code examples in this blog posts are available on GitHub.
Singletons in Java
The main idea on how to implement it, is by creating a class with properties and methods as you normally would do.
However, now, you don't want the outside world to create instances of your class. Thus, you make your constructors
private. This does leave you with a problem: how does the first instance gets created? This is where getInstance()
chimes in. This is a public static function that you add to your class. This function should return the only instance of
your class. There are two ways in which that first instance gets initialized.
You can initialize your singleton instance using lazy or eager initialization. Both have their advantages. But what's
the difference? When you are using eager initialization, the only class instance will always be created. This is not
the case when you are using lazy initialization. Because then the only instance will be created after calling
the getInstance
method.
Eager initialization
Let's create a singleton class using eager initialization. Recall that using eager initialization, the instance always
gets created. In the following example, the class Person
is singleton:
1public class Person { 2 3 private final static Person instance = new Person(); 4 private final String name = "Frodo Baggings"; 5 6 private Person() { 7 } 8 9 public static Person getInstance() { 10 return instance; 11 } 12 13 public String getName() { 14 return name; 15 } 16 17}
There are a few things that you should notice now. On line 3, private final static Person instance = new Person();
,
the only actual Person
instance gets created. Then, on line 6-7, we have an empty private constructor. We had to
explicitly add this constructor, because by making it private, others can't create instances. And finally, on line 9-11,
we have our getInstance()
method. As you can see, this method returns our already created instance of Person
.
If we create a Main
method and call getInstance
twice, we can verify that both objects are actually the same. One
way to do that is by checking out their hash codes, they will be the same:
1public static void main(final String[] args) { 2 final Person person1 = Person.getInstance(); 3 final Person person2 = Person.getInstance(); 4 5 // Person 1 and 2 both have the same hash code: 6 System.out.println("Hash code person1: " + person1.hashCode()); 7 System.out.println("Hash code person2: " + person2.hashCode()); 8}
Lazy initialization
Now let's create an example using lazy initialization. Recall that using lazy initialization, you don't always have a
created instance: there are always 0 or 1 instances. After calling getInstance
, you first instance shall be created.
The advantage of this, over eager initialization, is that if you do not use the singleton class, you don't have to hold
on to resources that you don't use. In the following example, using lazy initialization, our Book
class is singleton:
1public class Book { 2 3 private static Book instance; 4 private final String name; 5 6 private Book(final String name) { 7 this.name = name; 8 } 9 10 public static synchronized Book getInstance(final String name) { 11 if (instance == null) { 12 instance = new Book(name); 13 } 14 15 return instance; 16 } 17 18 public String getName() { 19 return name; 20 } 21 22}
Our book has one property: name
. On line 3 you see our instance property, however, it hasn't been initialized yet. On
the lines 6-8, you see, again, a private constructor. Then, on line 10-16, we have our getInstance
method. You'll
probably notice that it's bigger than our eager example. Within the if-statement, if (instance == null)
, we check if
an instance has already been created. If not: create it or otherwise, return the already created instance.
In the following Main
method, we create two books. Notice that we provide two different names: Name 1
and Name 2
.
We then print the names, and we see that "Name 1" gets printed both times. That's because the first call
of getInstance
creates our Book
instance with the given name. The second time we called getInstance
, that first
instance actually gets returns. Thus, nothing actually happens with that second name:
1public static void main(final String[] args) { 2 final Book book1 = Book.getInstance("Name 1"); 3 final Book book2 = Book.getInstance("Name 2"); 4 5 System.out.println("Name book1: " + book1.getName()); // Out: Name 1 6 System.out.println("Name book2: " + book2.getName()); // Out: Name 1 7}
Beware of multi-threads
When you use lazy initialization, you must be careful in multithreaded applications. In multithreaded applications, it
may happen that different threads both create singleton instances, causing you application to have multiple instances of
the same class, that should be singleton. That's why we make our getInstance
method so-called thread-safe with
concurrency control. One way to do that is by making the method synchronized
, you can see that in the above lazy
initialization example.
Singletons in Kotlin
See all that java code above? Check out how we make a singleton class Earth
in Kotlin:
1object Earth { 2 const val greeting = "Hello!" 3}
That's it! And it's even thread safe. In Kotlin we use the object
-keyword to create thread-safe singletons. You can
add properties as you wish, in this case we added greeting
, that we can access like this:
1fun main() { 2 println(Earth.greeting); 3}
Thus concludes the singleton design pattern Java and Kotlin. Hopefully you've learned something, and you can bring it into practice. All examples are available in our GitHub repository.