General concepts you've got to understand so everything could possibly make sense to you.
In java we have 2 concept of calling methods[1]:
-
Static dispatching - The Java compiler knows in compile time which function code block to call
-
Dynamic dispatching - The Java compilers inserts a code, that on runtime decides which function code block to call
Hiding means that in some scopes, the new f
will be called instead of f
. Note that it depends on the scope. It only happens where Static dispatching is used:
- (static?) fields
- static methods
- private methods.
Overriding, on the other hand, means that every time that you call f
, the new f
will be called instead. Therefore, it only happens where Dynamic dispatching is used:
- constructors
- class non-private methods
Every instance has a runtime type. The type is T
in new T(...)
. This is the real type of the instance which influences the Dynamic dispatching.
However, since we are doing OOP, we can treat an instance as an upper type (for example, one can treat String
as Object
). Therefore, every expression in Java has a "static" type, the type that the Java compiler is sure the expression is of. Note that the static type of expression depends on the scope. The static type influence the static dispatching
Given an instance x
of (runtime) type X extends\implements T
with static type T
, you can only call methods/access fields defined on T
, since the compiler only knows about them as it treats x
as instance of T
.
Given a symbol s
we want to access/call, the lookup order is:
- In method scope
- static class scope
- class scope
- static parent class scope ...
- static
Object
scope Object
scope- "imported" scope
Note: It's OK to have a (static?) method and a (static?) field with the same name (since you can't do: field(...)
or ::field
)
- arguments are evaluted
- If this constructor call another constructor, evaluate the arguments and call the other constructor (go back to 2)
- Call super constructor
- execute class initializers (
{}
in class body) and evaluate instance variable initializersString x = ...
from top to bottom. - execute the rest of the method body
On exception stop the initialization and throw the error
Say C extends B extends A
and we have an instance C c
:
- inside a method body in
B
(meaning: insideclass B { ... }
) the static type ofc
isB
- InsideT
the compiler thinks that you areT
.
Fun facts:
-
You can call a static method on an instance:
a.staticMethod()
-
You can't have a static and non-static method with the same signture.
Why? Given
A a
withstatic void f()
andvoid f()
, what doesa.f()
mean?
Static methods are static dispatched. Therefore, they are affected by hidding:
class A {
static void f() { System.out.println("A"); }
void g() {
f();
}
}
class B {
static void f() { System.out.println("B"); }
@Override
void g() {
f();
}
}
B.f(); // "B"
A.f(); // "A"
B b = new B();
b.f(); // "B", the compiler thinks `b` is `B`, and B::f exists, so it is called
(A b).f(); // "A", the compiler thinks `b` is `A`, and A::f exists, so it is called
b.g(); // "B", since `g` implemented in `B`, and B::f hides A::f - static dispatch
(A b).g(); // "B", since `g` implemented in `B`, and the compilers thinks we are `B` - static dispatch
[1] Actually more, but most of them are specialized version of those 2. Another one is for dynamic typing capability of the JVM.
Virtual methods (non-private methods) are called using dynamic dispatch, therefore are subject to overriding concept.
- The annotation
@Override
is completely optional. However, if it is used, the compiler throw compilation error if used incorrectly.
class A {
void a() { System.out.println("A"); }
void f() { a(); }
void g() {
f();
}
}
class B {
void b() { System.out.println("B"); }
void f() { b(); } // overrides A::f, not hidding!!!
}
B b = new B();
b.f(); // "B", since dynamic type is B, call B::f, which calls b, which prints "B"
(A b).f(); // // "B", since dynamic type is still B
b.g(); // "B", call A::g (since B doesn't override), but A::f is virtual, and the dynamic type is B, so it calls B::f which prints "B"
(A b).g(); // "B", Same as above
Fields are accessed using static dispatch, therefore they work exactly like static methods.
class A {
String f = "A";
void g() {
System.out.println(f);
}
void h() {
System.out.println(f);
}
}
class B {
String f = "B";
@Override
void g() {
System.out.println(f);
}
}
B b = new B();
b.g(); // "B", `B::g` is called, and B::f hides A::f.
(A b).g(); // "`B::g` is called
b.h(); // "A", `A::h` is called, and the compiler static dispatch fields, so since the static type is A, the compiler only knows about A::f.
(A b).h(); // "A", "`A::h` is called