Weeks
JavaWeek 1 · Types, Memory, Abstraction and Objects
Week 1 Notes

Java,
Week 1

Programming Concepts using Java, Week 1: why languages exist, types, how memory works (stack, heap, garbage collection), abstraction and modularity, and the first look at objects and classes. No Java syntax yet: this week builds the ideas that every Java interview tests.

8 chapters17 worked examples~65 min read
TypesMemoryStack and heapOOPSubtypingInterview prep

Chapters

  1. 01Programming Languages, Abstraction and StylesLecture 1
  2. 02TypesLecture 2
  3. 03Memory ManagementLecture 3
  4. 04Abstraction and ModularityLecture 4
  5. 05Object-Oriented ProgrammingLecture 5
  6. 06Classes and ObjectsLecture 6
  7. 07Interview Question BankInterview
  8. 08Week 1 Summary SheetAll lectures
01
Lecture 1

Programming Languages, Abstraction and Styles

Why languages exist, what abstraction buys us, compilers and interpreters, imperative versus declarative style, and a first look at types, ADTs and objects.

1.1What is a programming language?

Programming language

A language is a medium for communication. A programming language is the medium in which we communicate computational instructions to a machine.

Originally, a language was directly tied to the architecture of the computer. Memory locations store values and registers do the arithmetic. Even adding two numbers takes three separate steps:

  • Load a value from memory location M into register R.
  • Add the contents of registers R1 and R2 and store the result back in R1.
  • Write the value in R1 to memory location M'.
Same job, two levelsMachine levelLoad M -> R1Load M2 -> R2Add R1+R2 -> R1Store R1 -> M3High leveltotal = a + b1 line, readablecompileRegisters R1, R2 do arithmetic; M, M2, M3 are memory locations
Figure 1. The same job at machine level and at high level
Analogy. Machine level is like telling a driver "press the clutch, shift to first, release slowly...". A high-level language is just saying "take me to the airport". Both reach the same place, but one is tedious and easy to get wrong.

So the lecture's first point: working directly with the machine is tedious and error prone. That is the reason programming languages exist.

1.2Abstraction: what a high-level language gives us

The ideas we already use in computational thinking are abstractions. A good language lets us express them directly, without thinking about registers:

IdeaPythonJava (preview)
Named variablex = 10int x = 10;
Conditional executionif x > 5:if (x > 5) { ... }
Iterationfor v in data:for (int v : data) { ... }
Function / procedure, recursiondef f(a):static int f(int a) { ... }
Aggregate data (array, list, dictionary)[1, 2], {"a": 1}int[], ArrayList, HashMap

The language then translates this "high level" code into "low level" machine language. The two translators are the compiler and the interpreter.

1.3Compiler, interpreter and where Java sits (extra)

TranslatorHow it worksExample
CompilerTranslates the whole program first, then you run the result. Errors are reported before running.C, C++
InterpreterReads and executes the program line by line. Errors show when that line is reached.Python
BothCompile to an intermediate bytecode first, then a virtual machine runs it.Java
Hello.javayou writejavacchecks typesHello.classbytecodeJVMruns bytecodeSame .class file runs on Windows, Linux or macOS: each has its own JVMCompile once (javac) + interpret / JIT-compile at run time (JVM)
Figure 2. Java: compile once to bytecode, run on any JVM
TermWhat it isMemory hook
JVMJava Virtual Machine: runs bytecode (interprets it and JIT-compiles hot parts).the engine
JREJVM + the standard class libraries: enough to run programs.engine + parts
JDKJRE + development tools such as javac: needed to write and build.the whole workshop
Why bytecode? The compiler checks types once, so errors are caught early, and the same .class file runs on every machine that has a JVM. That is "write once, run anywhere". Only the JVM is platform specific.

1.4The trade-off: expressiveness for efficiency

A higher level language gives us less control over how our code is mapped to the architecture. We give that control up and get something worth more: fewer errors from a mismatch between what we intended and what we implemented.

What we give up
  • Hand-tuning every machine instruction
  • Some raw speed (extra layers, garbage collection, checks)
What we gain
  • Code that states our intent clearly
  • Fewer bugs, faster development, portability
Remember
  • Every language is a compromise. This is the sentence the lecture ends on, and it explains why there are so many languages.

1.5Styles of programming: imperative vs declarative

Imperative

How to compute. Step-by-step instructions on what is to be done.

Declarative

What the computation should produce. Often uses induction: express it in terms of smaller computations. Usually no intermediate variables. A combination of small transformations is called functional programming.

Example 1: add the values in a list

Imperative: intermediate values sum and x, and an explicit loop that examines each element.

main.py
def sum_list(l):
    sum = 0
    for x in l:
        sum += x
    return sum
print(sum_list([1, 2, 3]))
Output
6

Declarative: describe the answer by induction. No intermediate variables.

main.py
def sum_list(l):
    if l == []:
        return 0
    else:
        return l[0] + sum_list(l[1:])
print(sum_list([1, 2, 3]))
Output
6
  • Base case: the empty list has sum 0.
  • Inductive step: add the first element to the sum of the rest of the list.
Calls go downAnswers come back upsum_list([1,2,3])1 + 5 = 6sum_list([2,3])2 + 3 = 5sum_list([3])3 + 0 = 3sum_list([])0 (base case)
Figure 3. sum_list([1,2,3]): calls go down, answers come back up

Example 2: sum of squares of the even numbers up to n

main.py
def sum_square_even(n):
    sum = 0
    for x in range(n + 1):
        if x % 2 == 0:
            sum += x * x
    return sum
print(sum_square_even(10))
Output
220
main.py
def even(x):
    return x % 2 == 0

def square(x):
    return x * x

def sum_square_even(n):
    return sum(map(square, filter(even, range(n + 1))))
print(sum_square_even(10))
Output
220

Both give 220 (0 + 4 + 16 + 36 + 64 + 100). In the second version each tiny job has a name: even keeps, square transforms, sum combines.

Key point from the lecture. We can code functionally in an imperative language. Doing so helps us find the natural units of reusable code (even and square can be used elsewhere).

The same program in Java. Each Python function becomes a static method with declared types, and filter and map become a stream pipeline (Java 8+):

Main.java
import java.util.stream.IntStream;

public class Main {
    static boolean even(int x) {
        return x % 2 == 0;
    }

    static int square(int x) {
        return x * x;
    }

    static int sumSquareEven(int n) {
        return IntStream.rangeClosed(0, n)
                        .filter(Main::even)
                        .map(Main::square)
                        .sum();
    }

    public static void main(String[] args) {
        System.out.println(sumSquareEven(10));
    }
}
Output
220
  • IntStream.rangeClosed(0, n) is Python's range(n + 1): it includes n.
  • Main::even hands the method even to filter, like passing the function even in Python.
  • Every name now has a declared type (int, boolean): the static typing of Chapter 2.
ImperativeDeclarative / functional
Focushow, step by stepwhat the result is
Variablesintermediate variables that changeusually none
Repetitionloops (for, while)recursion, map, filter
Typical languagesC, Java, PythonSQL, Haskell, Java streams

1.6Names, types and values

  • Internally everything is a sequence of bits. There is no difference between data and instructions, let alone between numbers, characters and booleans.
  • For a compiler or interpreter, our code is its data: it reads our program as input.
  • So we impose a notion of type to create discipline: it says how to read the bits, what values are allowed, and which operations are permitted.

Strict type-checking catches bugs early, like a dimension mismatch in science (you cannot add 5 metres to 3 seconds). There are two kinds of slip:

  • Incorrect expression evaluation: an operation applied to the wrong kinds of values.
  • Incorrect assignment: the value does not match the variable's type.
main.py
print(5 + "3")
Error
TypeError: unsupported operand type(s) for +: 'int' and 'str'
Main.java
int x = "hello";   // Java refuses to even compile this
Error
error: incompatible types: String cannot be converted to int

Python found the problem only when the line ran; Java reports it before anything runs. Chapter 2 is entirely about this difference.

1.7Collections, abstract data types and objects (preview)

Collections matter: arrays, lists, dictionaries. An abstract data type (ADT) is a structured collection with a fixed interface, and its implementation is kept separate from that interface.

  • A stack is a sequence, but it only allows push and pop.
  • A priority queue allows insert and delete-max. It can be implemented with a sorted list, an unsorted list or a heap, and the users cannot tell which.
main.py
import heapq

class PQList:                      # unsorted list inside
    def __init__(self):
        self.items = []
    def insert(self, x):
        self.items.append(x)
    def delete_max(self):
        m = max(self.items)
        self.items.remove(m)
        return m

class PQHeap:                      # heap inside
    def __init__(self):
        self.h = []
    def insert(self, x):
        heapq.heappush(self.h, -x)
    def delete_max(self):
        return -heapq.heappop(self.h)

for pq in (PQList(), PQHeap()):
    for v in (5, 9, 2):
        pq.insert(v)
    print(pq.delete_max(), pq.delete_max())
Output
9 5
9 5

Same interface, same answers, different insides. (The two lines of output are the two implementations.)

Implementationinsertdelete-max
sorted listslow: find the place (up to n steps)fast: take the last item
unsorted listfast: just appendslow: scan for the max (n steps)
heapabout log n stepsabout log n steps

Object-oriented programming, in one line

OOP focuses on data types, and functions are invoked through the object instead of passing the data to the function. Python shows both styles side by side:

main.py
a = [3, 1, 2]
r = a.sort()          # method of the object: sorts a itself
print(a, r)

b = [3, 1, 2]
c = sorted(b)         # function given the data: returns a new list
print(b, c)
Output
[1, 2, 3] None
[3, 1, 2] [1, 2, 3]
Careful. a.sort() changes the list and returns None, so a = a.sort() destroys your list. This exact trap is asked a lot.

1.8What is yet to come, and why so many languages

ConceptWhere it is going
Object-oriented programmingClasses, objects, subtyping, inheritance (Lectures 5 and 6 of this week, then the rest of the course)
Exception handlingDealing with runtime errors gracefully
ConcurrencySeveral things happening at once
Event-driven programmingPrograms that react to clicks, messages, timers
  • Java is the illustrative language: imperative, object oriented, and it incorporates almost all of these features.
  • Design decisions are discussed wherever relevant, because every language makes some compromises.
  • That is why there is a whole "zoo" of languages, and why new ones are still created.
Heads-up. There is no Java code in this week's lectures: all examples are Python, because you already know it. The Java snippets in these notes are previews, so that the concepts connect to what you will write from next week.

1.9Exam tips and common mistakes

Exam tips
  • Imperative = "how, with steps and variables". Declarative = "what, by describing the result". If a question mentions a loop and a running total, it is imperative.
  • For a recursive definition always name the base case and the inductive step.
  • Learn the trade-off sentence: less control over the architecture, but fewer intent-vs-implementation errors.
Common mistakes
  • Calling Java "only compiled" or "only interpreted". It is both: javac then the JVM.
  • Thinking a declarative program has no steps. The steps are just hidden inside map, filter and recursion.
  • Writing a = a.sort() and then using a: it is now None.
  • Mixing up JDK, JRE and JVM.

1.10Interview corner: what they may ask

Likely questionChanceCrisp answer
Is Java compiled or interpreted?Very likelyBoth. javac compiles source to bytecode; the JVM interprets it and JIT-compiles the hot parts to machine code.
JDK vs JRE vs JVM?Very likelyJVM runs bytecode; JRE = JVM + libraries (to run); JDK = JRE + tools like javac (to develop).
Why is Java platform independent?Very likelyThe compiler emits bytecode, not CPU code, and every platform has its own JVM that understands it. The JVM itself is platform dependent.
Imperative vs declarative? Which is Java?LikelyImperative (how) vs declarative (what). Java is imperative and object oriented, with functional features (lambdas, streams) since Java 8.
Why are there so many programming languages?SometimesEvery language trades something for something else (speed, safety, expressiveness, ease), and new needs keep appearing.
Next · Lecture 2Types
02
Lecture 2

Types

What types are for, dynamic versus static typing, why static analysis helps, type synonyms, and type inference.

2.1The role of types

Everything is stored as bits. A type is what turns a pile of bits into something meaningful. Types do three jobs:

JobWhat it meansExample
1. Interpret the bitsView the bit sequence as an integer, a float, a character...; fixes the range of values and the allowed operations.01000001 → 65 or 'A'
2. Name and structure ideasEspecially at a higher level: give names to concepts in our program.Point instead of (float, float)
3. Catch bugs earlyDetect incorrect expression evaluation and incorrect assignment.5 + "3"
0 1 0 0 0 0 0 1in memory:read as int65read as char'A'read as 8 booleansF T F F F F F TBits have no meaning on their own: the TYPE decides what they mean
Figure 4. The same 8 bits mean different things under different types
main.py
n = 65
print(format(n, '08b'))   # the bits of 65
print(chr(n))             # the same number read as a character
Output
01000001
A

2.2Dynamic vs static typing

Every variable has a type. The question is how that type is decided.

Dynamic typing (Python)

The type is derived from the current value. The same name can hold an int now and a float later. An uninitialised name has no type at all.

Static typing (Java)

A type is associated with the name in advance. You must declare names and their types (int x), and an incompatible value (x = 7.5) is illegal.

main.py
x = 10
print(type(x))
x = 7.5
print(type(x))
Output
<class 'int'>
<class 'float'>
main.py
print(y)
Error
NameError: name 'y' is not defined

In Java the same attempt is stopped by the compiler:

Main.java
int x = 10;
x = 7.5;       // not allowed
Error
error: incompatible types: possible lossy conversion from double to int
Dynamic (Python)Static (Java)
Type decidedfrom the current valuefrom the declaration, before running
Can a name change type?yesno
Type errors foundonly when the line runsat compile time
Speedslower: types are watched while runningfaster: checks already done
Writing effortless typingdeclarations needed

2.3Why static typing catches what dynamic typing misses

With dynamic typing it is difficult to catch errors such as typos. This function should return all factors of n, but one name is misspelt:

main.py
def factors(n):
    factorlist = []
    for i in range(1, n + 1):
        if n % i == 0:
            factorlst = factorlist + [i]   # typo here!
    return factorlist

print(factors(6))
Output
[]

No error at all. The typo silently created a new variable factorlst, and the real list stayed empty. The answer should have been [1, 2, 3, 6]. A statically typed language needs every name declared first, so the misspelt name is flagged immediately:

Main.java
factorlst = factorlist + i;   // factorlst was never declared
Error
error: cannot find symbol
Static typing (Java): caught BEFORE runningwrite codecompileERROR herecannot find symbolDynamic typing (Python): found only when/if it runswrite coderunno error at all!answer silently wrong: []The earlier a bug is found, the cheaper it is to fix
Figure 5. The same typo caught at compile time vs found (maybe) at run time

Empty user-defined objects

A linked list is a sequence of Node objects, and the empty list is conveniently written as None. Without declaring the type of l, Python cannot say what type it has once l = None:

main.py
class Node:
    def __init__(self, v):
        self.value = v
        self.next = None

l = None
print(type(l).__name__)
l = Node(1)
print(type(l).__name__)
Output
NoneType
Node
Main.java
Node l = null;        // l is declared as a Node, and is empty for now
l = new Node(1);      // still a Node

2.4Types as organising concepts

Even simple type synonyms clarify code. A 2D point is a pair of floats and a 3D point is a triple; naming them makes the intent transparent for whoever writes, reads or maintains the code.

main.py
Point2D = tuple[float, float]
Point3D = tuple[float, float, float]

def shift(p: Point2D, dx: float) -> Point2D:
    return (p[0] + dx, p[1])

print(shift((1.0, 2.0), 0.5))
Output
(1.5, 2.0)

Java has no typedef; you create a real type instead (a class, or a record in newer Java):

Main.java
record Point2D(double x, double y) { }     // Java 16+

The bigger idea is abstract data types and objects. In a banking application Account and Customer are separate types:

OperationApplies to
deposit, withdraw, transferAccount (not Customer)
update personal detailsCustomer (not Account)
Main.java
customer.deposit(500);    // a Customer has no deposit()
Error
error: cannot find symbol

2.5Static analysis

  • Find errors as early as possible: it saves cost and effort.
  • In general a compiler cannot check that a program will work correctly. This is the halting problem (Alan Turing): no program can decide, for every program, whether it will stop or what it will finally do.
  • But with variable declarations the compiler can detect type errors at compile time. That is static analysis.
  • Dynamic typing would catch these only when the code runs, and running is also slower because types are monitored at the same time.

The compiler can also optimise using what static analysis knows:

OptimisationIdea
Re-order statementsarrange them to optimise reads and writes
Reuse computed expressionsif x*y appears twice, compute it once and store it
Compile-time vs run-time errors. A compile-time error stops the program from being built at all (cannot find symbol, incompatible types). A run-time error happens while it runs (dividing by zero, a missing file). Static typing moves a whole class of mistakes from the second kind to the first.

2.6Type inference

Some languages deduce the types of variables statically, from the context in which they are used. After x = 7, the line y = x + 15 forces y to be an int. If the inferred type is consistent across the whole program, everything is fine.

Main.java
var x = 7;          // Java 10+: inferred as int
var y = x + 15;     // inferred as int
y = "hello";        // still an error
Error
error: incompatible types: String cannot be converted to int
Interview trap. var is not dynamic typing. The compiler works the type out once, at compile time, and then it is fixed forever. It also works only for local variables that have an initial value.

2.7Strong vs weak typing (extra)

People mix up two different questions. Static vs dynamic asks when types are checked. Strong vs weak asks how strictly mismatched types are refused.

Types checked at compile timeTypes checked at run time
Strict (strong)JavaPython (5 + "3" is an error)
Lenient (weak)C (many silent conversions)JavaScript (5 + "3" gives "53")
Java detail. "Total: " + 5 is legal in Java because + with a String means concatenation. That is a rule of the language, not a loose type check.

2.8Exam tips and common mistakes

Exam tips
  • "When is the error found?" Static: before running. Dynamic: when that line runs.
  • Static analysis can catch type errors; it can not prove a program correct (halting problem).
  • Quote the three uses of types: make sense of bits, organise concepts, help the compiler (bugs and optimisation).
Common mistakes
  • Saying static typing guarantees a correct program. It only guarantees type consistency.
  • Treating var as dynamic typing.
  • Thinking Python is "weakly typed" because it is dynamic. It is dynamic and strong.
  • Forgetting that an uninitialised Python name has no type: using it is a NameError.

2.9Interview corner: what they may ask

Likely questionChanceCrisp answer
Static vs dynamic typing? Which is Java?Very likelyStatic: types are fixed at declaration and checked at compile time. Dynamic: types come from values and are checked at run time. Java is statically typed.
Advantages of static typing?Very likelyErrors caught before running, better tooling and documentation, and faster code because the compiler can optimise and nothing is type-checked at run time.
Strongly vs weakly typed?LikelyStrong languages refuse mismatched operations instead of silently converting. Python and Java are strong; JavaScript is weak. It is a different axis from static/dynamic.
What does var do? Is Java dynamic then?LikelyLocal type inference: the compiler deduces the type from the initial value. The type is still static and fixed, so Java is not dynamic.
Why can't a compiler catch every bug?SometimesWhether a general program is correct or even halts is undecidable (halting problem). Compilers check types and structure, not full correctness.
Compile-time error vs run-time error?Very likelyCompile-time: the code is rejected before it runs (cannot find symbol). Run-time: it fails while running (ArithmeticException, NullPointerException).
Next · Lecture 3Memory Management
03
Lecture 3

Memory Management

Scope and lifetime, activation records on the stack, how arguments are passed, the heap, and garbage collection.

3.1Keeping track of variables: scope and lifetime

Variables store intermediate values. Most are local to a function; some are global, outside any function; and some data is created dynamically, like the nodes of a list. Two ideas keep this straight:

Scope

When the variable is available for use: can this line use this name?

Lifetime

How long the storage stays allocated: does the memory still exist?

The lecture's example: f loops over x and calls g(x), and g also loops with its own x. The x of f is not in scope inside g, yet its storage is still allocated: it is alive but hidden. This is the "hole in the scope".

main.py
def g(m):
    for x in range(m):
        pass
    return m

def f(l):
    y = 0
    for x in l:
        y = y + g(x)
        print("f still sees its own x =", x)
    return y

print(f([2, 3]))
Output
f still sees its own x = 2
f still sees its own x = 3
5
Memory stack while g() runs inside f()f(l)older recordl = [2, 3]y = 0x = 2alive, but hiddeng(m)top of stackm = 2x = 0 <- g's own xIN SCOPE nowonly names in the toprecord: m and g's xALIVE, not in scopef's x (and y, l): storagestill allocated, but thename x means g's x= "hole in the scope"Scope = can I use the name? Lifetime = is the storage still allocated?
Figure 6. While g runs, the x of f is alive but not in scope
Main.java
class Counter {
    int x = 5;                       // a field: lives as long as the object
    void show() {
        int x = 10;                  // a local x hides the field x
        System.out.println(x);       // 10
        System.out.println(this.x);  // 5   (the hidden one, reached explicitly)
    }
}
Java scope rules in one breath. A local variable is in scope from its declaration to the end of its { } block. Java does not let a local hide another local (variable x is already defined), but a local may hide a field, as above. There are no true global variables in Java: shared data lives in static fields of a class.

3.2The memory stack and activation records

  • Each function needs storage for its local variables. When it is called, an activation record is created for it.
  • Activation records are stacked: the newest is on top, and it is popped when the function exits.
  • The control link points to the start of the previous record (who called me).
  • The return value link tells where to store the result.
Memory (stack)Storage for factorial(3)n3factorial(n-1)(waiting...)Storage for factorial(2)n2factorial(n-1)(waiting...)Control linkReturn value linkControl linkpoints to the START of theprevious record (who called me)Return value linksays WHERE to write my answer:the factorial(n-1) slot of thecallerNewest record = top of stackPopped when the call returnsOne record per call: each call has its own n
Figure 7. The lecture's diagram: factorial(3) calls factorial(2)

Read the picture carefully, because it is a favourite for questions. The top record is the newest call, factorial(2). Its control link goes back to the start of the record of factorial(3), and its return value link goes to the empty factorial(n-1) slot of factorial(3), which is where its answer will be written.

main.py
def factorial(n, depth=0):
    pad = "  " * depth
    print(pad + "call   factorial(" + str(n) + ")   stack depth", depth + 1)
    if n <= 1:
        result = 1
    else:
        result = n * factorial(n - 1, depth + 1)
    print(pad + "return factorial(" + str(n) + ") =", result)
    return result

factorial(3)
Output
call   factorial(3)   stack depth 1
  call   factorial(2)   stack depth 2
    call   factorial(1)   stack depth 3
    return factorial(1) = 1
  return factorial(2) = 2
return factorial(3) = 6
StepStack (bottom to top)What happens
1f(3)n = 3; needs factorial(2), so it waits
2f(3), f(2)a new record is pushed on top
3f(3), f(2), f(1)base case: 1
4f(3), f(2)f(1) popped; its 1 is written to the slot of f(2)
5f(3)f(2) popped after 2 * 1 = 2
6(empty)f(3) popped after 3 * 2 = 6
  • Scope of a variable: it is in the activation record at the top of the stack.
  • Lifetime: the storage is still allocated on the stack, so it is alive even when buried under newer records.
Nuance (extra). The lecture says global variables are reached "by following control links". As a mental picture that is fine. In real Java and Python the compiler works out where a name lives from where it is written in the program text (lexical scope), not by walking the call chain.

A finite stack can overflow

A stack has a fixed size. A function that calls itself with no stopping condition keeps pushing records until it runs out:

main.py
def forever(n):
    return forever(n + 1)

forever(0)
Error
RecursionError: maximum recursion depth exceeded

Java does the same and reports a StackOverflowError. Both are the stack running out, caused by missing or unreachable base cases.

3.3Passing arguments to a function

When a function is called, the arguments are substituted for the formal parameters. The parameters are part of the activation record, filled in at the call, as if there were implicit assignment statements at the start of the function. For f(x, myl) with def f(a, l): that means a = x and l = myl.

Call by value

Copy the value. Updating the value inside the function has no side effect outside.

Call by reference

The parameter points to the same location as the argument. It can have side effects: it can update the contents.

main.py
def f(a, l):
    a = a + 1            # changes only the copy
    l.append(99)         # changes the shared list
    l = [0]              # only re-points l
    print("inside:", a, l)

x = 7
myl = [8, 9, 10]
f(x, myl)
print("outside:", x, myl)
Output
inside: 8 [0]
outside: 7 [8, 9, 10, 99]
f(x, myl) with x = 7 and myl = [8, 9, 10]A number is COPIEDx7a7a = xchanging a does not touch xA list is SHARED (one object, two names)myll[8, 9, 10]one listInside f:l.append(99)changes the shared list -> caller sees [8, 9, 10, 99]l = [0]only re-points l -> caller's myl is unchangeda = a + 1changes the copy -> caller's x is still 7
Figure 8. A number is copied; a list is shared
The lecture's sentence. Call by reference "can update the contents, but cannot change the reference itself". That is exactly what the output above shows for the list: append was seen outside, but l = [0] was not.

Java: always pass by value (the classic interview question)

Main.java
static void change(int a, int[] arr) {
    a = 99;                    // changes only the copy
    arr[0] = 99;               // changes the shared array
    arr = new int[] {0};       // only re-points the local copy of the reference
}

int x = 7;
int[] m = {8, 9, 10};
change(x, m);
// x is still 7;   m is now {99, 9, 10}
  • Java always copies the value of the argument.
  • For a primitive (int, double...) the value is the number itself, so the caller never sees a change.
  • For an object or array the value that is copied is the reference. Both names then reach the same object, so changing its contents is visible, but re-pointing your copy of the reference is not.
