8

I have a Junit4 test case which statically imports the org.junit.Assert.assertEquals method(s).

import static org.junit.Assert.assertEquals;

In this class I have created a utility method for asserting some complex internal classes which do not implement equals (and also have a hard time implementing it).

private void assertEquals(MyObj o1, MyObj o2)
{
    assertEquals(o1.getSomething(), o2.getSomething());
    assertEquals(o1.getSomethingElse(), o2.getSomethingElse());
    ...
}

I expected the code to behave as if I am "overloading" the assertEquals method(s) that I'm importing, but it looks like my private non-static method is hiding the statically imported methods. I also tried turning my method to be public and static (all permutations) but with no success - I had to rename it.

Any reason why it behaves this way? I couldn't find any reference to this behavior in the documentation.

RonK
  • 9,472
  • 8
  • 51
  • 87
  • 1
    What is stopping you from changing the signature (or even the name) of the internal method? – spot35 Jul 06 '11 at 12:10
  • 1
    @DomSelvon, Changing the signature doesn't help. About the method name - nothing is stopping me - the question is now purely for knowledge (already changed the name) – RonK Jul 06 '11 at 12:28

4 Answers4

3

What you observed is calling Shadowing. When two types in java have the same simple name, one of them will shadow the other. the shadowed type then can't be used by it's simple name.

The most common type of shadowing is a parameter to hide a field. usually result in setter code to look like setMyInt(int myInt) {this.myInt = myInt; }

Now let's read the relevant documentation:

A static-import-on-demand declaration never causes any other declaration to be shadowed.

This indicate a static import on demand always comes last, so any type with the same simple name as a import on demand declaration will always shadow (hide) the static import.

Dorus
  • 7,276
  • 1
  • 30
  • 36
  • That indeed seems like the case - although I have to confess, that after reading both your explanation and the Spec - I still don't understand why one method with a certain signature shadows another one – RonK Jul 06 '11 at 15:03
  • Can you try to explain what behavior you would expect in this case? Both methods have the same simple name, there is no way for the compiler to magically guess what method you tried too call (the private one and the static imported one), so it has to rely on the shadowing rules to pick one. – Dorus Jul 06 '11 at 15:47
  • Or did you mean you still don't get why it prefer one method over the other? That's just carefully reading and apply the rules in the spec. Overall you can say it prefer local stuff, then class, then inherited, then imported, then imported on demand. – Dorus Jul 06 '11 at 15:50
  • 2
    My expectation was that it will allow for method overloading. Why does `assertEquals(int, String, HTTPRequest)` shadow `assertEquals(Object, Object)`. It makes no sense to do deny the programmer of something which can be quite easily distinguished – RonK Jul 06 '11 at 16:05
  • 1
    Good question. Surprises me a little that it does that, but thinking a little more, it makes sense. Method overloading in one class is complicated enough, doing so between classes? YUK. Only source i can get you for this decision are the [java design goals](http://java.sun.com/docs/white/langenv/Intro.doc2.html#349): `Primary characteristics of the Java programming language include a simple language`. – Dorus Jul 06 '11 at 16:56
  • The quote from the JLS is not relevant here. `import static org.junit.Assert.assertEquals;` is not a "static-import-on-demand declaration", which must end in `.*;`. It is a "single-static-import declaration". Interestingly, I cannot find anything in the JLS that says single-static-import declarations does not shadow methods that are in scope. – Sweeper Mar 31 '23 at 11:31
1

Overloading and overwriteing works in an inheritance tree. But a static import doesn't build a inheritance.

If you want to use the assertEquals of junit in your own assertEquals method you must qualify it with the className e.g. Assert.assertEquals.

Use a nonstatic import of org.junit.Assert.

cpater
  • 1,049
  • 10
  • 12
1

You have stumbled onto method hiding, where the presence of a local method "hides" one from another class (often a super class).

I have always felt that statically importing methods is, while syntactically possible, somehow "wrong".

As a style, I prefer to import the class and use TheirClass.method() in my code. Doing so makes it clear that the method is not a local method, and one of the hallmarks of good code is clarity.

I recommend you import org.junit.Assert and use Assert.assertEquals(...).

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • This style makes it certainly clear where the method comes from. But as far as clarity is concerned the important question is "What does it do?" and with that respect you approximatly doubled the character count without clarifying anything. – Jens Schauder Jul 06 '11 at 12:57
  • Like Dorus pointed out, this is [_shadowing_](http://docs.oracle.com/javase/specs/jls/se7/html/jls-6.html#jls-6.4.1) and not _hiding_, because the `Assert.assertEquals` method is not inherited. – Dan Berindei Aug 21 '14 at 12:30
  • +1 for recommending class import over method import. So much ambiguity avoided! For example, now I could safely write a class that compares operations from JUnit4 and JUnit5. Or many other use cases where method signatures would overlap. – jrbe228 Aug 10 '22 at 13:12
0

This makes sense. Suppose javac does what you want, it picks your assertEquals(MyObj, MyObj) method today. What if tomorrow org.junit.Assert adds its own assertEquals(MyObj, MyObj) method? The meaning of invocation assertEquals(mo1,mo2) changed dramatically without you knowing it.

At question is the meaning of the name assertEquals. Javac needs to decide that this is a name of method(s) in org.junit.Assert. Only after that, it can do method overloading resolution: examine all methods in org.junit.Assert with the name assertEquals, pick the most appropriate one.

It's conceivable that java could handle method overloading from multiple classes, however as the first paragraph shows, it causes great uncertainty to developer of which class the method he is calling. Because these classes are unrelated, method semantics can differ greatly.

If at compile time, it's without any doubt to developr which class the method belongs to, it is still possible that the class will overload the method tomorrow therefore changing the target method invoked. However, because that is done by the same class, we can hold it responsible. For example, if org.junit.Assert decides to add a new assertEquals(MyObj, MyObj) method, it must be aware that some previous invocations of assertEquals(Object,Object) are now rerouted to the new method, and it must make sure that there's no semantics change that will break the call sites.

irreputable
  • 44,725
  • 9
  • 65
  • 93