Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8336043: Add quality of implementation discussion to Object.{equals, toString, hashCode} #20128

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

jddarcy
Copy link
Member

@jddarcy jddarcy commented Jul 10, 2024

First pass at adding some quality of implementation discussions around the overridable methods of Object.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change requires CSR request JDK-8336046 to be approved

Issues

  • JDK-8336043: Add quality of implementation discussion to Object.{equals, toString, hashCode} (Enhancement - P4)
  • JDK-8336046: Add quality of implementation discussion to Object.{equals, toString, hashCode} (CSR)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/20128/head:pull/20128
$ git checkout pull/20128

Update a local copy of the PR:
$ git checkout pull/20128
$ git pull https://git.openjdk.org/jdk.git pull/20128/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 20128

View PR using the GUI difftool:
$ git pr show -t 20128

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/20128.diff

Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 10, 2024

👋 Welcome back darcy! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Jul 10, 2024

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot changed the title JDK-8336043: Add quality of implementation discussion to Object.{equals, toString, hashCode} 8336043: Add quality of implementation discussion to Object.{equals, toString, hashCode} Jul 10, 2024
@openjdk openjdk bot added the csr Pull request needs approved CSR before integration label Jul 10, 2024
@openjdk
Copy link

openjdk bot commented Jul 10, 2024

@jddarcy The following label will be automatically applied to this pull request:

  • core-libs

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command.

* collections}.
*
* As as a quality of implementation concern, a particular
* implementation of this method may or may not support generating
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"may not support generating hash codes" sounds weird; maybe like "may or may not guard against cyclic data structures in recursive hash code generation."

I think the key is "recursive" here, as only recursive implementations are at risk of these faults.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, recursion is the easiest way to accidentally write an infinite loop, but I think a few other ones would be possible too ;-) I'll considering updating the wording to highlight the most likely hazards; thanks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately the typical implementation pattern is to recur through the component fields of the object. Not recursion through this but rather the hashCode implementation of some object will typically invoke hashCode of its component fields. Of course this can result in infinite recursion (really, StackOverflowError) if the object graph contains cycles, but in general, implementations of these methods don't guard against this possibility. (Nor should they. Adding such guard code seems impractical.)

Note that the default implementations of these methods for records and for Collection classes doesn't guard against cycles. (The collections contain some special cases for collection that contains itself, but not cycles in general.)

I have a general concern about advice that isn't actionable, such as "may not support" or "care should be taken" etc. If we really want to say something about cyclic data structures, maybe we could say something like this. For data structures that may contain cycles, these implementations should avoid traversing component fields that may lead to cycles. A class could simply inherit the method implementations from Object. Alternatively, the class could contain a unique ID that's used for determining the result of equals/hashCode/toString. (Of course, determining uniqueness is up to the application.)

@openjdk openjdk bot added the rfr Pull request is ready for review label Jul 10, 2024
@mlbridge
Copy link

mlbridge bot commented Jul 10, 2024

Webrevs

* to provide reasonable implementations of these methods. In
* particular, the implementations should take care to avoid using
* excessive memory, computational time, or any other resources.
* Additionally, during typical operation these methods should
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A note on terminology, I used "typical" for the new notes rather than "normal" to avoid conflating the discussion with the JLS concept of code "completing normally" (as opposed to completing abruptly due to an exception):

https://docs.oracle.com/javase/specs/jls/se22/html/jls-14.html#jls-14.1

* maintenance domain, any overrides of these methods should take care
* to provide reasonable implementations of these methods. In
* particular, the implementations should take care to avoid using
* excessive memory, computational time, or any other resources.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it's not wrong, advising against "excessive" usage is so vague as not to be actionable. Given some piece of code it's hard to say whether or not it does anything excessive. Consider a List with a million elements; as a practical matter, any one of these methods requires visiting every element. Is that excessive? Why or why not?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it's not wrong, advising against "excessive" usage is so vague as not to be actionable. Given some piece of code it's hard to say whether or not it does anything excessive. Consider a List with a million elements; as a practical matter, any one of these methods requires visiting every element. Is that excessive? Why or why not?

Hmm. In rare cases, I think providing not-quite-actionable advice is on net helpful.

My basic goal here is to convey "when you're implementing these methods, be considerate to your expected users." If that results in some extra contemplation during development or code review about whether or not the worst-case behavior of hashCode/toString/equals is excessive, I think that would be a reasonable outcome from this change.