Careful with the word "reference". In true call by reference (C++ &, C# ref) the parameter is an alias for the caller's variable, so re-pointing it would change the caller. Java never does that. The lecture's "cannot change the reference itself" describes the Java/Python behaviour.

3.4The heap

Consider a function that inserts a value into a linked list. The storage for the new node is allocated inside the function, yet the node must persist after the function exits. It cannot live in the activation record, because that is popped. We need a separate area for such dynamically allocated data: the heap.

MemoryStackfree spaceHeapgrowsgrowsinsert(l, 9) just finishedframe of callerhead3•7•9/stays reachable via headNode 9 was created inside insert(),yet it lives on: it sits on the HEAP,not in the activation record.heap = memory area
Figure 9. Stack and heap grow towards each other; a heap node outlives the function
main.py
class Node:
    def __init__(self, v, nxt=None):
        self.value = v
        self.next = nxt

def push_front(head, v):
    node = Node(v, head)       # created inside the function
    return node                # ...but it outlives the call

head = None
for v in (9, 7, 3):
    head = push_front(head, v)

cur = head
while cur is not None:
    print(cur.value, end=" ")
    cur = cur.next
Output
3 7 9 
  • The heap is not the "heap" data structure used for priority queues. It is just a memory area.
  • Conceptually it is allocated from the opposite end of memory to the stack; the free space is in between.
  • Heap storage outlives the activation record. It is reached through some variable that is in scope.
StackHeap
Holdsactivation records: parameters, local variables, linksdynamically allocated data (objects, arrays, nodes)
Freedautomatically when the function exitswhen it is unreachable (garbage collector) or freed by hand
Runs out asStackOverflowError (too deep recursion)OutOfMemoryError (too many live objects)
In Javalocal variables and referencesevery object created with new, and arrays

3.5Managing heap storage: manual vs garbage collection

On the stack, storage is freed when the function exits. On the heap, after we delete a node from a linked list it becomes dead storage: nothing points to it any more. How do we get that memory back?

Manual memory management (C)
  • The programmer requests and returns storage: p = malloc(...) and free(p).
  • Error prone: forget free and you get a memory leak; free too early and you get an invalid access.
Automatic garbage collection (Java, Python)
  • The run-time environment checks and cleans up dead storage.
  • Mark all storage reachable from program variables; return all unmarked cells to free space.
  • Trade-off: convenience for the programmer vs a performance penalty.
1. Mark: start from the program variables and follow referencesheadtmpvariables (roots)ABCD2. Sweep: anything NOT marked is garbage, return it to free spaceEFGno variablepoints hereEven a cycle (E -> F -> G -> E) is garbage if no variable can reach it
Figure 10. Mark what is reachable from the variables, sweep the rest
main.py
import gc, weakref

class Node:
    pass

a = Node()
b = Node()
a.other = b
b.other = a                    # a and b point at each other (a cycle)
probe = weakref.ref(a)         # a probe that does not keep a alive
del a, b                       # no variable reaches them any more
print(probe() is None)         # still in memory?
gc.collect()                   # a collection pass
print(probe() is None)
Output
False
True

Nothing could reach the two nodes after del, yet they were still in memory (False) because they pointed at each other. Reachability from variables, not "is anything pointing at me", is the test, so one collection pass freed them (True).

  • Java's collector is generational: new objects start in a young area that is cleaned often, and survivors are moved to an old area cleaned rarely.
  • Collection can pause the program briefly: that is the performance penalty from the lecture.
  • System.gc() is only a request; you cannot force a collection.
  • Garbage collection does not make leaks impossible: an object that is still reachable but no longer needed (for example kept in a static list that only grows) is never freed.

3.6Where does each thing live? (a Java snapshot)

Main.java
static void demo() {
    int count = 3;                 // value 3 on the stack
    int[] marks = new int[3];      // reference on the stack, array object on the heap
    Account a = new Account();     // reference on the stack, Account object on the heap
}                                  // record popped; the objects become garbage
                                   // once nothing reaches them
ThingWhereFreed when
countstack (in the record of demo)demo returns
marks (the reference)stackdemo returns
the array objectheapafter no variable can reach it, at the garbage collector's pleasure

3.7Exam tips and common mistakes

Exam tips
  • Draw the stack when asked about recursion: one record per call, newest on top, popped on return.
  • "Scope" = visible now. "Lifetime" = storage exists. A variable can be alive but out of scope, never the reverse.
  • For parameter passing, decide first: is it a number (copied) or an object (reference copied)?
  • Control link = who called me. Return value link = where my answer goes.
Common mistakes
  • Saying Java passes objects "by reference". It passes the reference by value.
  • Confusing the heap memory area with the heap data structure.
  • Thinking the garbage collector frees an object when its variable goes out of scope. It frees it when it is unreachable.
  • Mixing up StackOverflowError (too deep recursion) and OutOfMemoryError (heap full).

3.8Interview corner: what they may ask

Likely questionChanceCrisp answer
Stack vs heap?Very likelyStack: per-call activation records (locals, parameters), freed automatically on return. Heap: dynamically allocated objects that outlive the call, reclaimed by the garbage collector.
Is Java pass by value or pass by reference?Very likelyAlways by value. For objects, the value copied is the reference, so the callee can change the object's contents but cannot re-point the caller's variable.
How does garbage collection work? Can you force it?Very likelyIt marks everything reachable from the program variables and reclaims the rest (modern JVMs are generational). System.gc() is only a hint.
Can Java have memory leaks?LikelyYes: objects that stay reachable (static collections, caches, listeners never removed) are never collected even though they are no longer needed.
StackOverflowError vs OutOfMemoryError?Very likelyStack overflow: the call stack is exhausted, usually by runaway recursion. Out of memory: the heap cannot satisfy an allocation.
What is scope vs lifetime? Give a case where they differ.LikelyScope: where a name is usable. Lifetime: how long its storage exists. A local hidden by an inner variable of the same name (a "hole in the scope") is alive but out of scope.
What is an activation record?LikelyThe block of stack storage made for one call: parameters, locals, a control link to the caller's record and a place for the return value.
Next · Lecture 4Abstraction and Modularity
04
Lecture 4

Abstraction and Modularity

Stepwise refinement (program and data), components with interfaces and specifications, and the language features that support abstraction.

4.1Stepwise refinement

  • Begin with a high-level description of the task.
  • Refine the task into subtasks, then elaborate each subtask further.
  • Subtasks can be coded by different people.
  • Program refinement focuses on the code; the data structures hardly change.
Level 1: the whole task in one lineprint first thousand prime numbersrefineLevel 2: split into subtasksdeclare table pfill table p with first thousand primesprint table prefine againLevel 3: real statements (the data is now an array)integer array p[1:1000]for k from 1 through 1000: make p[k] equal to the kth prime numberfor k from 1 through 1000: print p[k]different people cancode each subtask
Figure 11. The lecture's example: print the first thousand primes

This is the classic top-down method. Each level says what in a little more detail until the last level is real code. Here is level 3 turned into Python:

main.py
def first_primes(count):            # fill table p with the first `count` primes
    p = []
    n = 2
    while len(p) < count:
        if all(n % q != 0 for q in p if q * q <= n):
            p.append(n)
        n += 1
    return p

p = first_primes(1000)              # declare table p, then fill it
print(len(p), p[0], p[-1])          # print table p (summary only)
Output
1000 2 7919

1000 primes, the first is 2 and the 1000th is 7919. Notice that the three top-level steps (declare, fill, print) can be written, tested and replaced independently.

4.2Data refinement, and why it cascades

Take a banking application. Typical functions: CreateAccount(), Deposit() / Withdraw() and PrintStatement(). How do we represent each amount? We only need the current balance, so overall an array of balances is enough.

main.py
balance = [0, 0]                       # one number per account

def deposit(acc, amt):
    balance[acc] += amt

def withdraw(acc, amt):
    balance[acc] -= amt

def print_statement(acc):
    print("balance", balance[acc])

deposit(0, 1000)
withdraw(0, 200)
withdraw(0, 300)
print_statement(0)
Output
balance 500

Now the bank asks for PrintTransactions(). We must record every transaction of each account, so the data representation changes. That is data refinement, and it has a cascading impact: every function that touches accounts must be rewritten.

main.py
transactions = [[], []]                # a list of amounts per account

def deposit(acc, amt):
    transactions[acc].append(+amt)         # rewritten

def withdraw(acc, amt):
    transactions[acc].append(-amt)         # rewritten

def print_statement(acc):
    print("balance", sum(transactions[acc]))   # rewritten

def print_transactions(acc):               # new
    for t in transactions[acc]:
        print(t)

deposit(0, 1000)
withdraw(0, 200)
withdraw(0, 300)
print_statement(0)
print_transactions(0)
Output
balance 500
1000
-200
-300
Before: only the current balance500120080balance[ ] : one number per accountAfter: PrintTransactions() needs historyaccount 0: balance 500+1000 -200 -300transactions list per accountEvery function that touches accounts must change too:CreateAccount()Deposit()Withdraw()PrintStatement()Cascading impact: changing how data is stored forces changes in all code that uses it.Objects fix this: the data and the methods that use it live together in one place.Program refinement = refine the code, data stays. Data refinement = the data itself changes.
Figure 12. Changing the data forces changes in every function that uses it

This is the problem that objects solve. If the data and the functions that use it live together in one class, refining the data representation only touches that class. Users of Account call deposit() and never see the list:

main.py
class Account:
    def __init__(self):
        self.transactions = []         # private business of Account
    def deposit(self, amt):
        self.transactions.append(+amt)
    def withdraw(self, amt):
        self.transactions.append(-amt)
    def balance(self):
        return sum(self.transactions)
    def print_transactions(self):
        for t in self.transactions:
            print(t)

a = Account()
a.deposit(1000)
a.withdraw(200)
a.withdraw(300)
print(a.balance())
a.print_transactions()
Output
500
1000
-200
-300
Program refinementData refinement
What changesthe code gets more detailedthe data representation itself
Effect on other codesmall, localcascades to every user of the data
Example"fill table p" becomes a loopbalances become transaction lists

4.3Modular software development

  • Use refinement to divide the solution into components.
  • Build a prototype of each component to validate the design.
  • Improve each component independently, preserving its interface and specification.
A component is described byMeaningFor a function
Interfacewhat is visible to the other components (typically function calls)header, argument types, return type
Specificationthe behaviour of the component as seen through the interfacethe intended input-output behaviour
OthercomponentsComponentInterface + Specificationheaders, argument and return types+ what it promises to doImplementation (hidden)v1: sorted listv2: heapThey only seethe top partSwap v1 for v2: nobody outside notices, because interface and specification stayed the same
Figure 13. Others see the interface and specification; the implementation can change

In Java the word interface is literal: it lists the operations and says nothing about how they are done. The specification travels as a documentation comment.

Main.java
interface PriorityQueue {
    /** Adds x to the queue. */
    void insert(int x);

    /** Removes and returns the largest value. Must not be called on an empty queue. */
    int deleteMax();
}

class ListPQ implements PriorityQueue { /* unsorted list inside */ }
class HeapPQ implements PriorityQueue { /* heap inside */ }
  • The main challenge is a suitable language to write specifications: it must balance abstraction and detail, and should not become another programming language.
  • We cannot algorithmically check that a specification is met. That is the halting problem again.

4.4How a language supports abstraction

KindMechanismWhat it givesIn Java
Control abstractionfunctions and proceduresencapsulate a block of code and reuse it in different contextsmethods
Data abstractionabstract data types (ADTs)a set of values plus the permitted operations; the representation is hidden; interaction only through the public interfaceclasses with private fields, interfaces
Object-oriented programmingADTs in a hierarchyimplicit reuse of implementations: subtyping and inheritanceextends, implements

The lecture's example of broken data abstraction: if a stack is implemented as a list, we should not be able to observe or modify the internal elements. Python cannot stop us:

main.py
class Stack:
    def __init__(self):
        self.items = []
    def push(self, x):
        self.items.append(x)
    def pop(self):
        return self.items.pop()

s = Stack()
s.push(1)
s.push(2)
s.items[0] = 99          # reaches inside the representation
print(s.pop(), s.pop())
Output
2 99

A real stack would give 2 1. The outsider rewrote the bottom element through a back door. In Java, private List<Integer> items; closes that door at compile time. Chapter 6 shows why this matters.

4.5Top-down vs bottom-up

Top-downBottom-up
Start fromthe whole tasksimple building blocks
Thenrefine into subtaskscombine blocks into bigger ones
Example hereprint first thousand primeseven, square then sum(map(...))
Real projects use both. You decompose the problem top-down and reuse existing library blocks bottom-up.

4.6Exam tips and common mistakes

Exam tips
  • Interface = what is visible. Specification = what it promises. Implementation = how (hidden, replaceable).
  • Program refinement changes code; data refinement changes data and cascades.
  • When asked "why can't we check the specification automatically?", answer: the halting problem.
Common mistakes
  • Using "interface" and "implementation" as if they were the same thing.
  • Thinking modularity means "many files". It means components that can change independently.
  • Forgetting that Python lets outsiders reach inside an object; the abstraction is only a convention there.

4.7Interview corner: what they may ask

Likely questionChanceCrisp answer
Abstraction vs encapsulation?Very likelyAbstraction: show what something does, hide how (a design idea, e.g. an interface). Encapsulation: bundle data with its methods and restrict access (private), the mechanism that protects the hidden part.
What is an interface in Java and why use one?Very likelyA contract listing operations without implementing them. Code depends on the contract, so implementations can be swapped (list-based vs heap-based priority queue) without changing callers.
What is loose coupling and high cohesion?LikelyCohesion: one component does one related job. Coupling: how much components depend on each other's insides. Data refinement cascading everywhere is a symptom of tight coupling.
Top-down vs bottom-up design?SometimesTop-down: break the task into subtasks. Bottom-up: combine simple blocks. Real design mixes both.
How do you document the behaviour of a method?SometimesAs a specification: a Javadoc comment (what it takes, returns, and any conditions), plus tests. It cannot be proven automatically in general.
Next · Lecture 5Object-Oriented Programming
05
Lecture 5

Object-Oriented Programming

What an object is, where OOP came from, and its four distinguishing features: abstraction, subtyping, dynamic lookup and inheritance.

5.1Objects

Object

An object is like an abstract data type: hidden data with a set of public operations. All interaction goes through the operations, which are called messages, methods or member functions.

An object is a uniform way of packaging any mix of data and behaviour. It can hold one integer, such as a counter, or it can be an entire file system or database.

main.py
class Counter:
    def __init__(self):
        self.count = 0
    def click(self):
        self.count += 1
    def value(self):
        return self.count

c = Counter()
c.click()
c.click()
print(c.value())
Output
2

The lecture names four distinguishing features of object-oriented programming. Each gets a section below.

FeatureOne-line meaning
Abstractionpublic interface, private implementation
Subtypinga hierarchy of types; interfaces are compatible
Dynamic lookupthe method that runs is chosen at run time
Inheritancereuse of implementations

5.2History: Simula and the event queue

Objects were first introduced in Simula, a simulation language from the 1960s. Event-based simulation follows one basic pattern:

  • Maintain a queue of events to be simulated.
  • Simulate the event at the head of the queue.
  • Add all the events it spawns to the queue.

The challenge. A well-typed queue holds values of one type, yet this queue must hold all kinds of events, and we want one generic simulate step without elaborate checking of cases. First the clumsy way:

main.py
def simulate(event):
    if event == "arrival":
        print("customer joins")
    elif event == "departure":
        print("customer leaves")
    elif event == "breakdown":
        print("machine stops")
    # a new kind of event means editing this function again

for e in ["arrival", "departure", "breakdown", "arrival"]:
    simulate(e)
Output
customer joins
customer leaves
machine stops
customer joins

And the object way: every kind of event is its own type with its own simulate:

main.py
class Event:
    def simulate(self):
        raise NotImplementedError

class Arrival(Event):
    def simulate(self):
        print("customer joins")

class Departure(Event):
    def simulate(self):
        print("customer leaves")

class Breakdown(Event):
    def simulate(self):
        print("machine stops")

queue = [Arrival(), Departure(), Breakdown(), Arrival()]
for e in queue:
    e.simulate()                 # one generic call, no case checks
Output
customer joins
customer leaves
machine stops
customer joins
Queue of events (front on the left)Arrivalsimulate()customer joinsDeparturesimulate()customer leavesBreakdownsimulate()machine stopsArrivalsimulate()customer joinsThe simulator just does: event = queue.pop(); event.simulate()It never checks "what kind of event is this?"Each object knows how to simulate itself, and new events it spawns go back into the queue.Queue is well-typed (holds "Event"), yet holds many kinds: that is subtyping
Figure 14. One generic call; each event knows how to simulate itself
Why this matters. To add a new Maintenance event you write one new class. Nothing that already works is edited. This "open for extension, closed for modification" idea is behind a lot of interview design questions.

5.3Feature 1: Abstraction

  • Objects are similar to abstract data types: public interface, private implementation.
  • Changing the implementation should not affect interactions with the object.
  • It is a data-centric view: focus on what data we need to maintain and manipulate.
  • Recall stepwise refinement: it could affect code and data. Tying methods to data makes that easier to coordinate, because refining the data naturally goes with updating the methods that use it.
Main.java
public class Counter {
    private int count;                 // hidden
    public void click()  { count++; }  // public operations
    public int value()   { return count; }
}

5.4Feature 2: Subtyping

A well-typed queue holds values of a fixed type, yet the simulation queue holds many kinds. The way out is to arrange types in a hierarchy. A subtype is a specialisation of a type.

Subtype rule

If A is a subtype of B, then wherever an object of type B is needed, an object of type A can be used. Every A is also a B. Think of sets: if X⊆YX \subseteq Y, every x∈Xx \in X is also in YY.

If A is a subtype of B: A ⊆ BBAevery A is also a Bso A fits wherevera B is neededf() exists in B, so A has f() too;A may implement f() differentlyInheritance exampleEmployeename, date of joiningbasic personal dataManagerkeeps everything of Employee+ date of promotion+ seniority in current role
Figure 15. A subtype is a subset; Manager keeps everything of Employee and adds more
  • If f() is a method in B and A is a subtype of B, then every object of A also supports f().
  • The implementation of f() can be different in A.
Main.java
Event e = new Arrival();      // Arrival is a subtype of Event, so this is allowed
Event[] queue = { new Arrival(), new Departure(), new Breakdown() };
Liskov substitution (extra, popular in interviews). The subtype rule is more than syntax: code written for a B should still behave correctly when it is given an A. If a subtype breaks what callers expect, it is a bad subtype even when it compiles.

5.5Feature 3: Dynamic lookup

  • Whether a method can be called on an object is a static property: type-checking decides it.
  • How the method acts is a dynamic property of how the object is implemented.
  • In a graphics program, different kinds of objects are rendered with the same operation; each object knows how to render itself.
main.py
class Shape:
    def draw(self):
        print("some shape")

class Circle(Shape):
    def draw(self):
        print("circle")

class Square(Shape):
    def draw(self):
        print("square")

for s in [Circle(), Square(), Shape()]:
    s.draw()
Output
circle
square
some shape
v : Bstatic type (declared)1. Compile time"Does B have f()?" yes -> allowedv.f()2. Run timeobject of type Aruntime type; A may override f()A's version of f() is the one that runsAllowed or not = decided by the declared type (static, compiler).What actually happens = decided by the real object (dynamic, run time).Overloading (+ on int / float) is chosen by static types only. Dynamic lookup is not.
Figure 16. Static type decides what is allowed; runtime type decides what runs

Different from overloading. The operation + is addition for int and for float: the internal implementation differs, but the choice is made from the static types. With dynamic lookup a variable v of static type B may refer to an object of subtype A, and the implementation that runs depends on the runtime type A.

Main.java
static void show(Shape s)  { System.out.println("shape");  }
static void show(Circle c) { System.out.println("circle"); }

Shape s = new Circle();    // static type Shape, runtime type Circle
show(s);                   // prints "shape"  : overloads are picked by the STATIC type
s.draw();                  // Circle's draw() : overriding is picked by the RUNTIME type
OverloadingOverriding (dynamic lookup)
Same name, ...different parameter listssame signature in a subclass
Chosenat compile time, by static typesat run time, by the object's real type
Also calledstatic / compile-time polymorphismdynamic / run-time polymorphism

5.6Feature 4: Inheritance

Inheritance is re-use of implementations. Example: different types of employees. Employee objects store basic personal data and the date of joining. Manager objects retain all of that and add more: a date of promotion and a seniority in the current role.

main.py
class Employee:
    def __init__(self, name, joined):
        self.name = name
        self.joined = joined
    def describe(self):
        return self.name + " joined " + str(self.joined)

class Manager(Employee):
    def __init__(self, name, joined, promoted):
        super().__init__(name, joined)     # reuse Employee's setup
        self.promoted = promoted
    def seniority(self, today):
        return today - self.promoted

m = Manager("Asha", 2015, 2020)
print(m.describe())                         # inherited, not rewritten
print(m.seniority(2026))
Output
Asha joined 2015
6
  • Usually one hierarchy of types captures both subtyping and inheritance: A can inherit from B if A is a subtype of B.
  • Philosophically they differ: subtyping is a relationship of interfaces; inheritance is a relationship of implementations.
Main.java
class Manager extends Employee {
    private int promoted;
    Manager(String name, int joined, int promoted) {
        super(name, joined);            // reuse Employee's constructor
        this.promoted = promoted;
    }
}

5.7Subtyping vs inheritance: the Deque example

  • A deque is a double-ended queue: insert-front(), delete-front(), insert-rear(), delete-rear().
  • A stack can be built from it using only insert-front and delete-front; a queue using only insert-rear and delete-front.
  • So Stack and Queue inherit from Deque, reusing its implementation.
  • But they are not subtypes of Deque. If v of type Deque points to a Stack, we cannot safely call insert-rear() or delete-rear(). Their interfaces are not compatible.
  • In fact Deque is a subtype of both Stack and Queue: it supports every operation they do.
Dequeall four operationsinsert-frontdelete-frontinsert-reardelete-rearStackuses only the front onesinsert-frontdelete-frontinsert-reardelete-rearQueueuses insert-rear + delete-frontinsert-frontdelete-frontinsert-reardelete-rearInheritance (reuse code):Stack, Queue -> Deque YESSubtyping (can stand in):Deque -> Stack, Queue YESStack is NOT a subtype of Deque: it cannot do insert-rear / delete-rear.
Figure 17. Inheritance reuses code; subtyping is about which interface can stand in for which
main.py
class Deque:
    def __init__(self):
        self.items = []
    def insert_front(self, x):
        self.items.insert(0, x)
    def delete_front(self):
        return self.items.pop(0)
    def insert_rear(self, x):
        self.items.append(x)
    def delete_rear(self):
        return self.items.pop()

class Stack(Deque):              # inherits ALL of Deque's code
    pass

s = Stack()
s.insert_front(1)
s.insert_front(2)
print(s.delete_front(), s.delete_front())   # stack behaviour
s.insert_rear(7)                             # Python lets a Stack do this...
print(s.items)
Output
2 1
[7]

The inheritance worked (the code was reused), but the last two lines show the problem: a Stack that offers insert_rear is not a real stack interface. Python does not stop us; Java's design pushes you towards the right choice:

You wantIn Java use
Subtyping only (an interface that others can rely on)interface + implements
Subtyping and reuse of codeclass B extends A
Reuse of code without becoming a subtypecomposition: keep an object inside as a private field
Main.java
class Stack {
    private final Deque deque = new Deque();   // reuse by composition
    void push(int x) { deque.insertFront(x); }
    int pop()        { return deque.deleteFront(); }
    // insertRear is simply not available on a Stack
}
"Is-a" vs "has-a". Inheritance should model is-a (a Manager is an Employee). When the relationship is has-a or "uses the code of", prefer composition. "Favour composition over inheritance" is a very common interview line.
Multiple inheritance. A Java class can extend only one class but implement many interfaces. This avoids the "diamond problem" (two parents providing conflicting code for the same method) while still allowing a type to have several subtype relationships.

5.8Lecture words vs interview words

Interviewers usually ask for the "four pillars". They are the same ideas under slightly different names, so link them in your head:

Interview pillarLecture featureHow to say it
EncapsulationAbstraction (private implementation)data and the methods that use it bundled together, with the data hidden behind private
AbstractionAbstraction (public interface), ADTscallers see what an object does through an interface, not how
InheritanceInheritancea subclass reuses and extends a superclass's implementation
PolymorphismSubtyping + Dynamic lookupone call, many behaviours: overloading (compile time) and overriding (run time)
Is Java purely object-oriented? No. It has primitive types (int, double...) that are not objects, and static members that belong to a class, not to an object. That is a deliberate compromise for speed, in the spirit of "every language makes compromises".

5.9Exam tips and common mistakes

Exam tips
  • Subtyping = interfaces. Inheritance = implementations. Say these two words in the answer.
  • Subtype arrow direction: the subtype supports more or equal operations. That is why Deque is the subtype of Stack.
  • For dynamic lookup always separate: can I call it? (static type) and what runs? (runtime type).
Common mistakes
  • Assuming that inheriting always means being a subtype.
  • Mixing up overloading (static, different parameters) and overriding (dynamic, same signature).
  • Saying a subtype "has fewer" methods. A subtype can only add or specialise, never remove.
  • Using inheritance just to reuse code when the relationship is not is-a.

5.10Interview corner: what they may ask

Likely questionChanceCrisp answer
Explain the four pillars of OOP.Very likelyEncapsulation (data + methods bundled, data hidden), abstraction (expose what, hide how), inheritance (reuse and extend a parent), polymorphism (one call, behaviour chosen by type). Give one tiny example each.
Overloading vs overriding?Very likelyOverloading: same name, different parameters, resolved at compile time by static types. Overriding: a subclass redefines an inherited method with the same signature, resolved at run time by the object's real type.
Compile-time vs run-time polymorphism?Very likelyCompile-time = overloading (static binding). Run-time = overriding through a superclass reference (dynamic dispatch / late binding).
Inheritance vs composition: when which?Very likelyInheritance for a true is-a relationship where the subtype can always stand in for the parent. Otherwise compose: hold the other object as a private field and delegate. It is more flexible and does not leak the parent's whole interface.
Why does Java not allow multiple inheritance of classes?Very likelyTo avoid the diamond problem and ambiguity. Multiple interfaces are allowed, since they give subtyping without conflicting implementations.
Is Java purely object oriented?Very likelyNo: primitives are not objects and static members belong to the class.
What is the Liskov substitution principle?LikelyObjects of a subtype must be usable wherever the supertype is expected without breaking the program's correct behaviour.
What is dynamic dispatch (late binding)?LikelyThe JVM picks the method implementation at run time from the object's actual class, not from the variable's declared type.
Next · Lecture 6Classes and Objects
06
Lecture 6

Classes and Objects

Class versus object, constructors and methods, changing a class's internals, and why Python's lack of enforcement leads to Java.

6.1Programming with objects: class and object

Class
  • A template for a data type
  • how the data is stored
  • how the public functions manipulate it
Object
  • A concrete instance of that template
  • each object keeps its own copy of the local data
  • calling a method = sending a message to the object

Example: 2D points. A point has coordinates (x, y). Each point object stores its own x and y; these are called instance variables. For a point p they are p.x and p.y. Inside the class, self is a special name for the current object (Java calls it this).

class Point (template)instance variables:x, ymethods:__init__(a, b)translate(dx, dy)odistance()how data is stored+ how it is changedp1 = Point(5, 7)p1.x = 5p1.y = 7p2 = Point()p2.x = 0p2.y = 0createEach object keeps its OWN copy of x and y: p1.translate(1, 1) does not move p2
Figure 18. One class, many objects, each with its own data

6.2The constructor

When we create an object we need to set it up. A constructor function with a fixed name is called implicitly. In Python it is __init__() and its first parameter is always self.

main.py
class Point:
    def __init__(self, a=0, b=0):
        self.x = a
        self.y = b

p = Point(5, 7)
q = Point()
print(p.x, p.y, q.x, q.y)
Output
5 7 0 0
Correction (extra). The notes' first version of this constructor ends with self.y = y. That is a slip: the parameter is called b, and y does not exist, so it would raise a NameError. The class above uses the correct self.y = b (the lecture's later slide has it right).
Main.java
public class Point {
    private double x, y;               // instance variables

