Skip to content
Computer Science Fundamentals
Source Code
← Home

Object-oriented programming

100 questions

Right
0
Wrong
0
Not answered
100
Difficulty
Number of questions

100 questions match this filter

Theory summary

Look around your room. A lamp has a switch and a light. A pet has a name and can eat and play. Each thing keeps some information about itself and knows how to do some actions. Object-oriented programming, or OOP, builds programs the same way: the program is a team of small that keep their own information and ask each other to do things.

Almost every app you use is written this way: games, banks, shops, phone apps. Big programs are hard to change without breaking something. OOP gives programmers tools to decide where each piece of knowledge lives, so that a change in one place does not break ten others. That is the real goal of every idea in this summary.

You will learn what classes and objects are, how objects hide their insides (encapsulation), how one class can build on another (inheritance), how the same call can do different things (polymorphism), what interfaces and abstract classes are for, why we often prefer building with parts instead of inheriting, how to spot messy code, what happens when things go wrong (exceptions), how to write code that works for many types (generics), how memory is cleaned, how to read and write data, and how other languages do OOP without classes. Most examples use Java, the language of the quiz. At the end you will be ready to answer it.

The big picture: objects talking to objects

Think of a school. The teacher does not open the student's backpack and look inside. She asks: 'please show me your homework'. The student decides how to find it. In OOP, objects work like this. One object sends a request, called a method call, and the other object decides how to answer.

The five ideas of the summary
IdeaIn one sentenceEveryday picture
EncapsulationAn object keeps its data private and offers only safe actions.A vending machine: you press buttons, you cannot reach inside.
AbstractionWe show what an object can do and hide how it does it.You drive a car with a wheel and pedals, without knowing the engine.
InheritanceA new class starts from an old one and adds or changes things.A puppy is a dog, and a dog is an animal.
PolymorphismOne request, many answers, depending on the object.'Make a sound' makes a dog bark and a cat meow.
CompositionAn object is built from other objects.A bike has wheels, a chain and a bell.
Watch the video: Curso de POO Java, Gustavo Guanabara (in Portuguese)↑ Back to the contents

Classes, objects and encapsulation

A cookie cutter is not a cookie. It is the shape used to make many cookies. A class is the cutter: it says which data (called fields) every thing of this kind has and which actions (called methods) it can do. An object is one cookie made from it, also called an instance. Two accounts made from the same Account class have the same methods, but each one has its own balance.

class Account {
    private int balance;          // field: data of each object
    Account(int start) {          // constructor: runs when the object is born
        this.balance = start;
    }
    int balance() { return balance; }
}
Account a = new Account(10);
Account b = new Account(99);      // independent from a
A class and two objects

Constructors, this and static

  • A constructor sets up a new object. It has the same name as the class and no return type.
  • If you write no constructor, the compiler adds one with no arguments. As soon as you write any constructor, that free one disappears.
  • this.x means 'the field x of this object'. It helps when a parameter has the same name as the field.
  • this(...) calls another constructor of the same class. It must be the first line of the constructor.
  • A static field belongs to the class, not to an object. There is one copy for everybody, like the number on a school bell. A normal (instance) field exists once per object.

Order of construction

When new runs, Java first runs the constructor of the superclass (the class above, see inheritance). Then it runs the field initialisers and the { } initialisation blocks, in the order they are written in the file. Only at the end does the rest of the constructor body run. An int field that copies another field copies the value at that moment and stays unlinked afterwards.

Access levels

Java access levels, from the most closed to the most open. Use the most closed one that works.
ModifierWho can use the member
privateOnly the class itself
no modifier (package-private)Classes in the same package (folder of classes)
protectedThe package and the subclasses
publicEverybody

Passing objects and comparing them

Java passes everything by value. For an object, the value that is copied is the reference, like a paper with the address of a house. If a method uses the address to paint the door, the owner sees the new color. But if the method writes a new address on its own copy of the paper, the owner's paper does not change. In short: changing the object through a parameter is visible to the caller, reassigning the parameter is not.

== on two object variables asks 'is it the very same object?' (identity). equals is meant to ask 'do they have the same contents?', but the version inherited from Object also compares identity. A class must override equals (and hashCode) to compare fields. A Java record writes them for you.

Encapsulation

Encapsulation means the data of an object can only be reached through the methods of its own class. Then the class can protect its invariants, rules that must always be true, such as 'a balance is never negative'. It can also change how it stores things without breaking its users.

↑ Back to the contents

Life of an object: memory and garbage collection

Imagine your desk and a big storage room. Small notes stay on the desk and are thrown away when you leave. Bigger things go to the storage room. In Java, the desk is the stack: local variables and parameters live in the box of each method call and vanish when it returns. The storage room is the heap: every object made with new lives there. A variable like Point p holds only a reference (an address) to the object on the heap.

  STACK (desk)              HEAP (storage room)
 ┌─────────────┐
 │ p ──────────┼───────►  [ Point x=1 y=2 ]
 └─────────────┘
The variable p is on the stack, the object is on the heap

Who throws away objects nobody uses? In C++ with manual management, you write delete yourself. Forget it and you have a leak. Delete too early and you have a dangling pointer. Java gives this job to the garbage collector (GC).

What is garbage?

An object is garbage when the program has no way to reach it. The GC starts from the roots (local variables on the stacks and static fields), follows every reference, and marks what it reaches. What is not marked can be freed. This is tracing, or mark and sweep. Reachability is transitive: an object only pointed to by unreachable objects is unreachable too.

root a ──► [1] ──► [2] ──► [3]

after  a.next = null :
root a ──► [1]        [2] ──► [3]   (2 and 3 are garbage)
Setting null cuts a reference. 3 is still pointed to, but only by garbage
  • Assigning null removes only that one reference. If another path reaches the object, it stays alive.
  • Reference counting frees an object when its counter hits zero, but it cannot see cycles (A points to B and B points to A). Tracing, used by the JVM, handles cycles: if nobody reachable points to the pair, both are freed.
  • Being eligible does not mean being collected right now. The virtual machine decides when.

Generations and leaks

Most objects die young (a temporary result, a loop variable) and the ones that survive tend to live long. This is the generational hypothesis. So the heap is split: new objects are born in a small young area (eden) that is cleaned often, survivors are promoted to the old generation, cleaned rarely.

↑ Back to the contents

Inheritance: a class that builds on another

A puppy is a dog, and a dog is an animal. Everything true for animals is true for dogs, and dogs add their own tricks. Inheritance does this in code: class Manager extends Employee makes Manager a subclass of Employee (the superclass). It receives the members of the superclass and may add or change behaviour. Because it keeps every promise of the superclass, a Manager can be used wherever an Employee is expected. The opposite is false.

        Object
           │
        Animal
        ╱     ╲
      Dog     Cat
       │
     Puppy
A class tree. Every Java class has one direct superclass; the root is Object
  • Java allows one superclass only (single inheritance). This avoids getting two versions of one method by two paths. To fulfil many contracts, a class implements several interfaces.
  • Object is the root and gives every class toString, equals and hashCode.
  • A subclass object contains the fields of the whole chain. A private field of the superclass exists in it, but only the superclass methods can touch it. That keeps the superclass invariants safe.
  • final on a variable forbids reassignment, on a method forbids overriding, on a class forbids inheriting (like String).

Constructors in a chain

Every constructor begins by calling another: this(...) or super(...). If you write neither, the compiler inserts super(). If the superclass has only constructors with parameters, that hidden call cannot work and the code does not compile: the subclass must call super(args) explicitly. So construction climbs up to Object, and the bodies run from the most general class to the most specific.

new C()  where C extends B extends A

A body runs first  ->  B body  ->  C body
Bodies run from top to bottom of the tree

Overriding rules

To override a method, the subclass repeats its signature with a new body. It may offer more than the superclass, never less: the access can stay the same or widen (protected to public), the return type can be a subtype, and the checked exceptions cannot be broader. This way any code written for the superclass still works.

Casting up and down

An upcast (Animal a = new Cat()) is implicit and always safe. A downcast ((Dog) a) must be written and is checked while running against the real class of the object: if a holds a Cat, it throws ClassCastException. Test first with instanceof, which in modern Java also declares the new variable: if (a instanceof Dog d).

↑ Back to the contents

Polymorphism and dynamic dispatch

Say 'make a sound' to a room with a dog, a cat and a cow. Each one answers in its own way, and you did not need to know which animal it was. That is polymorphism, 'many forms'. In code, we write against a general type (a superclass or an interface), for example rule.apply(total), and every object runs its own version. New kinds can be added without changing the caller.

Two types for one variable

