Skip to content

Instantly share code, notes, and snippets.

@yoavst
Last active December 20, 2021 13:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yoavst/32a51fc9717f53fcc9a92261bb116a9c to your computer and use it in GitHub Desktop.
Save yoavst/32a51fc9717f53fcc9a92261bb116a9c to your computer and use it in GitHub Desktop.
Software 1

Java Cheatsheet

General concepts you've got to understand so everything could possibly make sense to you.

How a method is called

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 vs Overriding

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

runtime type vs "static" type

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.

Lookup order

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)

instance initialization order:

  1. arguments are evaluted
  2. If this constructor call another constructor, evaluate the arguments and call the other constructor (go back to 2)
  3. Call super constructor
  4. execute class initializers ({} in class body) and evaluate instance variable initializers String x = ... from top to bottom.
  5. execute the rest of the method body

On exception stop the initialization and throw the error

Methods

Say C extends B extends A and we have an instance C c:

  • inside a method body in B (meaning: inside class B { ... }) the static type of c is B - Inside T the compiler thinks that you are T.

Static methods

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 with static void f() and void f(), what does a.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

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

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment