(actual question at the end)
Hello World! I've got some disturbing thing happening here that I cannot wrap my head around...
Laszy as I am, I hate re-implementing the stupid euqals() emthods over and over again; also the hashCode() methods, but still focus the "equality" on selected member variables. I could also reduce the implementation by using a default interface that checks class instances, or using reflection, but I thought I'd got with this static utility method and a labmda:
public class StackOverflow_TestEq {
static public interface JcLambda_pG1_pG1_rBool<T> {
boolean test(T p1, T p2);
}
static public <T> boolean equals_internal(final T pThisObject, final Object pOtherObject, final JcLambda_pG1_pG1_rBool<T> pEqualizer) {
if (pThisObject == null) return pOtherObject == null;
if (pOtherObject == null) return false;
final Class<? extends Object> thisClass = pThisObject.getClass();
if (!thisClass.isInstance(pOtherObject)) return false;
@SuppressWarnings("unchecked") final T other = (T) pOtherObject;
final boolean res = pEqualizer.test(pThisObject, other);
// System.out.println("Testing " + pThisObject + " vs " + other + " => " + res);
return res;
}
/*
* TEST DEFINES
*/
static private interface CNameable {
String getName();
}
static private class CA implements CNameable {
String name = "Bier";
@Override public String toString() {
return getClass().getSimpleName() + "." + name;
}
@Override public String getName() {
return name;
}
}
static private class CB implements CNameable {
String name = "Schnaps";
@Override public String toString() {
return getClass().getSimpleName() + "." + name;
}
@Override public String getName() {
return name;
}
}
@SuppressWarnings("synthetic-access") static private class CC extends CA {
@SuppressWarnings({ "hiding" }) String name = "Wein";
@Override public String toString() {
return getClass().getSimpleName() + "." + name;
}
@Override public String getName() {
return name;
}
}
@SuppressWarnings("synthetic-access") static private class CD extends CA {
@SuppressWarnings({ "hiding" }) String name = "Bier";
@Override public String toString() {
return getClass().getSimpleName() + "." + name;
}
@Override public String getName() {
return name;
}
}
/*
* TEST
*/
@SuppressWarnings("synthetic-access") public static void main(final String[] args) {
final CA a = new CA();
final CB b = new CB();
final CC c = new CC();
final CD d = new CD();
final JcLambda_pG1_pG1_rBool<CNameable> res = new JcLambda_pG1_pG1_rBool<CNameable>() {
@Override public boolean test(final CNameable pP1, final CNameable pP2) {
// return pP1.getName().equals(pP2.getName());
return pP1.getName() == pP2.getName();
}
};
System.out.println("\nExplicit eq");
System.out.println("a=a:" + equals_internal(a, a, (p1, p2) -> p1.getName().equals(p2.getName())));
System.out.println("a=b:" + equals_internal(a, b, (p1, p2) -> p1.getName().equals(p2.getName())));
System.out.println("a=c:" + equals_internal(a, c, (p1, p2) -> p1.getName().equals(p2.getName())));
System.out.println("a=d:" + equals_internal(a, d, (p1, p2) -> p1.getName().equals(p2.getName())));
System.out.println("\nExplicit ==");
System.out.println("a=a:" + equals_internal(a, a, (p1, p2) -> p1.getName() == p2.getName()));
System.out.println("a=b:" + equals_internal(a, b, (p1, p2) -> p1.getName() == p2.getName()));
System.out.println("a=c:" + equals_internal(a, c, (p1, p2) -> p1.getName() == p2.getName()));
System.out.println("a=d:" + equals_internal(a, d, (p1, p2) -> p1.getName() == p2.getName()));
System.out.println("\nImplicit");
System.out.println("a=a:" + equals_internal(a, a, res));
System.out.println("a=b:" + equals_internal(a, b, res));
System.out.println("a=c:" + equals_internal(a, c, res));
System.out.println("a=d:" + equals_internal(a, d, res));
System.out.println("\nExplicit eqName");
System.out.println("a=a:" + equals_internal(a, a, (p1, p2) -> p1.name.equals(p2.name)));
System.out.println("a=b:" + equals_internal(a, b, (p1, p2) -> p1.name.equals(p2.name)));
System.out.println("a=c:" + equals_internal(a, c, (p1, p2) -> p1.name.equals(p2.name)));
System.out.println("a=d:" + equals_internal(a, d, (p1, p2) -> p1.name.equals(p2.name)));
System.out.println("\nExplicit ==name");
System.out.println("a=a:" + equals_internal(a, a, (p1, p2) -> p1.name == p2.name));
System.out.println("a=b:" + equals_internal(a, b, (p1, p2) -> p1.name == p2.name));
System.out.println("a=c:" + equals_internal(a, c, (p1, p2) -> p1.name == p2.name));
System.out.println("a=d:" + equals_internal(a, d, (p1, p2) -> p1.name == p2.name));
}
}
Output:
Explicit eq
a=a:true
a=b:false
a=c:false
a=d:true
Explicit ==
a=a:true
a=b:false
a=c:false
a=d:true
Implicit
a=a:true
a=b:false
a=c:false
a=d:true
Explicit eqName
a=a:true
a=b:false
a=c:true
a=d:true
Explicit ==name
a=a:true
a=b:false
a=c:true
a=d:true
So... I was expecting all 5 variants (eq
, ==
, Implicit
, eqName
, ==name
) to give me back the same results (true, false, false, true)...
But eqName
and ==name
do not. Why do they return a=c
as true
instead of false
?
Does it have to do with the name shadowing that is happening there? So instead of accessing CC.name
the comparison lambda is always accessing CA.name
?
To analyze why the result of the following is true
equals_internal(a, c, (p1, p2) -> p1.name == p2.name)
you have to look at the signature of equals_internal
:
equals_internal(final T pThisObject, final Object pOtherObject, final JcLambda_pG1_pG1_rBool<T> pEqualizer)
So in this case, T
is the type of a
, i.e. CA
.
Therefore the JcLambda_pG1_pG1_rBool<T>
you are passing to the static method is a JcLambda_pG1_pG1_rBool<CA>
, which means p1
and p2
are of type CA
, so p1.name == p2.name
compares the name
property of the base class CA
, and "Bier" == "Bier"
is true due to the String pool.
The result of equals_internal(a, c, (p1, p2) -> p1.name.equals(p2.name))
is true
for the same reason (only here you don't have to rely on the String pool returning the same String
instance for both "Bier"
, since you are using String.equals
).
In the first 3 cases the result is different, because you are using the getName()
method, which is overridden in the sub-class, and returns the name
property of the sub-class.
To summarize, the different behavior is caused by that fact that, unlike methods, instance variables cannot be overridden.