In Shape s = new Circle(); the variable has a declared (static) type, Shape, and the object has a real (dynamic) type, Circle. The compiler uses the declared type to check that the call is allowed. The virtual machine uses the real type to pick the code. Choosing at run time is dynamic dispatch (also late binding). It is usually done with a per-class table of methods, called a vtable.

s.area()
  declared type Shape  -> compiler: does area() exist? yes
  real type Circle     -> run time: use Circle's area()

search starts at the real class and climbs:
Puppy -> Dog -> Animal -> Object
Two steps for one call

If a subclass does not override the method, the search climbs and finds the nearest superclass version. Objects of the superclass itself keep the original. Inside an override, super.draw() calls the version of the direct superclass of the class where it is written, with no dynamic dispatch. It lets you extend instead of replace.

Overloading is not overriding

PointOverloadingOverriding
WhatSame name, different parameters, same classSame signature, new body in a subclass
ChosenAt compile time, by the declared types of the argumentsAt run time, by the real class of the receiver
Exampleprint(Object) and print(String)Dog.sound() replaces Animal.sound()

Java does single dispatch: at run time only the class of the object that receives the call counts, never the classes of the arguments. If you pass a String stored in an Object variable, the compiler picks print(Object) and it stays that way. When behaviour must depend on both objects, programmers use double dispatch (the Visitor pattern).

Why it matters: killing the if chains

If the same decision by type (if role == DEVELOPER ... else if role == DBA ...) is repeated in many methods, every new role means editing all of them, and forgetting one is easy. With polymorphism, each role is a class that implements a common interface and carries its own rules. Old code does not change: open for extension, closed for modification (the open-closed principle).

Example of rules in a list, applied in order, each starting from the result of the previous one: a price of 10000 cents, then 10% off, then 500 off, then 10% off again.

Price in cents after each discount rule
start
10,000 cents
10% off
9,000 cents
minus 500
8,500 cents
10% off
7,650 cents
↑ Back to the contents

Abstraction: interfaces and abstract classes

A wall socket does not care if you plug in a lamp or a toaster. It only defines the shape of the plug. An interface is the plug: it lists the method signatures that can be asked of an object, and the classes that implement it supply the code. Code written against the interface works with any implementation, present or future.

An abstract class is a recipe with a missing step. It can have fields, constructors and finished methods, but leaves one or more methods without a body, marked abstract. Because part of it is missing, you cannot create an object directly from it. The first concrete subclass must give a body to every inherited abstract method.

FeatureInterfaceAbstract class
Instance fields and constructorsNoYes
How many a class can useMany (implements A, B)One (uses the only extends)
Best forA role that unrelated types can playSharing state and code among close relatives

Default methods and the diamond

Once, adding a method to an interface broke every class that implemented it. A default method brings a body that classes inherit, so forEach could enter the Java 8 collections without breaking old code. But a class may then receive two defaults with the same signature. Java settles it with three rules:

  1. A method from a class always beats one from an interface (the 'class wins' rule). So a default can never change a class that already had that method, and cannot redefine toString or equals.
  2. A more specific interface beats the one it extends.
  3. If there is still a tie, the class does not compile until it overrides the method itself.

Template Method

The abstract class can fix the order of the steps in a final method that subclasses cannot change, and leave some steps abstract or overridable. The subclass fills the gaps, and the overall order stays guaranteed by the superclass. This is the Template Method pattern.

abstract class Game {
    final void play() {          // order is fixed
        start(); turn(); end();
    }
    void start() { }             // optional step
    abstract void turn();        // each game fills this
    void end() { }
}
The recipe is fixed, one step is open

Two design principles

  • Dependency inversion: business rules and details should both depend on abstractions. If Checkout does new SaoPauloTax() inside, it is glued to that class. Receive a Tax interface through the constructor (constructor injection) and it can use any tax.
  • Interface segregation: prefer small interfaces, one per role. A wide Document interface (read, write, delete) forces a read-only class to throw UnsupportedOperationException, which breaks whoever trusts the contract. Split it, and let methods ask for the narrowest type they need (Readable for a preview).
↑ Back to the contents

Composition against inheritance

There are two ways to reuse a class. Inheritance says 'is a': a dog is an animal. Composition says 'has a': a car has an engine. A car cannot be used where an engine is expected, so Car extends Engine is wrong. The deciding question is can one be used in place of the other? If yes, inheritance can be considered. If not, hold the other object in a field and delegate work to it.

class Car {
    private final Engine engine;      // has-a
    Car(Engine engine) { this.engine = engine; }
    void start() { engine.ignite(); } // delegates
}
Composition and delegation