* <em>not</em> throw any exception or other throwable; as always, a
* {@link VirtualMachineError} is possible during the execution of a
* method, often due to factors outside of the method's direct
* control.
Copy link
Member

@stuart-marks stuart-marks Jul 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Should not throw any exception or other throwable" is overly broad. However, there is a narrower sense where code that implements these methods "shouldn't" throw anything. I'd suggest focusing on precondition checking. Specifically, no object should ever be in a state such that calling one of these methods results in IllegalStateException or other exception based on the state of the object. In addition, no argument passed to equals() should ever cause IllegalArgumentException, ClassCastException, NullPointerException, or other exception based on the argument.

(This comment applies to other locations where the "excessive" wording is used.)

Copy link
Contributor

@kevinb9n kevinb9n Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would hope to spend as little space on this as possible, perhaps "This method should avoid throwing or propagating any exceptions unless it legitimately cannot adhere to this contract."
(or shorter)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would hope to spend as little space on this as possible, perhaps "This method should avoid throwing or propagating any exceptions unless it legitimately cannot adhere to this contract." (or shorter)

Pushed a version using that worked, but I expect discussion will continue.

* typical operation. In particular, {@link ClassCastException}
* should not be thrown if the argument has an incomparable type
* to this object and {@link NullPointerException} should not be
* thrown if the argument is {@code null}. The implementation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For these cases the recommendation should be to return false instead of throwing such exceptions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point; updated accordingly.

@stuart-marks
Copy link
Member

@kevinb9n You should take a look at this.

@rgiulietti
Copy link
Contributor

Here's a classical example of a directed acyclic graph (DAG, no cycles) consisting of 100 ArrayLists, each of size 2.
It takes some 1000x the age of the universe to compute hashCode() or to compare two such DAGs with equals(), so no machine will ever come to completion.
And toString() throws an OOME because the results wouldn't fit in the limits of String.

This is to say that even without cycles and even with quite small data structures as here, we might encounter excessive resource usages in space or time by just invoking these methods.
(The same would hold by replacing ArrayList with a record of 2 components that does not override the default methods.)

import java.util.ArrayList;

public class DAG {

    private static final int DEPTH = 100;

    public static void main(String[] args) {
        ArrayList<Object> dag0 = createSmallDAG();
        ArrayList<Object> dag1 = createSmallDAG();
        System.out.println(dag0.equals(dag1));
        System.out.println(dag0.hashCode());
        System.out.println(dag0);
    }

    private static ArrayList<Object> createSmallDAG() {
        ArrayList<Object> n = growDAG(null);
        for (int i = 1; i < DEPTH; ++i) {
            n = growDAG(n);
        }
        return n;
    }

    private static ArrayList<Object> growDAG(ArrayList<Object> n) {
        ArrayList<Object> m = new ArrayList<>();
        m.add(n);
        m.add(n);
        return m;
    }

}

@stuart-marks
Copy link
Member

Bloch's Effective Java, 3/e, Items 10, 11, and 12 have some useful background information on implementing equals, hashCode, and toString.

@jddarcy
Copy link
Member Author

jddarcy commented Jul 11, 2024

Bloch's Effective Java, 3/e, Items 10, 11, and 12 have some useful background information on implementing equals, hashCode, and toString.

Agreed; I reread over those items before posting the PR.

The "typical" wording was meant to cover guidance like Bloch's "have equals throw an AssertionError if the method should never be called if it escapes its intended domain."

* in the context of {@linkplain java.util##CollectionsFramework
* collections}.
*
* As as a quality of implementation concern, a particular
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"As as" typo.
"particular" is unnecessary. "a" -> "an"

@bridgekeeper
Copy link

bridgekeeper bot commented Sep 4, 2024

@jddarcy This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@jddarcy
Copy link
Member Author

jddarcy commented Sep 6, 2024

Keep alive.

@bridgekeeper
Copy link

bridgekeeper bot commented Oct 4, 2024

@jddarcy This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@jddarcy
Copy link
Member Author

jddarcy commented Oct 6, 2024

Keep alive again.

@@ -166,6 +208,7 @@ public Object() {}
* argument; {@code false} otherwise.
* @see #hashCode()
* @see java.util.HashMap
* @see ##objectOverrides Guidance for overriding {@code Object} methods
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this rendered as code font? I think only <-starting text is not rendered as code font.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
core-libs [email protected] csr Pull request needs approved CSR before integration rfr Pull request is ready for review
Development

Successfully merging this pull request may close these issues.

6 participants