    public Point(double a, double b) { // constructor: same name as the class, no return type
        this.x = a;
        this.y = b;
    }
    public Point() { this(0, 0); }     // Java has no default arguments: overload instead
}
PythonJava
Constructor__init__(self, ...)method named like the class, no return type
Current objectself (written in every method)this (implicit)
Default valuesdefault arguments (a=0)overloaded constructors
CreatePoint(5, 7)new Point(5, 7)

6.3Adding methods: changing state vs just reading it

Translation shifts a point by Δx,Δy\Delta x, \Delta y: (x,y)↦(x+Δx,  y+Δy)(x, y) \mapsto (x+\Delta x,\; y+\Delta y). It updates the instance variables. Distance from the origin, d=x2+y2d = \sqrt{x^2 + y^2}, does not change the object: its state is unchanged.

main.py
import math

class Point:
    def __init__(self, a=0, b=0):
        self.x = a
        self.y = b

    def translate(self, dx, dy):       # changes the state
        self.x += dx
        self.y += dy

    def odistance(self):               # only reads the state
        return math.sqrt(self.x * self.x + self.y * self.y)

p = Point(3, 4)
print(p.odistance())
p.translate(3, 8)
print(p.x, p.y)
print(p.odistance())
Output
5.0
6 12
13.416407864998739
Kind of methodChanges the object?ExampleCommon name
Mutatoryestranslatesetter, setX()
Accessor / querynoodistancegetter, getX()