Aggregation and composition in UML

Both are whole-part links. In aggregation (hollow diamond) the part has its own life and may belong to several wholes: a team and its athletes. In composition (filled diamond) the part belongs to one whole, which creates and destroys it: a building and its flats, an order and its lines. Which one you choose depends on the system's requirements.

Why 'prefer composition over inheritance'

  • Strongest coupling. A subclass depends on how the superclass is written, not only on what it promises. With composition, you only use the public interface and can even swap the object while running.
  • Fragile base class. A CountingBag that overrides add and addAll can count items twice if the superclass addAll itself calls add. That internal detail is not in the contract and may change. If the counter instead holds a bag and delegates, the inner calls never come back to it.
  • Too much inherited. Stack extends ArrayList also inherits add(0, x) and remove, so a client can break the last-in-first-out rule. With composition the class exposes only push and pop. The real java.util.Stack (extends Vector) has this flaw.
  • Behaviour fixed at birth. Inheritance decides behaviour when the object is created. With composition, the varying part lives in another object behind an interface, and putting a different object in the field changes behaviour while running. It also avoids a subclass explosion when there are several axes (shipping and tax).
Watch the video: Nothing is Something, Sandi Metz

The Liskov substitution principle

A program written for the base type must stay correct with any subtype. In maths every square is a rectangle. But if Rectangle has setWidth and setHeight, a mutable Square that changes both sides breaks the user who sets 5 by 4 and expects area 20. So 'is-a' in inheritance is about behaviour, not about how things are classified in the real world.

Ask for no more, deliver no less
Part of the contractA subtype mayExample that breaks it
Precondition (what it asks)Be equal or weaker: accept at least as muchBase accepts any positive amount, subclass refuses amounts above 1000
Postcondition (what it guarantees)Be equal or stronger: promise at least as muchBase guarantees a non-negative result, subclass may return a negative one
↑ Back to the contents

Coupling, cohesion and code smells

A good toolbox keeps hammers with hammers and screws with screws. That is cohesion: the parts of a class work towards one goal. A cohesive class has one reason to change (the single responsibility principle), is small and easy to name and test. A sign of low cohesion is a class whose methods use different sets of fields.

Coupling is how much one class depends on others, like wires between boxes. You cannot remove it, since objects must work together, but you can choose it: depend on few things, and on stable ones such as interfaces. The classic goal is high cohesion and low coupling.

  • Afferent coupling: how many classes depend on me. If it is high, I carry a lot of responsibility and changing my public interface is risky.
  • Efferent coupling: how many classes I depend on. If it is high, I have many outside reasons to change.
  • A class used by 20 others and using none is the riskiest one to change in its interface. Point dependencies toward stable modules.

Code smells

A code smell is a pattern that suggests code is hard to understand or change. The program works and the tests pass, but the next change will be slow. Refactoring means changing the structure without changing the behaviour, and the proof is that the same tests pass before and after.

