-4

I'm working on a Java programming assignment. In this assignment, there's an immutable class Book and a mutable class BookCopy. You can have multiple BookCopy of the same Book.

Now here comes the tricky part, there's an interface Library that has a public void checkout(BookCopy copy) method, a public BookCopy buy(Book book) method which adds a new copy of the book to the library's collection and returns a defensive copy of the BookCopy, and a bunch of other functions including get()'s.

This is tricky because BookCopy is mutable so I do not override the equals() method and also because BookCopy is mutable, any function in the Library returning BookCopy will return defensive copies... so I have no idea how checkout(copy) will work since the passed in argument will always be a defensive copy AND because I did not override equals() in BookCopy, the argument will never equal anything in the Library private fields.

The assignment can be found at: https://openlearninglibrary.mit.edu/courses/course-v1:MITx+6.005.1x+3T2016/courseware/Week_9/ps3-beta/?activate_block_id=block-v1%3AMITx%2B6.005.1x%2B3T2016%2Btype%40sequential%2Bblock%40ps3-beta

5
  • People will be better able to help you if you show the relevant code snippets directly in Java, rather than trying to describe the Java in English.
    – Alexander
    Commented Sep 15, 2024 at 17:28
  • 1
    Being mutable does not preclude the ability to determine if two copies are equal. What exactly is mutable about BookCopy?
    – chepner
    Commented Sep 15, 2024 at 18:09
  • I'm guessing you misinterpreted some guideline somewhere about when to override equals(). It shouldn't matter that the object is mutable. The very fact that you're working with defensive copies means that you cannot use the in-built reference equality, but rather some form of value equality, and for that, you either have to check against some kind of a surrogate ID, or define equality in terms of the current value of several properties - which you can do by overriding equals(). Commented Sep 15, 2024 at 19:20
  • I can not override the equals() and hashCode() because of the interaction between mutable objects and Set/hashCode (add a mutable object to a set -> mutate object -> it's possible object will no longer be found in set)
    – Ray
    Commented Sep 15, 2024 at 23:36
  • 1
    -1, I hate questions where we have to work ourselves through links and downloads to find the relevant code which is in this case not just an addendum, but necessary to understand the problem. These kind of questions tends to become unreadable when the external link will vanish in the near future.
    – Doc Brown
    Commented Sep 16, 2024 at 9:45

3 Answers 3

2

"Finally, you'll be expected to implement equality appropriately for all the types." From Problem Set 3: The Librarians - Overview - Paragraph 1

Have you ever heard of value objects and entities? The difference is all about what establishes the objects identity. A value objects identity is about it's values. An entities is not. An entity could change every value and still be itself. Understand, this is about modeling. This is a choice we're making about how we treat something.

For example, if you loan me a dollar, and I give you back a dollar, only for you to get upset that I didn't give you back the same dollar, then you're treating your dollar like an entity and not a value object.

Caring more about the dollars serial number than the face value of the dollar is a modeling choice. It really has nothing to do with dollars being immutable. However, when dealing with something like a gift card, whose value can change over time, trying to model that things identity as a value object based on its current value becomes silly.

Immutable things can have their identities modeled either way. There are advantages to being able to model identity on values. But mutable things make basing identity on values a huge pain.

Identity is important to you here because how you implement equals (and you were told to implement equals) shows how you're modeling identity.

Overriding equals is not a no-no for mutable objects if you've given them an identity. Just because part of an object is mutable doesn't mean the whole thing is. Keep wherever you're storing the identity immutable and the rest can mutate as it likes. This will work with HashCodes and Sets just fine if you understand that those will be operating on the immutable identity and not the mutable values.

the assignment does not allow the change of the BookCopy(Book book) constructor - Ray

    /**
     * Make a new BookCopy, initially in good condition.
     * @param book the Book of which this is a copy
     */
    public BookCopy(Book book) {
        throw new RuntimeException("not implemented yet");
    }

Looks to me like you're being required to change it.

Don't change the method signatures and specifications: The public methods provided for you to implement in Book, BookCopy, and Library must use the method signatures and the specifications that we provided.

That means you can't simply pass in the UUID Doc Brown wanted you to use. Your constructor will have to reach out and get it (yuck).

Now this is student code so such shenanigans are forgivable. But, rather than making you import or implement some UUID library, a typical student solution for this problem is to use a static int. You can increment it in the constructor. It will tell you how many instances of BookCopy you have created (not that you really care about that). It wont tell you which book it's a copy of, or how many copies of that book exist, but it will still be unique and that's enough. Save whatever it was when the BookCopy was constructed and save whatever book it's a copy of and that's your identity.

11
  • Currently I only have two fields for BookCopy: Book book and Condition cond. Overriding equals() will mean a BookCopy is equal to another if they have the same condition and book. But in SmallLibrary, there's a Set<BookCopy> and it should be able to insert the same BookCopy as separate objects (same book and condition), so this will not work.
    – Ray
    Commented Sep 16, 2024 at 13:43
  • For the static int idea, the problem would be that whenever I return a defensive copy of BookCopy (for example in buy(Book book) in SmallLibrary), it will increment the static int. Basically, the tricky part is in the buy(Book) and checkout(BookCopy) functions: I want to be able to call checkout on a BookCopy, so I need the exact identity of the BookCopy that was created by buy(Book). At the same time, when a BookCopy is created in buy(Book), returning a reference to BookCopy might mean the client will call setCondition() which will mutate the BookCopy, which is stored in the SmallLibrary
    – Ray
    Commented Sep 16, 2024 at 13:44
  • One thing I will note is that the assignment doesn't seem to care about what happens to the Condition of a BookCopy anywhere, so maybe it is OK if a BookCopy Condition can be mutated outside the Library
    – Ray
    Commented Sep 16, 2024 at 13:53
  • Overriding equals() should mean a BookCopy is equal to another if they have the same identity. Compare value of that saved static int (and the Book that was copied if you want to be extra thorough). Commented Sep 16, 2024 at 14:07
  • The identity int that we get from the static int (lets call it id) can have a public getter. Just don't give it a setter. I don't see a requirement for defensive copies. What problem are you trying to solve with that? Commented Sep 16, 2024 at 14:13
1

From what you wrote, this sounds like a trivial problem. Each BookCopy object should have a unique identifier which checkout can use to identify it in the library. It could be simply a GUID, or a counter for each BookCopy, combined with the ID of the Book. The library itself should store the set of all of these IDs associated with the information if a book is currently checked out, who is currently posessing the book and since when.

Of course, the unique identifier of a BookCopy should stay constant after it is initialized once, at least that is what I would expect from such an object. Still a BookCopy object does not need to be immutable in full in case it contains more (non-identifying) attributes. You can also override equals for comparing the IDs, if you like (but if you don't like it, implement the lookup/equality checking using the IDs separately, in a function of your choice).

3
  • The ID of the book is a good idea but the assignment does not allow the change of the BookCopy(Book book) constructor so there is no way to initialize each BookCopy with its own ID. Overriding the equals method is a big no-no for mutable objects (because of hashCode and Set interaction). My feeling is that I have no choice but to expose the BookCopy objects in Library to get this to work... (ie don't return defensive copies of BookCopy/Set<BookCopy>)
    – Ray
    Commented Sep 15, 2024 at 23:24
  • "the assignment does not allow the change of the BookCopy(Book book) constructor , so there is no way to initialize each BookCopy with its own ID.". Nonsense, you are expected to implement the constructor - (maybe you meant not to change it's signature, but that's not necessary to generate unique IDs). You also need to add code for creating defensive copies, maybe a copy constructor.
    – Doc Brown
    Commented Sep 16, 2024 at 9:41
  • ... ok, after reading Candied Orange's answer, I saw it contains already the recommendations I gave you in the former comment, in a more detailed way.
    – Doc Brown
    Commented Sep 16, 2024 at 10:11
0

It might be that I'm stating the obvious, I'm helpless I find it difficult to refrain, what if is not the obvious and I'm just smart.

... because BookCopy is mutable so I do not override the equals() method...

...or override equals and hashCode methods and remove them from any data structure relying on hashCode before mutating the objects, mutate them and then add them back to the data structure, or develop own implementations of collections relying on hashCode to support removing the elements from the collection when collection's getters methods are called, kind of the collection saying "you need an element form the collection? take it, it's out, it's your concern what you do with it". It might be that the assignment is just a mean to get a new set of implementations for Collection interface safe to use with mutable objects.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.