6.4Changing the internal implementation

Suppose we store the point in polar coordinates (r,θ)(r, \theta) instead, with r=x2+y2r = \sqrt{x^2+y^2} and θ=tan⁡−1(y/x)\theta = \tan^{-1}(y/x). Now the distance from the origin is just r. Translation must convert (r,θ)(r,\theta) back to (x,y)(x,y) using x=rcos⁡θ, y=rsin⁡θx = r\cos\theta,\ y = r\sin\theta, add Δx,Δy\Delta x, \Delta y, and recompute r,θr, \theta.

main.py
import math

class Point:
    def __init__(self, a=0, b=0):
        self.r = math.sqrt(a * a + b * b)
        if a == 0:
            self.theta = math.pi / 2
        else:
            self.theta = math.atan(b / a)

    def odistance(self):
        return self.r

    def translate(self, dx, dy):
        x = self.r * math.cos(self.theta)
        y = self.r * math.sin(self.theta)
        x += dx
        y += dy
        self.r = math.sqrt(x * x + y * y)
        if x == 0:
            self.theta = math.pi / 2
        else:
            self.theta = math.atan(y / x)

p = Point(3, 4)
print(p.odistance())
p.translate(3, 8)
print(round(p.odistance(), 6))
Output
5.0
13.416408

Same interface (Point(a, b), translate, odistance), same answers, completely different insides. Users need not know whether the representation is (x,y)(x,y) or (r,θ)(r,\theta). That is abstraction working.

Public interface (what users may use)Point(a, b) translate(dx, dy) odistance()Version 1: Cartesianself.x, self.yodistance = sqrt(x*x + y*y)Version 2: Polarself.r, self.thetaodistance = r (no sqrt)Users call the interface, so swapping version 1 for 2 breaks nobody.But p.x = 4 reaches INSIDE version 1. After the swap there is no x:that code breaks. Python cannot stop it; Java's private can.
Figure 19. Users touch the interface; the representation behind it can be swapped
A bug hiding in the lecture code (extra). math.atan(b / a) only returns angles between -90 and +90 degrees, so it loses the quadrant when x is negative. Run through the polar version a point such as (-3, 4) and it comes back as (3, -4) (checked by running it). The correct tool is math.atan2(b, a). The lecture's point is the idea of swapping representations, not production-ready geometry, but do not copy this code.
main.py
import math
a, b = -3, 4
theta = math.atan(b / a)                  # lecture version
r = math.sqrt(a * a + b * b)
print(round(r * math.cos(theta), 6), round(r * math.sin(theta), 6))
theta = math.atan2(b, a)                  # correct version
print(round(r * math.cos(theta), 6), round(r * math.sin(theta), 6))
Output
3.0 -4.0
-3.0 4.0

6.5When abstraction is not enforced: breaking changes

Python allows direct access to instance variables from outside the class:

main.py
class Point:
    def __init__(self, a=0, b=0):
        self.x = a
        self.y = b

p = Point(5, 7)
p.x = 4                      # the point is now (4, 7)
print(p.x, p.y)
Output
4 7

This defeats the purpose of abstraction. If another programmer wrote p.x = 4 and we later switch to the polar representation, that code breaks. A change to the internals that damages other code is called a breaking change:

main.py
import math

class Point:                                  # now the polar version
    def __init__(self, a=0, b=0):
        self.r = math.sqrt(a * a + b * b)
        self.theta = math.atan2(b, a)

p = Point(5, 7)
print(p.x)
Error
AttributeError: 'Point' object has no attribute 'x'

6.6Subtyping and inheritance in classes: Rectangle and Square

Define Square as a subtype of Rectangle: a different constructor, the same instance variables.

main.py
class Rectangle:
    def __init__(self, w=0, h=0):
        self.width = w
        self.height = h
    def area(self):
        return self.width * self.height
    def perimeter(self):
        return 2 * (self.width + self.height)

class Square(Rectangle):
    def __init__(self, s=0):
        self.width = s
        self.height = s

s = Square(5)
print(s.area(), s.perimeter())
Output
25 20

Square inherits area() and perimeter() from Rectangle, and it all works. Now the subclass author wants a nicer instance variable, side:

main.py
class Rectangle:
    def __init__(self, w=0, h=0):
        self.width = w
        self.height = h
    def area(self):
        return self.width * self.height
    def perimeter(self):
        return 2 * (self.width + self.height)

class Square(Rectangle):
    def __init__(self, s=0):
        self.side = s

s = Square(5)
print(s.area(), s.perimeter())
Error
AttributeError: 'Square' object has no attribute 'width'

A subtype is not forced to be an extension of its parent: width and height were never defined for a Square. Worse, the subclass and the parent class are usually developed separately. The author of Rectangle may rename the variables:

main.py
class Rectangle:
    def __init__(self, w=0, h=0):
        self.wd = w
        self.ht = h
    def area(self):
        return self.wd * self.ht
    def perimeter(self):
        return 2 * (self.wd + self.ht)

class Square(Rectangle):
    def __init__(self, s=0):
        self.width = s
        self.height = s

s = Square(5)
print(s.area())
Error
AttributeError: 'Square' object has no attribute 'wd'
Rectangle sets width, height and uses them in area() and perimeter()Case 1: worksSquare setswidth = sheight = sarea() finds width, heightOKCase 2: breaksSquare setsside = s(no width/height)area() reads self.width: missingAttributeErrorCase 3: breaksRectangle renamedto wd, ht; Squarestill sets width/heightarea() reads self.wd: missingAttributeErrorParent and child are written separately:the child cannot know the parent's variable names.We need a way to hide the parent's variables (private) and a safe way to set them.Java: private fields in Rectangle + Square calls super(s, s) in its constructor.
Figure 20. Three versions of the same program: one works, two break
  • Square constructor sets width and height, but the parent now uses wd and ht: the names changed.
  • We need a way to hide private implementation details: declare each component private or public.
  • But then how can the Square constructor set the private wd and ht? Square does not, and should not, know their names.
  • So we need elaborate declarations: the type and the visibility of every variable, with static type checking to catch mistakes early.

How Java solves it

Main.java
class Rectangle {
    private double width, height;               // hidden: no outsider, no subclass, can touch them

    Rectangle(double w, double h) { width = w; height = h; }
    double area()      { return width * height; }
    double perimeter() { return 2 * (width + height); }
}

class Square extends Rectangle {
    Square(double side) {
        super(side, side);                      // ask the parent to set up ITS OWN variables
    }
}
  • The fields are private, so renaming them cannot break a subclass: nobody else could use the names.
  • super(side, side) lets the child construct the parent part without knowing the parent's variable names.
  • Static typing catches a wrong use immediately: sq.wd from outside is "has private access", a compile-time error.
Interview gem: is a Square a Rectangle? Here we used it only to show inheritance and privacy. If the rectangle is mutable (it has setWidth and setHeight), a Square cannot honour both independently, so substituting a Square for a Rectangle can break code written for rectangles. That is the classic violation of the Liskov substitution principle. Say this and you stand out.