Six smells from the catalogue of this repository
SmellWhat it looks likeUsual cure
Long Method40 lines in blocks separated by commentsExtract Method: each block becomes a named method
God ClassOne class validates, computes, e-mails and reports; hard to testExtract Class: one per responsibility, the old one coordinates
Feature EnvyA method uses only getters of another classMove Method to the class that owns the data (tell, don't ask)
Shotgun SurgeryOne change needs edits in many places (switches for payment methods)Gather them: an interface with one class per method
Primitive ObsessionStrings and ints for e-mail, tax number, money; swapped arguments compileSmall types (value objects) that validate in the constructor
Conditional chain on a type codeSame if chain by kind in many methodsReplace with polymorphism

For this repository's measurement, adding a fourth kind of delivery to a checkout:

Existing files edited to add one new kind
if chains
4 files
polymorphism
1 files

The trade is not free: with polymorphism, a new kind is one new class, but a new operation on all kinds edits the interface and every class. Choose by what changes more often.

Cyclomatic complexity

It counts the independent paths through a method, so it hints at how many tests you need. In the quiz, start at 1 and add 1 for every `for`, `if`, ternary `?:`, `&&` and `||`. A method with one if and one for has complexity 3. High values suggest extracting methods or using polymorphism.

↑ Back to the contents

Exceptions: when something goes wrong

In a relay race, if a runner drops the baton, she shouts and the team stops its normal plan until someone knows what to do. An exception is that shout. Throwing it interrupts the normal flow, and the virtual machine unwinds the call stack method by method, looking for a catch that matches. If it reaches the top without finding one, the thread ends and the error is printed. This lets a low-level method detect a problem and a higher level, which knows what to do, handle it, with no return codes passed hand to hand.

main()  ── has catch (IOException)
  └ load()
      └ read()   <- throws IOException here

search: read -> load -> main (found!)
The exception travels up until a catch matches

Checked and unchecked

Everything thrown descends from Throwable. Checked exceptions (for example IOException) are failures that can happen even in a correct program, like a missing file. The compiler forces you to handle them or declare them with throws. Unchecked ones (RuntimeException and its children, such as NullPointerException) usually show a bug, and Error is a serious failure of the machine. The compiler does not force you to catch them.

try, catch, finally

  • When a statement in try throws, the rest of the try is skipped. Control goes to the first matching catch, then to finally, and then the program continues after the whole structure. There is no way back to the failing line.
  • The catch blocks are tried in order and the first whose type equals the exception or is a superclass wins. So put the most specific first. Putting a superclass before its subclass does not compile, because the second block would be dead code.
  • finally runs however control leaves the try: normal end, return, caught or uncaught exception. It does not run only if the machine stops (for example System.exit).
static int value() {
    int x = 1;
    try { x = 2; return x; }   // result 2 is saved first
    finally { x = 3; }         // too late to change it
}                              // returns 2
The return value is set aside before finally runs

Try-with-resources

For files and connections, write try (Res a = ...; Res b = ...). Objects that implement AutoCloseable are closed for you when the block ends, with or without an exception. They are closed in the reverse order of opening, like a stack, because a later resource may use an earlier one.

Translate, but keep the cause

Each layer should speak its own language: whoever calls place(order) does not need to know SQL, so you catch SQLException and throw OrderNotSavedException. The new exception must carry the original as its cause: new OrderNotSavedException(msg, e). This is exception chaining; the printed trace shows both, joined by Caused by. Without it, the diagnosis is lost.

↑ Back to the contents

Generics and collections

Labeled boxes are better than mystery boxes. A List<String> is a list labeled 'strings only'. The compiler rejects add(42) and knows that get(0) returns a String. Without the label, everything is an Object, and every read needs a cast that may fail while running. Generics let you write a class or method once and use it with many types, still checked.

The main collections

Choose the interface by the contract, then the implementation by cost and order
InterfaceContractCommon implementations
ListOrdered by position, duplicates allowedArrayList, LinkedList
SetNo duplicatesHashSet (no order, fast), LinkedHashSet (insertion order), TreeSet (sorted, O(log n))
Queue / DequeWaiting line, one or both endsArrayDeque
MapUnique keys, each with one valueHashMap, TreeMap

In a Map, put with a new key adds an entry. With a key already there, it replaces the value and returns the old one, so the size counts distinct keys. TreeSet sorts by natural order (Comparable) or by a Comparator.

Rules of the Java generics

  • Only reference types. List<int> does not compile. Use the wrapper Integer (and Double, Boolean). The compiler converts in both directions (autoboxing and unboxing), at the cost of one object per element.
  • Invariance. Integer is a subtype of Number, but List<Integer> is not a List<Number>. If it were, you could add a 3.14 into a list of integers.
  • Bounds. An unbounded T only has the methods of Object. T extends Comparable<T> unlocks compareTo, so max(a, b) works for any type that compares with its own kind.
  • Wildcards. ? extends T for things you only read from (a producer). ? super T for things you only write to (a consumer). Memory aid PECS: producer extends, consumer super. Copying from List<Integer> to List<Number> uses copy(List<? super T> dst, List<? extends T> src).
  • Type erasure. After checking, the compiler replaces T by its bound (or Object) and inserts casts. The compiled code does not know what T was. So List<String> and List<Integer> have the same class at run time, and you cannot write new T(), new T[10] or x instanceof List<String>.

Fail-fast iterators

Collections of java.util count their modifications. If you remove from a list inside a for-each loop, the iterator notices and throws ConcurrentModificationException. The name is misleading: no second thread is needed. To remove while looping, use the iterator's own remove() or removeIf.

↑ Back to the contents

Streams, input and output, serialisation

A stream is a garden hose: data flows in one direction, and you take it piece by piece. A byte stream (InputStream, OutputStream) moves raw 8-bit numbers with no meaning. A character stream (Reader, Writer) adds the encoding, the rule that turns bytes into letters. Using the wrong kind on text corrupts accented letters.

Byte and character are the same only for plain ASCII. UTF-8 is variable length: the first 128 characters take 1 byte, an accented letter such as ç takes 2, the euro sign 3, an emoji 4. To count bytes of a text, add the size of each character (the word ação has 4 characters but 6 bytes). Read text with a character stream and state the encoding, or you will cut accented letters in half.

Bytes of one character in UTF-8
a
1 bytes
ç
2 bytes
€
3 bytes
emoji
4 bytes

Buffers

Going to the operating system for every single character is like walking to the library for each word. A buffered stream (BufferedReader) brings a whole page at once and hands out letters from memory. Writing is similar, with one warning: data reaches the destination only when the buffer fills, or on flush() or close().

Serialisation

An object in memory is a web of references that only makes sense inside the running program. To save it in a file or send it over a network, you serialise it: turn its state into a line of bytes (Java binary, with ObjectOutputStream) or text (JSON, XML). A Java class opts in by implementing the marker interface Serializable.

  • transient leaves a field out: use it for passwords, caches, connections. When the object is read back, it is rebuilt without calling the constructor of the serialisable class, and transient fields come back as null, 0 or false.
  • The stream also stores the class name and a version number. On reading it compares with the loaded class. If you changed the class and declared no serialVersionUID, the computed number changes and reading old files throws InvalidClassException. Declaring private static final long serialVersionUID lets the class evolve compatibly.
↑ Back to the contents

OOP across languages: traits, interfaces, protocols

Not every language has classes and inheritance like Java. But they all need ways to group data and behaviour and to let different things answer the same request. Here is how some do it.

Same need, different tools
LanguageHow it does polymorphismReuse
Java, C#Nominal interfaces: a class must declare implementsInheritance and composition
TypeScriptStructural interfaces: matching members are enoughClasses and composition
GoStructural interfaces, no classes, no inheritanceComposition
RustTraits on structs and enums, static (T: Display) or dynamic (dyn Trait)No implementation inheritance
ElixirProtocols (defprotocol) implemented per data typeFunctions and modules, no classes

Nominal against structural

In a nominal system (Java), a value belongs to a type because its class says so by name. In a structural system (TypeScript, Go), having the needed members is enough. A Book with a price fits Priced in TypeScript without saying implements, and a plain object { price: 5 } fits too, which is handy for test doubles. The same class without implements is refused in Java.

interface Priced { price: number }
class Book { constructor(public price: number) {} }
const items: Priced[] = [new Book(10), { price: 5 }];   // both accepted
Structural typing in TypeScript

Nominal typing records intent and avoids coincidences (two run methods that mean different things). Structural typing gives flexibility and needs no adapters for types you do not own. In Go, a small interface can be defined on the consumer side, and an existing type, even from another library, satisfies it unchanged.

The expression problem

Objects group code by variant (one class per kind). Adding a kind is easy, adding an operation means editing every class. Functions with pattern matching group code by operation. Adding an operation is easy, adding a kind means editing every function. This repository measured it: adding 'take and pay' to a cart was one new file with objects, and four edits inside one file with functions. The opposite change has the opposite cost.

↑ Back to the contents

Putting it all together

All the tools of OOP answer one question: where does each piece of knowledge live, and how does a change spread? Use this table when you meet a quiz question.

If the problem is...Think of...Because...
Outsiders break my rulesEncapsulation, defensive copyRules live next to the data
The same if by kind everywherePolymorphismEach kind carries its own rules
Hard to swap a dependencyInterface and constructor injectionDepend on abstractions
Subclass breaks when the parent changesCompositionInheritance is the strongest coupling
Reading old binary files failsserialVersionUIDThe format is coupled to the class
Memory grows forever with a GCReachability, static collectionsGC frees only the unreachable
Which method runs?Declared type for overloads, real type for overridesTwo different moments

Common mistakes

  • Believing that private is enough while a getter returns the internal list.
  • Mixing up overloading (compile time) and overriding (run time), and forgetting that static methods and fields are not overridden.
  • Using inheritance only to reuse code, or because 'a square is a rectangle'.
  • Calling an overridable method from a constructor.
  • Catching a superclass exception before its subclass, or throwing a new exception without the cause.
  • Thinking List<Integer> is a List<Number>, or that the type T still exists at run time.
  • Trusting the garbage collector to close files.
↑ Back to the contents