0

In Java, what is the actual reason behind the inability to write a method in a sub class which has the same name as a final method in the super class? (Please note that I am not trying to override the method, this is why I have put the keyword final.)

Please see the example below:

class A {
    public final void method() {
        System.out.println("in method A");
    }
}

class B extends A {
    public void method() {
        System.out.println("in method B");
    }
}

The problem is expressed as "'method()' cannot override 'method()' in 'A'; overridden method is final" in the IDE; however, I would like to understand what it is about this situation that leads the compiler to fail.

Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • 1
    Because you've made `method()` `final` in the superclass, which means you want the compiler to fail if you try to override it. – Andy Turner Jul 29 '21 at 14:13
  • 4
    *"I am not trying to override the method"* - but you *do* nonetheless. – luk2302 Jul 29 '21 at 14:13
  • As to why? Because the superclass guarantees all users of the class that `method` will behave EXACTLY as specified and it explicitly prevents overriding. If you now override it you may violate its internal invariances, break its state, behaviour, interface contract, whatever. – luk2302 Jul 29 '21 at 14:15
  • You are saying you are not overriding but what you are doing is method overriding & with a final method, it's not allowed. – Saurabhcdt Jul 29 '21 at 14:23

2 Answers2

2

Because in java, overriding isn't optional.

Names of methods at the class level.

At the class level (as in, what is in a class file, and what a JVM executes), method names include their return type and their parameter types (and, of course, the name). At the JVM level, varargs doesn't exist (it's an array instead), generics do not exist (they are erased for the purposes of signature), and the throws clause isn't a part of the story. But other than that, this method:

public void foo(String foo, int bar, boolean[] baz, long... args) throws Exception {}

turns into this name at the class file level:

foo(Ljava/lang/String;I[Z[J)V

which seems like gobbledygook, but [ is 'array of', the primitives get one letter each (Z for boolean, J for longs, I for integer), V is for void, and L is for: Object type. Now it makes sense.

That really is the method name at the class level, effectively (well, we call this its signature). ANY invocation of a method in java, at the class level, always uses the complete signature. This means javac simply cannot compile a method call unless it actually knows the exact method you're invoking, which is why javac doesn't work unless you have the full classpath of everything you're calling available as you compile.

Overriding isn't optional!

At the class level, if you define a method whose full signature matches, exactly, a signature in your parent class, then it is overriding that method. Period. You can't not. @Override as an annotation doesn't affect this in the slightest (That annotation merely causes the compiler to complain if you aren't overriding anything, it's compiler-checked documentation, that's all it is).

javac goes even further

As a language thing, javac will make bridges if you want to tighten the return type. Given:

class Parent {
    Object foo() { return null; }
}

class Child extends Parent {
    String foo() { return null; }
}

Then at the class level, the full signature of the one method in Parent is foo()Ljava/lang/Object; whereas the one in Child has foo()Ljava/lang/String; and thus these aren't the same method and Child's foo would appear not to be overriding Parent's foo.

But javac intervenes, and DOES make these override. It does this by actually making 2 methods in Child. You can see this in action! Write the above, compile it, and run javap -c -v on Child and you see these. javac makes 2 methods: Both foo()Ljava/lang/String; and foo()Ljava/lang/Object; (which does have the same signature and thus overrides, by definition, Parent's implementation). That second one is implemented as just calling the 'real' foo (the one returning string), and gets the synthetic flag.

Final is what it is

Which finally gets to your problem: Given that final says: I cannot be overridden, then, that's it. You've made 2 mutually exclusive rules now:

  • Parent's foo cannot be overriden
  • Child's foo, by definition (because its signatures match), overrides Parent's foo

Javac will just end it there, toss an error in your face, and call it a day. If you imagined some hypothetical javac update where this combination of factors ought to result in javac making a separate method: But, how? At the class level, same signature == same method (it's an override), so what do you propose? That java add a 0 to the end of the name?

If that's the plan, how should javac deal with this:

Parent p = new Child();
p.foo();

Which foo is intended there? foo()Ljava/lang/Object; from Parent, or foo0()L/java/Object; from child?

You can write a spec that gives an answer to this question (presumably, here it's obvious: Parent's foo; had you written Child c = new Child(); c.foo(); then foo0 was intended, but that makes the language quite complicated, and for what purpose?

The java language designers did not think this is a useful exercise and therefore didn't add this complication to the language. I'm pretty sure that was clearly the right call, but your opinion may of course be different.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
0

Final means not just that you can’t override it, it means you can’t work around having that method get called.

When you subclass the object, if you could make a method that shadows that final method, then you could prevent the superclass method from functioning, or substitute some other functionality than what the user of the object would expect. This would allow introducing malicious code and would defeat the purpose of making methods final.

In your case it sounds like making the superclass method final may not have been the best choice.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276