6.7Language support: what Python leaves to discipline

ConcernPythonJava
Hide implementation detailsconvention only (_name); anyone can still reach itprivate is enforced by the compiler
Declare typesoptional hints, not enforcedmandatory, checked at compile time
Subtype consistencyprogrammer disciplineextends / implements checked by the compiler
Java access levelWho can use it
privateonly the same class
(none, "package-private")classes in the same package
protectedsame package and subclasses
publiceveryone
The lecture's closing message
  • Python has no mechanism to enforce privacy. You can illegally manipulate private instance variables, and can introduce inconsistencies between subtypes and parent types.
  • So: use strong declarations to enforce privacy and types. Do not rely on programmer discipline. Catch bugs early through type checking. This is the reason for learning Java.

6.8Exam tips and common mistakes

Exam tips
  • Class = template (no data of its own). Object = instance (has its own data). One class, many objects.
  • Mutator changes instance variables; accessor only returns something computed from them.
  • If a question shows a subclass that sets different variables than the parent uses, the answer is a run-time error in Python and an access/construction error in Java.
  • Always explain a "breaking change" with: outside code depended on the internals.
Common mistakes
  • Forgetting self. in front of an instance variable inside a method (it becomes a local variable).
  • Thinking Square(Rectangle) automatically gets width and height. They exist only if the constructor of someone sets them.
  • Believing a class has its own data. Only objects do (static fields excepted).
  • Copying the lecture's atan-based polar code as if it handled all quadrants.

6.9Interview corner: what they may ask

