Reputation: 673
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
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
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.
synchronized
), mutexes, Concurrent*
, Atomic*
, etcUpvotes: 3
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