okaywit / guava-libraries

Automatically exported from code.google.com/p/guava-libraries
Apache License 2.0
0 stars 0 forks source link

Objects.exactCast #1499

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
Would be nice/useful to add the following method to 
"com.google.common.base.Objects"?

/**
 * Helper to cast the given object to the given type.
 *
 * The cast is to an exact same type. Casting to super-types is not supported.
 *
 * @param targetType The type to cast to
 * @param object     The object which gets casted to the targetType
 * @return The given object casted to the given type or <code>null</code> if
 *         the given object is from another type.
 */
<T> T exactCast(Class<T> targetType, Object object)

My primary use-case to reduce the boilerplate code in equals() implementations:

/**
 * Version with exactCast()
 */
public boolean equals(Object o) {
    if (o == null) {
        return false;
    }
    if (MyClass.class != o.getClass()) {
        return false;
    }   

    return Objects.equal(this.fieldOne, other.fieldOne)
        && Objects.equal(this.fieldTwo, other.fieldTwo);
}

/**
 * Version with exactCast()
 */
public boolean equals(Object o) {
    MyClass other = exactCast(MyClass.class, o);    

    return other != null
        && Objects.equal(this.fieldOne, other.fieldOne)
        && Objects.equal(this.fieldTwo, other.fieldTwo);
}

Not supporting super-types is because it's hard (impossible?) to do in GWT and 
the primary use-case for me is casting to the exact class.

If you find the feature useful I'd love to contribute the code for it.

Original issue reported on code.google.com by j...@barop.de on 7 Aug 2013 at 3:32

GoogleCodeExporter commented 9 years ago
it might make sense to return Optional<T> instead

Original comment by SeanPFl...@googlemail.com on 8 Aug 2013 at 3:19

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
FYI, checking that "MyClass.class != o.getClass()" rather than "!(o instanceof 
MyClass)" is bad practice for equals, because it means that 
myClass.equals(mySubclass) == false while mySubclass.equals(myClass) == true, 
which breaks the equals() contract. For that matter, 
mySubclass.equals(mySubclass) == false unless you override equals() in the 
subclass, and again, that tends to break the equals contract.

In general, the best way to deal with ensuring the object passed to equals is 
both non-null and an instance of the correct type is just:

if (o instanceof MyClass) {
  MyClass other = (MyClass) o;
  // check stuff
}

Original comment by cgdecker@google.com on 8 Aug 2013 at 3:40

GoogleCodeExporter commented 9 years ago
Thanks for your reply!

Checking for a `class` constant is indeed a bad idea because it causes touble 
in subclasses. But isn't the `instanceof` approach suffering from the same 
problem when `equals()` get's overridden?

Example:
static class A {
  @Override
  public boolean equals(Object obj) {
    return obj instanceof A;
  }
}
static class B extends A {
  @Override
  public boolean equals(Object obj) {
    return obj instanceof B;
  }
}

a.equals(b) => true
b.equals(a) => false

What about not checking for a `class` constant but for `this.getClass()`. This 
would guarantee symmetry with subclasses.

new proposal:
<T> T sameTypeOrNull(T object, Object other)

Then `equals()` could be implemented like this:

public boolean equals(Object o) {
    MyClass other = sameTypeOrNull(this, o);    

    return other != null
        && Objects.equal(this.fieldOne, other.fieldOne)
        && Objects.equal(this.fieldTwo, other.fieldTwo);
}

Original comment by j...@barop.de on 15 Aug 2013 at 12:17

GoogleCodeExporter commented 9 years ago
The `o instanceof MyClass` approach works with proxies, which is quite 
important. Overriding it in a subclass can't be done in a consistent way.

Testing `o.getClass() == getClass()` is symmetrical and allows sane overriding, 
but doesn't work with proxies. This is usually considered worse than the first 
approach.

Using `o.getClass() == MyClass.class` seem to be plain wrong, neither proxies 
nor overriding work.

There's no really good solution, as supporting both proxies and overriding 
correctly requires a helper method, see 
http://www.artima.com/lejava/articles/equality.html

Original comment by Maaarti...@gmail.com on 15 Aug 2013 at 1:30

GoogleCodeExporter commented 9 years ago
I see that the article I linked is a bit too long for what I wanted to clarify. 
The point is that you sometimes want the subclass instances to be able to 
`equals` to a superclass class instance (proxies or different implementation of 
the same concept) and sometimes not (subclasses adding new properties like 
ColoredPoint). For this a `canEqual` method is needed.

Original comment by Maaarti...@gmail.com on 15 Aug 2013 at 2:35

GoogleCodeExporter commented 9 years ago
> Testing `o.getClass() == getClass()` is symmetrical and allows sane 
overriding, but doesn't work with proxies.

For this, I have a "magic" getNonProxyClass() and test 
`getNonProxyClass(o.getClass()) == getNonProxyClass(getClass())`

when I need to support proxies (like in JPA entities) but do not want 
`canEqual` stuff (which works unless someone forgets to override it).

Original comment by piotr.fi...@gmail.com on 13 Nov 2013 at 12:49

GoogleCodeExporter commented 9 years ago
This issue has been migrated to GitHub.

It can be found at https://github.com/google/guava/issues/<issue id>

Original comment by cgdecker@google.com on 1 Nov 2014 at 4:12

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 1 Nov 2014 at 4:17

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 3 Nov 2014 at 9:08