Faisal
Faisal

Reputation: 673

Is it possible to make sure that two identical objects don't exist in JVM?

Consider this database model:

Book
isbn primary key
title

In a RDBMS, the database makes sure that two identical rows don't exist for the above model.

Similarly, in Java consider this object model:

Book
- isbn: int
- title: String
+ Book(isbn)

Let's say we are creating a Book object:

Book b = new Book(123456);

Later, in some other part of the code we are creating again an identical Book object:

Book c = new Book(123456);

Can Java make sure that no two objects exist in the JVM heap if they are identical? Just like a RDBMS does?

Upvotes: 2

Views: 151

Answers (3)

jwenting
jwenting

Reputation: 5663

Neither of the other answers is technically correct.

They will often work, but in situations where multiple ClassLoaders are in play they will both fail.

Any object instance can ever only be unique within the context of a specific ClassLoader, thus 2 instances of the same Book can exist, even if you guard against multiples being created within a specific ClassLoader.

Usually this won't be a problem as many (especially simpler) programs will never have to deal with multiple ClassLoaders existing at the same time.

There is btw no real way to protect against this.

Upvotes: 3

oligofren
oligofren

Reputation: 22923

I do not know of a JVM internals specific way of doing this, but it is not that hard to achieve the basic goal. Joachim Sauer's answer goes into depth on why this might not be the greatest idea without some additional forethought :)

If you forego of thread safety, the code is basically just about creating a private constructor and use a factory method that keeps tab on created objects.

Pseudo Java follows

public class Book {
  // potential memory leak, see Joachim Sauer's answer (WeakReference)
  Map<Book> created = new Map<>();
  // other internal fields follow

  // can only be invoked from factory method
  private Book(String isbn){ /* internals */ }

  public Book get(String isbn){
     if(created.has(isbn)) return created.get(isbn);
     var b = new Book(isbn);
     b.add(isbn, b);
     return b;
  }
}

Converting this to a thread safe implementation is just about adding some details * and is another question. Avoiding the potential memory leak means reading up on weak references.

  • i.e. locks (synchronized), mutexes, Concurrent*, Atomic*, etc

Upvotes: 3

Joachim Sauer
Joachim Sauer

Reputation: 308061

There's no built-in mechanism in Java that automatically does this for you. You could build something for this, but probably shouldn't. And if you do, then probably not in the way that you show in your question.

First: let's assume that these objects are immutable, so the problem is reduced to "let no two objects be constructed that have the same attributes". This is not a necessary restriction, but this way I can already demonstrate the issues with this approach.

The first issue is that it requires you to keep track of each Book instance in your program in a single central place. You can do that quite easily by having a collection that you fill when an object is constructed.

However, this basically builds a massive memory leak into your program because if nothing else hangs on to this Book object, that collection still will reference it, preventing it from being garbage collected.

You can work around that issue by using WeakReference object to hold on to your Book objects.

Next, if you want to avoid duplicates, you almost certainly want a way to fetch the "original" instance of a Book if you can't create a new one. You can't do that if you simply use the constructor, since the constructor can't "return another object", it will always create and return a new object.

So instead of new Book(12345) you want something like BookFactory.getOrCreateBook(12345). That factory can then either fetch the existing Book object with the given id or create a new one, as required.

One way to make the memory leak issue easier to handle (and also to potentially allow multiple parallel sessions each with their own set of unique Book objects) is to make the BookFactory be a BookSession: i.e. you instantiate one and it keeps tracks of its books. Now that BookSession is the "root" of all Books and if it no longer gets referenced it (and all the books it created) can potentially be garbage collected.

All of this doesn't even get into thread safety which is solvable reasonably easily for immutable objects but can get quite convoluted if you want to allow modifications while still maintaining uniqueness.

A simple BookSession could look a little like this (note that I use a record for book only for brevity of this sample code, this would leave the constructor visible. In "real" code I'd use an equivalent normal class where the constructor isn't accessible to others):

record Book(int isbn, String title) {}

class BookSession {
    private final ConcurrentHashMap<Integer, Book> books = new ConcurrentHashMap<>();

    public Optional<Book> get(int isbn) {
        return Optional.ofNullable(books.get(isbn));
    }

    public Book getOrCreate(int isbn, String title) {
        return books.computeIfAbsent(isbn, (i) -> new Book(i, title));
    }
}

You can easily add other methods to the session (such as findByTitle or something like that).

And if you only ever want a single BookSession you could even have a public static final BookSession BOOKS somewhere, if you wanted (but at that point you have re-created the memory leak)

Upvotes: 6

Related Questions