View on GitHub


Java notes cheatsheet, focusing on fundamentals and useful interview tips

Table of Contents


Scope of Variables

Local Instance Class/Static
Declared in methods, constructors, or blocks Declared in a class, but outside a method/block Same as instance, but with static keyword
Created when block is entered, destroyed upon block exit Created when an object is created (new). When space is allocated for a block on the heap, a slot for each instance var is created. Created when program starts, destroyed when program ends.
No access modifiers Access modifiers OK. Visible to all methods & constructors in class. Access modifiers OK. Visible to all methods & constructors in class.
No default values. Have default values. Have default values.


Access Modifiers and Visibility

N/A Public Protected Default Private
Same Class Y Y Y Y
Same Package Subclass Y Y Y N
Same Package Non-subclass Y Y Y N
Different Package Subclass Y Y N N
Different Package Non-subclass Y N N N


Bitwise Operations

Width vs. Possible Values

The number of bits used (width) determines the numbers that can be encoded: 2^n total.

Numerical primitives


For the following examples, assume a = 60, b = 13, c= -2. Their binary 2’s complement representations are below.
a = 0011 1100
b = 0000 1101
c = 1111 1110

Operation Function Example
& AND a&b = 0000 1100 (12)
\| OR a\|b = 0011 1101 (61)
^ XOR a^b = 0011 0001 (49)
~ Complement (bitwise inversion) ~a = 1100 0011 (-61 in 2's complement)
<< Left shift a << 2 = 1111 0000 (-16 in 2's complement)
>> Arithmetic shift right c >> 2 = 1111 1111 (-1)
>>> Logical shift right (zero-fill) c >>> 2 = 0111 1111 (127)

Useful Tricks



Three types:

  1. Checked Exceptions: Notified by the compiler at compile-time
  2. Unchecked Exceptions: Runtime Exceptions
  3. Errors: Problems that arise beyond the control of the user and programmer, e.g. stack overflow

Exception Hierarchy

alt text

alt text


Automatically closes the resources used, e.g.

try (FileReader fr = new FileReader(filepath)) {
    // use the resource
} catch () {
    // handle the exception

User-defined Exceptions



Example: Given the following:

public interface Vegetarian { ... }
public class Animal { ... }
public class Deer extends Animal implements Vegetarian { ... }

then a Deer IS-A Animal, Vegetarian, Deer, and Object

Thus the following are all legal:

Deer d = new Deer();
Animal a = d;
Vegetarian v = d;
Object o = d;

All four of these references refer to the same Deer object on the heap.

Static Polymorphism

Static Polymorphism is polymorphism that is resolved at compile time. Method overloading is an example of static polymorphism.

For example, say we have the following code:

int add(int a, int b) {
    return a + b;

int add(int a, int b, int c) {
    return a + b + c;


int x = add(1, 2);    // Calls the first add method. x = 3
int y = add(1, 2, 3); // Calls the second add method. y = 6

When we make a call to the add function, we can tell which function will be called before even running our code, based on the type and number of our arguments. And, in fact, the compiler does just this – it resolves which method will be called at compile time, rather than waiting until runtime.

Dynamic Polymorphism

Dynamic Polymorphism is polymorphism that is resolved at runtime. Method overriding is an example of dynamic polymorphism.

For example, consider the following code:

class Parent {
    public void myMethod() {
        System.out.printline("I am the parent");

public class Child extends Parent {
    public void myMethod() {
        System.out.printline("I am the child");
    public static void main(String[] args) {
        Parent p = new Child();
        Child c = new Child();
        p.myMethod(); // I am the child
        c.myMethod(); // I am the child

Here, we instantiate two Child objects, one using a Parent reference p, and the other using a Child reference c.

While invoking c.myMethod(), the compiler sees myMethod() in the Child class at compile time, and the JVM invokes myMethod() in the Child class at run time.

myMethod() on p is quite different because p is a Parent reference. When the compiler sees p.myMethod(), the compiler sees the myMethod() method in the Parent class. Here, at compile time, the compiler used myMethod() in Parent to validate this statement. At run time, however, the JVM invokes myMethod() in the Child class.

This behavior is also referred to as virtual method invocation, and these methods are referred to as virtual methods. An overriding method is invoked at runtime, no matter the data type the reference is that was used in the source code at compile time.

Method Overriding

Rules for overriding methods (NOT overloading!)

For more information, see Static vs Dynamic Binding.


Static vs Dynamic Binding

Association of a method call to a method body is known as binding. There are two types of binding:

  1. Static Binding (aka Early Binding): Binding resolved at compile time
  2. Dynamic Binding (aka Late Binding): Binding resolved at run time

In Java, static binding is used for the binding of static, private, and final methods. This is because these methods cannot be overridden, and thus calls to such methods are necessarily unambiguous. The type of the class these methods belong to can be determined at compile time.

This is important to know, for example, in situations where you might call a static method via an object (all though this is generally ill-advised), like in the example below.

class Human {
   public static void walk() {
       System.out.println("Human walks");
class Boy extends Human {
   public static void walk(){
       System.out.println("Boy walks");
   public static void main(String args[]) {
       Human b = new Boy();    // Reference is type Human, object is type Boy
       Human h = new Human();  // Reference is type Human, object is type Human
       b.walk();  // Human walks
       h.walk();  // Human walks

On the other hand, dynamic binding is used when the compiler is not able to resolve the binding at compile time:

class Human {
   public void walk() {
       System.out.println("Human walks");
class Boy extends Human {
   public void walk(){
       System.out.println("Boy walks");
   public static void main(String args[]) {
       Human b = new Boy();    // Reference is type Human, object is type Boy
       Human h = new Human();  // Reference is type Human, object is type Human
       b.walk();  // Boy walks
       h.walk();  // Human walks

Note that, as detailed in the Polymorphism section, the binding of overloaded methods is static, while the binding of overridden methods is dynamic.



An interface may have abstract methods, default methods, static methods, constants, and nested types. Method bodies exist only for default and static methods.

An interface contains behaviors that a class implements. All methods of an interface must be defined in the implementing class, unless the class itself is abstract.

Interfaces are similar to classes in that:

Interfaces are different from classes in that:

Interfaces and their methods are implicitly abstract. Their methods are implicitly public.

Tagging Interfaces

The most common use of extending interfaces occurs when the parent interface doesn’t have any methods. Example: The MouseListener interface in java.awt.event extends java.util.EventListener, which is defined as follows:

package java.util;
public interface EventListener {}

Thus, an interface with no methods is a tagging interface.
These have two basic design purposes:

  1. Creates a common parent
  2. Adds a data type to a class. A class that implements a tagging interface becomes an interface type through polymorphism


Nested Classes

Types of nested classes:

                                Nested classes
                  |                                      |
            Inner classes                      Static nested classes
   |              |                 |
 Inner      Method-local        Anonymous
classes     inner classes     inner classes

Java does NOT support multiple inheritance.
This means a class cannot inherit multiple classes.
However, a class can implement multiple interfaces.


Java Generics

public static <E> void printArray(E[] array) {
    for (E element : array)
        System.out.print(element + " ");
// Example usage:
Integer[] intArr = {1, 2, 3};
Double[] doubleArr = {1.1, 2.2, 3.3};


You can also bound the type of the Generic parameters, e.g.

public static <T extends Comparable<T>> T max(T x, T y) { ... }

Can be applied to Generic classes as well, e.g.

public class Box<T> {
    private T t;



Serialization allows an object to be represented as a sequence of bytes that includes the object’s data as well as info about the object’s type and the types of data stored in the object.

After a serialized object has been written to a file, it can be read from the file and deserialized to recreate the object in memory.

For a class to be serialized, it must meet two conditions:

  1. It must implement the interface
  2. All of the fields must be serializable. If a field is not serializable, it must be marked with the transient keyword

Example of serialization:

try {
    FileOutputStream fileOut = new FileOutputStream("/tmp/employee.ser");
    ObjectOutputStream out = new ObjectOutputStream(fileOut);
    out.writeObject(e); // Assume e is an Employee object

The data for object e is now saved in /tmp/employee.ser.
Note: convention is to use .ser (?)

Example of deserialization:

Employee e = null;
try {
    FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");
    ObjectInputStream in = new ObjectInputStream(fileIn);
    e = (Employee)in.readObject();

The return value of readObject() should be cast to the proper class.



Lifecycle of a thread:

Thread priorities range from MIN_PRIORITY (a constant of 1) to MAX_PRIORITY (a constant of 10).

There are two ways of creating a thread:

  1. Implement the Runnable interface
    • If you want a class to be executed as a thread, it must implement Runnable. This method takes three basic steps:
    1. Implement a run() method
    2. Instantiate a Thread object, like so: Thread (Runnable threadObj, String threadName), where threadObj is an instance of a class that implements Runnable, and threadName is the name given to the new thread
    3. Start a Thread object with start(), which executes a class to the class’s run() method
  2. Extend the Thread class
    • This approach provides more flexibility in handling multiple threads created using available methods in the Thread class.
    1. Override the run() method in Thread class
    2. After creating the Thread object, start it with the start() method

Thread Synchronization

What if two threads try to access the same resources? For example, if multiple threads try to write to the same file at the same time, they could corrupt the file.

Monitors allow us to make sure only one thread can access a shared resource at a time. Each object in Java has a monitor associated with it, which a thread can lock or unlock. Only one thread at a time may hold a lock on a monitor.

Use a synchronized block to contain shared data.

Inter-thread Communication

Threads can be built to exchange information using three simple methods:

  1. public void wait() – causes the current thread to wait until another thread invokes notify()
  2. public void notify() – wakes up a single thread that is waiting on the object’s monitor
  3. public void notifyAll() – wakes up all threads that called wait() on the same object

All three methods can only be called from within a synchronized context.

Thread Deadlock

Situation where two or more threads are blocked forever, waiting for each other. Occurs when multiple threads need the same locks, but obtain them in a different order. Obviously, we want to avoid this!


Java Collections Framework


alt text


There are four commonly used map implementations in Java: HashMap, TreeMap, LinkedHashMap, and Hashtable.

alt text

To summarize them:

The figure below summaries many of these differences.

║   Property   ║       HashMap       ║      TreeMap      ║     LinkedHashMap   ║
║ Iteration    ║  no guarantee order ║ sorted according  ║                     ║
║   Order      ║ will remain constant║ to the natural    ║    insertion-order  ║
║              ║      over time      ║    ordering       ║                     ║
║  Get/put     ║                     ║                   ║                     ║
║   remove     ║         O(1)        ║      O(log(n))    ║         O(1)        ║
║ containsKey  ║                     ║                   ║                     ║
║              ║                     ║   NavigableMap    ║                     ║
║  Interfaces  ║         Map         ║       Map         ║         Map         ║
║              ║                     ║    SortedMap      ║                     ║
║              ║                     ║                   ║                     ║
║     Null     ║       allowed       ║    only values    ║       allowed       ║
║ values/keys  ║                     ║                   ║                     ║
║              ║   Fail-fast behavior of an iterator cannot be guaranteed      ║
║   Fail-fast  ║ impossible to make any hard guarantees in the presence of     ║
║   behavior   ║           unsynchronized concurrent modification              ║
║              ║                     ║                   ║                     ║
║Implementation║      buckets        ║   Red-Black Tree  ║    double-linked    ║
║              ║                     ║                   ║       buckets       ║
║      Is      ║                                                               ║
║ synchronized ║              implementation is not synchronized               ║

ArrayList vs. Vector

  1. Synchronization: Vector is synchronized, which means only one thread can access it at a time, while ArrayList is not synchronized, which means multiple threads could read it at the same time.
  2. Performance: ArrayList is faster, as Vector incurs slight overhead in acquiring the lock.
  3. Growth: Vector and ArrayList both grow and shrink dynamically, but ArrayList increments 50% of the current array size if the number of elements exceeds its capacity, while Vector increments 100%.
  4. Traversal: Vector can use both Enumeration and Iterator for traversing elements, while ArrayList can only use Iterator.

Generally, you’ll want to use an ArrayList; in the single-threaded case it’s a better choice, and in the multi-threaded case, you get better control over locking. Want to allow concurrent reads? Fine. Want to perform one synchronization for a batch of ten writes? Also fine. It does require a little more care on your end, but it’s likely what you want. Also note that if you have an ArrayList, you can use the Collections.synchronizedList function to create a synchronized list, thus getting you the equivalent of a Vector.


Common Design Patterns

Singleton Class

Controls object creation, limiting # of objects to only one. Since there is only one instance, instance fields will occur once per class, similar to static fields.
Singletons often control access to resources like database connections or sockets.

public class Singleton {
    private static Singleton singleton = new Singleton();
    private Singleton () {} // private constructor prevents any other class from instantiating
    public static Singleton getInstance() { return singleton; }
    protected static void demoMethod() { System.out.println("demo"); }

Example usage:

Singleton tmp = Singleton.getInstance();

Some useful posts on the variations of the Singleton pattern in Java:


Number Wrapper Classes

 |       |       |       |      |      |
Byte  Integer  Double  Short  Float  Long

Converting primitive data types into objects is called boxing.
Similarly, the reverse operation is called unboxing.


Cloning Arrays

Two methods of copying an array are using System.arraycopy() and clone().
An example of System.arraycopy():

int[] dest = new int[orig.length];
System.arraycopy(orig, 0, dest, 0, orig.length);

An example of clone():

int[] dest = orig.clone();

Long story short, it seems the first method may be quicker on shorter arrays, but on larger datasets they have similar performance. Furthermore, clone() is much more compact and easy to read. Thus, tend to prefer clone().

For more information, see this article.


Other Useful Keywords

final Keyword

Can be applied to variables, methods, and classes.




abstract Keyword

Can be applied to both methods and classes.



synchronized Keyword

Indicates a block of code that can only be executed by one thread at a time. Can be applied to methods or independent blocks.

A synchronized block can specify an object to use as a lock. This object is referred to as a “monitor” object.

See this post or the accompanying video for more information.

transient Keyword

An instance variable marked as transient tells the JVM to skip that variable when serializing the object containing it

throws Keyword

Used to postpone the handling of a checked (compile-time) exception. E.g.

public class MyClass {
    public void deposit(double amount) throws RemoteException {
        // Method implementation ...
        throw new RemoteException();

volatile Keyword

Tells the JVM that a thread accessing the variable must merge its own private copy of the variable with the master copy in memory. In technical terms, any variable marked as volatile will only ever be read from & written to main memory, bypassing any CPU caching.

If a variable is not declared volatile, we have no guarantee about when exactly the master copy will be accessed or modified. This can subtly cause problems in multithreaded applications that may be difficult to debug.

volatile can only be used on instance variables.

See this fantastic post or the accompanying video for more information.