Likely questionChanceCrisp answer
Class vs object?Very likelyA class is the blueprint (fields + methods); an object is one instance with its own field values, created with new. Objects live on the heap.
What is a constructor? Can it be overloaded? Does it return?Very likelySpecial code run when an object is created, named like the class, with no return type. It can be overloaded; Java has no default arguments.
What are this and super?Very likelythis: the current object (the lecture's self). super: the parent part, used to call the parent constructor or an overridden method.
Why make fields private and use getters/setters?Very likelySo the internal representation can change without breaking callers, and so values can be validated in one place. Lecture 6's Point and Rectangle/Square demos are exactly this.
Is a Square a Rectangle? (Liskov)LikelyMathematically yes, but with mutable setters it breaks substitutability, so as a class design it is a poor is-a.
What is a breaking change?LikelyA change to a public interface or exposed internals that makes existing client code stop working.
Difference between mutator and accessor?SometimesA mutator changes the object's state (translate); an accessor only reads (odistance).
Next · InterviewInterview Question Bank
07
Interview

Interview Question Bank

What is likely to be asked from Week 1, with 30-second answers, deeper explanations and code-reading practice.

Interviewers use these concepts to test whether you understand Java, not whether you can type it. Every answer below starts with a 30-second version; open the button, then read the details.

1Definition

One clear sentence: what it is.

2Tiny example

Three or four lines of code, or a picture in words.

3The failure it prevents

Why it exists: what goes wrong without it.

Answer shape. Almost every follow-up is "give an example" or "what goes wrong if...". Practise answering in these three steps and you will rarely get stuck.

7.1Interview heat-map: what is most likely

Very likely8 topics
Pass by value, references"Is Java pass by value or reference?"Notes 3.3
Stack vs heap, garbage collection"Where do objects live? How does GC work?"Notes 3.2 - 3.5
Polymorphism, overloading vs overriding"Explain polymorphism with an example"Notes 5.5, 5.8
Inheritance vs composition, multiple inheritance"Why no multiple inheritance in Java?"Notes 5.7
Class vs object, constructors, this / super"What is a constructor?"Notes 6.1 - 6.7
Compiled vs interpreted, JDK / JRE / JVM"Why is Java platform independent?"Notes 1.3
Static vs dynamic typing, var"Is Java statically typed? Does var change that?"Notes 2.2 - 2.6
Abstraction vs encapsulation, interfaces"Difference between abstraction and encapsulation?"Notes 4.3, 4.4
Likely3 topics
StackOverflowError vs OutOfMemoryError"When does each occur?"Notes 3.2, 3.4
Scope vs lifetime, shadowing"What does this.x do when a local is also named x?"Notes 3.1
Liskov substitution, Square vs Rectangle"Should Square extend Rectangle?"Notes 5.4, 6.6
Sometimes2 topics
Imperative vs declarative, streams"What style is Java?"Notes 1.5
Halting problem, program vs data refinement"Why can't a compiler catch every bug?"Notes 2.5, 4.2

7.2Fundamentals

Interview 1Why Java instead of Python?
FundamentalsVery likelyNotes 2.2, 6.7

A very common opener. Answer with trade-offs, not loyalty.

Answer
Say this first (30 seconds)

Java trades some brevity for safety: static types and enforced private catch whole classes of bugs at compile time and make big programs safe to refactor. Python is quicker for small scripts.

Go deeper
  • Static typing: type errors and typos are caught at compile time (factorlst would not compile).
  • Enforced privacy: outside code cannot depend on internals, so classes can be refactored safely.
  • Performance and portability: bytecode, a JIT-compiling JVM, the same .class everywhere.
  • The cost: more typing and ceremony. Every language is a compromise.
Follow-up they may askWhich would you pick for a small data script, and why?
Interview 2What happens when you run a Java program?
FundamentalsVery likelyNotes 1.3

Walk through it from the source file to the CPU.

Answer
Say this first (30 seconds)

javac compiles .java to bytecode in .class files; the JVM loads it, interprets it and JIT-compiles hot methods, and manages memory with stack frames, a heap and a garbage collector.

Go deeper
  • javac Hello.java checks syntax and types and writes Hello.class.
  • java Hello starts the JVM, which loads the class and begins at main.
  • Bytecode is portable: only the JVM is platform specific.
  • JDK = JRE + tools (javac); JRE = JVM + libraries; JVM runs bytecode.
Follow-up they may askWhat is the difference between JDK, JRE and JVM?
Interview 3Why does Python accept x = 7.5 after x = 10 but Java refuses?
FundamentalsLikelyNotes 2.2

Explain dynamic versus static typing through this one example.

Answer
Say this first (30 seconds)

In Python the type belongs to the value, so a name can point at anything. In Java the type belongs to the variable, fixed at declaration, so the compiler rejects x = 7.5 before the program runs.

Go deeper
  • Gain: early errors, speed, tooling.
  • Cost: declarations and less flexibility.
  • Static typing cannot prove a program correct (halting problem); it only guarantees type consistency.
Interview 4Give an example of "every language makes compromises" in Java.
FundamentalsSometimesNotes 1.4

The lecture's closing line, turned into an example.

Answer
Say this first (30 seconds)

Java gives up some raw speed and control (garbage collection, bounds checks) to get safety and fewer bugs.

Go deeper
  • Safety vs speed: array bounds are checked and memory is garbage collected, so Java is a little slower than hand-managed C but far safer.
  • Purity vs speed: primitives are not objects.
  • Simplicity vs power: single inheritance of classes avoids the diamond problem but is less flexible.

7.3Memory

Interview 5Is Java pass by value or pass by reference? Prove it.
MemoryVery likelyNotes 3.3

Take x = 7, an array m holding 8, 9, 10, and a StringBuilder s holding "hi", then call change(x, m, s).

Main.java
static void change(int a, int[] arr, StringBuilder sb) {
    a = 99;
    arr[0] = 99;
    sb.append("!");
    arr = new int[] {0};
    sb = new StringBuilder("new");
}
Answer
Say this first (30 seconds)

Always by value. Java copies the argument; for an object the copy is the reference, so the callee can change the object's contents but cannot make the caller's variable point elsewhere.

Go deeper
  • x is still 7: the number was copied.
  • m[0] is now 99 and s is "hi!": the reference was copied, so both names reach the same object.
  • m and s still point at the originals: re-pointing the local copies did nothing outside. In true pass by reference they would have changed.
Follow-up they may askWrite a swap(int a, int b) method. Why can't it work in Java?
Interview 6Where do these live: stack or heap?
MemoryVery likelyNotes 3.2 - 3.6

Draw the memory for this method while it runs.

Main.java
void demo() {
    int count = 3;
    int[] marks = new int[3];
    Account a = new Account();
}
Answer
Say this first (30 seconds)

Locals and references live in the method's stack frame; every object and array lives on the heap, reached through a reference.

Go deeper
  • count: stack (a primitive local).
  • marks and a (the references): stack.
  • The array and the Account object: heap.
  • When demo() returns, its frame is popped; the two heap objects become garbage once nothing else refers to them.
Follow-up they may askWhat if the method stored a in a static list?
Interview 7How does garbage collection decide what to free?
MemoryVery likelyNotes 3.5

Explain it, then say when it cannot help.

Answer
Say this first (30 seconds)

It marks everything reachable from the program's variables (the roots) and reclaims everything unmarked, so even an unreachable cycle is freed.

Go deeper
  • Roots: local variables on the stacks and static fields.
  • Modern JVMs are generational: most objects die young, so the young area is collected often and cheaply.
  • You cannot force it: System.gc() is a hint.
  • It does not stop leaks: an object still reachable but no longer needed is never freed.
Follow-up they may askCan a Java program leak memory? How?
Interview 8Why does deep recursion crash, and how do you fix it?
MemoryLikelyNotes 3.2

Connect recursion to the stack of activation records.

Answer
Say this first (30 seconds)

Each call pushes an activation record and the stack is finite, so too many nested calls throw StackOverflowError, usually from a missing base case.

Go deeper
  • Fix the base case.
  • Or rewrite as a loop.
  • Rarely, raise the stack size with -Xss.
  • Do not confuse it with OutOfMemoryError, which is the heap running out.

7.4Object-oriented programming

Interview 9Explain polymorphism with an example.
OOPVery likelyNotes 5.5, 5.8

Use a Shape variable holding a Circle.

Main.java
Shape s = new Circle();
s.draw();            // runs Circle's draw(), not Shape's
Answer
Say this first (30 seconds)

One call, many behaviours. Overloading is chosen at compile time from the static types; overriding is chosen at run time from the object's real type (dynamic lookup).

Go deeper
  • Run-time: the declared type Shape allows draw(); the real type Circle decides which code runs.
  • Compile-time: same method name, different parameters.
  • The benefit: the Simula event loop never asks "what kind of event?". New kinds are new classes, with no edits to old code.
Follow-up they may askDoes overriding work for fields, static methods or private methods?
Interview 10Abstraction vs encapsulation: explain with a bank account.
OOPVery likelyNotes 4.4, 5.3

The most mixed-up pair in interviews.

Main.java
public class Account {
    private int balance;                    // hidden data
    public void deposit(int amt) { balance += amt; }
    public int  balance()        { return balance; }
}
Answer
Say this first (30 seconds)

Abstraction shows what and hides how through a small interface. Encapsulation is the mechanism: data and the methods that use it live together, behind private.

Go deeper
  • Encapsulation: balance is private, so nobody can set it to a wrong value directly.
  • Abstraction: callers think "deposit, read balance" and do not care whether it is one number or a list of transactions (Lecture 4's data refinement).
Follow-up they may askWhat is the cost of a getter for every field?
Interview 11Inheritance or composition? Use Stack and Deque.
OOPVery likelyNotes 5.7

The lecture's own example.

Answer
Say this first (30 seconds)

Inherit when the child is truly an is-a and can stand in for the parent. Otherwise hold the other object as a private field and delegate to it.

Go deeper
  • A Stack built on Deque would also inherit insertRear, so it could wrongly be used as a Deque: code reused, but not a subtype.
  • Composition reuses the code and exposes only what a stack should.
Follow-up they may askWhen is inheritance the right choice?
Interview 12What happens if a subclass constructor does not call super?
OOPLikelyNotes 6.6

Lecture 6's Square forgot its parent. What does Java do?

Answer
Say this first (30 seconds)

The compiler inserts an implicit super(). If the parent has no no-argument constructor, that is a compile-time error and you must write super(args) yourself.

Go deeper
  • Python's Square never set width and height, so it failed only at run time.
  • Java makes that mistake hard to commit.
Interview 13Interface vs abstract class?
OOPVery likelyNotes 5.7

Lecture 5's subtyping-versus-inheritance split in Java clothes.

Answer
Say this first (30 seconds)

An interface is a pure contract (subtyping only). An abstract class is a partly built parent (subtyping plus shared code and state). A class can implement many interfaces but extend only one class.

Go deeper
InterfaceAbstract class
Purposea contracta partial implementation to inherit
Inheritingmany allowedonly one
State, constructorsno instance stateallowed

Use an interface when unrelated classes share a role; an abstract class when subclasses really share code and state.

Interview 14How do you make a class immutable?
OOPLikelyNotes 6.3

Builds on mutator versus accessor.

Answer
Say this first (30 seconds)

Make it final, make every field private final, set them only in the constructor, give no mutators, and defensively copy any mutable object that goes in or out.

Go deeper
  • An immutable class has accessors only: no translate-like methods.
  • Such objects are safe to share, so they are also safe across threads.

7.5Code-reading questions to expect

Interviewers love a snippet and the question "what does this print?". Try each one before you open the answer.

Trace APass by value
Code readingVery likelyNotes 3.3
Main.java
static void tweak(int[] a, int[] b) {
    a[0] = 1;
    b = new int[] {9};
    b[0] = 2;
}
// main:
int[] p = {0}, q = {0};
tweak(p, q);
System.out.println(p[0] + " " + q[0]);
Answer
Prints
1 0
  • a shares the array of p, so a[0] = 1 shows outside.
  • b = new int[]{9} only re-points the local copy; q's array is untouched and the 2 goes to the new array.
Trace BOverload or override?
Code readingVery likelyNotes 5.5
Main.java
class Animal { void speak() { System.out.println("..."); } }
class Dog extends Animal { void speak() { System.out.println("woof"); } }

static void call(Animal a) { System.out.println("animal"); }
static void call(Dog d)    { System.out.println("dog"); }

Animal x = new Dog();
x.speak();
call(x);
Answer
Prints
woof
animal
  • x.speak() is overriding: the run-time type Dog decides.
  • call(x) is overloading: the static type Animal decides.
Trace CReachable or garbage?
Code readingVery likelyNotes 3.5
Main.java
Node a = new Node();
Node b = new Node();
a.next = b;
b.next = a;
a = null;
b = null;
Answer
Answer

Both objects are garbage.

  • They point at each other, but no variable on the stack can reach either.
  • Reachability from variables is the test, so a marking collector frees them.
Next · SummaryWeek 1 Summary Sheet
08
Summary

Week 1 Summary Sheet

Week 1 on one page: a lecture-by-lecture glance, key terms, the pairs to tell apart, the errors you can now read, a checklist and corrections to the source notes.

8.1Week 1 at a glance

L1Languages and styles

Abstraction hides the machine; Java compiles to bytecode for the JVM; imperative says how, declarative says what.

L2Types

Types give bits meaning and catch bugs early. Static (Java) is checked at compile time, dynamic (Python) at run time.

L3Memory

Calls live on the stack, long-lived data on the heap; Java passes by value; the garbage collector frees the unreachable.

L4Abstraction and modularity

Refine top-down; a component is interface plus specification; data refinement cascades.

L5Object-oriented programming

Abstraction, subtyping, dynamic lookup, inheritance. Subtyping is about interfaces, inheritance about implementations.

L6Classes and objects

A class is a template, an object an instance. Hide the representation or a change becomes a breaking change.

8.2Key terms, lecture by lecture

L1 · Languages and styles
Abstraction
Using something through what it does, not how it is built.
Compiler
Translates the whole program first.
Interpreter
Runs the program line by line.
Imperative
How: steps, variables, loops.
Declarative
What: describe the result, often by induction.
ADT
Values plus permitted operations, with a hidden representation.
L2 · Types
Static typing
Types declared in advance, checked at compile time.
Dynamic typing
Types from current values, checked at run time.
Type inference
The compiler deduces a type from context (var).
Static analysis
Finding errors and optimising without running the code.
Halting problem
No general algorithm can decide what any program will do.
L3 · Memory
Scope
Where a name can be used.
Lifetime
How long the storage stays allocated.
Activation record
Stack storage of one call: parameters, locals, links.
Control link
Points to the calling (previous) record.
Return value link
Says where this call writes its result.
Call by value
The argument is copied into the parameter.
Heap
Memory for data that outlives a call.
Garbage collection
Automatic freeing of unreachable storage.
L4 · Abstraction and modularity
Program refinement
Adding detail to the code; data stays.
Data refinement
Changing the representation; it cascades to users.
Interface
What is visible to other components.
Specification
The promised behaviour seen through the interface.
L5 · Object-oriented programming
Object
Hidden data plus public methods.
Subtype
A can be used wherever B is needed (A ⊆ B).
Dynamic lookup
The runtime type picks the method that runs.
Overloading
Chosen from the static types.
Inheritance
Reuse of another class's implementation.
L6 · Classes and objects
Class
The template for objects.
Constructor
Code that sets an object up when it is created.
Instance variable
Data each object keeps its own copy of.
Mutator / accessor
Changes the state / only reads it.
Breaking change
An internal change that breaks code depending on it.

8.3The pairs you must tell apart

Imperativehow, with steps
VS
Declarativewhat, as a description
Compilertranslates all first
VS
Interpreterruns line by line (Java does both)
Static typingchecked before running
VS
Dynamic typingchecked when the line runs
Scopecan I use the name?
VS
Lifetimeis the storage alive?
Control linkwho called me
VS
Return value linkwhere my answer goes
Stackper call, freed on return
VS
Heapoutlives calls, collected
Manual memorythe programmer frees
VS
Garbage collectionthe runtime frees
Program refinementcode detail changes
VS
Data refinementrepresentation changes, cascades
Interfacewhat is visible
VS
Specificationwhat it promises
Subtypingrelationship of interfaces
VS
Inheritancerelationship of implementations
Overloadingstatic types choose
VS
Dynamic lookupruntime type chooses
Classthe template
VS
Objectone instance with its own data
Mutatorchanges the state
VS
Accessoronly reads

8.4Messages and errors you can now read

What went wrongWhat you will seeWhen
value does not fit the declared typeerror: incompatible types: possible lossy conversion from double to intcompile time
wrong kind of object assignederror: incompatible types: String cannot be converted to intcompile time
misspelt or undeclared name, or a method that does not existerror: cannot find symbolcompile time
same local name declared twice in one scopeerror: variable x is already defined in method ...compile time
outside code touching a private fielderror: x has private access in Pointcompile time
runaway recursionjava.lang.StackOverflowErrorrun time
heap exhaustedjava.lang.OutOfMemoryError: Java heap spacerun time
Python: missing attribute (the Rectangle/Square demos)AttributeError: 'Square' object has no attribute 'width'run time
Python: runaway recursionRecursionError: maximum recursion depth exceededrun time

8.5Last-minute checklist

L1 · Languages and styles
  • Why languages exist; machine code is tedious
  • Java pipeline: .java → javac → bytecode → JVM
  • JDK / JRE / JVM
  • sum_list and sum_square_even both ways
L2 · Types
  • Three jobs of types
  • Static vs dynamic; the factorlst typo
  • Static analysis and the halting problem
  • Type inference and var
L3 · Memory
  • Scope vs lifetime; hole in the scope
  • The factorial stack diagram
  • Control link vs return value link
  • Call by value; Java is always by value
  • Heap, and mark-and-sweep garbage collection
L4 · Abstraction and modularity
  • Top-down vs bottom-up
  • Program vs data refinement; cascading impact
  • Component = interface + specification
  • Control abstraction vs data abstraction
L5 · Object-oriented programming
  • The four features
  • Subtype rule (A ⊆ B)
  • Dynamic lookup vs overloading
  • Deque: reuse is not subtyping
L6 · Classes and objects
  • Class vs object; constructor; self / this
  • Changing internals; breaking changes
  • Rectangle / Square and privacy
  • Why Python's lack of enforcement leads to Java

8.6Corrections made to the source notes

WhereThe source saysCorrect
Lecture 6, first Pointself.y = yself.y = b
Lecture 6, polar Pointatan(b / a) is enoughloses the quadrant for negative x; use atan2(b, a) (checked by running)
Lecture 4, primes figurethird block is cut off at the bottom in the PDFredrawn in full from the three levels
Spelling in the PDF"well-types", "shows down", "peformance", "withing"well-typed, slows down, performance, within
Revision overview
Languagesmachine vs high level
javac + JVM
imperative vs declarative
Typesstatic vs dynamic
static analysis
var = inference
Memoryscope vs lifetime
stack + activation records
heap + garbage collection
Week 1Concepts
Modularitystepwise refinement
interface + specification
data refinement cascades
OOPabstraction, subtyping
dynamic lookup, inheritance
Deque: reuse is not subtype
Classesclass vs object, constructor
breaking changes
private + super + static types
Revision: all of Week 1 on one page
Back to top